From 928d6d15f8cdce9e2fa6d3ab9f63361e663f245b Mon Sep 17 00:00:00 2001 From: "pinkforest(she/her)" <36498018+pinkforest@users.noreply.github.com> Date: Fri, 27 Jan 2023 17:06:24 +1100 Subject: [PATCH] Docs.rs + README changes for 2.x (#241) --- Cargo.toml | 10 +- README.md | 223 +- {res => docs/assets}/ed25519-malleability.png | Bin docs/assets/rustdoc-include-katex-header.html | 12 + res/batch-violin-benchmark.svg | 4251 ----------------- 5 files changed, 121 insertions(+), 4375 deletions(-) rename {res => docs/assets}/ed25519-malleability.png (100%) create mode 100644 docs/assets/rustdoc-include-katex-header.html delete mode 100644 res/batch-violin-benchmark.svg diff --git a/Cargo.toml b/Cargo.toml index 49bf6ca..ced06f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,13 +14,11 @@ description = "Fast and efficient ed25519 EdDSA key generations, signing, and ve exclude = [ ".gitignore", "TESTVECTORS", "VALIDATIONVECTORS", "res/*" ] rust-version = "1.60" -[badges] -travis-ci = { repository = "dalek-cryptography/ed25519-dalek", branch = "master"} - [package.metadata.docs.rs] -# Disabled for now since this is borked; tracking https://github.com/rust-lang/docs.rs/issues/302 -# rustdoc-args = ["--html-in-header", ".cargo/registry/src/github.com-1ecc6299db9ec823/curve25519-dalek-0.13.2/rustdoc-include-katex-header.html"] -rustdoc-args = ["--cfg", "docsrs"] +rustdoc-args = [ + "--html-in-header", "docs/assets/rustdoc-include-katex-header.html", + "--cfg", "docsrs", +] features = ["nightly", "batch", "pkcs8"] [dependencies] diff --git a/README.md b/README.md index d9b6d78..44123d1 100644 --- a/README.md +++ b/README.md @@ -3,117 +3,148 @@ Fast and efficient Rust implementation of ed25519 key generation, signing, and verification in Rust. -# Documentation +# Use -Documentation is available [here](https://docs.rs/ed25519-dalek). - -# Installation - -To install, add the following to your project's `Cargo.toml`: +To use, add the following to your project's `Cargo.toml`: ```toml [dependencies.ed25519-dalek] version = "1" ``` -# Minimum Supported Rust Version +# Feature Flags -This crate requires Rust 1.60.0 at a minimum. Older 1.x releases of this crate supported an MSRV of 1.41. +This crate is `#[no_std]` compatible with `default-features = false` -In the future, MSRV changes will be accompanied by a minor version bump. +| Feature | Default? | Description | +| :--- | :--- | :--- | +| `alloc` | ✓ | Enables features that require dynamic heap allocation | +| `std` | ✓ | std::error::Error types | +| `zeroize` | ✓ | Enables `Zeroize` for `SigningKey` | +| `asm` | | Assembly implementation of SHA-2 compression functions | +| `batch` | | Batch verification. Requires `alloc` | +| `digest` | | TODO | +| `legacy_compatibility` | | See: A Note on Signature Malleability | +| `pkcs8` | | PKCS#8 Support | +| `pem` | | PEM Support | +| `rand_core` | | TODO | -# Changelog +# Major Changes See [CHANGELOG.md](CHANGELOG.md) for a list of changes made in past version of this crate. -# Benchmarks +## 2.0.0 Breaking Changes -On an Intel Skylake i9-7900X running at 3.30 GHz, without TurboBoost, this code achieves -the following performance benchmarks: +* Update the MSRV from 1.41 to 1.60 +* `batch` is now `batch_deterministic` +* Removed `ExpandedSecretKey` API +* [curve25519-backend selection] is more automatic - ∃!isisⒶmistakenot:(master *=)~/code/rust/ed25519-dalek ∴ cargo bench - Compiling ed25519-dalek v0.7.0 (file:///home/isis/code/rust/ed25519-dalek) - Finished release [optimized] target(s) in 3.11s - Running target/release/deps/ed25519_benchmarks-721332beed423bce +[curve25519-backend selection]: https://github.com/dalek-cryptography/curve25519-dalek/#backends - Ed25519 signing time: [15.617 us 15.630 us 15.647 us] - Ed25519 signature verification time: [45.930 us 45.968 us 46.011 us] - Ed25519 keypair generation time: [15.440 us 15.465 us 15.492 us] +# Documentation -By enabling the avx2 backend (on machines with compatible microarchitectures), -the performance for signature verification is greatly improved: +Documentation is available [here](https://docs.rs/ed25519-dalek). - ∃!isisⒶmistakenot:(master *=)~/code/rust/ed25519-dalek ∴ export RUSTFLAGS=-Ctarget_cpu=native - ∃!isisⒶmistakenot:(master *=)~/code/rust/ed25519-dalek ∴ cargo bench --features=avx2_backend - Compiling ed25519-dalek v0.7.0 (file:///home/isis/code/rust/ed25519-dalek) - Finished release [optimized] target(s) in 4.28s - Running target/release/deps/ed25519_benchmarks-e4866664de39c84d - Ed25519 signing time: [15.923 us 15.945 us 15.967 us] - Ed25519 signature verification time: [33.382 us 33.411 us 33.445 us] - Ed25519 keypair generation time: [15.246 us 15.260 us 15.275 us] +# Policies -In comparison, the equivalent package in Golang performs as follows: +All on-by-default features of this library are covered by semantic versioning (SemVer) - ∃!isisⒶmistakenot:(master *=)~/code/go/src/github.com/agl/ed25519 ∴ go test -bench . - BenchmarkKeyGeneration 30000 47007 ns/op - BenchmarkSigning 30000 48820 ns/op - BenchmarkVerification 10000 119701 ns/op - ok github.com/agl/ed25519 5.775s +SemVer exemptions are outlined below for MSRV and public API. -Making key generation and signing a rough average of 2x faster, and -verification 2.5-3x faster depending on the availability of avx2. Of course, this -is just my machine, and these results—nowhere near rigorous—should be taken -with a handful of salt. +## Minimum Supported Rust Version -Translating to a rough cycle count: we multiply by a factor of 3.3 to convert -nanoseconds to cycles per second on a 3300 Mhz CPU, that's 110256 cycles for -verification and 52618 for signing, which is competitive with hand-optimised -assembly implementations. +| Releases | MSRV | +| :--- | :--- | +| 2.x | 1.60 | +| 1.x | 1.41 | + +MSRV changes will be accompanied by a minor version bump. + +## Public API SemVer Exemptions + +Breaking changes to SemVer exempted components affecting the public API will be accompanied by some version bump. + +Below are the specific policies: + +| Releases | Public API Component(s) | Policy | +| :--- | :--- | :--- | +| 2.x | Dependencies `digest`, `pkcs8` and `rand_core` | Minor SemVer bump | + +## Safety + +This crate does not require any unsafe and forbids all unsafe in-crate outside tests. + +# Performance + +Performance is a secondary goal behind correctness, safety, and clarity, but we +aim to be competitive with other implementations. + +## Benchmarks + +Benchmarks are run using [criterion.rs](https://github.com/japaric/criterion.rs): + +```sh +cargo bench --features "batch" +# Uses avx2 or ifma only if compiled for an appropriate target. +export RUSTFLAGS='--cfg curve25519_dalek_backend="simd" -C target_cpu=native' +cargo +nightly bench --features "batch" +``` + +On an Intel 10700K running at stock comparing between the `curve25519-dalek` backends. + +| Benchmark | u64 | simd +avx2 | fiat | +| :--- | :---- | :--- | :--- | +| signing | 15.017 µs | 13.906 µs -7.3967% | 15.877 µs +14.188% | +| signature verification | 40.144 µs | 25.963 µs -35.603% | 42.118 µs +62.758% | +| strict signature verification | 41.334 µs | 27.874 µs -32.660% | 43.985 µs +57.763% | +| batch signature verification/4 | 109.44 µs | 81.778 µs -25.079% | 117.80 µs +43.629% | +| batch signature verification/8 | 182.75 µs | 138.40 µs -23.871% | 195.86 µs +40.665% | +| batch signature verification/16 | 328.67 µs | 251.39 µs -23.744% | 351.55 µs +39.901% | +| batch signature verification/32 | 619.49 µs | 477.36 µs -23.053% | 669.41 µs +39.966% | +| batch signature verification/64 | 1.2136 ms | 936.85 µs -22.543% | 1.3028 ms +38.808% | +| batch signature verification/96 | 1.8677 ms | 1.2357 ms -33.936% | 2.0552 ms +66.439% | +| batch signature verification/128| 2.3281 ms | 1.5795 ms -31.996% | 2.5596 ms +61.678% | +| batch signature verification/256| 4.1868 ms | 2.8864 ms -31.061% | 4.6494 ms +61.081% | +| keypair generation | 13.973 µs | 13.108 µs -6.5062% | 15.099 µs +15.407% | Additionally, if you're using a CSPRNG from the `rand` crate, the `nightly` feature will enable `u128`/`i128` features there, resulting in potentially faster performance. -If your protocol or application is able to batch signatures for verification, -the `verify_batch()` function has greatly improved performance. On the -aforementioned Intel Skylake i9-7900X, verifying a batch of 96 signatures takes -1.7673ms. That's 18.4094us, or roughly 60750 cycles, per signature verification, -more than double the speed of batch verification given in the original paper -(this is likely not a fair comparison as that was a Nehalem machine). -The numbers after the `/` in the test name refer to the size of the batch: +## Batch Performance - ∃!isisⒶmistakenot:(master *=)~/code/rust/ed25519-dalek ∴ export RUSTFLAGS=-Ctarget_cpu=native - ∃!isisⒶmistakenot:(master *=)~/code/rust/ed25519-dalek ∴ cargo bench --features=avx2_backend batch - Compiling ed25519-dalek v0.8.0 (file:///home/isis/code/rust/ed25519-dalek) - Finished release [optimized] target(s) in 34.16s - Running target/release/deps/ed25519_benchmarks-cf0daf7d68fc71b6 - Ed25519 batch signature verification/4 time: [105.20 us 106.04 us 106.99 us] - Ed25519 batch signature verification/8 time: [178.66 us 179.01 us 179.39 us] - Ed25519 batch signature verification/16 time: [325.65 us 326.67 us 327.90 us] - Ed25519 batch signature verification/32 time: [617.96 us 620.74 us 624.12 us] - Ed25519 batch signature verification/64 time: [1.1862 ms 1.1900 ms 1.1943 ms] - Ed25519 batch signature verification/96 time: [1.7611 ms 1.7673 ms 1.7742 ms] - Ed25519 batch signature verification/128 time: [2.3320 ms 2.3376 ms 2.3446 ms] - Ed25519 batch signature verification/256 time: [5.0124 ms 5.0290 ms 5.0491 ms] +If your protocol or application is able to batch signatures for verification, +the `verify_batch()` function has greatly improved performance. As you can see, there's an optimal batch size for each machine, so you'll likely -want to test the benchmarks on your target CPU to discover the best size. For -this machine, around 100 signatures per batch is the optimum: +want to test the benchmarks on your target CPU to discover the best size. -![](https://github.com/dalek-cryptography/ed25519-dalek/blob/master/res/batch-violin-benchmark.svg) +## (Micro)Architecture Specific Backends -Additionally, thanks to Rust, this implementation has both type and memory -safety. It's also easily readable by a much larger set of people than those who -can read qhasm, making it more readily and more easily auditable. We're of -the opinion that, ultimately, these features—combined with speed—are more -valuable than simply cycle counts alone. +`ed25519-dalek` uses the backends from the `curve25519-dalek` crate. + +By default the serial backend is used and depending on the target +platform either the 32 bit or the 64 bit serial formula is automatically used. + +To address variety of usage scenarios various backends are available that +include hardware optimisations as well as a formally verified fiat crypto +backend that does not use any hardware optimisations. + +These backends can be overriden with various configuration predicates (cfg) + +Please see the [curve25519_dalek backend documentation](https://docs.rs/curve25519-dalek/latest/curve25519_dalek). + +# Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md) # A Note on Signature Malleability The signatures produced by this library are malleable, as discussed in [the original paper](https://ed25519.cr.yp.to/ed25519-20110926.pdf): -![](https://github.com/dalek-cryptography/ed25519-dalek/blob/master/res/ed25519-malleability.png) +![](https://cdn.jsdelivr.net/gh/dalek-cryptography/ed25519-dalek/docs/assets/ed25519-malleability.png) While the scalar component of our `Signature` struct is strictly *not* malleable, because reduction checks are put in place upon `Signature` @@ -175,51 +206,7 @@ prime order, but having a small cofactor of 8. If you wish to also eliminate this source of signature malleability, please review the -[documentation for the `verify_strict()` function](https://doc.dalek.rs/ed25519_dalek/struct.PublicKey.html#method.verify_strict). - -# Features - -## #![no_std] - -This library aims is fully `#![no_std]` compliant. No features need to be -enabled or disabled to suppose no-std. - -## Nightly Compilers - -To cause your application to build `ed25519-dalek` with the nightly feature -enabled by default, instead do: - -```toml -[dependencies.ed25519-dalek] -version = "1" -features = ["nightly"] -``` - -To cause your application to instead build with the nightly feature enabled -when someone builds with `cargo build --features="nightly"` add the following -to the `Cargo.toml`: - -```toml -[features] -nightly = ["ed25519-dalek/nightly"] -``` - -## Serde - -To enable [serde](https://serde.rs) support, build `ed25519-dalek` with the -`serde` feature. - -## (Micro)Architecture Specific Backends - -By default, `ed25519-dalek` builds against `curve25519-dalek`'s `u64_backend` -feature, which uses Rust's `i128` feature to achieve roughly double the speed as -the `u32_backend` feature. When targetting 32-bit systems, however, you'll -likely want to compile with `cargo build --no-default-features ---features="u32_backend"`. If you're building for a machine with avx2 -instructions, there's also the experimental `simd_backend`s, currently -comprising either avx2 or avx512 backends. To use them, compile with -`RUSTFLAGS="-C target_cpu=native" cargo build --no-default-features ---features="simd_backend"` +[documentation for the `verify_strict()` function](https://docs.rs/ed25519-dalek/latest/ed25519_dalek/struct.PublicKey.html#method.verify_strict). ## Batch Signature Verification diff --git a/res/ed25519-malleability.png b/docs/assets/ed25519-malleability.png similarity index 100% rename from res/ed25519-malleability.png rename to docs/assets/ed25519-malleability.png diff --git a/docs/assets/rustdoc-include-katex-header.html b/docs/assets/rustdoc-include-katex-header.html new file mode 100644 index 0000000..d240432 --- /dev/null +++ b/docs/assets/rustdoc-include-katex-header.html @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/res/batch-violin-benchmark.svg b/res/batch-violin-benchmark.svg deleted file mode 100644 index 418fa1d..0000000 --- a/res/batch-violin-benchmark.svg +++ /dev/null @@ -1,4251 +0,0 @@ - - - -Gnuplot -Produced by GNUPLOT 5.0 patchlevel 6 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Ed25519 batch signature verification/256 - - - - - Ed25519 batch signature verification/128 - - - - - Ed25519 batch signature verification/96 - - - - - Ed25519 batch signature verification/64 - - - - - Ed25519 batch signature verification/32 - - - - - Ed25519 batch signature verification/16 - - - - - Ed25519 batch signature verification/8 - - - - - Ed25519 batch signature verification/4 - - - - - - - - - - - - - 0 - - - - - - - - - - - - - 1 - - - - - - - - - - - - - 2 - - - - - - - - - - - - - 3 - - - - - - - - - - - - - 4 - - - - - - - - - - - - - 5 - - - - - - - - - - - - - 6 - - - - - - - - - Input - - - - - Average time (ms) - - - - - Ed25519 batch signature verification: Violin plot - - - PDF - - - PDF - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - gnuplot_plot_2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - gnuplot_plot_3 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - gnuplot_plot_4 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - gnuplot_plot_5 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - gnuplot_plot_6 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - gnuplot_plot_7 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - gnuplot_plot_8 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -