From 2658e9e94eaeb5b267508cc28c49f2b9f376f722 Mon Sep 17 00:00:00 2001 From: Jason Davies Date: Thu, 29 Sep 2022 12:07:21 +0100 Subject: [PATCH 01/21] Fix link to audit by Inference AG. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f58e8c14..74dc081c 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ These libraries are currently under development and have not been fully-reviewed The following audits have been done on the MASP protocol: -* [Inference AG](https://github.com/anoma/namada-audit/blob/main/audits/report-anoma-inference.pdf) +* [Inference AG](https://github.com/anoma/namada/blob/main/audits/report-anoma-inference.pdf) * [Least Authority](https://leastauthority.com/static/publications/LeastAuthority_Tezos_Foundation_Multi_Asset_Shielded_Pool_Audit_Report.pdf) In addition, the original Zcash Sapling protocol was audited without the MASP extensions: From d331c4e4cdb9f7d35e9adcfdcb705f7571190f03 Mon Sep 17 00:00:00 2001 From: joe <55120843+joebebel@users.noreply.github.com> Date: Sat, 1 Apr 2023 15:17:45 -0700 Subject: [PATCH 02/21] Merge murisi/masp-incentive, newest librustzcash + v5 tx format + remove librustzcash as dep (#41) Co-authored-by: Murisi Tarusenga Co-authored-by: Song Xuyang --- .github/workflows/ci.yml | 155 +- Cargo.toml | 1 + README.md | 5 +- cobertura.xml | 1 + docs/Makefile.uptodate | 0 docs/protocol.pdf | Bin 0 -> 403683 bytes docs/protocol.ver | 2 + masp_note_encryption/CHANGELOG.md | 14 + masp_note_encryption/Cargo.toml | 35 + masp_note_encryption/LICENSE-APACHE | 202 ++ masp_note_encryption/LICENSE-MIT | 21 + masp_note_encryption/README.md | 28 + masp_note_encryption/src/batch.rs | 86 + masp_note_encryption/src/lib.rs | 705 ++++++ masp_primitives/Cargo.toml | 86 +- masp_primitives/src/asset_type.rs | 68 +- masp_primitives/src/consensus.rs | 388 ++++ masp_primitives/src/constants.rs | 4 +- masp_primitives/src/convert.rs | 186 +- masp_primitives/src/keys.rs | 216 +- masp_primitives/src/lib.rs | 13 +- masp_primitives/src/memo.rs | 413 ++++ masp_primitives/src/merkle_tree.rs | 615 +++++- masp_primitives/src/primitives.rs | 330 --- masp_primitives/src/sapling.rs | 721 ++++++- masp_primitives/src/sapling/group_hash.rs | 43 + masp_primitives/src/sapling/keys.rs | 277 +++ .../src/sapling/note_encryption.rs | 1509 +++++++++++++ .../src/{ => sapling}/pedersen_hash.rs | 0 masp_primitives/src/{ => sapling}/prover.rs | 46 +- .../src/{ => sapling}/redjubjub.rs | 56 +- masp_primitives/src/sapling/util.rs | 37 + .../src/test_vectors/note_encryption.rs | 1750 +++++++-------- .../src/test_vectors/note_encryption_new.rs | 564 +++++ .../src/test_vectors/pedersen_hash_vectors.rs | 6 +- masp_primitives/src/transaction.rs | 646 ++++++ masp_primitives/src/transaction/builder.rs | 660 ++++++ masp_primitives/src/transaction/components.rs | 13 + .../src/transaction/components/amount.rs | 447 ++++ .../src/transaction/components/sapling.rs | 664 ++++++ .../transaction/components/sapling/builder.rs | 782 +++++++ .../transaction/components/sapling/fees.rs | 20 + .../src/transaction/components/transparent.rs | 221 ++ .../components/transparent/builder.rs | 225 ++ .../components/transparent/fees.rs | 31 + masp_primitives/src/transaction/fees.rs | 28 + masp_primitives/src/transaction/fees/fixed.rs | 48 + masp_primitives/src/transaction/sighash.rs | 77 + masp_primitives/src/transaction/sighash_v5.rs | 51 + masp_primitives/src/transaction/txid.rs | 389 ++++ masp_primitives/src/zip32.rs | 1284 +---------- masp_primitives/src/zip32/sapling.rs | 1922 +++++++++++++++++ masp_proofs/Cargo.toml | 43 +- masp_proofs/benches/convert.rs | 18 +- masp_proofs/benches/sapling.rs | 7 +- masp_proofs/examples/download-params.rs | 2 +- masp_proofs/examples/serialize-params.rs | 9 +- masp_proofs/params/.gitattributes | 3 + masp_proofs/params/masp-convert.vk | 3 + masp_proofs/params/masp-output.vk | 3 + masp_proofs/params/masp-spend.vk | 3 + masp_proofs/src/circuit.rs | 1 + masp_proofs/src/circuit/convert.rs | 30 +- masp_proofs/src/circuit/ecc.rs | 1114 ++++++++++ masp_proofs/src/circuit/pedersen_hash.rs | 9 +- masp_proofs/src/circuit/sapling.rs | 66 +- masp_proofs/src/constants.rs | 55 +- masp_proofs/src/downloadreader.rs | 84 + masp_proofs/src/hashreader.rs | 18 +- masp_proofs/src/lib.rs | 404 +++- masp_proofs/src/params.rs | 11 +- masp_proofs/src/prover.rs | 24 +- masp_proofs/src/sapling/mod.rs | 2 +- masp_proofs/src/sapling/prover.rs | 21 +- masp_proofs/src/sapling/verifier.rs | 120 +- masp_proofs/src/sapling/verifier/batch.rs | 211 ++ masp_proofs/src/sapling/verifier/single.rs | 119 + rust-toolchain | 1 - rust-toolchain.toml | 3 + 79 files changed, 15315 insertions(+), 3160 deletions(-) create mode 100644 cobertura.xml create mode 100644 docs/Makefile.uptodate create mode 100644 docs/protocol.pdf create mode 100644 docs/protocol.ver create mode 100644 masp_note_encryption/CHANGELOG.md create mode 100644 masp_note_encryption/Cargo.toml create mode 100644 masp_note_encryption/LICENSE-APACHE create mode 100644 masp_note_encryption/LICENSE-MIT create mode 100644 masp_note_encryption/README.md create mode 100644 masp_note_encryption/src/batch.rs create mode 100644 masp_note_encryption/src/lib.rs create mode 100644 masp_primitives/src/consensus.rs create mode 100644 masp_primitives/src/memo.rs delete mode 100644 masp_primitives/src/primitives.rs create mode 100644 masp_primitives/src/sapling/group_hash.rs create mode 100644 masp_primitives/src/sapling/keys.rs create mode 100644 masp_primitives/src/sapling/note_encryption.rs rename masp_primitives/src/{ => sapling}/pedersen_hash.rs (100%) rename masp_primitives/src/{ => sapling}/prover.rs (85%) rename masp_primitives/src/{ => sapling}/redjubjub.rs (88%) create mode 100644 masp_primitives/src/sapling/util.rs create mode 100644 masp_primitives/src/test_vectors/note_encryption_new.rs create mode 100644 masp_primitives/src/transaction.rs create mode 100644 masp_primitives/src/transaction/builder.rs create mode 100644 masp_primitives/src/transaction/components.rs create mode 100644 masp_primitives/src/transaction/components/amount.rs create mode 100644 masp_primitives/src/transaction/components/sapling.rs create mode 100644 masp_primitives/src/transaction/components/sapling/builder.rs create mode 100644 masp_primitives/src/transaction/components/sapling/fees.rs create mode 100644 masp_primitives/src/transaction/components/transparent.rs create mode 100644 masp_primitives/src/transaction/components/transparent/builder.rs create mode 100644 masp_primitives/src/transaction/components/transparent/fees.rs create mode 100644 masp_primitives/src/transaction/fees.rs create mode 100644 masp_primitives/src/transaction/fees/fixed.rs create mode 100644 masp_primitives/src/transaction/sighash.rs create mode 100644 masp_primitives/src/transaction/sighash_v5.rs create mode 100644 masp_primitives/src/transaction/txid.rs create mode 100644 masp_primitives/src/zip32/sapling.rs create mode 100644 masp_proofs/params/.gitattributes create mode 100644 masp_proofs/params/masp-convert.vk create mode 100644 masp_proofs/params/masp-output.vk create mode 100644 masp_proofs/params/masp-spend.vk create mode 100644 masp_proofs/src/circuit/ecc.rs create mode 100644 masp_proofs/src/downloadreader.rs create mode 100644 masp_proofs/src/sapling/verifier/batch.rs create mode 100644 masp_proofs/src/sapling/verifier/single.rs delete mode 100644 rust-toolchain create mode 100644 rust-toolchain.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a9680d1a..2d3aca5c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,19 +11,18 @@ jobs: os: [ubuntu-latest, windows-latest, macOS-latest] steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v3 with: - toolchain: 1.56.1 - override: true - + lfs: true + - name: Checkout LFS objects + run: git lfs checkout - name: Fetch path to MASP parameters working-directory: ./masp_proofs shell: bash run: echo "MASP_PARAMS=$(cargo run --release --example get-params-path --features directories)" >> $GITHUB_ENV - name: Cache MASP parameters id: cache-params - uses: actions/cache@v2 + uses: actions/cache@v3.2.4 with: path: ${{ env.MASP_PARAMS }} key: ${{ runner.os }}-params @@ -33,16 +32,9 @@ jobs: run: cargo run --release --example download-params --features download-params - name: Run tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --all-features --verbose --release --all + run: cargo test --all-features --verbose --release --all - name: Run slow tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --all-features --verbose --release --all -- --ignored - + run: cargo test --all-features --verbose --release --all -- --ignored build: name: Build target ${{ matrix.target }} runs-on: ubuntu-latest @@ -53,17 +45,14 @@ jobs: - wasm32-wasi steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v3 with: - toolchain: 1.56.1 - override: true + lfs: true + - name: Checkout LFS objects + run: git lfs checkout - name: Add target run: rustup target add ${{ matrix.target }} - - name: cargo fetch - uses: actions-rs/cargo@v1 - with: - command: fetch + - run: cargo fetch - name: Build masp_proofs for target working-directory: ./masp_proofs run: cargo build --verbose --no-default-features --target ${{ matrix.target }} @@ -72,35 +61,22 @@ jobs: bitrot: name: Bitrot check runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - toolchain: 1.56.1 - override: true + - uses: actions/checkout@v3 # Build benchmarks to prevent bitrot - name: Build benchmarks - uses: actions-rs/cargo@v1 - with: - command: build - args: --all --benches + run: cargo build --all --benches clippy: - name: Lint #Clippy (1.56.1) + name: Lint #Clippy (MSRV) timeout-minutes: 30 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - toolchain: 1.56.1 - components: clippy - override: true + - uses: actions/checkout@v3 - name: Run clippy uses: actions-rs/clippy-check@v1 with: - name: Clippy (1.56.1) + name: Clippy (MSRV) token: ${{ secrets.GITHUB_TOKEN }} args: --all-features --all-targets -- -D warnings @@ -110,12 +86,10 @@ jobs: runs-on: ubuntu-latest continue-on-error: true steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - toolchain: beta - components: clippy - override: true + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@beta + id: toolchain + - run: rustup override set ${{steps.toolchain.outputs.name}} - name: Run Clippy (beta) uses: actions-rs/clippy-check@v1 continue-on-error: true @@ -124,95 +98,24 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} args: --all-features --all-targets -- -W clippy::all - codecov: - name: Code coverage - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - # Use stable for this to ensure that cargo-tarpaulin can be built. - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - override: true - - name: Install cargo-tarpaulin - uses: actions-rs/cargo@v1 - with: - command: install - args: cargo-tarpaulin - - - name: Fetch path to MASP parameters - working-directory: ./masp_proofs - shell: bash - run: echo "MASP_PARAMS=$(cargo run --release --example get-params-path --features directories)" >> $GITHUB_ENV - - name: Cache MASP parameters - id: cache-params - uses: actions/cache@v2 - with: - path: ${{ env.MASP_PARAMS }} - key: ${{ runner.os }}-params - - name: Fetch MASP parameters - if: steps.cache-params.outputs.cache-hit != 'true' - working-directory: ./masp_proofs - run: cargo run --release --example download-params --features download-params - - - name: Generate coverage report - uses: actions-rs/cargo@v1 - with: - command: tarpaulin - args: --all-features --release --timeout 600 --out Xml - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v2.1.0 - doc-links: name: Intra-doc links runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - toolchain: 1.56.1 - override: true - - name: cargo fetch - uses: actions-rs/cargo@v1 - with: - command: fetch - - # Ensure intra-documentation links all resolve correctly - # Requires #![deny(intra_doc_link_resolution_failure)] in crates. + - uses: actions/checkout@v3 + - run: cargo fetch + # Requires #![deny(rustdoc::broken_intra_doc_links)] in crates. - name: Check intra-doc links - uses: actions-rs/cargo@v1 - with: - command: doc - args: --all --document-private-items + run: cargo doc --all --document-private-items fmt: name: Rustfmt timeout-minutes: 30 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - toolchain: 1.56.1 - override: true - - # cargo fmt does not build the code, and running it in a fresh clone of - # the codebase will fail because the protobuf code has not been generated. - - name: cargo build - uses: actions-rs/cargo@v1 - with: - command: build - args: --all - - # Ensure all code has been formatted with rustfmt - - run: rustup component add rustfmt + - uses: actions/checkout@v3 - name: Check formatting - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check + run: cargo fmt --all -- --check security: name: Security - Cargo Audit @@ -220,8 +123,8 @@ jobs: container: image: rustlang/rust:nightly steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install cargo-audit run: cargo install cargo-audit - name: Cargo Audit - run: cargo audit + run: cargo audit \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index cf0b4471..e56ffe8c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ + "masp_note_encryption", "masp_proofs", "masp_primitives" ] diff --git a/README.md b/README.md index 74dc081c..fbd80654 100644 --- a/README.md +++ b/README.md @@ -28,10 +28,7 @@ This Rust repository, by itself, is not a complete implementation of the MASP, a The `masp_proofs` crate contains the modified Spend and Output circuits to support multiple assets. -Much of the code that supports the original Sapling circuits can be shared and reused with the MASP circuits. Therefore, the Zcash code is reused in two ways: - -1. Some code can be reused unmodified from `zcash_primitives` and `zcash_proofs` crates from `librustzcash`, which are directly imported from `librustzcash`'s GitHub repository. -2. Code from `zcash_primitives` and `zcash_proofs` that cannot be reused without modifications, or due to Rust import visibility rules, is placed in `masp_primitives` and `masp_proofs`. +Much of the code that supports the original Sapling circuits can be shared and reused with the MASP circuits. Therefore, certain code from `zcash_primitives` and `zcash_proofs` is placed in `masp_primitives` and `masp_proofs`. New code related to asset types is included in `masp_primitives`. diff --git a/cobertura.xml b/cobertura.xml new file mode 100644 index 00000000..2319fc18 --- /dev/null +++ b/cobertura.xml @@ -0,0 +1 @@ +/Users/bebel/heliax/masp \ No newline at end of file diff --git a/docs/Makefile.uptodate b/docs/Makefile.uptodate new file mode 100644 index 00000000..e69de29b diff --git a/docs/protocol.pdf b/docs/protocol.pdf new file mode 100644 index 0000000000000000000000000000000000000000..30be12d4781f35aba2bcfebaf5c7a16d8e381f08 GIT binary patch literal 403683 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

sKRuED6=Zd9TSZe--i!uNgaF> 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 SSQwcYfy@Zx%b&(M)#$ literal 0 HcmV?d00001 diff --git a/docs/protocol.ver b/docs/protocol.ver new file mode 100644 index 00000000..b05130c9 --- /dev/null +++ b/docs/protocol.ver @@ -0,0 +1,2 @@ +\toggletrue{issapling} +\renewcommand{\docversion}{Version [\SaplingSpec]} \ No newline at end of file diff --git a/masp_note_encryption/CHANGELOG.md b/masp_note_encryption/CHANGELOG.md new file mode 100644 index 00000000..b3a09977 --- /dev/null +++ b/masp_note_encryption/CHANGELOG.md @@ -0,0 +1,14 @@ +# Changelog +All notable changes to this library will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this library adheres to Rust's notion of +[Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Changed +- MSRV is now 1.56.1. + +## [0.1.0] - 2021-12-17 +Initial release (of `zcash_note_encryption`). diff --git a/masp_note_encryption/Cargo.toml b/masp_note_encryption/Cargo.toml new file mode 100644 index 00000000..afd7920d --- /dev/null +++ b/masp_note_encryption/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "masp_note_encryption" +description = "Note encryption for MASP transactions" +version = "0.2.0" +authors = [ + "Jack Grigg ", + "Kris Nuttycombe ", + "joe@heliax.dev" +] +homepage = "https://github.com/anoma/masp" +repository = "https://github.com/anoma/masp" +readme = "README.md" +license = "MIT OR Apache-2.0" +edition = "2021" +categories = ["cryptography::cryptocurrencies"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[dependencies] +cipher = { version = "0.4", default-features = false } +chacha20 = { version = "0.9", default-features = false } +chacha20poly1305 = { version = "0.10", default-features = false } +rand_core = { version = "0.6", default-features = false } +subtle = { version = "2.3", default-features = false } +borsh = {version = "0.9", features = ["const-generics"]} + +[features] +default = ["alloc"] +alloc = [] +pre-zip-212 = [] + +[lib] +bench = false diff --git a/masp_note_encryption/LICENSE-APACHE b/masp_note_encryption/LICENSE-APACHE new file mode 100644 index 00000000..1e5006dc --- /dev/null +++ b/masp_note_encryption/LICENSE-APACHE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + diff --git a/masp_note_encryption/LICENSE-MIT b/masp_note_encryption/LICENSE-MIT new file mode 100644 index 00000000..9500c140 --- /dev/null +++ b/masp_note_encryption/LICENSE-MIT @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2021 Electric Coin Company + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/masp_note_encryption/README.md b/masp_note_encryption/README.md new file mode 100644 index 00000000..aaa3f772 --- /dev/null +++ b/masp_note_encryption/README.md @@ -0,0 +1,28 @@ +# masp_note_encryption + +This crate implements the [in-band secret distribution scheme] for the MASP protocol. It provides reusable methods that implement common note encryption +and trial decryption logic, and enforce protocol-agnostic verification requirements. + +Protocol-specific logic is handled via the `Domain` trait. Implementation of this +trait is provided in the [`masp_primitives`] crate; +users with their own existing types can similarly implement the trait themselves. + +[in-band secret distribution scheme]: https://zips.z.cash/protocol/protocol.pdf#saplingandorchardinband +[`masp_primitives`]: https://github.com/anoma/masp + +## License + +Licensed under either of + + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or + http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally +submitted for inclusion in the work by you, as defined in the Apache-2.0 +license, shall be dual licensed as above, without any additional terms or +conditions. diff --git a/masp_note_encryption/src/batch.rs b/masp_note_encryption/src/batch.rs new file mode 100644 index 00000000..ad704167 --- /dev/null +++ b/masp_note_encryption/src/batch.rs @@ -0,0 +1,86 @@ +//! APIs for batch trial decryption. + +use alloc::vec::Vec; // module is alloc only + +use crate::{ + try_compact_note_decryption_inner, try_note_decryption_inner, BatchDomain, EphemeralKeyBytes, + ShieldedOutput, COMPACT_NOTE_SIZE, ENC_CIPHERTEXT_SIZE, +}; + +/// Trial decryption of a batch of notes with a set of recipients. +/// +/// This is the batched version of [`crate::try_note_decryption`]. +/// +/// Returns a vector containing the decrypted result for each output, +/// with the same length and in the same order as the outputs were +/// provided, along with the index in the `ivks` slice associated with +/// the IVK that successfully decrypted the output. +#[allow(clippy::type_complexity)] +pub fn try_note_decryption>( + ivks: &[D::IncomingViewingKey], + outputs: &[(D, Output)], +) -> Vec> { + batch_note_decryption(ivks, outputs, try_note_decryption_inner) +} + +/// Trial decryption of a batch of notes for light clients with a set of recipients. +/// +/// This is the batched version of [`crate::try_compact_note_decryption`]. +/// +/// Returns a vector containing the decrypted result for each output, +/// with the same length and in the same order as the outputs were +/// provided, along with the index in the `ivks` slice associated with +/// the IVK that successfully decrypted the output. +#[allow(clippy::type_complexity)] +pub fn try_compact_note_decryption>( + ivks: &[D::IncomingViewingKey], + outputs: &[(D, Output)], +) -> Vec> { + batch_note_decryption(ivks, outputs, try_compact_note_decryption_inner) +} + +fn batch_note_decryption, F, FR, const CS: usize>( + ivks: &[D::IncomingViewingKey], + outputs: &[(D, Output)], + decrypt_inner: F, +) -> Vec> +where + F: Fn(&D, &D::IncomingViewingKey, &EphemeralKeyBytes, &Output, &D::SymmetricKey) -> Option, +{ + if ivks.is_empty() { + return (0..outputs.len()).map(|_| None).collect(); + }; + + // Fetch the ephemeral keys for each output, and batch-parse and prepare them. + let ephemeral_keys = D::batch_epk(outputs.iter().map(|(_, output)| output.ephemeral_key())); + + // Derive the shared secrets for all combinations of (ivk, output). + // The scalar multiplications cannot benefit from batching. + let items = ephemeral_keys.iter().flat_map(|(epk, ephemeral_key)| { + ivks.iter().map(move |ivk| { + ( + epk.as_ref().map(|epk| D::ka_agree_dec(ivk, epk)), + ephemeral_key, + ) + }) + }); + + // Run the batch-KDF to obtain the symmetric keys from the shared secrets. + let keys = D::batch_kdf(items); + + // Finish the trial decryption! + keys.chunks(ivks.len()) + .zip(ephemeral_keys.iter().zip(outputs.iter())) + .map(|(key_chunk, ((_, ephemeral_key), (domain, output)))| { + key_chunk + .iter() + .zip(ivks.iter().enumerate()) + .filter_map(|(key, (i, ivk))| { + key.as_ref() + .and_then(|key| decrypt_inner(domain, ivk, ephemeral_key, output, key)) + .map(|out| (out, i)) + }) + .next() + }) + .collect::>>() +} diff --git a/masp_note_encryption/src/lib.rs b/masp_note_encryption/src/lib.rs new file mode 100644 index 00000000..182b6897 --- /dev/null +++ b/masp_note_encryption/src/lib.rs @@ -0,0 +1,705 @@ +//! Note encryption for MASP transactions. +//! +//! This crate implements the [in-band secret distribution scheme] for the MASP Sapling +//! protocol. It provides reusable methods that implement common note encryption +//! and trial decryption logic, and enforce protocol-agnostic verification requirements. +//! +//! Protocol-specific logic is handled via the [`Domain`] trait. Implementations of this +//! trait are provided in the [`masp_primitives`] (for MASP Sapling) crate; +//! users with their own existing types can similarly implement the trait themselves. +//! +//! [in-band secret distribution scheme]: https://zips.z.cash/protocol/protocol.pdf#saplingandorchardinband +//! [`masp_primitives`]: https://github.com/anoma/masp + +#![no_std] +#![cfg_attr(docsrs, feature(doc_cfg))] +// Catch documentation errors caused by code changes. +#![deny(rustdoc::broken_intra_doc_links)] +#![deny(unsafe_code)] +// TODO: #![deny(missing_docs)] + +#[cfg(feature = "alloc")] +extern crate alloc; +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + +use core::convert::TryInto; + +use chacha20::{ + cipher::{StreamCipher, StreamCipherSeek}, + ChaCha20, +}; +use chacha20poly1305::{aead::AeadInPlace, ChaCha20Poly1305, KeyInit}; +use cipher::KeyIvInit; + +//use crate::constants::ASSET_IDENTIFIER_LENGTH; +pub const ASSET_IDENTIFIER_LENGTH: usize = 32; +use borsh::{BorshDeserialize, BorshSerialize}; +use rand_core::RngCore; +use subtle::{Choice, ConstantTimeEq}; + +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +pub mod batch; + +/// The size of a compact note. +pub const COMPACT_NOTE_SIZE: usize = 1 + // version + 11 + // diversifier + 8 + // value + ASSET_IDENTIFIER_LENGTH + // asset_type.identifier + 32; // rseed (or rcm prior to ZIP 212) +/// The size of [`NotePlaintextBytes`]. +pub const NOTE_PLAINTEXT_SIZE: usize = COMPACT_NOTE_SIZE + 512; +/// The size of [`OutPlaintextBytes`]. +pub const OUT_PLAINTEXT_SIZE: usize = 32 + // pk_d + 32; // esk +const AEAD_TAG_SIZE: usize = 16; +/// The size of an encrypted note plaintext. +pub const ENC_CIPHERTEXT_SIZE: usize = NOTE_PLAINTEXT_SIZE + AEAD_TAG_SIZE; +/// The size of an encrypted outgoing plaintext. +pub const OUT_CIPHERTEXT_SIZE: usize = OUT_PLAINTEXT_SIZE + AEAD_TAG_SIZE; + +/// A symmetric key that can be used to recover a single Sapling or Orchard output. +pub struct OutgoingCipherKey(pub [u8; 32]); + +impl From<[u8; 32]> for OutgoingCipherKey { + fn from(ock: [u8; 32]) -> Self { + OutgoingCipherKey(ock) + } +} + +impl AsRef<[u8]> for OutgoingCipherKey { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +/// Newtype representing the byte encoding of an [`EphemeralPublicKey`]. +/// +/// [`EphemeralPublicKey`]: Domain::EphemeralPublicKey +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct EphemeralKeyBytes(pub [u8; 32]); + +impl AsRef<[u8]> for EphemeralKeyBytes { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl From<[u8; 32]> for EphemeralKeyBytes { + fn from(value: [u8; 32]) -> EphemeralKeyBytes { + EphemeralKeyBytes(value) + } +} + +impl ConstantTimeEq for EphemeralKeyBytes { + fn ct_eq(&self, other: &Self) -> Choice { + self.0.ct_eq(&other.0) + } +} + +/// Newtype representing the byte encoding of a note plaintext. +pub struct NotePlaintextBytes(pub [u8; NOTE_PLAINTEXT_SIZE]); +/// Newtype representing the byte encoding of a outgoing plaintext. +pub struct OutPlaintextBytes(pub [u8; OUT_PLAINTEXT_SIZE]); + +#[derive(Copy, Clone, PartialEq, Eq)] +enum NoteValidity { + Valid, + Invalid, +} + +/// Trait that encapsulates protocol-specific note encryption types and logic. +/// +/// This trait enables most of the note encryption logic to be shared between Sapling and +/// Orchard, as well as between different implementations of those protocols. +pub trait Domain { + type EphemeralSecretKey: ConstantTimeEq; + type EphemeralPublicKey; + type PreparedEphemeralPublicKey; + type SharedSecret; + type SymmetricKey: AsRef<[u8]>; + type Note; + type Recipient; + type DiversifiedTransmissionKey; + type IncomingViewingKey; + type OutgoingViewingKey; + type ValueCommitment; + type ExtractedCommitment; + type ExtractedCommitmentBytes: Eq + for<'a> From<&'a Self::ExtractedCommitment>; + type Memo; + + /// Derives the `EphemeralSecretKey` corresponding to this note. + /// + /// Returns `None` if the note was created prior to [ZIP 212], and doesn't have a + /// deterministic `EphemeralSecretKey`. + /// + /// [ZIP 212]: https://zips.z.cash/zip-0212 + fn derive_esk(note: &Self::Note) -> Option; + + /// Extracts the `DiversifiedTransmissionKey` from the note. + fn get_pk_d(note: &Self::Note) -> Self::DiversifiedTransmissionKey; + + /// Prepare an ephemeral public key for more efficient scalar multiplication. + fn prepare_epk(epk: Self::EphemeralPublicKey) -> Self::PreparedEphemeralPublicKey; + + /// Derives `EphemeralPublicKey` from `esk` and the note's diversifier. + fn ka_derive_public( + note: &Self::Note, + esk: &Self::EphemeralSecretKey, + ) -> Self::EphemeralPublicKey; + + /// Derives the `SharedSecret` from the sender's information during note encryption. + fn ka_agree_enc( + esk: &Self::EphemeralSecretKey, + pk_d: &Self::DiversifiedTransmissionKey, + ) -> Self::SharedSecret; + + /// Derives the `SharedSecret` from the recipient's information during note trial + /// decryption. + fn ka_agree_dec( + ivk: &Self::IncomingViewingKey, + epk: &Self::PreparedEphemeralPublicKey, + ) -> Self::SharedSecret; + + /// Derives the `SymmetricKey` used to encrypt the note plaintext. + /// + /// `secret` is the `SharedSecret` obtained from [`Self::ka_agree_enc`] or + /// [`Self::ka_agree_dec`]. + /// + /// `ephemeral_key` is the byte encoding of the [`EphemeralPublicKey`] used to derive + /// `secret`. During encryption it is derived via [`Self::epk_bytes`]; during trial + /// decryption it is obtained from [`ShieldedOutput::ephemeral_key`]. + /// + /// [`EphemeralPublicKey`]: Self::EphemeralPublicKey + /// [`EphemeralSecretKey`]: Self::EphemeralSecretKey + fn kdf(secret: Self::SharedSecret, ephemeral_key: &EphemeralKeyBytes) -> Self::SymmetricKey; + + /// Encodes the given `Note` and `Memo` as a note plaintext. + /// + /// # Future breaking changes + /// + /// The `recipient` argument is present as a secondary way to obtain the diversifier; + /// this is due to a historical quirk of how the Sapling `Note` struct was implemented + /// in the `zcash_primitives` crate. `recipient` will be removed from this method in a + /// future crate release, once [`zcash_primitives` has been refactored]. + /// + /// [`zcash_primitives` has been refactored]: https://github.com/zcash/librustzcash/issues/454 + fn note_plaintext_bytes( + note: &Self::Note, + recipient: &Self::Recipient, + memo: &Self::Memo, + ) -> NotePlaintextBytes; + + /// Derives the [`OutgoingCipherKey`] for an encrypted note, given the note-specific + /// public data and an `OutgoingViewingKey`. + fn derive_ock( + ovk: &Self::OutgoingViewingKey, + cv: &Self::ValueCommitment, + cmstar_bytes: &Self::ExtractedCommitmentBytes, + ephemeral_key: &EphemeralKeyBytes, + ) -> OutgoingCipherKey; + + /// Encodes the outgoing plaintext for the given note. + fn outgoing_plaintext_bytes( + note: &Self::Note, + esk: &Self::EphemeralSecretKey, + ) -> OutPlaintextBytes; + + /// Returns the byte encoding of the given `EphemeralPublicKey`. + fn epk_bytes(epk: &Self::EphemeralPublicKey) -> EphemeralKeyBytes; + + /// Attempts to parse `ephemeral_key` as an `EphemeralPublicKey`. + /// + /// Returns `None` if `ephemeral_key` is not a valid byte encoding of an + /// `EphemeralPublicKey`. + fn epk(ephemeral_key: &EphemeralKeyBytes) -> Option; + + /// Derives the `ExtractedCommitment` for this note. + fn cmstar(note: &Self::Note) -> Self::ExtractedCommitment; + + /// Parses the given note plaintext from the recipient's perspective. + /// + /// The implementation of this method must check that: + /// - The note plaintext version is valid (for the given decryption domain's context, + /// which may be passed via `self`). + /// - The note plaintext contains valid encodings of its various fields. + /// - Any domain-specific requirements are satisfied. + /// + /// `&self` is passed here to enable the implementation to enforce contextual checks, + /// such as rules like [ZIP 212] that become active at a specific block height. + /// + /// [ZIP 212]: https://zips.z.cash/zip-0212 + /// + /// # Panics + /// + /// Panics if `plaintext` is shorter than [`COMPACT_NOTE_SIZE`]. + fn parse_note_plaintext_without_memo_ivk( + &self, + ivk: &Self::IncomingViewingKey, + plaintext: &[u8], + ) -> Option<(Self::Note, Self::Recipient)>; + + /// Parses the given note plaintext from the sender's perspective. + /// + /// The implementation of this method must check that: + /// - The note plaintext version is valid (for the given decryption domain's context, + /// which may be passed via `self`). + /// - The note plaintext contains valid encodings of its various fields. + /// - Any domain-specific requirements are satisfied. + /// - `ephemeral_key` can be derived from `esk` and the diversifier within the note + /// plaintext. + /// + /// `&self` is passed here to enable the implementation to enforce contextual checks, + /// such as rules like [ZIP 212] that become active at a specific block height. + /// + /// [ZIP 212]: https://zips.z.cash/zip-0212 + fn parse_note_plaintext_without_memo_ovk( + &self, + pk_d: &Self::DiversifiedTransmissionKey, + esk: &Self::EphemeralSecretKey, + ephemeral_key: &EphemeralKeyBytes, + plaintext: &NotePlaintextBytes, + ) -> Option<(Self::Note, Self::Recipient)>; + + /// Extracts the memo field from the given note plaintext. + /// + /// # Compatibility + /// + /// `&self` is passed here in anticipation of future changes to memo handling, where + /// the memos may no longer be part of the note plaintext. + fn extract_memo(&self, plaintext: &NotePlaintextBytes) -> Self::Memo; + + /// Parses the `DiversifiedTransmissionKey` field of the outgoing plaintext. + /// + /// Returns `None` if `out_plaintext` does not contain a valid byte encoding of a + /// `DiversifiedTransmissionKey`. + fn extract_pk_d(out_plaintext: &OutPlaintextBytes) -> Option; + + /// Parses the `EphemeralSecretKey` field of the outgoing plaintext. + /// + /// Returns `None` if `out_plaintext` does not contain a valid byte encoding of an + /// `EphemeralSecretKey`. + fn extract_esk(out_plaintext: &OutPlaintextBytes) -> Option; +} + +/// Trait that encapsulates protocol-specific batch trial decryption logic. +/// +/// Each batchable operation has a default implementation that calls through to the +/// non-batched implementation. Domains can override whichever operations benefit from +/// batched logic. +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +pub trait BatchDomain: Domain { + /// Computes `Self::kdf` on a batch of items. + /// + /// For each item in the batch, if the shared secret is `None`, this returns `None` at + /// that position. + fn batch_kdf<'a>( + items: impl Iterator, &'a EphemeralKeyBytes)>, + ) -> Vec> { + // Default implementation: do the non-batched thing. + items + .map(|(secret, ephemeral_key)| secret.map(|secret| Self::kdf(secret, ephemeral_key))) + .collect() + } + + /// Computes `Self::epk` on a batch of ephemeral keys. + /// + /// This is useful for protocols where the underlying curve requires an inversion to + /// parse an encoded point. + /// + /// For usability, this returns tuples of the ephemeral keys and the result of parsing + /// them. + fn batch_epk( + ephemeral_keys: impl Iterator, + ) -> Vec<(Option, EphemeralKeyBytes)> { + // Default implementation: do the non-batched thing. + ephemeral_keys + .map(|ephemeral_key| { + ( + Self::epk(&ephemeral_key).map(Self::prepare_epk), + ephemeral_key, + ) + }) + .collect() + } +} + +/// Trait that provides access to the components of an encrypted transaction output. +/// +/// Implementations of this trait are required to define the length of their ciphertext +/// field. In order to use the trial decryption APIs in this crate, the length must be +/// either [`ENC_CIPHERTEXT_SIZE`] or [`COMPACT_NOTE_SIZE`]. +pub trait ShieldedOutput { + /// Exposes the `ephemeral_key` field of the output. + fn ephemeral_key(&self) -> EphemeralKeyBytes; + + /// Exposes the `cmu_bytes` or `cmx_bytes` field of the output. + fn cmstar_bytes(&self) -> D::ExtractedCommitmentBytes; + + /// Exposes the note ciphertext of the output. + fn enc_ciphertext(&self) -> &[u8; CIPHERTEXT_SIZE]; +} + +/// A struct containing context required for encrypting Sapling and Orchard notes. +/// +/// This struct provides a safe API for encrypting Sapling and Orchard notes. In particular, it +/// enforces that fresh ephemeral keys are used for every note, and that the ciphertexts are +/// consistent with each other. +/// +/// Implements section 4.19 of the +/// [Zcash Protocol Specification](https://zips.z.cash/protocol/nu5.pdf#saplingandorchardinband) + +pub struct NoteEncryption { + epk: D::EphemeralPublicKey, + esk: D::EphemeralSecretKey, + note: D::Note, + to: D::Recipient, + memo: D::Memo, + /// `None` represents the `ovk = ⊥` case. + ovk: Option, +} + +impl NoteEncryption { + /// Construct a new note encryption context for the specified note, + /// recipient, and memo. + pub fn new( + ovk: Option, + note: D::Note, + to: D::Recipient, + memo: D::Memo, + ) -> Self { + let esk = D::derive_esk(¬e).expect("ZIP 212 is active."); + NoteEncryption { + epk: D::ka_derive_public(¬e, &esk), + esk, + note, + to, + memo, + ovk, + } + } + + /// For use only with Sapling. This method is preserved in order that test code + /// be able to generate pre-ZIP-212 ciphertexts so that tests can continue to + /// cover pre-ZIP-212 transaction decryption. + #[cfg(feature = "pre-zip-212")] + #[cfg_attr(docsrs, doc(cfg(feature = "pre-zip-212")))] + pub fn new_with_esk( + esk: D::EphemeralSecretKey, + ovk: Option, + note: D::Note, + to: D::Recipient, + memo: D::Memo, + ) -> Self { + NoteEncryption { + epk: D::ka_derive_public(¬e, &esk), + esk, + note, + to, + memo, + ovk, + } + } + + /// Exposes the ephemeral secret key being used to encrypt this note. + pub fn esk(&self) -> &D::EphemeralSecretKey { + &self.esk + } + + /// Exposes the encoding of the ephemeral public key being used to encrypt this note. + pub fn epk(&self) -> &D::EphemeralPublicKey { + &self.epk + } + + /// Generates `encCiphertext` for this note. + pub fn encrypt_note_plaintext(&self) -> [u8; ENC_CIPHERTEXT_SIZE] { + let pk_d = D::get_pk_d(&self.note); + let shared_secret = D::ka_agree_enc(&self.esk, &pk_d); + let key = D::kdf(shared_secret, &D::epk_bytes(&self.epk)); + let input = D::note_plaintext_bytes(&self.note, &self.to, &self.memo); + + let mut output = [0u8; ENC_CIPHERTEXT_SIZE]; + output[..NOTE_PLAINTEXT_SIZE].copy_from_slice(&input.0); + let tag = ChaCha20Poly1305::new(key.as_ref().into()) + .encrypt_in_place_detached( + [0u8; 12][..].into(), + &[], + &mut output[..NOTE_PLAINTEXT_SIZE], + ) + .unwrap(); + output[NOTE_PLAINTEXT_SIZE..].copy_from_slice(&tag); + + output + } + + /// Generates `outCiphertext` for this note. + pub fn encrypt_outgoing_plaintext( + &self, + cv: &D::ValueCommitment, + cmstar: &D::ExtractedCommitment, + rng: &mut R, + ) -> [u8; OUT_CIPHERTEXT_SIZE] { + let (ock, input) = if let Some(ovk) = &self.ovk { + let ock = D::derive_ock(ovk, cv, &cmstar.into(), &D::epk_bytes(&self.epk)); + let input = D::outgoing_plaintext_bytes(&self.note, &self.esk); + + (ock, input) + } else { + // ovk = ⊥ + let mut ock = OutgoingCipherKey([0; 32]); + let mut input = [0u8; OUT_PLAINTEXT_SIZE]; + + rng.fill_bytes(&mut ock.0); + rng.fill_bytes(&mut input); + + (ock, OutPlaintextBytes(input)) + }; + + let mut output = [0u8; OUT_CIPHERTEXT_SIZE]; + output[..OUT_PLAINTEXT_SIZE].copy_from_slice(&input.0); + let tag = ChaCha20Poly1305::new(ock.as_ref().into()) + .encrypt_in_place_detached([0u8; 12][..].into(), &[], &mut output[..OUT_PLAINTEXT_SIZE]) + .unwrap(); + output[OUT_PLAINTEXT_SIZE..].copy_from_slice(&tag); + + output + } +} + +/// Trial decryption of the full note plaintext by the recipient. +/// +/// Attempts to decrypt and validate the given shielded output using the given `ivk`. +/// If successful, the corresponding note and memo are returned, along with the address to +/// which the note was sent. +/// +/// Implements section 4.19.2 of the +/// [Zcash Protocol Specification](https://zips.z.cash/protocol/nu5.pdf#decryptivk). +pub fn try_note_decryption>( + domain: &D, + ivk: &D::IncomingViewingKey, + output: &Output, +) -> Option<(D::Note, D::Recipient, D::Memo)> { + let ephemeral_key = output.ephemeral_key(); + + let epk = D::prepare_epk(D::epk(&ephemeral_key)?); + let shared_secret = D::ka_agree_dec(ivk, &epk); + let key = D::kdf(shared_secret, &ephemeral_key); + + try_note_decryption_inner(domain, ivk, &ephemeral_key, output, &key) +} + +fn try_note_decryption_inner>( + domain: &D, + ivk: &D::IncomingViewingKey, + ephemeral_key: &EphemeralKeyBytes, + output: &Output, + key: &D::SymmetricKey, +) -> Option<(D::Note, D::Recipient, D::Memo)> { + let enc_ciphertext = output.enc_ciphertext(); + + let mut plaintext = + NotePlaintextBytes(enc_ciphertext[..NOTE_PLAINTEXT_SIZE].try_into().unwrap()); + + ChaCha20Poly1305::new(key.as_ref().into()) + .decrypt_in_place_detached( + [0u8; 12][..].into(), + &[], + &mut plaintext.0, + enc_ciphertext[NOTE_PLAINTEXT_SIZE..].into(), + ) + .ok()?; + + let (note, to) = parse_note_plaintext_without_memo_ivk( + domain, + ivk, + ephemeral_key, + &output.cmstar_bytes(), + &plaintext.0, + )?; + let memo = domain.extract_memo(&plaintext); + + Some((note, to, memo)) +} + +fn parse_note_plaintext_without_memo_ivk( + domain: &D, + ivk: &D::IncomingViewingKey, + ephemeral_key: &EphemeralKeyBytes, + cmstar_bytes: &D::ExtractedCommitmentBytes, + plaintext: &[u8], +) -> Option<(D::Note, D::Recipient)> { + let (note, to) = domain.parse_note_plaintext_without_memo_ivk(ivk, plaintext)?; + + if let NoteValidity::Valid = check_note_validity::(¬e, ephemeral_key, cmstar_bytes) { + Some((note, to)) + } else { + None + } +} + +fn check_note_validity( + note: &D::Note, + ephemeral_key: &EphemeralKeyBytes, + cmstar_bytes: &D::ExtractedCommitmentBytes, +) -> NoteValidity { + if &D::ExtractedCommitmentBytes::from(&D::cmstar(note)) == cmstar_bytes { + if let Some(derived_esk) = D::derive_esk(note) { + if D::epk_bytes(&D::ka_derive_public(note, &derived_esk)) + .ct_eq(ephemeral_key) + .into() + { + NoteValidity::Valid + } else { + NoteValidity::Invalid + } + } else { + // Before ZIP 212 + NoteValidity::Valid + } + } else { + // Published commitment doesn't match calculated commitment + NoteValidity::Invalid + } +} + +/// Trial decryption of the compact note plaintext by the recipient for light clients. +/// +/// Attempts to decrypt and validate the given compact shielded output using the +/// given `ivk`. If successful, the corresponding note is returned, along with the address +/// to which the note was sent. +/// +/// Implements the procedure specified in [`ZIP 307`]. +/// +/// [`ZIP 307`]: https://zips.z.cash/zip-0307 +pub fn try_compact_note_decryption>( + domain: &D, + ivk: &D::IncomingViewingKey, + output: &Output, +) -> Option<(D::Note, D::Recipient)> { + let ephemeral_key = output.ephemeral_key(); + + let epk = D::prepare_epk(D::epk(&ephemeral_key)?); + let shared_secret = D::ka_agree_dec(ivk, &epk); + let key = D::kdf(shared_secret, &ephemeral_key); + + try_compact_note_decryption_inner(domain, ivk, &ephemeral_key, output, &key) +} + +fn try_compact_note_decryption_inner>( + domain: &D, + ivk: &D::IncomingViewingKey, + ephemeral_key: &EphemeralKeyBytes, + output: &Output, + key: &D::SymmetricKey, +) -> Option<(D::Note, D::Recipient)> { + // Start from block 1 to skip over Poly1305 keying output + let mut plaintext = [0; COMPACT_NOTE_SIZE]; + plaintext.copy_from_slice(output.enc_ciphertext()); + let mut keystream = ChaCha20::new(key.as_ref().into(), [0u8; 12][..].into()); + keystream.seek(64); + keystream.apply_keystream(&mut plaintext); + + parse_note_plaintext_without_memo_ivk( + domain, + ivk, + ephemeral_key, + &output.cmstar_bytes(), + &plaintext, + ) +} + +/// Recovery of the full note plaintext by the sender. +/// +/// Attempts to decrypt and validate the given shielded output using the given `ovk`. +/// If successful, the corresponding note and memo are returned, along with the address to +/// which the note was sent. +/// +/// Implements [Zcash Protocol Specification section 4.19.3][decryptovk]. +/// +/// [decryptovk]: https://zips.z.cash/protocol/nu5.pdf#decryptovk +pub fn try_output_recovery_with_ovk>( + domain: &D, + ovk: &D::OutgoingViewingKey, + output: &Output, + cv: &D::ValueCommitment, + out_ciphertext: &[u8; OUT_CIPHERTEXT_SIZE], +) -> Option<(D::Note, D::Recipient, D::Memo)> { + let ock = D::derive_ock(ovk, cv, &output.cmstar_bytes(), &output.ephemeral_key()); + try_output_recovery_with_ock(domain, &ock, output, out_ciphertext) +} + +/// Recovery of the full note plaintext by the sender. +/// +/// Attempts to decrypt and validate the given shielded output using the given `ock`. +/// If successful, the corresponding note and memo are returned, along with the address to +/// which the note was sent. +/// +/// Implements part of section 4.19.3 of the +/// [Zcash Protocol Specification](https://zips.z.cash/protocol/nu5.pdf#decryptovk). +/// For decryption using a Full Viewing Key see [`try_output_recovery_with_ovk`]. +pub fn try_output_recovery_with_ock>( + domain: &D, + ock: &OutgoingCipherKey, + output: &Output, + out_ciphertext: &[u8; OUT_CIPHERTEXT_SIZE], +) -> Option<(D::Note, D::Recipient, D::Memo)> { + let enc_ciphertext = output.enc_ciphertext(); + + let mut op = OutPlaintextBytes([0; OUT_PLAINTEXT_SIZE]); + op.0.copy_from_slice(&out_ciphertext[..OUT_PLAINTEXT_SIZE]); + + ChaCha20Poly1305::new(ock.as_ref().into()) + .decrypt_in_place_detached( + [0u8; 12][..].into(), + &[], + &mut op.0, + out_ciphertext[OUT_PLAINTEXT_SIZE..].into(), + ) + .ok()?; + + let pk_d = D::extract_pk_d(&op)?; + let esk = D::extract_esk(&op)?; + + let ephemeral_key = output.ephemeral_key(); + let shared_secret = D::ka_agree_enc(&esk, &pk_d); + // The small-order point check at the point of output parsing rejects + // non-canonical encodings, so reencoding here for the KDF should + // be okay. + let key = D::kdf(shared_secret, &ephemeral_key); + + let mut plaintext = NotePlaintextBytes([0; NOTE_PLAINTEXT_SIZE]); + plaintext + .0 + .copy_from_slice(&enc_ciphertext[..NOTE_PLAINTEXT_SIZE]); + + ChaCha20Poly1305::new(key.as_ref().into()) + .decrypt_in_place_detached( + [0u8; 12][..].into(), + &[], + &mut plaintext.0, + enc_ciphertext[NOTE_PLAINTEXT_SIZE..].into(), + ) + .ok()?; + + let (note, to) = + domain.parse_note_plaintext_without_memo_ovk(&pk_d, &esk, &ephemeral_key, &plaintext)?; + let memo = domain.extract_memo(&plaintext); + + // ZIP 212: Check that the esk provided to this function is consistent with the esk we + // can derive from the note. + if let Some(derived_esk) = D::derive_esk(¬e) { + if (!derived_esk.ct_eq(&esk)).into() { + return None; + } + } + + if let NoteValidity::Valid = + check_note_validity::(¬e, &ephemeral_key, &output.cmstar_bytes()) + { + Some((note, to, memo)) + } else { + None + } +} diff --git a/masp_primitives/Cargo.toml b/masp_primitives/Cargo.toml index e58257b2..daec9491 100644 --- a/masp_primitives/Cargo.toml +++ b/masp_primitives/Cargo.toml @@ -1,51 +1,95 @@ [package] name = "masp_primitives" description = "Rust implementations of the experimental MASP primitives (derived from zcash_primitives)" -version = "0.5.0" +version = "0.9.0" authors = [ "Jack Grigg ", "Kris Nuttycombe ", "joe ", + "Murisi Tarusenga ", + "Heliax AG ", ] homepage = "https://github.com/anoma/masp" repository = "https://github.com/anoma/masp" readme = "README.md" license = "MIT OR Apache-2.0" -edition = "2018" +edition = "2021" +rust-version = "1.65" +categories = ["cryptography::cryptocurrencies"] [package.metadata.docs.rs] all-features = true [dependencies] -aes = "0.7" -bitvec = "0.22" -bip0039 = { version = "0.9", features = ["std", "all-languages"] } -blake2b_simd = "1" -blake2s_simd = "1" -bls12_381 = "0.6" -byteorder = "1" -chacha20poly1305 = "0.9" -ff = "0.11" -fpe = "0.5" -group = "0.11" -hex = "0.4" -incrementalmerkletree = "=0.3.0-beta.2" -jubjub = "0.8" -lazy_static = "1" -proptest = { version = "1.0.0", optional = true } +zcash_encoding = { version = "0.0", git = "https://github.com/zcash/librustzcash", rev = "43c18d0" } + +# Dependencies exposed in a public API: +# (Breaking upgrades to these require a breaking upgrade to this crate.) +# - CSPRNG rand = "0.8" rand_core = "0.6" + +# - Digests (output types exposed) +blake2b_simd = "1" +sha2 = "0.9" + +# - Metrics +memuse = "0.2.1" + +# - Secret management subtle = "2.2.3" -zcash_primitives = { git = "https://github.com/zcash/librustzcash", rev = "43c18d0" } -zcash_encoding = { version = "0.0", git = "https://github.com/zcash/librustzcash", rev = "43c18d0" } + +# - Shielded protocols +bls12_381 = "0.7" +ff = "0.12" +group = { version = "0.12.1", features = ["wnaf-memuse"] } +incrementalmerkletree = "0.3" +jubjub = "0.9" +nonempty = "0.7" + +# - Static constants +lazy_static = "1" + +# - Test dependencies +proptest = { version = "1.0.0", optional = true } + +# - Transparent inputs +secp256k1 = { version = "0.24.1", features = [ "rand" ] } + +# - ZIP 339 +bip0039 = { version = "0.9", features = ["std", "all-languages"] } + +# Dependencies used internally: +# (Breaking upgrades to these are usually backwards-compatible, but check MSRVs.) +# - Encodings +byteorder = "1" +hex = "0.4" + +# - Shielded protocols +bitvec = "1" +blake2s_simd = "1" + +# - ZIP 32 +aes = "0.7" +fpe = "0.5" + +borsh = {version = "0.9", features = ["const-generics"]} +[dependencies.masp_note_encryption] +version = "0.2" +path = "../masp_note_encryption" +features = ["pre-zip-212"] [dev-dependencies] -criterion = "0.3" +chacha20poly1305 = "0.10" +criterion = "0.4" proptest = "1.0.0" +assert_matches = "1.3.0" rand_xorshift = "0.3" [features] +transparent-inputs = [] test-dependencies = ["proptest"] +default = ["transparent-inputs"] [badges] maintenance = { status = "actively-developed" } diff --git a/masp_primitives/src/asset_type.rs b/masp_primitives/src/asset_type.rs index fe5c56eb..b36bf46a 100644 --- a/masp_primitives/src/asset_type.rs +++ b/masp_primitives/src/asset_type.rs @@ -3,14 +3,21 @@ use crate::{ ASSET_IDENTIFIER_LENGTH, ASSET_IDENTIFIER_PERSONALIZATION, GH_FIRST_BLOCK, VALUE_COMMITMENT_GENERATOR_PERSONALIZATION, }, - primitives::ValueCommitment, + sapling::ValueCommitment, }; use blake2s_simd::Params as Blake2sParams; +use borsh::{BorshDeserialize, BorshSerialize}; use group::{cofactor::CofactorGroup, Group, GroupEncoding}; +use std::{ + cmp::Ordering, + fmt::{Display, Formatter}, + hash::{Hash, Hasher}, +}; -#[derive(Debug)] +#[derive(Debug, BorshSerialize, BorshDeserialize, Clone, Copy, Eq)] pub struct AssetType { identifier: [u8; ASSET_IDENTIFIER_LENGTH], //32 byte asset type preimage + #[borsh_skip] nonce: Option, } @@ -143,19 +150,56 @@ impl AssetType { } } -impl Copy for AssetType {} +impl PartialEq for AssetType { + fn eq(&self, other: &Self) -> bool { + self.get_identifier() == other.get_identifier() + } +} -impl Clone for AssetType { - fn clone(&self) -> Self { - AssetType { - identifier: self.identifier, - nonce: self.nonce, - } +impl Display for AssetType { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + write!(f, "{}", hex::encode(self.get_identifier())) } } -impl PartialEq for AssetType { - fn eq(&self, other: &Self) -> bool { - self.get_identifier() == other.get_identifier() +impl Hash for AssetType { + fn hash(&self, state: &mut H) { + self.get_identifier().hash(state) + } +} + +impl PartialOrd for AssetType { + fn partial_cmp(&self, other: &Self) -> Option { + self.get_identifier().partial_cmp(other.get_identifier()) + } +} + +impl Ord for AssetType { + fn cmp(&self, other: &Self) -> Ordering { + self.get_identifier().cmp(other.get_identifier()) + } +} + +impl std::str::FromStr for AssetType { + type Err = std::io::Error; + + fn from_str(s: &str) -> Result { + let vec = hex::decode(s).map_err(|x| Self::Err::new(std::io::ErrorKind::InvalidData, x))?; + Self::from_identifier( + &vec.try_into() + .map_err(|_| Self::Err::from(std::io::ErrorKind::InvalidData))?, + ) + .ok_or_else(|| Self::Err::from(std::io::ErrorKind::InvalidData)) + } +} + +#[cfg(any(test, feature = "test-dependencies"))] +pub mod testing { + use proptest::prelude::*; + + prop_compose! { + pub fn arb_asset_type()(name in proptest::collection::vec(prop::num::u8::ANY, 0..64)) -> super::AssetType { + super::AssetType::new(&name).unwrap() + } } } diff --git a/masp_primitives/src/consensus.rs b/masp_primitives/src/consensus.rs new file mode 100644 index 00000000..75fc8b77 --- /dev/null +++ b/masp_primitives/src/consensus.rs @@ -0,0 +1,388 @@ +//! Consensus logic and parameters. + +use memuse::DynamicUsage; +use std::cmp::{Ord, Ordering}; +use std::convert::TryFrom; +use std::fmt; +use std::ops::{Add, Bound, RangeBounds, Sub}; + +/// A wrapper type representing blockchain heights. Safe conversion from +/// various integer types, as well as addition and subtraction, are provided. +#[repr(transparent)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct BlockHeight(u32); + +memuse::impl_no_dynamic_usage!(BlockHeight); + +pub const H0: BlockHeight = BlockHeight(0); + +impl BlockHeight { + pub const fn from_u32(v: u32) -> BlockHeight { + BlockHeight(v) + } +} + +impl fmt::Display for BlockHeight { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(formatter) + } +} + +impl Ord for BlockHeight { + fn cmp(&self, other: &Self) -> Ordering { + self.0.cmp(&other.0) + } +} + +impl PartialOrd for BlockHeight { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl From for BlockHeight { + fn from(value: u32) -> Self { + BlockHeight(value) + } +} + +impl From for u32 { + fn from(value: BlockHeight) -> u32 { + value.0 + } +} + +impl TryFrom for BlockHeight { + type Error = std::num::TryFromIntError; + + fn try_from(value: u64) -> Result { + u32::try_from(value).map(BlockHeight) + } +} + +impl From for u64 { + fn from(value: BlockHeight) -> u64 { + value.0 as u64 + } +} + +impl TryFrom for BlockHeight { + type Error = std::num::TryFromIntError; + + fn try_from(value: i32) -> Result { + u32::try_from(value).map(BlockHeight) + } +} + +impl TryFrom for BlockHeight { + type Error = std::num::TryFromIntError; + + fn try_from(value: i64) -> Result { + u32::try_from(value).map(BlockHeight) + } +} + +impl From for i64 { + fn from(value: BlockHeight) -> i64 { + value.0 as i64 + } +} + +impl Add for BlockHeight { + type Output = Self; + + fn add(self, other: u32) -> Self { + BlockHeight(self.0 + other) + } +} + +impl Add for BlockHeight { + type Output = Self; + + fn add(self, other: Self) -> Self { + self + other.0 + } +} + +impl Sub for BlockHeight { + type Output = Self; + + fn sub(self, other: u32) -> Self { + if other > self.0 { + panic!("Subtraction resulted in negative block height."); + } + + BlockHeight(self.0 - other) + } +} + +impl Sub for BlockHeight { + type Output = Self; + + fn sub(self, other: Self) -> Self { + self - other.0 + } +} + +/// MASP consensus parameters. +pub trait Parameters: Clone { + /// Returns the activation height for a particular network upgrade, + /// if an activation height has been set. + fn activation_height(&self, nu: NetworkUpgrade) -> Option; + + /// Determines whether the specified network upgrade is active as of the + /// provided block height on the network to which this Parameters value applies. + fn is_nu_active(&self, nu: NetworkUpgrade, height: BlockHeight) -> bool { + self.activation_height(nu).map_or(false, |h| h <= height) + } +} + +/// Marker struct for the production network. +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +pub struct MainNetwork; + +memuse::impl_no_dynamic_usage!(MainNetwork); + +pub const MAIN_NETWORK: MainNetwork = MainNetwork; + +impl Parameters for MainNetwork { + fn activation_height(&self, nu: NetworkUpgrade) -> Option { + match nu { + NetworkUpgrade::MASP => Some(H0), + } + } +} + +/// Marker struct for the test network. +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +pub struct TestNetwork; + +memuse::impl_no_dynamic_usage!(TestNetwork); + +pub const TEST_NETWORK: TestNetwork = TestNetwork; + +impl Parameters for TestNetwork { + fn activation_height(&self, nu: NetworkUpgrade) -> Option { + match nu { + NetworkUpgrade::MASP => Some(BlockHeight(1)), // Activate MASP at height 1 so pre-ZIP 212 tests work at height 0 + } + } +} + +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +pub enum Network { + MainNetwork, + TestNetwork, +} + +memuse::impl_no_dynamic_usage!(Network); + +impl Parameters for Network { + fn activation_height(&self, nu: NetworkUpgrade) -> Option { + match self { + Network::MainNetwork => MAIN_NETWORK.activation_height(nu), + Network::TestNetwork => TEST_NETWORK.activation_height(nu), + } + } +} + +/// An event that occurs at a specified height on the Zcash chain, at which point the +/// consensus rules enforced by the network are altered. +/// +/// See [ZIP 200](https://zips.z.cash/zip-0200) for more details. +#[derive(Clone, Copy, Debug)] +pub enum NetworkUpgrade { + /// The [MASP] network upgrade. + /// + /// [MASP]: https://github.com/anoma/masp/ + MASP, +} + +memuse::impl_no_dynamic_usage!(NetworkUpgrade); + +impl fmt::Display for NetworkUpgrade { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + NetworkUpgrade::MASP => write!(f, "MASP"), + } + } +} + +impl NetworkUpgrade { + fn branch_id(self) -> BranchId { + match self { + NetworkUpgrade::MASP => BranchId::MASP, + } + } +} + +/// The network upgrades on the Zcash chain in order of activation. +/// +/// This order corresponds to the activation heights, but because Rust enums are +/// full-fledged algebraic data types, we need to define it manually. +const UPGRADES_IN_ORDER: &[NetworkUpgrade] = &[NetworkUpgrade::MASP]; + +pub const ZIP212_GRACE_PERIOD: u32 = 0; + +/// A globally-unique identifier for a set of consensus rules within the Zcash chain. +/// +/// Each branch ID in this enum corresponds to one of the epochs between a pair of Zcash +/// network upgrades. For example, `BranchId::Overwinter` corresponds to the blocks +/// starting at Overwinter activation, and ending the block before Sapling activation. +/// +/// The main use of the branch ID is in signature generation: transactions commit to a +/// specific branch ID by including it as part of [`signature_hash`]. This ensures +/// two-way replay protection for transactions across network upgrades. +/// +/// See [ZIP 200](https://zips.z.cash/zip-0200) for more details. +/// +/// [`signature_hash`]: crate::transaction::sighash::signature_hash +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum BranchId { + /// The consensus rules deployed by [`NetworkUpgrade::MASP`]. + MASP, +} + +memuse::impl_no_dynamic_usage!(BranchId); + +impl TryFrom for BranchId { + type Error = &'static str; + + fn try_from(value: u32) -> Result { + match value { + 0xe9ff_75a6 => Ok(BranchId::MASP), + _ => Err("Unknown consensus branch ID"), + } + } +} + +impl From for u32 { + fn from(consensus_branch_id: BranchId) -> u32 { + match consensus_branch_id { + BranchId::MASP => 0xe9ff_75a6, + } + } +} + +impl BranchId { + /// Returns the branch ID corresponding to the consensus rule set that is active at + /// the given height. + /// + /// This is the branch ID that should be used when creating transactions. + pub fn for_height(parameters: &P, height: BlockHeight) -> Self { + for nu in UPGRADES_IN_ORDER.iter().rev() { + if parameters.is_nu_active(*nu, height) { + return nu.branch_id(); + } + } + + // Sapling rules apply before any network upgrade + BranchId::MASP + } + + /// Returns the range of heights for the consensus epoch associated with this branch id. + /// + /// The resulting tuple implements the [`RangeBounds`] trait. + pub fn height_range(&self, params: &P) -> Option> { + self.height_bounds(params).map(|(lower, upper)| { + ( + Bound::Included(lower), + upper.map_or(Bound::Unbounded, Bound::Excluded), + ) + }) + } + + /// Returns the range of heights for the consensus epoch associated with this branch id. + /// + /// The return type of this value is slightly more precise than [`Self::height_range`]: + /// - `Some((x, Some(y)))` means that the consensus rules corresponding to this branch id + /// are in effect for the range `x..y` + /// - `Some((x, None))` means that the consensus rules corresponding to this branch id are + /// in effect for the range `x..` + /// - `None` means that the consensus rules corresponding to this branch id are never in effect. + pub fn height_bounds( + &self, + params: &P, + ) -> Option<(BlockHeight, Option)> { + match self { + BranchId::MASP => params.activation_height(NetworkUpgrade::MASP).map(|lower| { + let upper = None; + (lower, upper) + }), + } + } +} + +#[cfg(any(test, feature = "test-dependencies"))] +pub mod testing { + use proptest::sample::select; + use proptest::strategy::{Just, Strategy}; + + use super::{BlockHeight, BranchId, Parameters}; + + pub fn arb_branch_id() -> impl Strategy { + select(vec![BranchId::MASP]) + } + + pub fn arb_height( + branch_id: BranchId, + params: &P, + ) -> impl Strategy> { + branch_id + .height_bounds(params) + .map_or(Strategy::boxed(Just(None)), |(lower, upper)| { + Strategy::boxed( + (lower.0..upper.map_or(std::u32::MAX, |u| u.0)) + .prop_map(|h| Some(BlockHeight(h))), + ) + }) + } +} + +#[cfg(test)] +mod tests { + use std::convert::TryFrom; + + use super::{ + BlockHeight, BranchId, NetworkUpgrade, Parameters, MAIN_NETWORK, UPGRADES_IN_ORDER, + }; + + #[test] + fn nu_ordering() { + for i in 1..UPGRADES_IN_ORDER.len() { + let nu_a = UPGRADES_IN_ORDER[i - 1]; + let nu_b = UPGRADES_IN_ORDER[i]; + match ( + MAIN_NETWORK.activation_height(nu_a), + MAIN_NETWORK.activation_height(nu_b), + ) { + (Some(a), Some(b)) if a < b => (), + (Some(_), None) => (), + (None, None) => (), + _ => panic!( + "{} should not be before {} in UPGRADES_IN_ORDER", + nu_a, nu_b + ), + } + } + } + + #[test] + fn nu_is_active() { + assert!(MAIN_NETWORK.is_nu_active(NetworkUpgrade::MASP, BlockHeight(0))); + } + + #[test] + fn branch_id_from_u32() { + assert_eq!(BranchId::try_from(3925833126), Ok(BranchId::MASP)); + assert!(BranchId::try_from(1).is_err()); + } + + #[test] + fn branch_id_for_height() { + assert_eq!( + BranchId::for_height(&MAIN_NETWORK, BlockHeight(0)), + BranchId::MASP, + ); + } +} diff --git a/masp_primitives/src/constants.rs b/masp_primitives/src/constants.rs index 26bf13c7..c298e40f 100644 --- a/masp_primitives/src/constants.rs +++ b/masp_primitives/src/constants.rs @@ -33,7 +33,7 @@ pub const SPENDING_KEY_GENERATOR_PERSONALIZATION: &[u8; 8] = b"MASP__G_"; pub const PROOF_GENERATION_KEY_BASE_GENERATOR_PERSONALIZATION: &[u8; 8] = b"MASP__H_"; /// BLAKE2s Personalization for the value commitment generator for the value -pub const VALUE_COMMITMENT_GENERATOR_PERSONALIZATION: &[u8; 8] = b"MASP__v_"; //b"MASP__cv"; +pub const VALUE_COMMITMENT_GENERATOR_PERSONALIZATION: &[u8; 8] = b"MASP__v_"; pub const VALUE_COMMITMENT_RANDOMNESS_PERSONALIZATION: &[u8; 8] = b"MASP__r_"; /// BLAKE2s Personalization for the nullifier position generator (for computing rho) @@ -266,7 +266,7 @@ mod tests { use jubjub::SubgroupPoint; use super::*; - use zcash_primitives::sapling::group_hash::group_hash; + use crate::sapling::group_hash::group_hash; fn find_group_hash(m: &[u8], personalization: &[u8; 8]) -> SubgroupPoint { let mut tag = m.to_vec(); diff --git a/masp_primitives/src/convert.rs b/masp_primitives/src/convert.rs index 92de2fff..82d78a36 100644 --- a/masp_primitives/src/convert.rs +++ b/masp_primitives/src/convert.rs @@ -1,12 +1,24 @@ -use crate::asset_type::AssetType; -use crate::pedersen_hash::{pedersen_hash, Personalization}; -use crate::primitives::ValueCommitment; +use crate::{ + sapling::{ + pedersen_hash::{pedersen_hash, Personalization}, + Node, ValueCommitment, + }, + transaction::components::amount::Amount, +}; +use borsh::{BorshDeserialize, BorshSerialize}; use group::{Curve, GroupEncoding}; +use std::{ + io::{self, Write}, + iter::Sum, + ops::{Add, AddAssign, Sub, SubAssign}, +}; -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct AllowedConversion { /// The asset type that the note represents - pub assets: Vec<(AssetType, i64)>, + assets: Amount, + /// Memorize generator because it's expensive to recompute + generator: jubjub::ExtendedPoint, } impl AllowedConversion { @@ -22,7 +34,7 @@ impl AllowedConversion { let mut asset_generator_bytes = vec![]; // Write the asset generator, cofactor not cleared - asset_generator_bytes.extend_from_slice(&self.asset_generator().to_bytes()); + asset_generator_bytes.extend_from_slice(&self.generator.to_bytes()); assert_eq!(asset_generator_bytes.len(), 32); @@ -44,10 +56,32 @@ impl AllowedConversion { .get_u() } + /// Computes the value commitment for a given amount and randomness + pub fn value_commitment(&self, value: u64, randomness: jubjub::Fr) -> ValueCommitment { + ValueCommitment { + asset_generator: self.generator, + value, + randomness, + } + } + /// Returns [`self.cmu`] in the correct representation for inclusion in the MASP + /// AllowedConversions commitment tree. + pub fn commitment(&self) -> Node { + Node::from_scalar(self.cmu()) + } +} + +impl From for Amount { + fn from(allowed_conversion: AllowedConversion) -> Amount { + allowed_conversion.assets + } +} + +impl From for AllowedConversion { /// Produces an asset generator without cofactor cleared - pub fn asset_generator(&self) -> jubjub::ExtendedPoint { + fn from(assets: Amount) -> Self { let mut asset_generator = jubjub::ExtendedPoint::identity(); - for (asset, value) in self.assets.iter() { + for (asset, value) in assets.components() { // Compute the absolute value (failing if -i64::MAX is // the value) let abs = match value.checked_abs() { @@ -69,15 +103,137 @@ impl AllowedConversion { // Add to asset generator asset_generator += value_balance; } - asset_generator + AllowedConversion { + assets, + generator: asset_generator, + } } +} - /// Computes the value commitment for a given amount and randomness - pub fn value_commitment(&self, value: u64, randomness: jubjub::Fr) -> ValueCommitment { - ValueCommitment { - asset_generator: self.asset_generator(), - value, - randomness, +impl BorshSerialize for AllowedConversion { + fn serialize(&self, writer: &mut W) -> borsh::maybestd::io::Result<()> { + self.assets.write(writer)?; + writer.write_all(&self.generator.to_bytes())?; + Ok(()) + } +} + +impl BorshDeserialize for AllowedConversion { + /// This deserialization is unsafe because it does not do the expensive + /// computation of checking whether the asset generator corresponds to the + /// deserialized amount. + fn deserialize(buf: &mut &[u8]) -> borsh::maybestd::io::Result { + let assets = Amount::read(buf)?; + let gen_bytes = + <::Repr as BorshDeserialize>::deserialize(buf)?; + let generator = Option::from(jubjub::ExtendedPoint::from_bytes(&gen_bytes)) + .ok_or_else(|| io::Error::from(io::ErrorKind::InvalidData))?; + let allowed_conversion: AllowedConversion = assets.clone().into(); + if allowed_conversion.generator != generator { + return Err(io::Error::from(io::ErrorKind::InvalidData)); + } + Ok(AllowedConversion { assets, generator }) + } +} + +impl Add for AllowedConversion { + type Output = Self; + + fn add(self, rhs: Self) -> Self { + Self { + assets: self.assets + rhs.assets, + generator: self.generator + rhs.generator, } } } + +impl AddAssign for AllowedConversion { + fn add_assign(&mut self, rhs: Self) { + self.assets += rhs.assets; + self.generator += rhs.generator; + } +} + +impl Sub for AllowedConversion { + type Output = Self; + + fn sub(self, rhs: Self) -> Self { + Self { + assets: self.assets - rhs.assets, + generator: self.generator - rhs.generator, + } + } +} + +impl SubAssign for AllowedConversion { + fn sub_assign(&mut self, rhs: Self) { + self.assets -= rhs.assets; + self.generator -= rhs.generator; + } +} + +impl Sum for AllowedConversion { + fn sum>(iter: I) -> Self { + iter.fold(AllowedConversion::from(Amount::zero()), Add::add) + } +} + +#[cfg(test)] +mod tests { + use crate::asset_type::AssetType; + use crate::convert::AllowedConversion; + use crate::transaction::components::amount::Amount; + + /// Generate ZEC asset type + fn zec() -> AssetType { + AssetType::new(b"ZEC").unwrap() + } + /// Generate BTC asset type + fn btc() -> AssetType { + AssetType::new(b"BTC").unwrap() + } + /// Generate XAN asset type + fn xan() -> AssetType { + AssetType::new(b"XAN").unwrap() + } + #[test] + fn test_homomorphism() { + // Left operand + let a = Amount::from_pair(zec(), 5).unwrap() + + Amount::from_pair(btc(), 6).unwrap() + + Amount::from_pair(xan(), 7).unwrap(); + // Right operand + let b = Amount::from_pair(zec(), 2).unwrap() + Amount::from_pair(xan(), 10).unwrap(); + // Test homomorphism + assert_eq!( + AllowedConversion::from(a.clone() + b.clone()), + AllowedConversion::from(a) + AllowedConversion::from(b) + ); + } + #[test] + fn test_serialization() { + // Make conversion + let a: AllowedConversion = (Amount::from_pair(zec(), 5).unwrap() + + Amount::from_pair(btc(), 6).unwrap() + + Amount::from_pair(xan(), 7).unwrap()) + .into(); + // Serialize conversion + let mut data = Vec::new(); + use borsh::BorshSerialize; + a.serialize(&mut data).unwrap(); + // Deserialize conversion + let mut ptr = &data[..]; + use borsh::BorshDeserialize; + let b = AllowedConversion::deserialize(&mut ptr).unwrap(); + // Check that all bytes have been finished + assert!( + ptr.is_empty(), + "AllowedConversion bytes should be exhausted" + ); + // Test that serializing then deserializing produces same object + assert_eq!( + a, b, + "serialization followed by deserialization changes value" + ); + } +} diff --git a/masp_primitives/src/keys.rs b/masp_primitives/src/keys.rs index 95b461a2..5e0954ec 100644 --- a/masp_primitives/src/keys.rs +++ b/masp_primitives/src/keys.rs @@ -1,18 +1,6 @@ -//! Sapling key components. -//! -//! Implements [section 4.2.2] of the Zcash Protocol Specification. -//! -//! [section 4.2.2]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents - -use crate::{ - constants::{PROOF_GENERATION_KEY_GENERATOR, SPENDING_KEY_GENERATOR}, - primitives::{ProofGenerationKey, ViewingKey}, -}; use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams}; -use ff::PrimeField; -use group::{Group, GroupEncoding}; -use std::io::{self, Read, Write}; -use subtle::CtOption; + +pub use crate::sapling::keys::OutgoingViewingKey; pub const PRF_EXPAND_PERSONALIZATION: &[u8; 16] = b"MASP__ExpandSeed"; @@ -32,203 +20,3 @@ pub fn prf_expand_vec(sk: &[u8], ts: &[&[u8]]) -> Blake2bHash { } h.finalize() } - -/// An outgoing viewing key -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct OutgoingViewingKey(pub [u8; 32]); - -/// A Sapling expanded spending key -#[derive(Clone)] -pub struct ExpandedSpendingKey { - pub ask: jubjub::Fr, - pub nsk: jubjub::Fr, - pub ovk: OutgoingViewingKey, -} - -/// A Sapling full viewing key -#[derive(Debug)] -pub struct FullViewingKey { - pub vk: ViewingKey, - pub ovk: OutgoingViewingKey, -} - -impl ExpandedSpendingKey { - pub fn from_spending_key(sk: &[u8]) -> Self { - let ask = jubjub::Fr::from_bytes_wide(prf_expand(sk, &[0x00]).as_array()); - let nsk = jubjub::Fr::from_bytes_wide(prf_expand(sk, &[0x01]).as_array()); - let mut ovk = OutgoingViewingKey([0u8; 32]); - ovk.0 - .copy_from_slice(&prf_expand(sk, &[0x02]).as_bytes()[..32]); - ExpandedSpendingKey { ask, nsk, ovk } - } - - pub fn proof_generation_key(&self) -> ProofGenerationKey { - ProofGenerationKey { - ak: SPENDING_KEY_GENERATOR * self.ask, - nsk: self.nsk, - } - } - - pub fn read(mut reader: R) -> io::Result { - let mut ask_repr = [0u8; 32]; - reader.read_exact(ask_repr.as_mut())?; - let ask = Option::from(jubjub::Fr::from_repr(ask_repr)) - .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "ask not in field"))?; - - let mut nsk_repr = [0u8; 32]; - reader.read_exact(nsk_repr.as_mut())?; - let nsk = Option::from(jubjub::Fr::from_repr(nsk_repr)) - .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "nsk not in field"))?; - - let mut ovk = [0u8; 32]; - reader.read_exact(&mut ovk)?; - - Ok(ExpandedSpendingKey { - ask, - nsk, - ovk: OutgoingViewingKey(ovk), - }) - } - - pub fn write(&self, mut writer: W) -> io::Result<()> { - writer.write_all(self.ask.to_repr().as_ref())?; - writer.write_all(self.nsk.to_repr().as_ref())?; - writer.write_all(&self.ovk.0)?; - - Ok(()) - } - - pub fn to_bytes(&self) -> [u8; 96] { - let mut result = [0u8; 96]; - self.write(&mut result[..]) - .expect("should be able to serialize an ExpandedSpendingKey"); - result - } -} - -impl Clone for FullViewingKey { - fn clone(&self) -> Self { - FullViewingKey { - vk: ViewingKey { - ak: self.vk.ak, - nk: self.vk.nk, - }, - ovk: self.ovk, - } - } -} - -impl FullViewingKey { - pub fn from_expanded_spending_key(expsk: &ExpandedSpendingKey) -> Self { - FullViewingKey { - vk: ViewingKey { - ak: SPENDING_KEY_GENERATOR * expsk.ask, - nk: PROOF_GENERATION_KEY_GENERATOR * expsk.nsk, - }, - ovk: expsk.ovk, - } - } - - pub fn read(mut reader: R) -> io::Result { - let ak = { - let mut buf = [0u8; 32]; - reader.read_exact(&mut buf)?; - jubjub::SubgroupPoint::from_bytes(&buf).and_then(|p| CtOption::new(p, !p.is_identity())) - }; - let nk = { - let mut buf = [0u8; 32]; - reader.read_exact(&mut buf)?; - jubjub::SubgroupPoint::from_bytes(&buf) - }; - if ak.is_none().into() { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "ak not of prime order", - )); - } - if nk.is_none().into() { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "nk not in prime-order subgroup", - )); - } - let ak = ak.unwrap(); - let nk = nk.unwrap(); - - let mut ovk = [0u8; 32]; - reader.read_exact(&mut ovk)?; - - Ok(FullViewingKey { - vk: ViewingKey { ak, nk }, - ovk: OutgoingViewingKey(ovk), - }) - } - - pub fn write(&self, mut writer: W) -> io::Result<()> { - writer.write_all(&self.vk.ak.to_bytes())?; - writer.write_all(&self.vk.nk.to_bytes())?; - writer.write_all(&self.ovk.0)?; - - Ok(()) - } - - pub fn to_bytes(&self) -> [u8; 96] { - let mut result = [0u8; 96]; - self.write(&mut result[..]) - .expect("should be able to serialize a FullViewingKey"); - result - } -} - -#[cfg(any(test, feature = "test-dependencies"))] -pub mod testing { - use proptest::collection::vec; - use proptest::prelude::{any, prop_compose}; - - use crate::primitives::PaymentAddress; - use crate::zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}; - - prop_compose! { - pub fn arb_extended_spending_key()(v in vec(any::(), 32..252)) -> ExtendedSpendingKey { - ExtendedSpendingKey::master(&v) - } - } - - prop_compose! { - pub fn arb_shielded_addr()(extsk in arb_extended_spending_key()) -> PaymentAddress { - let extfvk = ExtendedFullViewingKey::from(&extsk); - extfvk.default_address().1 - } - } -} - -#[cfg(test)] -mod tests { - use group::{Group, GroupEncoding}; - - use super::FullViewingKey; - use crate::constants::SPENDING_KEY_GENERATOR; - - #[test] - fn ak_must_be_prime_order() { - let mut buf = [0; 96]; - let identity = jubjub::SubgroupPoint::identity(); - - // Set both ak and nk to the identity. - buf[0..32].copy_from_slice(&identity.to_bytes()); - buf[32..64].copy_from_slice(&identity.to_bytes()); - - // ak is not allowed to be the identity. - assert_eq!( - FullViewingKey::read(&buf[..]).unwrap_err().to_string(), - "ak not of prime order" - ); - - // Set ak to a basepoint. - let basepoint = SPENDING_KEY_GENERATOR; - buf[0..32].copy_from_slice(&basepoint.to_bytes()); - - // nk is allowed to be the identity. - assert!(FullViewingKey::read(&buf[..]).is_ok()); - } -} diff --git a/masp_primitives/src/lib.rs b/masp_primitives/src/lib.rs index 426f9331..7649170d 100644 --- a/masp_primitives/src/lib.rs +++ b/masp_primitives/src/lib.rs @@ -8,17 +8,22 @@ #![deny(rustdoc::broken_intra_doc_links)] // Temporary until we have addressed all Result cases. #![allow(clippy::result_unit_err)] +// Allow absurd MAX_MONEY comparisons in case MAX_MONEY is ever changed +#![allow(clippy::absurd_extreme_comparisons)] +// Allow manual RangeIncludes for now +#![allow(clippy::manual_range_contains)] +// TODO +#![allow(clippy::derive_hash_xor_eq)] pub mod asset_type; +pub mod consensus; pub mod constants; pub mod convert; pub mod keys; +pub mod memo; pub mod merkle_tree; -pub mod pedersen_hash; -pub mod primitives; -pub mod prover; -pub mod redjubjub; pub mod sapling; +pub mod transaction; pub mod zip32; #[cfg(test)] diff --git a/masp_primitives/src/memo.rs b/masp_primitives/src/memo.rs new file mode 100644 index 00000000..aeaf664d --- /dev/null +++ b/masp_primitives/src/memo.rs @@ -0,0 +1,413 @@ +//! Structs for handling encrypted memos. + +use borsh::{BorshDeserialize, BorshSerialize}; +use std::cmp::Ordering; +use std::convert::{TryFrom, TryInto}; +use std::error; +use std::fmt; +use std::ops::Deref; +use std::str; + +/// Format a byte array as a colon-delimited hex string. +/// +/// - Source: +/// - License: MIT / Apache 2.0 +fn fmt_colon_delimited_hex(f: &mut fmt::Formatter<'_>, bytes: B) -> fmt::Result +where + B: AsRef<[u8]>, +{ + let len = bytes.as_ref().len(); + + for (i, byte) in bytes.as_ref().iter().enumerate() { + write!(f, "{:02x}", byte)?; + + if i != len - 1 { + write!(f, ":")?; + } + } + + Ok(()) +} + +/// Errors that may result from attempting to construct an invalid memo. +#[derive(Debug, PartialEq, Eq)] +pub enum Error { + InvalidUtf8(std::str::Utf8Error), + TooLong(usize), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::InvalidUtf8(e) => write!(f, "Invalid UTF-8: {}", e), + Error::TooLong(n) => write!(f, "Memo length {} is larger than maximum of 512", n), + } + } +} + +impl error::Error for Error {} + +/// The unencrypted memo bytes received alongside a shielded note in a Zcash transaction. +#[derive(Clone, BorshSerialize, BorshDeserialize)] +pub struct MemoBytes(pub(crate) Box<[u8; 512]>); + +impl fmt::Debug for MemoBytes { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "MemoBytes(")?; + fmt_colon_delimited_hex(f, &self.0[..])?; + write!(f, ")") + } +} + +impl PartialEq for MemoBytes { + fn eq(&self, rhs: &MemoBytes) -> bool { + self.0[..] == rhs.0[..] + } +} + +impl Eq for MemoBytes {} + +impl PartialOrd for MemoBytes { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for MemoBytes { + fn cmp(&self, rhs: &Self) -> Ordering { + self.0[..].cmp(&rhs.0[..]) + } +} + +impl MemoBytes { + /// Creates a `MemoBytes` indicating that no memo is present. + pub fn empty() -> Self { + let mut bytes = [0u8; 512]; + bytes[0] = 0xF6; + MemoBytes(Box::new(bytes)) + } + + /// Creates a `MemoBytes` from a slice, exactly as provided. + /// + /// Returns an error if the provided slice is longer than 512 bytes. Slices shorter + /// than 512 bytes are padded with null bytes. + /// + /// Note that passing an empty slice to this API (or an all-zeroes slice) will result + /// in a memo representing an empty string. What you almost certainly want in this + /// case is [`MemoBytes::empty`], which uses a specific encoding to indicate that no + /// memo is present. + pub fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() > 512 { + return Err(Error::TooLong(bytes.len())); + } + + let mut memo = [0u8; 512]; + memo[..bytes.len()].copy_from_slice(bytes); + Ok(MemoBytes(Box::new(memo))) + } + + /// Returns the raw byte array containing the memo bytes, including null padding. + pub fn as_array(&self) -> &[u8; 512] { + &self.0 + } + + /// Returns a slice of the raw bytes, excluding null padding. + pub fn as_slice(&self) -> &[u8] { + let first_null = self + .0 + .iter() + .enumerate() + .rev() + .find(|(_, &b)| b != 0) + .map(|(i, _)| i + 1) + .unwrap_or_default(); + + &self.0[..first_null] + } +} + +/// Type-safe wrapper around String to enforce memo length requirements. +#[derive(Clone, PartialEq, Eq)] +pub struct TextMemo(String); + +impl From for String { + fn from(memo: TextMemo) -> String { + memo.0 + } +} + +impl Deref for TextMemo { + type Target = str; + + #[inline] + fn deref(&self) -> &str { + self.0.deref() + } +} + +/// An unencrypted memo received alongside a shielded note in a Zcash transaction. +#[derive(Clone)] +pub enum Memo { + /// An empty memo field. + Empty, + /// A memo field containing a UTF-8 string. + Text(TextMemo), + /// Some unknown memo format from ✨*the future*✨ that we can't parse. + Future(MemoBytes), + /// A memo field containing arbitrary bytes. + Arbitrary(Box<[u8; 511]>), +} + +impl fmt::Debug for Memo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Memo::Empty => write!(f, "Memo::Empty"), + Memo::Text(memo) => write!(f, "Memo::Text(\"{}\")", memo.0), + Memo::Future(bytes) => write!(f, "Memo::Future({:0x})", bytes.0[0]), + Memo::Arbitrary(bytes) => { + write!(f, "Memo::Arbitrary(")?; + fmt_colon_delimited_hex(f, &bytes[..])?; + write!(f, ")") + } + } + } +} + +impl Default for Memo { + fn default() -> Self { + Memo::Empty + } +} + +impl PartialEq for Memo { + fn eq(&self, rhs: &Memo) -> bool { + match (self, rhs) { + (Memo::Empty, Memo::Empty) => true, + (Memo::Text(a), Memo::Text(b)) => a == b, + (Memo::Future(a), Memo::Future(b)) => a.0[..] == b.0[..], + (Memo::Arbitrary(a), Memo::Arbitrary(b)) => a[..] == b[..], + _ => false, + } + } +} + +impl TryFrom for Memo { + type Error = Error; + + /// Parses a `Memo` from its ZIP 302 serialization. + /// + /// Returns an error if the provided slice does not represent a valid `Memo` (for + /// example, if the slice is not 512 bytes, or the encoded `Memo` is non-canonical). + fn try_from(bytes: MemoBytes) -> Result { + match bytes.0[0] { + 0xF6 if bytes.0.iter().skip(1).all(|&b| b == 0) => Ok(Memo::Empty), + 0xFF => Ok(Memo::Arbitrary(Box::new(bytes.0[1..].try_into().unwrap()))), + b if b <= 0xF4 => str::from_utf8(bytes.as_slice()) + .map(|r| Memo::Text(TextMemo(r.to_owned()))) + .map_err(Error::InvalidUtf8), + _ => Ok(Memo::Future(bytes)), + } + } +} + +impl From for MemoBytes { + /// Serializes the `Memo` per ZIP 302. + fn from(memo: Memo) -> Self { + match memo { + // Small optimisation to avoid a clone + Memo::Future(memo) => memo, + memo => (&memo).into(), + } + } +} + +impl From<&Memo> for MemoBytes { + /// Serializes the `Memo` per ZIP 302. + fn from(memo: &Memo) -> Self { + match memo { + Memo::Empty => MemoBytes::empty(), + Memo::Text(s) => { + let mut bytes = [0u8; 512]; + let s_bytes = s.0.as_bytes(); + // s_bytes.len() is guaranteed to be <= 512 + bytes[..s_bytes.len()].copy_from_slice(s_bytes); + MemoBytes(Box::new(bytes)) + } + Memo::Future(memo) => memo.clone(), + Memo::Arbitrary(arb) => { + let mut bytes = [0u8; 512]; + bytes[0] = 0xFF; + bytes[1..].copy_from_slice(arb.as_ref()); + MemoBytes(Box::new(bytes)) + } + } + } +} + +impl Memo { + /// Parses a `Memo` from its ZIP 302 serialization. + /// + /// Returns an error if the provided slice does not represent a valid `Memo` (for + /// example, if the slice is not 512 bytes, or the encoded `Memo` is non-canonical). + pub fn from_bytes(bytes: &[u8]) -> Result { + MemoBytes::from_bytes(bytes).and_then(TryFrom::try_from) + } + + /// Serializes the `Memo` per ZIP 302. + pub fn encode(&self) -> MemoBytes { + self.into() + } +} + +impl str::FromStr for Memo { + type Err = Error; + + /// Returns a `Memo` containing the given string, or an error if the string is too long. + fn from_str(memo: &str) -> Result { + if memo.is_empty() { + Ok(Memo::Empty) + } else if memo.len() <= 512 { + Ok(Memo::Text(TextMemo(memo.to_owned()))) + } else { + Err(Error::TooLong(memo.len())) + } + } +} + +#[cfg(test)] +mod tests { + use std::convert::TryInto; + use std::str::FromStr; + + use super::{Error, Memo, MemoBytes}; + + #[test] + fn memo_from_str() { + assert_eq!( + Memo::from_str("").unwrap().encode(), + MemoBytes(Box::new([ + 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + ])) + ); + assert_eq!( + Memo::from_str( + "thiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis \ + iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis \ + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \ + veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeryyyyyyyyyyyyyyyyyyyyyyyyyy \ + looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong \ + meeeeeeeeeeeeeeeeeeemooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo \ + but it's just short enough" + ) + .unwrap() + .encode(), + MemoBytes(Box::new([ + 0x74, 0x68, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x73, 0x20, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x73, 0x20, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x20, 0x76, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, + 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, + 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, + 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, + 0x65, 0x65, 0x72, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, + 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, + 0x79, 0x20, 0x6c, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, + 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, + 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, + 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, + 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, + 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6e, 0x67, 0x20, 0x6d, + 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, + 0x65, 0x65, 0x65, 0x65, 0x65, 0x6d, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, + 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, + 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, + 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, + 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x20, 0x62, 0x75, 0x74, 0x20, + 0x69, 0x74, 0x27, 0x73, 0x20, 0x6a, 0x75, 0x73, 0x74, 0x20, 0x73, 0x68, 0x6f, 0x72, + 0x74, 0x20, 0x65, 0x6e, 0x6f, 0x75, 0x67, 0x68 + ])) + ); + assert_eq!( + Memo::from_str( + "thiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis \ + iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis \ + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \ + veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeryyyyyyyyyyyyyyyyyyyyyyyyyy \ + looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong \ + meeeeeeeeeeeeeeeeeeemooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo \ + but it's now a bit too long" + ), + Err(Error::TooLong(513)) + ); + } + + #[test] + fn future_memo() { + let bytes = [0xFE; 512]; + assert_eq!( + MemoBytes::from_bytes(&bytes).unwrap().try_into(), + Ok(Memo::Future(MemoBytes(Box::new(bytes)))) + ); + } + + #[test] + fn arbitrary_memo() { + let bytes = [42; 511]; + let memo = Memo::Arbitrary(Box::new(bytes)); + let raw = memo.encode(); + let encoded = raw.as_array(); + assert_eq!(encoded[0], 0xFF); + assert_eq!(encoded[1..], bytes[..]); + assert_eq!(MemoBytes::from_bytes(encoded).unwrap().try_into(), Ok(memo)); + } +} diff --git a/masp_primitives/src/merkle_tree.rs b/masp_primitives/src/merkle_tree.rs index d8adb778..fd1f6801 100644 --- a/masp_primitives/src/merkle_tree.rs +++ b/masp_primitives/src/merkle_tree.rs @@ -1,15 +1,88 @@ //! Implementation of a Merkle tree of commitments used to prove the existence of notes. -use byteorder::{LittleEndian, ReadBytesExt}; +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; + +use borsh::{BorshDeserialize, BorshSerialize}; +use core::convert::TryFrom; +use incrementalmerkletree::{ + self, + bridgetree::{self, Leaf}, + Altitude, +}; use std::collections::VecDeque; use std::io::{self, Read, Write}; - -use incrementalmerkletree::{self, bridgetree}; +use std::iter::repeat; use zcash_encoding::{Optional, Vector}; -use zcash_primitives::{ - merkle_tree::Hashable, - sapling::{SAPLING_COMMITMENT_TREE_DEPTH, SAPLING_COMMITMENT_TREE_DEPTH_U8}, -}; + +use crate::sapling::SAPLING_COMMITMENT_TREE_DEPTH; + +/// A hashable node within a Merkle tree. +pub trait Hashable: Clone + Copy { + /// Parses a node from the given byte source. + fn read(reader: R) -> io::Result; + + /// Serializes this node. + fn write(&self, writer: W) -> io::Result<()>; + + /// Returns the parent node within the tree of the two given nodes. + fn combine(_: usize, _: &Self, _: &Self) -> Self; + + /// Returns a blank leaf node. + fn blank() -> Self; + + /// Returns the empty root for the given depth. + fn empty_root(_: usize) -> Self; +} + +/// A hashable node within a Merkle tree. +pub trait HashSer { + /// Parses a node from the given byte source. + fn read(reader: R) -> io::Result + where + Self: Sized; + + /// Serializes this node. + fn write(&self, writer: W) -> io::Result<()>; +} + +impl Hashable for T +where + T: incrementalmerkletree::Hashable + HashSer + Copy, +{ + /// Parses a node from the given byte source. + fn read(reader: R) -> io::Result { + ::read(reader) + } + + /// Serializes this node. + fn write(&self, writer: W) -> io::Result<()> { + ::write(self, writer) + } + + /// Returns the parent node within the tree of the two given nodes. + fn combine(alt: usize, lhs: &Self, rhs: &Self) -> Self { + ::combine( + Altitude::from( + u8::try_from(alt).expect("Tree heights greater than 255 are unsupported."), + ), + lhs, + rhs, + ) + } + + /// Returns a blank leaf node. + fn blank() -> Self { + ::empty_leaf() + } + + /// Returns the empty root for the given depth. + fn empty_root(alt: usize) -> Self { + ::empty_root(Altitude::from( + u8::try_from(alt).expect("Tree heights greater than 255 are unsupported."), + )) + } +} + struct PathFiller { queue: VecDeque, } @@ -28,11 +101,177 @@ impl PathFiller { } } +/// An immutable commitment tree +#[derive(Clone, Debug, Default)] +pub struct FrozenCommitmentTree(Vec, usize); + +impl FrozenCommitmentTree { + /// Construct a commitment tree with the given leaf nodes + pub fn new(leafs: &[Node]) -> Self { + // This capacity is sufficient to hold a Merkle tree (where an empty node + // is added onto some rows to ensure that they are of even size) with the + // given number of leaves. This follows from the identity ceil(ceil(x/m)/n)=ceil(x/(mn)) + let mut tree = Vec::with_capacity(leafs.len() * 2 + SAPLING_COMMITMENT_TREE_DEPTH - 1); + tree.extend_from_slice(leafs); + // Infer the rest of the tree + Self::complete(tree, 0, leafs.len(), 0, leafs.len()) + } + /// Merge the n-1 full Merkle trees with the last possibly unfilled one. All + /// full trees must have the same size which must be a power of 2 and the + /// tree must be smaller than this size. + pub fn merge(subtrees: &[FrozenCommitmentTree]) -> Self { + if subtrees.is_empty() { + return Self(Vec::new(), 0); + } else if subtrees.len() == 1 { + return subtrees[0].clone(); + } + let size = subtrees[0].size(); + assert!(size.is_power_of_two()); + for subtree in subtrees.iter().rev().skip(1) { + assert_eq!(subtree.size(), size); + } + // Combine the 1 or more supplied subtrees + let mut height = 0; + let mut prev_first_start = 0; + let mut prev_first_width = subtrees[0].size(); + let mut prev_last_start = 0; + let mut prev_last_width = subtrees.last().unwrap().size(); + let mut prev_start = 0; + let mut prev_width = (subtrees.len() - 1) * prev_first_width + prev_last_width; + let leafs = prev_width; + let mut tree = Vec::with_capacity(leafs * 2 + SAPLING_COMMITMENT_TREE_DEPTH - 1); + loop { + // Need to make sure that right child is present for parent + if prev_last_width % 2 == 1 && prev_first_width > 1 { + prev_last_width += 1; + prev_width += 1; + } + // Combine all the rows at the current level + for subtree in &subtrees[0..(subtrees.len() - 1)] { + tree.extend_from_slice( + &subtree.0[prev_first_start..(prev_first_start + prev_first_width)], + ); + } + tree.extend_from_slice( + &subtrees.last().unwrap().0[prev_last_start..(prev_last_start + prev_last_width)], + ); + // Quit when we are the top of the full trees + if prev_first_width == 1 { + break; + } + // Update our positions on the full and unfull trees + prev_first_start += prev_first_width; + prev_first_width /= 2; + prev_last_start += prev_last_width; + prev_last_width /= 2; + prev_start += prev_width; + prev_width /= 2; + height += 1; + } + // Now that we have taken as many levels as possible from the + // supplied subtrees, infer the rest + Self::complete(tree, prev_start, prev_width, height, leafs) + } + /// Complete the construction of given Merkle tree given the highest row data + fn complete( + mut tree: Vec, + mut prev_start: usize, + mut prev_width: usize, + heightp: usize, + leafs: usize, + ) -> Self { + // Add higher and higher rows of the Merkle tree + for height in heightp..SAPLING_COMMITMENT_TREE_DEPTH { + if prev_width % 2 == 1 { + // Add a dummy for the right-most parent's right child + prev_width += 1; + tree.push(Node::empty_root(height)) + } + for j in 0..(prev_width / 2) { + // Add the nodes of the next row dependent upon previous row + let comb = Node::combine( + height, + &tree[prev_start + 2 * j], + &tree[prev_start + 2 * j + 1], + ); + tree.push(comb); + } + // Next row will be adjacent to current row in vector + prev_start += prev_width; + prev_width /= 2; + } + Self(tree, leafs) + } + /// Get the root node of the commitment tree + pub fn root(&self) -> Node { + self.0 + .last() + .cloned() + .unwrap_or_else(|| Node::empty_root(SAPLING_COMMITMENT_TREE_DEPTH)) + } + /// Construct a merkle path to the given position in commitment tree + pub fn path(&self, mut pos: usize) -> MerklePath { + let mut path = MerklePath { + auth_path: vec![], + position: pos as u64, + }; + let mut start = 0; + let mut width = self.1; + + for height in 0..SAPLING_COMMITMENT_TREE_DEPTH { + if width % 2 == 1 { + width += 1; + } + if pos % 2 == 0 { + // The current node is a left child + let node = if pos + 1 < width { + // Node is within current row + self.0[start + pos + 1] + } else { + // Node is to the right of current row + Node::empty_root(height) + }; + path.auth_path.push((node, false)); + } else { + // The current node is a right child + let node = if pos - 1 < width { + self.0[start + pos - 1] + } else { + Node::empty_root(height) + }; + path.auth_path.push((node, true)); + } + // Move to the parent of the current node + start += width; + width /= 2; + pos /= 2; + } + path + } + /// Returns the number of leaf nodes in the tree. + pub fn size(&self) -> usize { + self.1 + } +} + +impl BorshSerialize for FrozenCommitmentTree { + fn serialize(&self, writer: &mut W) -> borsh::maybestd::io::Result<()> { + (&self.0, self.1).serialize(writer) + } +} + +impl BorshDeserialize for FrozenCommitmentTree { + fn deserialize(buf: &mut &[u8]) -> borsh::maybestd::io::Result { + let tup: (Vec, usize) = BorshDeserialize::deserialize(buf)?; + Ok(Self(tup.0, tup.1)) + } +} + /// A Merkle tree of note commitments. /// /// The depth of the Merkle tree is fixed at 32, equal to the depth of the Sapling /// commitment tree. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct CommitmentTree { pub(crate) left: Option, pub(crate) right: Option, @@ -49,7 +288,35 @@ impl CommitmentTree { } } - pub fn to_frontier(&self) -> bridgetree::Frontier + pub fn from_frontier(frontier: &bridgetree::Frontier) -> Self + where + Node: Clone, + { + frontier.value().map_or_else(Self::empty, |f| { + let (left, right) = match f.leaf() { + Leaf::Left(v) => (Some(v.clone()), None), + Leaf::Right(l, r) => (Some(l.clone()), Some(r.clone())), + }; + let mut ommers_iter = f.ommers().iter().cloned(); + let upos: usize = f.position().into(); + Self { + left, + right, + parents: (1..DEPTH) + .into_iter() + .map(|i| { + if upos & (1 << i) == 0 { + None + } else { + ommers_iter.next() + } + }) + .collect(), + } + }) + } + + pub fn to_frontier(&self) -> bridgetree::Frontier where Node: incrementalmerkletree::Hashable + Clone, { @@ -94,10 +361,18 @@ impl CommitmentTree { } fn is_complete(&self, depth: usize) -> bool { - self.left.is_some() - && self.right.is_some() - && self.parents.len() == depth - 1 - && self.parents.iter().all(|p| p.is_some()) + if depth == 0 { + self.left.is_some() && self.right.is_none() && self.parents.is_empty() + } else { + self.left.is_some() + && self.right.is_some() + && self + .parents + .iter() + .chain(repeat(&None)) + .take(depth - 1) + .all(|p| p.is_some()) + } } } @@ -181,20 +456,29 @@ impl CommitmentTree { &self.right.unwrap_or_else(|| filler.next(0)), ); - // 2) Hash in parents up to the currently-filled depth. - // - Roots of the empty subtrees are used as needed. - let mid_root = self - .parents + // 2) Extend the parents to the desired depth with None values, then hash from leaf to + // root. Roots of the empty subtrees are used as needed. + self.parents .iter() + .chain(repeat(&None)) + .take(depth - 1) .enumerate() .fold(leaf_root, |root, (i, p)| match p { Some(node) => Node::combine(i + 1, node, &root), None => Node::combine(i + 1, &root, &filler.next(i + 1)), - }); + }) + } +} - // 3) Hash in roots of the empty subtrees up to the final depth. - ((self.parents.len() + 1)..depth) - .fold(mid_root, |root, d| Node::combine(d, &root, &filler.next(d))) +impl BorshSerialize for CommitmentTree { + fn serialize(&self, writer: &mut W) -> borsh::maybestd::io::Result<()> { + self.write(writer) + } +} + +impl BorshDeserialize for CommitmentTree { + fn deserialize(buf: &mut &[u8]) -> borsh::maybestd::io::Result { + Self::read(buf) } } @@ -209,7 +493,7 @@ impl CommitmentTree { /// ``` /// use ff::{Field, PrimeField}; /// use rand_core::OsRng; -/// use zcash_primitives::{ +/// use masp_primitives::{ /// merkle_tree::{CommitmentTree, IncrementalWitness}, /// sapling::Node, /// }; @@ -218,18 +502,18 @@ impl CommitmentTree { /// /// let mut tree = CommitmentTree::::empty(); /// -/// tree.append(Node::new(bls12_381::Scalar::random(&mut rng).to_repr())); -/// tree.append(Node::new(bls12_381::Scalar::random(&mut rng).to_repr())); +/// tree.append(Node::from_scalar(bls12_381::Scalar::random(&mut rng))); +/// tree.append(Node::from_scalar(bls12_381::Scalar::random(&mut rng))); /// let mut witness = IncrementalWitness::from_tree(&tree); /// assert_eq!(witness.position(), 1); /// assert_eq!(tree.root(), witness.root()); /// -/// let cmu = Node::new(bls12_381::Scalar::random(&mut rng).to_repr()); +/// let cmu = Node::from_scalar(bls12_381::Scalar::random(&mut rng)); /// tree.append(cmu); /// witness.append(cmu); /// assert_eq!(tree.root(), witness.root()); /// ``` -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct IncrementalWitness { tree: CommitmentTree, filled: Vec, @@ -394,24 +678,40 @@ impl IncrementalWitness { return None; } - for (i, p) in self.tree.parents.iter().enumerate() { + for (i, p) in self + .tree + .parents + .iter() + .chain(repeat(&None)) + .take(depth - 1) + .enumerate() + { auth_path.push(match p { Some(node) => (*node, true), None => (filler.next(i + 1), false), }); } - for i in self.tree.parents.len()..(depth - 1) { - auth_path.push((filler.next(i + 1), false)); - } assert_eq!(auth_path.len(), depth); Some(MerklePath::from_path(auth_path, self.position() as u64)) } } +impl BorshSerialize for IncrementalWitness { + fn serialize(&self, writer: &mut W) -> borsh::maybestd::io::Result<()> { + self.write(writer) + } +} + +impl BorshDeserialize for IncrementalWitness { + fn deserialize(buf: &mut &[u8]) -> borsh::maybestd::io::Result { + Self::read(buf) + } +} + /// A path from a position in a particular commitment tree to the root of that tree. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct MerklePath { pub auth_path: Vec<(Node, bool)>, pub position: u64, @@ -432,16 +732,46 @@ impl MerklePath { } fn from_slice_with_depth(mut witness: &[u8], depth: usize) -> Result { - // Skip the first byte, which should be "depth" to signify the length of - // the following vector of Pedersen hashes. - if witness[0] != depth as u8 { + let path = Self::deserialize(&mut witness).map_err(|_| ())?; + if path.auth_path.len() != depth { return Err(()); } - witness = &witness[1..]; + // The witness should be empty now; if it wasn't, the caller would + // have provided more information than they should have, indicating + // a bug downstream + if witness.is_empty() { + Ok(path) + } else { + Err(()) + } + } + + /// Returns the root of the tree corresponding to this path applied to `leaf`. + pub fn root(&self, leaf: Node) -> Node { + self.auth_path + .iter() + .enumerate() + .fold( + leaf, + |root, (i, (p, leaf_is_on_right))| match leaf_is_on_right { + false => Node::combine(i, &root, p), + true => Node::combine(i, p, &root), + }, + ) + } +} + +impl BorshDeserialize for MerklePath { + fn deserialize(witness: &mut &[u8]) -> Result { + // Skip the first byte, which should be "depth" to signify the length of + // the following vector of Pedersen hashes. + let depth = witness[0] as usize; + *witness = &witness[1..]; // Begin to construct the authentication path - let iter = witness.chunks_exact(33); - witness = iter.remainder(); + // Do not use any data in the witness after the expected depth + let iter = witness[..33 * depth + 8].chunks_exact(33); + *witness = iter.remainder(); // The vector works in reverse let mut auth_path = iter @@ -462,13 +792,14 @@ impl MerklePath { Err(()) } }) - .collect::, _>>()?; + .collect::, _>>() + .map_err(|_| std::io::Error::from(std::io::ErrorKind::InvalidData))?; if auth_path.len() != depth { - return Err(()); + return Err(std::io::Error::from(std::io::ErrorKind::InvalidData)); } // Read the position from the witness - let position = witness.read_u64::().map_err(|_| ())?; + let position = witness.read_u64::()?; // Given the position, let's finish constructing the authentication // path @@ -478,42 +809,50 @@ impl MerklePath { tmp >>= 1; } - // The witness should be empty now; if it wasn't, the caller would - // have provided more information than they should have, indicating - // a bug downstream - if witness.is_empty() { - Ok(MerklePath { - auth_path, - position, - }) - } else { - Err(()) - } + Ok(MerklePath { + auth_path, + position, + }) } +} - /// Returns the root of the tree corresponding to this path applied to `leaf`. - pub fn root(&self, leaf: Node) -> Node { - self.auth_path - .iter() - .enumerate() - .fold( - leaf, - |root, (i, (p, leaf_is_on_right))| match leaf_is_on_right { - false => Node::combine(i, &root, p), - true => Node::combine(i, p, &root), - }, - ) +impl BorshSerialize for MerklePath { + fn serialize(&self, witness: &mut W) -> Result<(), std::io::Error> { + let mut position = 0u64; + // Write path length + witness.write_u8(self.auth_path.len() as u8)?; + for (i, (node, b)) in self.auth_path.iter().enumerate().rev() { + // Write node into temporary object to measure data length + let mut node_bytes = Vec::new(); + node.write(&mut node_bytes)?; + // Write node length + witness.write_u8(node_bytes.len() as u8)?; + // Write node data + witness.write_all(&node_bytes)?; + position |= (*b as u64) << i; + } + // Write bit vector indicating positions + witness.write_u64::(position)?; + Ok(()) } } #[cfg(test)] mod tests { - use super::{CommitmentTree, Hashable, IncrementalWitness, MerklePath, PathFiller}; - use crate::sapling::Node; + use borsh::BorshSerialize; + use incrementalmerkletree::bridgetree::Frontier; + use proptest::prelude::*; use std::convert::TryInto; use std::io::{self, Read, Write}; + use crate::sapling::{testing::arb_node, Node}; + + use super::{ + testing::{arb_commitment_tree, TestNode}, + CommitmentTree, FrozenCommitmentTree, Hashable, IncrementalWitness, MerklePath, PathFiller, + }; + const HEX_EMPTY_ROOTS: [&str; 33] = [ "0100000000000000000000000000000000000000000000000000000000000000", "325aea4964041359acb6d15fa724089dd7242a7a61b1d9db50983e402d88ff1d", @@ -550,6 +889,52 @@ mod tests { "2d924d748574cf8b52f92b40d84f3781c8036defd40bc688ea182b1e52e8bf32", ]; + #[test] + fn test_frozen_tree() { + let commitments = [ + "b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f55", + "225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b11458", + "7c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e036c", + "50421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a030", + "aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc12", + "f76748d40d5ee5f9a608512e7954dd515f86e8f6d009141c89163de1cf351a02", + "bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0b2e", + "da1adda2ccde9381e11151686c121e7f52d19a990439161c7eb5a9f94be5a511", + "3a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f77446", + "c7ca8f7df8fd997931d33985d935ee2d696856cc09cc516d419ea6365f163008", + "f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c3702", + "e6d4d7685894d01b32f7e081ab188930be6c2b9f76d6847b7f382e3dddd7c608", + "8cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e826", + "22fab8bcdb88154dbf5877ad1e2d7f1b541bc8a5ec1b52266095381339c27c03", + "f43e3aac61e5a753062d4d0508c26ceaf5e4c0c58ba3c956e104b5d2cf67c41c", + "3a3661bc12b72646c94bc6c92796e81953985ee62d80a9ec3645a9a95740ac15", + ]; + for right in 8..16 { + let mut orig = CommitmentTree::empty(); + let mut cmus = Vec::new(); + let mut paths: Vec> = Vec::new(); + for commitment in commitments.iter().take(right) { + let cmu = hex::decode(commitment).unwrap(); + let cmu = Node::new(cmu[..].try_into().unwrap()); + orig.append(cmu).unwrap(); + cmus.push(cmu); + for path in &mut paths { + path.append(cmu).unwrap(); + } + paths.push(IncrementalWitness::from_tree(&orig)); + } + let frozen1 = FrozenCommitmentTree::new(&cmus[0..8]); + let frozen2 = FrozenCommitmentTree::new(&cmus[8..right]); + let frozen = FrozenCommitmentTree::merge(&[frozen1, frozen2]); + assert_eq!(orig.root(), frozen.root()); + for (i, path) in paths.iter().enumerate() { + let path = path.path().unwrap(); + assert_eq!(path.auth_path, frozen.path(i).auth_path); + assert_eq!(path.position, frozen.path(i).position); + } + } + } + const TESTING_DEPTH: usize = 4; struct TestCommitmentTree(CommitmentTree); @@ -970,8 +1355,7 @@ mod tests { "018cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e826000300019b64d9eaba791462043329211f21bdd22ecbb61e5b6c453cf5d8a0be7f7dc33901342eb1bd2cbfc1f19aaf4b4651d3d08a2dfbe39536617bf6e969c6ce8e2258670222fab8bcdb88154dbf5877ad1e2d7f1b541bc8a5ec1b52266095381339c27c03f1e52e9dd784324156f4cb229367e927c27c3d38fbf497a8c1e65afa986b9f3200", "018cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e8260122fab8bcdb88154dbf5877ad1e2d7f1b541bc8a5ec1b52266095381339c27c030300019b64d9eaba791462043329211f21bdd22ecbb61e5b6c453cf5d8a0be7f7dc33901342eb1bd2cbfc1f19aaf4b4651d3d08a2dfbe39536617bf6e969c6ce8e22586701f1e52e9dd784324156f4cb229367e927c27c3d38fbf497a8c1e65afa986b9f3200", "01f43e3aac61e5a753062d4d0508c26ceaf5e4c0c58ba3c956e104b5d2cf67c41c000301897c4d556be5693f038bc0db64fb02a8058bbdf49c7b223cdd838de0db18d063019b64d9eaba791462043329211f21bdd22ecbb61e5b6c453cf5d8a0be7f7dc33901342eb1bd2cbfc1f19aaf4b4651d3d08a2dfbe39536617bf6e969c6ce8e225867013a3661bc12b72646c94bc6c92796e81953985ee62d80a9ec3645a9a95740ac1500", - ] - ; + ]; fn assert_root_eq(root: Node, expected: &str) { let mut tmp = [0u8; 32]; @@ -1031,7 +1415,6 @@ mod tests { let mut last_cmu = None; let mut paths_i = 0; let mut witness_ser_i = 0; - for i in 0..16 { let cmu = hex::decode(commitments[i]).unwrap(); @@ -1064,6 +1447,10 @@ mod tests { ) .unwrap(); assert_eq!(path, expected); + let mut reser_vec = Vec::new(); + path.serialize(&mut reser_vec) + .expect("should be able to serialize path"); + assert_eq!(reser_vec, hex::decode(paths[paths_i]).unwrap()); assert_eq!(path.root(*leaf), witness.root()); paths_i += 1; } else { @@ -1080,6 +1467,7 @@ mod tests { last_cmu = Some(cmu); } + // Tree should be full now let node = Node::blank(); assert!(tree.append(node).is_err()); @@ -1087,4 +1475,95 @@ mod tests { assert!(witness.append(node).is_err()); } } + + proptest! { + #[test] + fn prop_commitment_tree_roundtrip(ct in arb_commitment_tree(32, arb_node(), 8)) { + let frontier: Frontier = ct.to_frontier(); + let ct0 = CommitmentTree::from_frontier(&frontier); + assert_eq!(ct, ct0); + let frontier0: Frontier = ct0.to_frontier(); + assert_eq!(frontier, frontier0); + } + } + + #[test] + fn test_commitment_tree_complete() { + let mut t: CommitmentTree = CommitmentTree::empty(); + for n in 1u64..=32 { + t.append(TestNode(n)).unwrap(); + // every tree of a power-of-two height is complete + let is_complete = n.count_ones() == 1; + let level = 63 - n.leading_zeros(); //log2 + assert_eq!( + is_complete, + t.is_complete(level.try_into().unwrap()), + "Tree {:?} {} complete at height {}", + t, + if is_complete { + "should be" + } else { + "should not be" + }, + n + ); + } + } +} + +#[cfg(any(test, feature = "test-dependencies"))] +pub mod testing { + use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; + use core::fmt::Debug; + use proptest::collection::vec; + use proptest::prelude::*; + use std::collections::hash_map::DefaultHasher; + use std::hash::Hasher; + use std::io::{self, Read, Write}; + + use super::{CommitmentTree, Hashable}; + + pub fn arb_commitment_tree>( + min_size: usize, + arb_node: T, + depth: u8, + ) -> impl Strategy> { + assert!((1 << depth) >= min_size + 100); + vec(arb_node, min_size..(min_size + 100)).prop_map(move |v| { + let mut tree = CommitmentTree::empty(); + for node in v.into_iter() { + tree.append(node).unwrap(); + } + tree.parents.resize_with((depth - 1).into(), || None); + tree + }) + } + + #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] + pub(crate) struct TestNode(pub(crate) u64); + + impl Hashable for TestNode { + fn read(mut reader: R) -> io::Result { + reader.read_u64::().map(TestNode) + } + + fn write(&self, mut writer: W) -> io::Result<()> { + writer.write_u64::(self.0) + } + + fn combine(_: usize, a: &TestNode, b: &TestNode) -> TestNode { + let mut hasher = DefaultHasher::new(); + hasher.write_u64(a.0); + hasher.write_u64(b.0); + TestNode(hasher.finish()) + } + + fn blank() -> TestNode { + TestNode(0) + } + + fn empty_root(alt: usize) -> TestNode { + (0..alt).fold(Self::blank(), |v, lvl| Self::combine(lvl, &v, &v)) + } + } } diff --git a/masp_primitives/src/primitives.rs b/masp_primitives/src/primitives.rs deleted file mode 100644 index 123f8c9f..00000000 --- a/masp_primitives/src/primitives.rs +++ /dev/null @@ -1,330 +0,0 @@ -//! Structs for core MASP primitives. - -use crate::{ - asset_type::AssetType, - constants, - keys::prf_expand, - pedersen_hash::{pedersen_hash, Personalization}, -}; -use blake2s_simd::Params as Blake2sParams; -use byteorder::{LittleEndian, WriteBytesExt}; -use ff::PrimeField; -use group::{cofactor::CofactorGroup, Curve, Group, GroupEncoding}; -use rand_core::{CryptoRng, RngCore}; -use std::convert::TryInto; -use zcash_primitives::sapling::{group_hash::group_hash, Nullifier, Rseed}; - -#[derive(Clone)] -pub struct ValueCommitment { - pub asset_generator: jubjub::ExtendedPoint, - pub value: u64, - pub randomness: jubjub::Fr, -} - -impl ValueCommitment { - pub fn commitment(&self) -> jubjub::SubgroupPoint { - (CofactorGroup::clear_cofactor(&self.asset_generator) * jubjub::Fr::from(self.value)) - + (constants::VALUE_COMMITMENT_RANDOMNESS_GENERATOR * self.randomness) - } -} - -#[derive(Clone)] -pub struct ProofGenerationKey { - pub ak: jubjub::SubgroupPoint, - pub nsk: jubjub::Fr, -} - -impl ProofGenerationKey { - pub fn to_viewing_key(&self) -> ViewingKey { - ViewingKey { - ak: self.ak, - nk: constants::PROOF_GENERATION_KEY_GENERATOR * self.nsk, - } - } -} - -#[derive(Debug, Clone)] -pub struct ViewingKey { - pub ak: jubjub::SubgroupPoint, - pub nk: jubjub::SubgroupPoint, -} - -impl ViewingKey { - pub fn rk(&self, ar: jubjub::Fr) -> jubjub::SubgroupPoint { - self.ak + constants::SPENDING_KEY_GENERATOR * ar - } - - pub fn ivk(&self) -> SaplingIvk { - let mut h = [0; 32]; - h.copy_from_slice( - Blake2sParams::new() - .hash_length(32) - .personal(constants::CRH_IVK_PERSONALIZATION) - .to_state() - .update(&self.ak.to_bytes()) - .update(&self.nk.to_bytes()) - .finalize() - .as_bytes(), - ); - - // Drop the most significant five bits, so it can be interpreted as a scalar. - h[31] &= 0b0000_0111; - - SaplingIvk(jubjub::Fr::from_repr(h).unwrap()) - } - pub fn to_payment_address(&self, diversifier: Diversifier) -> Option { - self.ivk().to_payment_address(diversifier) - } -} - -#[derive(Debug, Clone)] -pub struct SaplingIvk(pub jubjub::Fr); - -impl SaplingIvk { - pub fn to_payment_address(&self, diversifier: Diversifier) -> Option { - diversifier.g_d().and_then(|g_d| { - let pk_d = g_d * self.0; - - PaymentAddress::from_parts(diversifier, pk_d) - }) - } - - pub fn to_repr(&self) -> [u8; 32] { - self.0.to_repr() - } -} - -#[derive(Copy, Clone, Debug, PartialEq)] -pub struct Diversifier(pub [u8; 11]); - -impl Diversifier { - pub fn g_d(&self) -> Option { - group_hash(&self.0, constants::KEY_DIVERSIFICATION_PERSONALIZATION) - } -} - -/// A Sapling payment address. -/// -/// # Invariants -/// -/// `pk_d` is guaranteed to be prime-order (i.e. in the prime-order subgroup of Jubjub, -/// and not the identity). -#[derive(Clone, Debug)] -pub struct PaymentAddress { - pk_d: jubjub::SubgroupPoint, - diversifier: Diversifier, -} - -impl PartialEq for PaymentAddress { - fn eq(&self, other: &Self) -> bool { - self.pk_d == other.pk_d && self.diversifier == other.diversifier - } -} - -impl PaymentAddress { - /// Constructs a PaymentAddress from a diversifier and a Jubjub point. - /// - /// Returns None if `pk_d` is the identity. - pub fn from_parts(diversifier: Diversifier, pk_d: jubjub::SubgroupPoint) -> Option { - if pk_d.is_identity().into() { - None - } else { - Some(PaymentAddress { pk_d, diversifier }) - } - } - - /// Constructs a PaymentAddress from a diversifier and a Jubjub point. - /// - /// Only for test code, as this explicitly bypasses the invariant. - #[cfg(test)] - #[allow(dead_code)] - pub(crate) fn from_parts_unchecked( - diversifier: Diversifier, - pk_d: jubjub::SubgroupPoint, - ) -> Self { - PaymentAddress { pk_d, diversifier } - } - - /// Parses a PaymentAddress from bytes. - pub fn from_bytes(bytes: &[u8; 43]) -> Option { - let diversifier = { - let mut tmp = [0; 11]; - tmp.copy_from_slice(&bytes[0..11]); - Diversifier(tmp) - }; - // Check that the diversifier is valid - diversifier.g_d()?; - - let pk_d = jubjub::SubgroupPoint::from_bytes(bytes[11..43].try_into().unwrap()); - if pk_d.is_some().into() { - PaymentAddress::from_parts(diversifier, pk_d.unwrap()) - } else { - None - } - } - - /// Returns the byte encoding of this `PaymentAddress`. - pub fn to_bytes(&self) -> [u8; 43] { - let mut bytes = [0; 43]; - bytes[0..11].copy_from_slice(&self.diversifier.0); - bytes[11..].copy_from_slice(&self.pk_d.to_bytes()); - bytes - } - - /// Returns the [`Diversifier`] for this `PaymentAddress`. - pub fn diversifier(&self) -> &Diversifier { - &self.diversifier - } - - /// Returns `pk_d` for this `PaymentAddress`. - pub fn pk_d(&self) -> &jubjub::SubgroupPoint { - &self.pk_d - } - - pub fn g_d(&self) -> Option { - self.diversifier.g_d() - } - - pub fn create_note( - &self, - asset_type: AssetType, - value: u64, - randomness: Rseed, - ) -> Option { - self.g_d().map(|g_d| Note { - asset_type, - value, - rseed: randomness, - g_d, - pk_d: self.pk_d, - }) - } -} - -#[derive(Clone, Debug)] -pub struct Note { - /// The asset type that the note represents - pub asset_type: AssetType, - /// The value of the note - pub value: u64, - /// The diversified base of the address, GH(d) - pub g_d: jubjub::SubgroupPoint, - /// The public key of the address, g_d^ivk - pub pk_d: jubjub::SubgroupPoint, - /// rseed - pub rseed: Rseed, -} - -impl PartialEq for Note { - fn eq(&self, other: &Self) -> bool { - self.value == other.value - && self.asset_type == other.asset_type - && self.g_d == other.g_d - && self.pk_d == other.pk_d - && self.rcm() == other.rcm() - } -} - -impl Note { - pub fn uncommitted() -> bls12_381::Scalar { - // The smallest u-coordinate that is not on the curve - // is one. - bls12_381::Scalar::one() - } - - /// Computes the note commitment, returning the full point. - fn cm_full_point(&self) -> jubjub::SubgroupPoint { - // Calculate the note contents, as bytes - let mut note_contents = vec![]; - - // Write the asset generator, cofactor not cleared - note_contents.extend_from_slice(&self.asset_type.asset_generator().to_bytes()); - - // Writing the value in little endian - (&mut note_contents) - .write_u64::(self.value) - .unwrap(); - - // Write g_d - note_contents.extend_from_slice(&self.g_d.to_bytes()); - - // Write pk_d - note_contents.extend_from_slice(&self.pk_d.to_bytes()); - - assert_eq!(note_contents.len(), 32 + 32 + 32 + 8); - - // Compute the Pedersen hash of the note contents - let hash_of_contents = pedersen_hash( - Personalization::NoteCommitment, - note_contents - .into_iter() - .flat_map(|byte| (0..8).map(move |i| ((byte >> i) & 1) == 1)), - ); - - // Compute final commitment - (constants::NOTE_COMMITMENT_RANDOMNESS_GENERATOR * self.rcm()) + hash_of_contents - } - - /// Computes the nullifier given the viewing key and - /// note position - pub fn nf(&self, viewing_key: &ViewingKey, position: u64) -> Nullifier { - // Compute rho = cm + position.G - let rho = self.cm_full_point() - + (constants::NULLIFIER_POSITION_GENERATOR * jubjub::Fr::from(position)); - - // Compute nf = BLAKE2s(nk | rho) - Nullifier::from_slice( - Blake2sParams::new() - .hash_length(32) - .personal(constants::PRF_NF_PERSONALIZATION) - .to_state() - .update(&viewing_key.nk.to_bytes()) - .update(&rho.to_bytes()) - .finalize() - .as_bytes(), - ) - .unwrap() - } - - /// Computes the note commitment - pub fn cmu(&self) -> bls12_381::Scalar { - // The commitment is in the prime order subgroup, so mapping the - // commitment to the u-coordinate is an injective encoding. - jubjub::ExtendedPoint::from(self.cm_full_point()) - .to_affine() - .get_u() - } - - pub fn rcm(&self) -> jubjub::Fr { - match self.rseed { - Rseed::BeforeZip212(rcm) => rcm, - Rseed::AfterZip212(rseed) => { - jubjub::Fr::from_bytes_wide(prf_expand(&rseed, &[0x04]).as_array()) - } - } - } - - pub fn generate_or_derive_esk(&self, rng: &mut R) -> jubjub::Fr { - match self.derive_esk() { - None => { - // create random 64 byte buffer - let mut buffer = [0u8; 64]; - rng.fill_bytes(&mut buffer); - - // reduce to uniform value - jubjub::Fr::from_bytes_wide(&buffer) - } - Some(esk) => esk, - } - } - - /// Returns the derived `esk` if this note was created after ZIP 212 activated. - pub fn derive_esk(&self) -> Option { - match self.rseed { - Rseed::BeforeZip212(_) => None, - Rseed::AfterZip212(rseed) => Some(jubjub::Fr::from_bytes_wide( - prf_expand(&rseed, &[0x05]).as_array(), - )), - } - } -} diff --git a/masp_primitives/src/sapling.rs b/masp_primitives/src/sapling.rs index 69b643cc..914894f0 100644 --- a/masp_primitives/src/sapling.rs +++ b/masp_primitives/src/sapling.rs @@ -1,24 +1,49 @@ //! Structs and constants specific to the Sapling shielded pool. -use crate::{ - constants::SPENDING_KEY_GENERATOR, - pedersen_hash::{pedersen_hash, Personalization}, - primitives::Note, -}; +pub mod group_hash; +pub mod keys; +pub mod note_encryption; +pub mod pedersen_hash; +pub mod prover; +pub mod redjubjub; +pub mod util; + use bitvec::{order::Lsb0, view::AsBits}; -use ff::PrimeField; -use group::{Curve, GroupEncoding}; +use blake2s_simd::Params as Blake2sParams; +use borsh::{BorshDeserialize, BorshSerialize}; +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use ff::{Field, PrimeField}; +use group::{cofactor::CofactorGroup, Curve, Group, GroupEncoding}; use incrementalmerkletree::{self, Altitude}; use lazy_static::lazy_static; use rand_core::{CryptoRng, RngCore}; -use std::io::{self, Read, Write}; +use std::{ + array::TryFromSliceError, + cmp::Ordering, + convert::TryFrom, + fmt::{Display, Formatter}, + hash::{Hash, Hasher}, + io::{self, Read, Write}, + str::FromStr, +}; +use subtle::{Choice, ConstantTimeEq, CtOption}; -use crate::redjubjub::{PrivateKey, PublicKey, Signature}; -use zcash_primitives::{ +use crate::{ + asset_type::AssetType, + constants::{self, SPENDING_KEY_GENERATOR}, + keys::prf_expand, merkle_tree::{HashSer, Hashable}, - sapling::SAPLING_COMMITMENT_TREE_DEPTH, + transaction::components::amount::MAX_MONEY, +}; + +use self::{ + group_hash::group_hash, + pedersen_hash::{pedersen_hash, Personalization}, + redjubjub::{PrivateKey, PublicKey, Signature}, }; +pub const SAPLING_COMMITMENT_TREE_DEPTH: usize = 32; + /// Compute a parent node in the Sapling commitment tree given its two children. pub fn merkle_hash(depth: usize, lhs: &[u8; 32], rhs: &[u8; 32]) -> [u8; 32] { let lhs = { @@ -54,15 +79,23 @@ pub fn merkle_hash(depth: usize, lhs: &[u8; 32], rhs: &[u8; 32]) -> [u8; 32] { } /// A node within the Sapling commitment tree. -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Default)] pub struct Node { repr: [u8; 32], } impl Node { - pub fn new(repr: [u8; 32]) -> Self { + #[cfg(test)] + pub(crate) fn new(repr: [u8; 32]) -> Self { Node { repr } } + + /// Constructs a new note commitment tree node from a [`bls12_381::Scalar`] + pub fn from_scalar(cmu: bls12_381::Scalar) -> Self { + Self { + repr: cmu.to_repr(), + } + } } impl incrementalmerkletree::Hashable for Node { @@ -87,7 +120,7 @@ impl HashSer for Node { fn read(mut reader: R) -> io::Result { let mut repr = [0u8; 32]; reader.read_exact(&mut repr)?; - Ok(Node::new(repr)) + Ok(Node { repr }) } fn write(&self, mut writer: W) -> io::Result<()> { @@ -138,8 +171,666 @@ pub(crate) fn spend_sig_internal( // Compute the signature's message for rk/spend_auth_sig let mut data_to_be_signed = [0u8; 64]; data_to_be_signed[0..32].copy_from_slice(&rk.0.to_bytes()); - (&mut data_to_be_signed[32..64]).copy_from_slice(&sighash[..]); + data_to_be_signed[32..64].copy_from_slice(&sighash[..]); // Do the signing rsk.sign(&data_to_be_signed, rng, SPENDING_KEY_GENERATOR) } + +#[derive(Clone)] +pub struct ValueCommitment { + pub asset_generator: jubjub::ExtendedPoint, + pub value: u64, + pub randomness: jubjub::Fr, +} + +impl ValueCommitment { + pub fn commitment(&self) -> jubjub::SubgroupPoint { + (CofactorGroup::clear_cofactor(&self.asset_generator) * jubjub::Fr::from(self.value)) + + (constants::VALUE_COMMITMENT_RANDOMNESS_GENERATOR * self.randomness) + } +} + +#[derive(Clone)] +pub struct ProofGenerationKey { + pub ak: jubjub::SubgroupPoint, + pub nsk: jubjub::Fr, +} + +impl ProofGenerationKey { + pub fn to_viewing_key(&self) -> ViewingKey { + ViewingKey { + ak: self.ak, + nk: NullifierDerivingKey(constants::PROOF_GENERATION_KEY_GENERATOR * self.nsk), + } + } +} + +/// A key used to derive the nullifier for a Sapling note. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct NullifierDerivingKey(pub jubjub::SubgroupPoint); + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct ViewingKey { + pub ak: jubjub::SubgroupPoint, + pub nk: NullifierDerivingKey, +} + +impl Hash for ViewingKey { + fn hash(&self, state: &mut H) + where + H: Hasher, + { + self.ak.to_bytes().hash(state); + self.nk.0.to_bytes().hash(state); + } +} + +impl ViewingKey { + pub fn rk(&self, ar: jubjub::Fr) -> jubjub::SubgroupPoint { + self.ak + constants::SPENDING_KEY_GENERATOR * ar + } + + pub fn ivk(&self) -> SaplingIvk { + let mut h = [0; 32]; + h.copy_from_slice( + Blake2sParams::new() + .hash_length(32) + .personal(constants::CRH_IVK_PERSONALIZATION) + .to_state() + .update(&self.ak.to_bytes()) + .update(&self.nk.0.to_bytes()) + .finalize() + .as_bytes(), + ); + + // Drop the most significant five bits, so it can be interpreted as a scalar. + h[31] &= 0b0000_0111; + + SaplingIvk(jubjub::Fr::from_repr(h).unwrap()) + } + + pub fn to_payment_address(&self, diversifier: Diversifier) -> Option { + self.ivk().to_payment_address(diversifier) + } + + pub fn read(mut reader: R) -> io::Result { + let ak = { + let mut buf = [0u8; 32]; + reader.read_exact(&mut buf)?; + jubjub::SubgroupPoint::from_bytes(&buf).and_then(|p| CtOption::new(p, !p.is_identity())) + }; + let nk = { + let mut buf = [0u8; 32]; + reader.read_exact(&mut buf)?; + jubjub::SubgroupPoint::from_bytes(&buf) + }; + if ak.is_none().into() { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "ak not of prime order", + )); + } + if nk.is_none().into() { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "nk not in prime-order subgroup", + )); + } + let ak = ak.unwrap(); + let nk = nk.unwrap(); + + Ok(ViewingKey { + ak, + nk: NullifierDerivingKey(nk), + }) + } + + pub fn write(&self, mut writer: W) -> io::Result<()> { + writer.write_all(&self.ak.to_bytes())?; + writer.write_all(&self.nk.0.to_bytes())?; + + Ok(()) + } + + pub fn to_bytes(&self) -> [u8; 64] { + let mut result = [0u8; 64]; + self.write(&mut result[..]) + .expect("should be able to serialize a ViewingKey"); + result + } +} + +impl BorshSerialize for ViewingKey { + fn serialize(&self, writer: &mut W) -> borsh::maybestd::io::Result<()> { + self.write(writer) + } +} + +impl BorshDeserialize for ViewingKey { + fn deserialize(buf: &mut &[u8]) -> borsh::maybestd::io::Result { + Self::read(buf) + } +} + +impl PartialOrd for ViewingKey { + fn partial_cmp(&self, other: &Self) -> Option { + self.to_bytes().partial_cmp(&other.to_bytes()) + } +} + +impl Ord for ViewingKey { + fn cmp(&self, other: &Self) -> Ordering { + self.to_bytes().cmp(&other.to_bytes()) + } +} + +#[derive(Debug, Clone)] +pub struct SaplingIvk(pub jubjub::Fr); + +impl SaplingIvk { + pub fn to_payment_address(&self, diversifier: Diversifier) -> Option { + diversifier.g_d().and_then(|g_d| { + let pk_d = g_d * self.0; + + PaymentAddress::from_parts(diversifier, pk_d) + }) + } + + pub fn to_repr(&self) -> [u8; 32] { + self.0.to_repr() + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize)] +pub struct Diversifier(pub [u8; 11]); + +impl Diversifier { + pub fn g_d(&self) -> Option { + group_hash(&self.0, constants::KEY_DIVERSIFICATION_PERSONALIZATION) + } +} + +/// A Sapling payment address. +/// +/// # Invariants +/// +/// `pk_d` is guaranteed to be prime-order (i.e. in the prime-order subgroup of Jubjub, +/// and not the identity). +#[derive(Clone, Copy, Debug)] +pub struct PaymentAddress { + pk_d: jubjub::SubgroupPoint, + diversifier: Diversifier, +} + +impl PartialEq for PaymentAddress { + fn eq(&self, other: &Self) -> bool { + self.pk_d == other.pk_d && self.diversifier == other.diversifier + } +} + +impl Eq for PaymentAddress {} + +impl PaymentAddress { + /// Constructs a PaymentAddress from a diversifier and a Jubjub point. + /// + /// Returns None if `pk_d` is the identity. + pub fn from_parts(diversifier: Diversifier, pk_d: jubjub::SubgroupPoint) -> Option { + if pk_d.is_identity().into() { + None + } else { + Some(PaymentAddress { pk_d, diversifier }) + } + } + + /// Constructs a PaymentAddress from a diversifier and a Jubjub point. + /// + /// Only for test code, as this explicitly bypasses the invariant. + #[cfg(test)] + #[allow(dead_code)] + pub(crate) fn from_parts_unchecked( + diversifier: Diversifier, + pk_d: jubjub::SubgroupPoint, + ) -> Self { + PaymentAddress { pk_d, diversifier } + } + + /// Parses a PaymentAddress from bytes. + pub fn from_bytes(bytes: &[u8; 43]) -> Option { + let diversifier = { + let mut tmp = [0; 11]; + tmp.copy_from_slice(&bytes[0..11]); + Diversifier(tmp) + }; + // Check that the diversifier is valid + diversifier.g_d()?; + + let pk_d = jubjub::SubgroupPoint::from_bytes(bytes[11..43].try_into().unwrap()); + if pk_d.is_some().into() { + PaymentAddress::from_parts(diversifier, pk_d.unwrap()) + } else { + None + } + } + + /// Returns the byte encoding of this `PaymentAddress`. + pub fn to_bytes(&self) -> [u8; 43] { + let mut bytes = [0; 43]; + bytes[0..11].copy_from_slice(&self.diversifier.0); + bytes[11..].copy_from_slice(&self.pk_d.to_bytes()); + bytes + } + + /// Returns the [`Diversifier`] for this `PaymentAddress`. + pub fn diversifier(&self) -> &Diversifier { + &self.diversifier + } + + /// Returns `pk_d` for this `PaymentAddress`. + pub fn pk_d(&self) -> &jubjub::SubgroupPoint { + &self.pk_d + } + + pub fn g_d(&self) -> Option { + self.diversifier.g_d() + } + + pub fn create_note(&self, asset_type: AssetType, value: u64, rseed: Rseed) -> Option { + self.g_d().map(|g_d| Note { + asset_type, + value, + rseed, + g_d, + pk_d: self.pk_d, + }) + } +} + +impl Display for PaymentAddress { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + write!(f, "{}", hex::encode(self.to_bytes())) + } +} + +impl FromStr for PaymentAddress { + type Err = io::Error; + + fn from_str(s: &str) -> Result { + let vec = hex::decode(s).map_err(|x| io::Error::new(io::ErrorKind::InvalidData, x))?; + BorshDeserialize::try_from_slice(&vec) + } +} + +impl PartialOrd for PaymentAddress { + fn partial_cmp(&self, other: &Self) -> Option { + self.to_bytes().partial_cmp(&other.to_bytes()) + } +} +impl Ord for PaymentAddress { + fn cmp(&self, other: &Self) -> Ordering { + self.to_bytes().cmp(&other.to_bytes()) + } +} +impl Hash for PaymentAddress { + fn hash(&self, state: &mut H) { + self.to_bytes().hash(state) + } +} + +impl BorshSerialize for PaymentAddress { + fn serialize(&self, writer: &mut W) -> borsh::maybestd::io::Result<()> { + writer.write(self.to_bytes().as_ref()).and(Ok(())) + } +} +impl BorshDeserialize for PaymentAddress { + fn deserialize(buf: &mut &[u8]) -> borsh::maybestd::io::Result { + let data = buf + .get(..43) + .ok_or_else(|| io::Error::from(io::ErrorKind::UnexpectedEof))?; + let res = Self::from_bytes(data.try_into().unwrap()); + let pa = res.ok_or_else(|| io::Error::from(io::ErrorKind::InvalidData))?; + *buf = &buf[43..]; + Ok(pa) + } +} + +/// Enum for note randomness before and after [ZIP 212](https://zips.z.cash/zip-0212). +/// +/// Before ZIP 212, the note commitment trapdoor `rcm` must be a scalar value. +/// After ZIP 212, the note randomness `rseed` is a 32-byte sequence, used to derive +/// both the note commitment trapdoor `rcm` and the ephemeral private key `esk`. +#[derive(Copy, Clone, Debug)] +pub enum Rseed { + BeforeZip212(jubjub::Fr), + AfterZip212([u8; 32]), +} + +/// Typesafe wrapper for nullifier values. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct Nullifier(pub [u8; 32]); + +impl Nullifier { + pub fn from_slice(bytes: &[u8]) -> Result { + bytes.try_into().map(Nullifier) + } + + pub fn to_vec(&self) -> Vec { + self.0.to_vec() + } +} +impl AsRef<[u8]> for Nullifier { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl ConstantTimeEq for Nullifier { + fn ct_eq(&self, other: &Self) -> Choice { + self.0.ct_eq(&other.0) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct NoteValue(u64); + +impl TryFrom for NoteValue { + type Error = (); + + fn try_from(value: u64) -> Result { + if value <= MAX_MONEY as u64 { + Ok(NoteValue(value)) + } else { + Err(()) + } + } +} + +impl From for u64 { + fn from(value: NoteValue) -> u64 { + value.0 + } +} + +#[derive(Clone, Debug)] +pub struct Note { + /// The asset type that the note represents + pub asset_type: AssetType, + /// The value of the note + pub value: u64, + /// The diversified base of the address, GH(d) + pub g_d: jubjub::SubgroupPoint, + /// The public key of the address, g_d^ivk + pub pk_d: jubjub::SubgroupPoint, + /// rseed + pub rseed: Rseed, +} + +impl PartialEq for Note { + fn eq(&self, other: &Self) -> bool { + self.value == other.value + && self.asset_type == other.asset_type + && self.g_d == other.g_d + && self.pk_d == other.pk_d + && self.rcm() == other.rcm() + } +} + +impl Note { + pub fn uncommitted() -> bls12_381::Scalar { + // The smallest u-coordinate that is not on the curve + // is one. + bls12_381::Scalar::one() + } + + /// Computes the note commitment, returning the full point. + fn cm_full_point(&self) -> jubjub::SubgroupPoint { + // Calculate the note contents, as bytes + let mut note_contents = vec![]; + + // Write the asset generator, cofactor not cleared + note_contents.extend_from_slice(&self.asset_type.asset_generator().to_bytes()); + + // Writing the value in little endian + note_contents.write_u64::(self.value).unwrap(); + + // Write g_d + note_contents.extend_from_slice(&self.g_d.to_bytes()); + + // Write pk_d + note_contents.extend_from_slice(&self.pk_d.to_bytes()); + + assert_eq!(note_contents.len(), 32 + 32 + 32 + 8); + + // Compute the Pedersen hash of the note contents + let hash_of_contents = pedersen_hash( + Personalization::NoteCommitment, + note_contents + .into_iter() + .flat_map(|byte| (0..8).map(move |i| ((byte >> i) & 1) == 1)), + ); + + // Compute final commitment + (constants::NOTE_COMMITMENT_RANDOMNESS_GENERATOR * self.rcm()) + hash_of_contents + } + + /// Computes the nullifier given the nullifier deriving key and + /// note position + pub fn nf(&self, nk: &NullifierDerivingKey, position: u64) -> Nullifier { + // Compute rho = cm + position.G + let rho = self.cm_full_point() + + (constants::NULLIFIER_POSITION_GENERATOR * jubjub::Fr::from(position)); + + // Compute nf = BLAKE2s(nk | rho) + Nullifier::from_slice( + Blake2sParams::new() + .hash_length(32) + .personal(constants::PRF_NF_PERSONALIZATION) + .to_state() + .update(&nk.0.to_bytes()) + .update(&rho.to_bytes()) + .finalize() + .as_bytes(), + ) + .unwrap() + } + + /// Computes the note commitment + pub fn cmu(&self) -> bls12_381::Scalar { + // The commitment is in the prime order subgroup, so mapping the + // commitment to the u-coordinate is an injective encoding. + jubjub::ExtendedPoint::from(self.cm_full_point()) + .to_affine() + .get_u() + } + + pub fn rcm(&self) -> jubjub::Fr { + match self.rseed { + Rseed::BeforeZip212(rcm) => rcm, + Rseed::AfterZip212(rseed) => { + jubjub::Fr::from_bytes_wide(prf_expand(&rseed, &[0x04]).as_array()) + } + } + } + + pub fn generate_or_derive_esk(&self, rng: &mut R) -> jubjub::Fr { + self.generate_or_derive_esk_internal(rng) + } + + pub(crate) fn generate_or_derive_esk_internal(&self, rng: &mut R) -> jubjub::Fr { + match self.derive_esk() { + None => jubjub::Fr::random(rng), + Some(esk) => esk, + } + } + + /// Returns the derived `esk` if this note was created after ZIP 212 activated. + pub fn derive_esk(&self) -> Option { + match self.rseed { + Rseed::BeforeZip212(_) => None, + Rseed::AfterZip212(rseed) => Some(jubjub::Fr::from_bytes_wide( + prf_expand(&rseed, &[0x05]).as_array(), + )), + } + } + + /// Returns [`self.cmu`] in the correct representation for inclusion in the Sapling + /// note commitment tree. + pub fn commitment(&self) -> Node { + Node { + repr: self.cmu().to_repr(), + } + } +} + +impl BorshSerialize for Note { + fn serialize(&self, writer: &mut W) -> borsh::maybestd::io::Result<()> { + // Write asset type + self.asset_type.serialize(writer)?; + // Write note value + writer.write_u64::(self.value)?; + // Write diversified base + writer.write_all(&self.g_d.to_bytes())?; + // Write diversified transmission key + writer.write_all(&self.pk_d.to_bytes())?; + match self.rseed { + Rseed::BeforeZip212(rcm) => { + // Write note plaintext lead byte + writer.write_u8(1)?; + // Write rseed + writer.write_all(&rcm.to_repr()) + } + Rseed::AfterZip212(rseed) => { + // Write note plaintext lead byte + writer.write_u8(2)?; + // Write rseed + writer.write_all(&rseed) + } + }?; + Ok(()) + } +} + +impl BorshDeserialize for Note { + fn deserialize(buf: &mut &[u8]) -> borsh::maybestd::io::Result { + // Read asset type + let asset_type = AssetType::deserialize(buf)?; + // Read note value + let value = buf.read_u64::()?; + // Read diversified base + let g_d_bytes = <[u8; 32]>::deserialize(buf)?; + let g_d = Option::from(jubjub::SubgroupPoint::from_bytes(&g_d_bytes)) + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "g_d not in field"))?; + // Read diversified transmission key + let pk_d_bytes = <[u8; 32]>::deserialize(buf)?; + let pk_d = Option::from(jubjub::SubgroupPoint::from_bytes(&pk_d_bytes)) + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "pk_d not in field"))?; + // Read note plaintext lead byte + let rseed_type = buf.read_u8()?; + // Read rseed + let rseed_bytes = <[u8; 32]>::deserialize(buf)?; + let rseed = if rseed_type == 0x01 { + let data = Option::from(jubjub::Fr::from_bytes(&rseed_bytes)) + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "rseed not in field"))?; + Rseed::BeforeZip212(data) + } else { + Rseed::AfterZip212(rseed_bytes) + }; + // Finally construct note object + Ok(Note { + asset_type, + value, + g_d, + pk_d, + rseed, + }) + } +} +#[cfg(any(test, feature = "test-dependencies"))] +pub mod testing { + use proptest::prelude::*; + use std::cmp::min; + + use crate::transaction::components::amount::MAX_MONEY; + + use super::{ + keys::testing::arb_full_viewing_key, Diversifier, Node, Note, NoteValue, PaymentAddress, + Rseed, SaplingIvk, + }; + + prop_compose! { + pub fn arb_note_value()(value in 0u64..=MAX_MONEY as u64) -> NoteValue { + NoteValue::try_from(value).unwrap() + } + } + + prop_compose! { + /// The + pub fn arb_positive_note_value(bound: u64)( + value in 1u64..=(min(bound, MAX_MONEY as u64)) + ) -> NoteValue { + NoteValue::try_from(value).unwrap() + } + } + + prop_compose! { + pub fn arb_incoming_viewing_key()(fvk in arb_full_viewing_key()) -> SaplingIvk { + fvk.vk.ivk() + } + } + + pub fn arb_payment_address() -> impl Strategy { + arb_incoming_viewing_key().prop_flat_map(|ivk: SaplingIvk| { + any::<[u8; 11]>().prop_filter_map( + "Sampled diversifier must generate a valid Sapling payment address.", + move |d| ivk.to_payment_address(Diversifier(d)), + ) + }) + } + + prop_compose! { + pub fn arb_node()(value in prop::array::uniform32(prop::num::u8::ANY)) -> Node { + Node { + repr: value + } + } + } + + prop_compose! { + pub fn arb_note(value: NoteValue)( + asset_type in crate::asset_type::testing::arb_asset_type(), + addr in arb_payment_address(), + rseed in prop::array::uniform32(prop::num::u8::ANY).prop_map(Rseed::AfterZip212) + ) -> Note { + Note { + value: value.into(), + g_d: addr.g_d().unwrap(), // this unwrap is safe because arb_payment_address always generates an address with a valid g_d + pk_d: *addr.pk_d(), + rseed, + asset_type + } + } + } +} + +#[cfg(test)] +mod tests { + use crate::{ + sapling::testing::{arb_note, arb_positive_note_value}, + sapling::Note, + transaction::components::amount::MAX_MONEY, + }; + use borsh::{BorshDeserialize, BorshSerialize}; + use proptest::prelude::*; + + proptest! { + #![proptest_config(ProptestConfig::with_cases(10))] + #[test] + fn note_serialization(note in arb_positive_note_value(MAX_MONEY as u64).prop_flat_map(arb_note)) { + // BorshSerialize + let borsh = note.try_to_vec().unwrap(); + // BorshDeserialize + let de_note: Note = BorshDeserialize::deserialize(&mut borsh.as_ref()).unwrap(); + prop_assert_eq!(note, de_note); + } + } +} diff --git a/masp_primitives/src/sapling/group_hash.rs b/masp_primitives/src/sapling/group_hash.rs new file mode 100644 index 00000000..5a9f06a0 --- /dev/null +++ b/masp_primitives/src/sapling/group_hash.rs @@ -0,0 +1,43 @@ +//! Implementation of [group hashing into Jubjub][grouphash]. +//! +//! [grouphash]: https://zips.z.cash/protocol/protocol.pdf#concretegrouphashjubjub + +use ff::PrimeField; +use group::{cofactor::CofactorGroup, Group, GroupEncoding}; + +use crate::constants; +use blake2s_simd::Params; + +/// Produces a random point in the Jubjub curve. +/// The point is guaranteed to be prime order +/// and not the identity. +#[allow(clippy::assertions_on_constants)] +pub fn group_hash(tag: &[u8], personalization: &[u8]) -> Option { + assert_eq!(personalization.len(), 8); + + // Check to see that scalar field is 255 bits + assert!(bls12_381::Scalar::NUM_BITS == 255); + + let h = Params::new() + .hash_length(32) + .personal(personalization) + .to_state() + .update(constants::GH_FIRST_BLOCK) + .update(tag) + .finalize(); + + let p = jubjub::ExtendedPoint::from_bytes(h.as_array()); + if p.is_some().into() { + // ::clear_cofactor is implemented using + // ExtendedPoint::mul_by_cofactor in the jubjub crate. + let p = CofactorGroup::clear_cofactor(&p.unwrap()); + + if p.is_identity().into() { + None + } else { + Some(p) + } + } else { + None + } +} diff --git a/masp_primitives/src/sapling/keys.rs b/masp_primitives/src/sapling/keys.rs new file mode 100644 index 00000000..cb265ee1 --- /dev/null +++ b/masp_primitives/src/sapling/keys.rs @@ -0,0 +1,277 @@ +//! Sapling key components. +//! +//! Implements [section 4.2.2] of the Zcash Protocol Specification. +//! +//! [section 4.2.2]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents + +use crate::{ + constants::{PROOF_GENERATION_KEY_GENERATOR, SPENDING_KEY_GENERATOR}, + keys::prf_expand, +}; +use ff::PrimeField; +use group::{Group, GroupEncoding}; +use std::{ + fmt::{Display, Formatter}, + hash::{Hash, Hasher}, + io::{self, Read, Write}, + str::FromStr, +}; +use subtle::CtOption; + +use super::{NullifierDerivingKey, ProofGenerationKey, ViewingKey}; + +/// Errors that can occur in the decoding of Sapling spending keys. +pub enum DecodingError { + /// The length of the byte slice provided for decoding was incorrect. + LengthInvalid { expected: usize, actual: usize }, + /// Could not decode the `ask` bytes to a jubjub field element. + InvalidAsk, + /// Could not decode the `nsk` bytes to a jubjub field element. + InvalidNsk, +} + +/// An outgoing viewing key +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct OutgoingViewingKey(pub [u8; 32]); + +/// A Sapling expanded spending key +#[derive(Clone, PartialEq, Eq, Copy)] +pub struct ExpandedSpendingKey { + pub ask: jubjub::Fr, + pub nsk: jubjub::Fr, + pub ovk: OutgoingViewingKey, +} + +impl Hash for ExpandedSpendingKey { + fn hash(&self, state: &mut H) { + self.ask.to_bytes().hash(state); + self.nsk.to_bytes().hash(state); + self.ovk.hash(state); + } +} + +impl ExpandedSpendingKey { + pub fn from_spending_key(sk: &[u8]) -> Self { + let ask = jubjub::Fr::from_bytes_wide(prf_expand(sk, &[0x00]).as_array()); + let nsk = jubjub::Fr::from_bytes_wide(prf_expand(sk, &[0x01]).as_array()); + let mut ovk = OutgoingViewingKey([0u8; 32]); + ovk.0 + .copy_from_slice(&prf_expand(sk, &[0x02]).as_bytes()[..32]); + ExpandedSpendingKey { ask, nsk, ovk } + } + + pub fn proof_generation_key(&self) -> ProofGenerationKey { + ProofGenerationKey { + ak: SPENDING_KEY_GENERATOR * self.ask, + nsk: self.nsk, + } + } + + /// Decodes the expanded spending key from its serialized representation + /// as part of the encoding of the extended spending key as defined in + /// [ZIP 32](https://zips.z.cash/zip-0032) + pub fn from_bytes(b: &[u8]) -> Result { + if b.len() != 96 { + return Err(DecodingError::LengthInvalid { + expected: 96, + actual: b.len(), + }); + } + + let ask = Option::from(jubjub::Fr::from_repr(b[0..32].try_into().unwrap())) + .ok_or(DecodingError::InvalidAsk)?; + let nsk = Option::from(jubjub::Fr::from_repr(b[32..64].try_into().unwrap())) + .ok_or(DecodingError::InvalidNsk)?; + let ovk = OutgoingViewingKey(b[64..96].try_into().unwrap()); + + Ok(ExpandedSpendingKey { ask, nsk, ovk }) + } + + pub fn read(mut reader: R) -> io::Result { + let mut repr = [0u8; 96]; + reader.read_exact(repr.as_mut())?; + Self::from_bytes(&repr).map_err(|e| match e { + DecodingError::InvalidAsk => { + io::Error::new(io::ErrorKind::InvalidData, "ask not in field") + } + DecodingError::InvalidNsk => { + io::Error::new(io::ErrorKind::InvalidData, "nsk not in field") + } + DecodingError::LengthInvalid { .. } => unreachable!(), + }) + } + + pub fn write(&self, mut writer: W) -> io::Result<()> { + writer.write_all(&self.to_bytes()) + } + + /// Encodes the expanded spending key to the its seralized representation + /// as part of the encoding of the extended spending key as defined in + /// [ZIP 32](https://zips.z.cash/zip-0032) + pub fn to_bytes(&self) -> [u8; 96] { + let mut result = [0u8; 96]; + result[0..32].copy_from_slice(&self.ask.to_repr()); + result[32..64].copy_from_slice(&self.nsk.to_repr()); + result[64..96].copy_from_slice(&self.ovk.0); + result + } +} + +/// A Sapling key that provides the capability to view incoming and outgoing transactions. +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +pub struct FullViewingKey { + pub vk: ViewingKey, + pub ovk: OutgoingViewingKey, +} + +impl Display for FullViewingKey { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + write!(f, "{}", hex::encode(self.to_bytes())) + } +} + +impl FromStr for FullViewingKey { + type Err = io::Error; + + fn from_str(s: &str) -> Result { + let vec = hex::decode(s).map_err(|x| io::Error::new(std::io::ErrorKind::InvalidData, x))?; + let mut rdr = vec.as_slice(); + let res = Self::read(&mut rdr)?; + if !rdr.is_empty() { + Err(io::Error::from(std::io::ErrorKind::InvalidData)) + } else { + Ok(res) + } + } +} + +impl FullViewingKey { + pub fn from_expanded_spending_key(expsk: &ExpandedSpendingKey) -> Self { + FullViewingKey { + vk: ViewingKey { + ak: SPENDING_KEY_GENERATOR * expsk.ask, + nk: NullifierDerivingKey(PROOF_GENERATION_KEY_GENERATOR * expsk.nsk), + }, + ovk: expsk.ovk, + } + } + + pub fn read(mut reader: R) -> io::Result { + let ak = { + let mut buf = [0u8; 32]; + reader.read_exact(&mut buf)?; + jubjub::SubgroupPoint::from_bytes(&buf).and_then(|p| CtOption::new(p, !p.is_identity())) + }; + let nk = { + let mut buf = [0u8; 32]; + reader.read_exact(&mut buf)?; + jubjub::SubgroupPoint::from_bytes(&buf) + }; + if ak.is_none().into() { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "ak not of prime order", + )); + } + if nk.is_none().into() { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "nk not in prime-order subgroup", + )); + } + let ak = ak.unwrap(); + let nk = NullifierDerivingKey(nk.unwrap()); + + let mut ovk = [0u8; 32]; + reader.read_exact(&mut ovk)?; + + Ok(FullViewingKey { + vk: ViewingKey { ak, nk }, + ovk: OutgoingViewingKey(ovk), + }) + } + + pub fn write(&self, mut writer: W) -> io::Result<()> { + writer.write_all(&self.vk.ak.to_bytes())?; + writer.write_all(&self.vk.nk.0.to_bytes())?; + writer.write_all(&self.ovk.0)?; + + Ok(()) + } + + pub fn to_bytes(&self) -> [u8; 96] { + let mut result = [0u8; 96]; + self.write(&mut result[..]) + .expect("should be able to serialize a FullViewingKey"); + result + } +} + +#[cfg(any(test, feature = "test-dependencies"))] +pub mod testing { + use proptest::collection::vec; + use proptest::prelude::{any, prop_compose}; + use std::fmt::{self, Debug, Formatter}; + + use super::{ExpandedSpendingKey, FullViewingKey}; + + impl Debug for ExpandedSpendingKey { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "Spending keys cannot be Debug-formatted.") + } + } + + use crate::sapling::PaymentAddress; + use crate::zip32::testing::arb_extended_spending_key; + use crate::zip32::ExtendedFullViewingKey; + + prop_compose! { + pub fn arb_expanded_spending_key()(v in vec(any::(), 32..252)) -> ExpandedSpendingKey { + ExpandedSpendingKey::from_spending_key(&v) + } + } + + prop_compose! { + pub fn arb_full_viewing_key()(sk in arb_expanded_spending_key()) -> FullViewingKey { + FullViewingKey::from_expanded_spending_key(&sk) + } + } + + prop_compose! { + pub fn arb_shielded_addr()(extsk in arb_extended_spending_key()) -> PaymentAddress { + let extfvk = ExtendedFullViewingKey::from(&extsk); + extfvk.default_address().1 + } + } +} + +#[cfg(test)] +mod tests { + use group::{Group, GroupEncoding}; + + use super::FullViewingKey; + use crate::constants::SPENDING_KEY_GENERATOR; + + #[test] + fn ak_must_be_prime_order() { + let mut buf = [0; 96]; + let identity = jubjub::SubgroupPoint::identity(); + + // Set both ak and nk to the identity. + buf[0..32].copy_from_slice(&identity.to_bytes()); + buf[32..64].copy_from_slice(&identity.to_bytes()); + + // ak is not allowed to be the identity. + assert_eq!( + FullViewingKey::read(&buf[..]).unwrap_err().to_string(), + "ak not of prime order" + ); + + // Set ak to a basepoint. + let basepoint = SPENDING_KEY_GENERATOR; + buf[0..32].copy_from_slice(&basepoint.to_bytes()); + + // nk is allowed to be the identity. + assert!(FullViewingKey::read(&buf[..]).is_ok()); + } +} diff --git a/masp_primitives/src/sapling/note_encryption.rs b/masp_primitives/src/sapling/note_encryption.rs new file mode 100644 index 00000000..fa3f5fe8 --- /dev/null +++ b/masp_primitives/src/sapling/note_encryption.rs @@ -0,0 +1,1509 @@ +//! Implementation of in-band secret distribution for MASP transactions. +use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams}; +use byteorder::{LittleEndian, WriteBytesExt}; +use ff::PrimeField; +use group::{cofactor::CofactorGroup, GroupEncoding, WnafBase, WnafScalar}; +use jubjub::{AffinePoint, ExtendedPoint}; +use memuse::DynamicUsage; +use std::convert::TryInto; + +use crate::asset_type::AssetType; +use masp_note_encryption::{ + try_compact_note_decryption, try_note_decryption, try_output_recovery_with_ock, + try_output_recovery_with_ovk, BatchDomain, Domain, EphemeralKeyBytes, NoteEncryption, + NotePlaintextBytes, OutPlaintextBytes, OutgoingCipherKey, ShieldedOutput, COMPACT_NOTE_SIZE, + ENC_CIPHERTEXT_SIZE, NOTE_PLAINTEXT_SIZE, OUT_PLAINTEXT_SIZE, +}; + +use crate::{ + consensus::{self, BlockHeight, NetworkUpgrade::MASP}, + memo::MemoBytes, + sapling::{keys::OutgoingViewingKey, Diversifier, Note, PaymentAddress, Rseed, SaplingIvk}, + transaction::{components::sapling::OutputDescription, GrothProofBytes}, +}; + +pub const KDF_SAPLING_PERSONALIZATION: &[u8; 16] = b"MASP__SaplingKDF"; +pub const PRF_OCK_PERSONALIZATION: &[u8; 16] = b"MASP__Derive_ock"; + +const PREPARED_WINDOW_SIZE: usize = 4; +type PreparedBase = WnafBase; +type PreparedBaseSubgroup = WnafBase; +type PreparedScalar = WnafScalar; + +/// A Sapling incoming viewing key that has been precomputed for trial decryption. +#[derive(Clone, Debug)] +pub struct PreparedIncomingViewingKey(PreparedScalar); + +impl DynamicUsage for PreparedIncomingViewingKey { + fn dynamic_usage(&self) -> usize { + self.0.dynamic_usage() + } + + fn dynamic_usage_bounds(&self) -> (usize, Option) { + self.0.dynamic_usage_bounds() + } +} + +impl PreparedIncomingViewingKey { + /// Performs the necessary precomputations to use a `SaplingIvk` for note decryption. + pub fn new(ivk: &SaplingIvk) -> Self { + Self(PreparedScalar::new(&ivk.0)) + } +} + +/// A Sapling ephemeral public key that has been precomputed for trial decryption. +#[derive(Clone, Debug)] +pub struct PreparedEphemeralPublicKey(PreparedBase); + +/// Sapling key agreement for note encryption. +/// +/// Implements section 5.4.4.3 of the Zcash Protocol Specification. +pub fn sapling_ka_agree(esk: &jubjub::Fr, pk_d: &jubjub::ExtendedPoint) -> jubjub::SubgroupPoint { + sapling_ka_agree_prepared(&PreparedScalar::new(esk), &PreparedBase::new(*pk_d)) +} + +fn sapling_ka_agree_prepared(esk: &PreparedScalar, pk_d: &PreparedBase) -> jubjub::SubgroupPoint { + // [8 esk] pk_d + // ::clear_cofactor is implemented using + // ExtendedPoint::mul_by_cofactor in the jubjub crate. + + (pk_d * esk).clear_cofactor() +} + +/// Sapling KDF for note encryption. +/// +/// Implements section 5.4.4.4 of the Zcash Protocol Specification. +fn kdf_sapling(dhsecret: jubjub::SubgroupPoint, ephemeral_key: &EphemeralKeyBytes) -> Blake2bHash { + Blake2bParams::new() + .hash_length(32) + .personal(KDF_SAPLING_PERSONALIZATION) + .to_state() + .update(&dhsecret.to_bytes()) + .update(ephemeral_key.as_ref()) + .finalize() +} + +/// Sapling PRF^ock. +/// +/// Implemented per section 5.4.2 of the Zcash Protocol Specification. +pub fn prf_ock( + ovk: &OutgoingViewingKey, + cv: &jubjub::ExtendedPoint, + cmu_bytes: &[u8; 32], + ephemeral_key: &EphemeralKeyBytes, +) -> OutgoingCipherKey { + OutgoingCipherKey( + Blake2bParams::new() + .hash_length(32) + .personal(PRF_OCK_PERSONALIZATION) + .to_state() + .update(&ovk.0) + .update(&cv.to_bytes()) + .update(cmu_bytes) + .update(ephemeral_key.as_ref()) + .finalize() + .as_bytes() + .try_into() + .unwrap(), + ) +} + +fn epk_bytes(epk: &jubjub::ExtendedPoint) -> EphemeralKeyBytes { + EphemeralKeyBytes(epk.to_bytes()) +} + +fn sapling_parse_note_plaintext_without_memo( + domain: &SaplingDomain

, + plaintext: &[u8], + get_validated_pk_d: F, +) -> Option<(Note, PaymentAddress)> +where + F: FnOnce(&Diversifier) -> Option, +{ + assert!(plaintext.len() >= COMPACT_NOTE_SIZE); + + // Check note plaintext version + if !plaintext_version_is_valid(&domain.params, domain.height, plaintext[0]) { + return None; + } + + // The unwraps below are guaranteed to succeed by the assertion above + let diversifier = Diversifier(plaintext[1..12].try_into().unwrap()); + let value = u64::from_le_bytes(plaintext[12..20].try_into().unwrap()); + let asset_type = AssetType::from_identifier(plaintext[20..52].try_into().unwrap())?; + let r: [u8; 32] = plaintext[52..COMPACT_NOTE_SIZE].try_into().unwrap(); + + let rseed = if plaintext[0] == 0x01 { + let rcm = Option::from(jubjub::Fr::from_repr(r))?; + Rseed::BeforeZip212(rcm) + } else { + Rseed::AfterZip212(r) + }; + + let pk_d = get_validated_pk_d(&diversifier)?; + + let to = PaymentAddress::from_parts(diversifier, pk_d)?; + let note = to.create_note(asset_type, value, rseed)?; + Some((note, to)) +} + +pub struct SaplingDomain { + params: P, + height: BlockHeight, +} + +impl DynamicUsage for SaplingDomain

{ + fn dynamic_usage(&self) -> usize { + self.params.dynamic_usage() + self.height.dynamic_usage() + } + + fn dynamic_usage_bounds(&self) -> (usize, Option) { + let (params_lower, params_upper) = self.params.dynamic_usage_bounds(); + let (height_lower, height_upper) = self.height.dynamic_usage_bounds(); + ( + params_lower + height_lower, + params_upper.zip(height_upper).map(|(a, b)| a + b), + ) + } +} + +impl SaplingDomain

{ + pub fn for_height(params: P, height: BlockHeight) -> Self { + Self { params, height } + } +} + +impl Domain for SaplingDomain

{ + type EphemeralSecretKey = jubjub::Scalar; + // It is acceptable for this to be a point because we enforce by consensus that + // points must not be small-order, and all points with non-canonical serialization + // are small-order. + type EphemeralPublicKey = jubjub::ExtendedPoint; + type PreparedEphemeralPublicKey = PreparedEphemeralPublicKey; + type SharedSecret = jubjub::SubgroupPoint; + type SymmetricKey = Blake2bHash; + type Note = Note; + type Recipient = PaymentAddress; + type DiversifiedTransmissionKey = jubjub::SubgroupPoint; + type IncomingViewingKey = PreparedIncomingViewingKey; + type OutgoingViewingKey = OutgoingViewingKey; + type ValueCommitment = jubjub::ExtendedPoint; + type ExtractedCommitment = bls12_381::Scalar; + type ExtractedCommitmentBytes = [u8; 32]; + type Memo = MemoBytes; + + fn derive_esk(note: &Self::Note) -> Option { + note.derive_esk() + } + + fn get_pk_d(note: &Self::Note) -> Self::DiversifiedTransmissionKey { + note.pk_d + } + + fn prepare_epk(epk: Self::EphemeralPublicKey) -> Self::PreparedEphemeralPublicKey { + PreparedEphemeralPublicKey(PreparedBase::new(epk)) + } + + fn ka_derive_public( + note: &Self::Note, + esk: &Self::EphemeralSecretKey, + ) -> Self::EphemeralPublicKey { + // epk is an element of jubjub's prime-order subgroup, + // but Self::EphemeralPublicKey is a full group element + // for efficiency of encryption. The conversion here is fine + // because the output of this function is only used for + // encoding and the byte encoding is unaffected by the conversion. + (note.g_d * esk).into() + } + + fn ka_agree_enc( + esk: &Self::EphemeralSecretKey, + pk_d: &Self::DiversifiedTransmissionKey, + ) -> Self::SharedSecret { + sapling_ka_agree(esk, pk_d.into()) + } + + fn ka_agree_dec( + ivk: &Self::IncomingViewingKey, + epk: &Self::PreparedEphemeralPublicKey, + ) -> Self::SharedSecret { + sapling_ka_agree_prepared(&ivk.0, &epk.0) + } + + /// Sapling KDF for note encryption. + /// + /// Implements section 5.4.4.4 of the Zcash Protocol Specification. + fn kdf(dhsecret: jubjub::SubgroupPoint, epk: &EphemeralKeyBytes) -> Blake2bHash { + kdf_sapling(dhsecret, epk) + } + + fn note_plaintext_bytes( + note: &Self::Note, + to: &Self::Recipient, + memo: &Self::Memo, + ) -> NotePlaintextBytes { + // Note plaintext encoding is defined in section 5.5 of the Zcash Protocol + // Specification. + let mut input = [0; NOTE_PLAINTEXT_SIZE]; + input[0] = match note.rseed { + Rseed::BeforeZip212(_) => 1, + Rseed::AfterZip212(_) => 2, + }; + input[1..12].copy_from_slice(&to.diversifier().0); + (&mut input[12..20]) + .write_u64::(note.value) + .unwrap(); + + input[20..52].copy_from_slice(note.asset_type.get_identifier()); + match note.rseed { + Rseed::BeforeZip212(rcm) => { + input[52..COMPACT_NOTE_SIZE].copy_from_slice(rcm.to_repr().as_ref()); + } + Rseed::AfterZip212(rseed) => { + input[52..COMPACT_NOTE_SIZE].copy_from_slice(&rseed); + } + } + + input[COMPACT_NOTE_SIZE..NOTE_PLAINTEXT_SIZE].copy_from_slice(&memo.as_array()[..]); + + NotePlaintextBytes(input) + } + + fn derive_ock( + ovk: &Self::OutgoingViewingKey, + cv: &Self::ValueCommitment, + cmu_bytes: &Self::ExtractedCommitmentBytes, + epk: &EphemeralKeyBytes, + ) -> OutgoingCipherKey { + prf_ock(ovk, cv, cmu_bytes, epk) + } + + fn outgoing_plaintext_bytes( + note: &Self::Note, + esk: &Self::EphemeralSecretKey, + ) -> OutPlaintextBytes { + let mut input = [0u8; OUT_PLAINTEXT_SIZE]; + input[0..32].copy_from_slice(¬e.pk_d.to_bytes()); + input[32..OUT_PLAINTEXT_SIZE].copy_from_slice(esk.to_repr().as_ref()); + + OutPlaintextBytes(input) + } + + fn epk_bytes(epk: &Self::EphemeralPublicKey) -> EphemeralKeyBytes { + epk_bytes(epk) + } + + fn epk(ephemeral_key: &EphemeralKeyBytes) -> Option { + // ZIP 216: We unconditionally reject non-canonical encodings, because these have + // always been rejected by consensus (due to small-order checks). + // https://zips.z.cash/zip-0216#specification + jubjub::ExtendedPoint::from_bytes(&ephemeral_key.0).into() + } + + fn parse_note_plaintext_without_memo_ivk( + &self, + ivk: &Self::IncomingViewingKey, + plaintext: &[u8], + ) -> Option<(Self::Note, Self::Recipient)> { + sapling_parse_note_plaintext_without_memo(self, plaintext, |diversifier| { + Some(&PreparedBaseSubgroup::new(diversifier.g_d()?) * &ivk.0) + }) + } + + fn parse_note_plaintext_without_memo_ovk( + &self, + pk_d: &Self::DiversifiedTransmissionKey, + esk: &Self::EphemeralSecretKey, + ephemeral_key: &EphemeralKeyBytes, + plaintext: &NotePlaintextBytes, + ) -> Option<(Self::Note, Self::Recipient)> { + sapling_parse_note_plaintext_without_memo(self, &plaintext.0, |diversifier| { + if (diversifier.g_d()? * esk).to_bytes() == ephemeral_key.0 { + Some(*pk_d) + } else { + None + } + }) + } + + fn cmstar(note: &Self::Note) -> Self::ExtractedCommitment { + note.cmu() + } + + fn extract_pk_d(op: &OutPlaintextBytes) -> Option { + jubjub::SubgroupPoint::from_bytes( + op.0[0..32].try_into().expect("slice is the correct length"), + ) + .into() + } + + fn extract_esk(op: &OutPlaintextBytes) -> Option { + jubjub::Fr::from_repr( + op.0[32..OUT_PLAINTEXT_SIZE] + .try_into() + .expect("slice is the correct length"), + ) + .into() + } + + fn extract_memo(&self, plaintext: &NotePlaintextBytes) -> Self::Memo { + MemoBytes::from_bytes(&plaintext.0[COMPACT_NOTE_SIZE..NOTE_PLAINTEXT_SIZE]).unwrap() + } +} + +impl BatchDomain for SaplingDomain

{ + fn batch_kdf<'a>( + items: impl Iterator, &'a EphemeralKeyBytes)>, + ) -> Vec> { + let (shared_secrets, ephemeral_keys): (Vec<_>, Vec<_>) = items.unzip(); + + let secrets: Vec<_> = shared_secrets + .iter() + .filter_map(|s| s.map(ExtendedPoint::from)) + .collect(); + let mut secrets_affine = vec![AffinePoint::identity(); shared_secrets.len()]; + group::Curve::batch_normalize(&secrets, &mut secrets_affine); + + let mut secrets_affine = secrets_affine.into_iter(); + shared_secrets + .into_iter() + .map(|s| s.and_then(|_| secrets_affine.next())) + .zip(ephemeral_keys.into_iter()) + .map(|(secret, ephemeral_key)| { + secret.map(|dhsecret| { + Blake2bParams::new() + .hash_length(32) + .personal(KDF_SAPLING_PERSONALIZATION) + .to_state() + .update(&dhsecret.to_bytes()) + .update(ephemeral_key.as_ref()) + .finalize() + }) + }) + .collect() + } + + fn batch_epk( + ephemeral_keys: impl Iterator, + ) -> Vec<(Option, EphemeralKeyBytes)> { + let ephemeral_keys: Vec<_> = ephemeral_keys.collect(); + let epks = jubjub::AffinePoint::batch_from_bytes(ephemeral_keys.iter().map(|b| b.0)); + epks.into_iter() + .zip(ephemeral_keys.into_iter()) + .map(|(epk, ephemeral_key)| { + ( + epk.map(jubjub::ExtendedPoint::from) + .map(Self::prepare_epk) + .into(), + ephemeral_key, + ) + }) + .collect() + } +} + +/// Creates a new encryption context for the given note. +/// +/// Setting `ovk` to `None` represents the `ovk = ⊥` case, where the note cannot be +/// recovered by the sender. +/// +/// NB: the example code here only covers the post-MASP case. +/// +/// # Examples +/// +/// ``` +/// use ff::Field; +/// use rand_core::OsRng; +/// use masp_primitives::{ +/// asset_type::AssetType, +/// keys::{OutgoingViewingKey, prf_expand}, +/// consensus::{TEST_NETWORK, TestNetwork, NetworkUpgrade, Parameters}, +/// memo::MemoBytes, +/// sapling::{ +/// note_encryption::sapling_note_encryption, +/// util::generate_random_rseed, +/// Diversifier, PaymentAddress, Rseed, ValueCommitment +/// }, +/// }; +/// +/// let mut rng = OsRng; +/// +/// let diversifier = Diversifier([10u8; 11]); +/// let pk_d = diversifier.g_d().unwrap(); +/// let to = PaymentAddress::from_parts(diversifier, pk_d).unwrap(); +/// let ovk = Some(OutgoingViewingKey([0; 32])); +/// +/// let value = 1000; +/// let rcv = jubjub::Fr::random(&mut rng); +/// let asset_type = AssetType::new(b"note_encryption").unwrap(); +/// let cv = asset_type.value_commitment(1, jubjub::Fr::random(&mut rng)); +/// +/// let height = TEST_NETWORK.activation_height(NetworkUpgrade::MASP).unwrap(); +/// let rseed = generate_random_rseed(&TEST_NETWORK, height, &mut rng); +/// let note = to.create_note(asset_type, value, rseed).unwrap(); +/// let cmu = note.cmu(); +/// +/// let mut enc = sapling_note_encryption::(ovk, note, to, MemoBytes::empty()); +/// let encCiphertext = enc.encrypt_note_plaintext(); +/// let outCiphertext = enc.encrypt_outgoing_plaintext(&cv.commitment().into(), &cmu, &mut rng); +/// ``` +pub fn sapling_note_encryption( + ovk: Option, + note: Note, + to: PaymentAddress, + memo: MemoBytes, +) -> NoteEncryption> { + NoteEncryption::new(ovk, note, to, memo) +} + +#[allow(clippy::if_same_then_else)] +#[allow(clippy::needless_bool)] +pub fn plaintext_version_is_valid( + params: &P, + height: BlockHeight, + leadbyte: u8, +) -> bool { + if params.is_nu_active(MASP, height) { + // return false if non-0x02 received when MASP is active + leadbyte == 0x02 + } else { + // Pre-ZIP 212 testnet + // Only used for the TEST_NETWORK prior to MASP activation, + // because note encryption test vectors use pre-ZIP-212 rseed derivation + leadbyte == 0x01 + } +} + +pub fn try_sapling_note_decryption< + P: consensus::Parameters, + Output: ShieldedOutput, ENC_CIPHERTEXT_SIZE>, +>( + params: &P, + height: BlockHeight, + ivk: &PreparedIncomingViewingKey, + output: &Output, +) -> Option<(Note, PaymentAddress, MemoBytes)> { + let domain = SaplingDomain { + params: params.clone(), + height, + }; + try_note_decryption(&domain, ivk, output) +} + +pub fn try_sapling_compact_note_decryption< + P: consensus::Parameters, + Output: ShieldedOutput, COMPACT_NOTE_SIZE>, +>( + params: &P, + height: BlockHeight, + ivk: &PreparedIncomingViewingKey, + output: &Output, +) -> Option<(Note, PaymentAddress)> { + let domain = SaplingDomain { + params: params.clone(), + height, + }; + + try_compact_note_decryption(&domain, ivk, output) +} + +/// Recovery of the full note plaintext by the sender. +/// +/// Attempts to decrypt and validate the given `enc_ciphertext` using the given `ock`. +/// If successful, the corresponding Sapling note and memo are returned, along with the +/// `PaymentAddress` to which the note was sent. +/// +/// Implements part of section 4.19.3 of the Zcash Protocol Specification. +/// For decryption using a Full Viewing Key see [`try_sapling_output_recovery`]. +pub fn try_sapling_output_recovery_with_ock( + params: &P, + height: BlockHeight, + ock: &OutgoingCipherKey, + output: &OutputDescription, +) -> Option<(Note, PaymentAddress, MemoBytes)> { + let domain = SaplingDomain { + params: params.clone(), + height, + }; + + try_output_recovery_with_ock(&domain, ock, output, &output.out_ciphertext) +} + +/// Recovery of the full note plaintext by the sender. +/// +/// Attempts to decrypt and validate the given `enc_ciphertext` using the given `ovk`. +/// If successful, the corresponding Sapling note and memo are returned, along with the +/// `PaymentAddress` to which the note was sent. +/// +/// Implements section 4.19.3 of the Zcash Protocol Specification. +#[allow(clippy::too_many_arguments)] +pub fn try_sapling_output_recovery( + params: &P, + height: BlockHeight, + ovk: &OutgoingViewingKey, + output: &OutputDescription, +) -> Option<(Note, PaymentAddress, MemoBytes)> { + let domain = SaplingDomain { + params: params.clone(), + height, + }; + + try_output_recovery_with_ovk(&domain, ovk, output, &output.cv, &output.out_ciphertext) +} + +#[cfg(test)] +mod tests { + use chacha20poly1305::{ + aead::{AeadInPlace, KeyInit}, + ChaCha20Poly1305, + }; + use ff::{Field, PrimeField}; + use group::Group; + use group::{cofactor::CofactorGroup, GroupEncoding}; + use rand_core::OsRng; + use rand_core::{CryptoRng, RngCore}; + use std::convert::TryInto; + + use masp_note_encryption::{ + batch, EphemeralKeyBytes, NoteEncryption, OutgoingCipherKey, ENC_CIPHERTEXT_SIZE, + NOTE_PLAINTEXT_SIZE, OUT_CIPHERTEXT_SIZE, OUT_PLAINTEXT_SIZE, + }; + + use super::{ + epk_bytes, kdf_sapling, prf_ock, sapling_ka_agree, sapling_note_encryption, + try_sapling_compact_note_decryption, try_sapling_note_decryption, + try_sapling_output_recovery, try_sapling_output_recovery_with_ock, SaplingDomain, + }; + + use crate::{ + consensus::{ + BlockHeight, NetworkUpgrade::MASP, Parameters, TestNetwork, TEST_NETWORK, + ZIP212_GRACE_PERIOD, + }, + keys::OutgoingViewingKey, + memo::MemoBytes, + sapling::{ + note_encryption::{AssetType, PreparedIncomingViewingKey}, + util::generate_random_rseed, + }, + sapling::{Diversifier, PaymentAddress, Rseed, SaplingIvk}, + transaction::components::{ + sapling::{self, CompactOutputDescription, OutputDescription}, + GROTH_PROOF_SIZE, + }, + }; + + fn random_enc_ciphertext( + height: BlockHeight, + mut rng: &mut R, + ) -> ( + OutgoingViewingKey, + OutgoingCipherKey, + PreparedIncomingViewingKey, + OutputDescription, + ) { + let ivk = SaplingIvk(jubjub::Fr::random(&mut rng)); + let prepared_ivk = PreparedIncomingViewingKey::new(&ivk); + + let (ovk, ock, output) = random_enc_ciphertext_with(height, &ivk, rng); + + assert!( + try_sapling_note_decryption(&TEST_NETWORK, height, &prepared_ivk, &output).is_some() + ); + assert!(try_sapling_compact_note_decryption( + &TEST_NETWORK, + height, + &prepared_ivk, + &CompactOutputDescription::from(output.clone()), + ) + .is_some()); + + let ovk_output_recovery = try_sapling_output_recovery(&TEST_NETWORK, height, &ovk, &output); + + let ock_output_recovery = + try_sapling_output_recovery_with_ock(&TEST_NETWORK, height, &ock, &output); + assert!(ovk_output_recovery.is_some()); + assert!(ock_output_recovery.is_some()); + assert_eq!(ovk_output_recovery, ock_output_recovery); + + (ovk, ock, prepared_ivk, output) + } + + fn random_enc_ciphertext_with( + height: BlockHeight, + ivk: &SaplingIvk, + mut rng: &mut R, + ) -> ( + OutgoingViewingKey, + OutgoingCipherKey, + OutputDescription, + ) { + let diversifier = Diversifier([10u8; 11]); + let pk_d = diversifier.g_d().unwrap() * ivk.0; + let pa = PaymentAddress::from_parts_unchecked(diversifier, pk_d); + + // Construct the value commitment for the proof instance + let value = 100u64; + + let asset_type = AssetType::new("BTC".as_bytes()).unwrap(); + let value_commitment = asset_type.value_commitment(value, jubjub::Fr::random(&mut rng)); + + let cv = value_commitment.commitment().into(); + + let rseed = generate_random_rseed(&TEST_NETWORK, height, &mut rng); + + let note = pa.create_note(asset_type, value, rseed).unwrap(); + let cmu = note.cmu(); + + let ovk = OutgoingViewingKey([0; 32]); + let ne = sapling_note_encryption::(Some(ovk), note, pa, MemoBytes::empty()); + let epk = *ne.epk(); + let ock = prf_ock(&ovk, &cv, &cmu.to_repr(), &epk_bytes(&epk)); + + let output = OutputDescription { + cv, + cmu, + ephemeral_key: epk.to_bytes().into(), + enc_ciphertext: ne.encrypt_note_plaintext(), + out_ciphertext: ne.encrypt_outgoing_plaintext(&cv, &cmu, &mut rng), + zkproof: [0u8; GROTH_PROOF_SIZE], + }; + + (ovk, ock, output) + } + + fn reencrypt_enc_ciphertext( + ovk: &OutgoingViewingKey, + cv: &jubjub::ExtendedPoint, + cmu: &bls12_381::Scalar, + ephemeral_key: &EphemeralKeyBytes, + enc_ciphertext: &mut [u8; ENC_CIPHERTEXT_SIZE], + out_ciphertext: &[u8; OUT_CIPHERTEXT_SIZE], + modify_plaintext: impl Fn(&mut [u8; NOTE_PLAINTEXT_SIZE]), + ) { + let ock = prf_ock(ovk, cv, &cmu.to_repr(), ephemeral_key); + + let mut op = [0; OUT_PLAINTEXT_SIZE]; + op.copy_from_slice(&out_ciphertext[..OUT_PLAINTEXT_SIZE]); + + ChaCha20Poly1305::new(ock.as_ref().into()) + .decrypt_in_place_detached( + [0u8; 12][..].into(), + &[], + &mut op, + out_ciphertext[OUT_PLAINTEXT_SIZE..].into(), + ) + .unwrap(); + + let pk_d = jubjub::SubgroupPoint::from_bytes(&op[0..32].try_into().unwrap()).unwrap(); + + let esk = jubjub::Fr::from_repr(op[32..OUT_PLAINTEXT_SIZE].try_into().unwrap()).unwrap(); + + let shared_secret = sapling_ka_agree(&esk, &pk_d.into()); + let key = kdf_sapling(shared_secret, ephemeral_key); + + let mut plaintext = [0; NOTE_PLAINTEXT_SIZE]; + plaintext.copy_from_slice(&enc_ciphertext[..NOTE_PLAINTEXT_SIZE]); + + ChaCha20Poly1305::new(key.as_bytes().into()) + .decrypt_in_place_detached( + [0u8; 12][..].into(), + &[], + &mut plaintext, + enc_ciphertext[NOTE_PLAINTEXT_SIZE..].into(), + ) + .unwrap(); + + modify_plaintext(&mut plaintext); + + let tag = ChaCha20Poly1305::new(key.as_ref().into()) + .encrypt_in_place_detached([0u8; 12][..].into(), &[], &mut plaintext) + .unwrap(); + + enc_ciphertext[..NOTE_PLAINTEXT_SIZE].copy_from_slice(&plaintext); + enc_ciphertext[NOTE_PLAINTEXT_SIZE..].copy_from_slice(&tag); + } + + fn find_invalid_diversifier() -> Diversifier { + // Find an invalid diversifier + let mut d = Diversifier([0; 11]); + loop { + for k in 0..11 { + d.0[k] = d.0[k].wrapping_add(1); + if d.0[k] != 0 { + break; + } + } + if d.g_d().is_none() { + break; + } + } + d + } + + fn find_valid_diversifier() -> Diversifier { + // Find a different valid diversifier + let mut d = Diversifier([0; 11]); + loop { + for k in 0..11 { + d.0[k] = d.0[k].wrapping_add(1); + if d.0[k] != 0 { + break; + } + } + if d.g_d().is_some() { + break; + } + } + d + } + + #[test] + fn decryption_with_invalid_ivk() { + let mut rng = OsRng; + let heights = [TEST_NETWORK.activation_height(MASP).unwrap()]; + + for &height in heights.iter() { + let (_, _, _, output) = random_enc_ciphertext(height, &mut rng); + + assert_eq!( + try_sapling_note_decryption( + &TEST_NETWORK, + height, + &PreparedIncomingViewingKey::new(&SaplingIvk(jubjub::Fr::random(&mut rng))), + &output + ), + None + ); + } + } + + #[test] + fn decryption_with_invalid_epk() { + let mut rng = OsRng; + let heights = [TEST_NETWORK.activation_height(MASP).unwrap()]; + + for &height in heights.iter() { + let (_, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng); + + output.ephemeral_key = jubjub::ExtendedPoint::random(&mut rng).to_bytes().into(); + + assert_eq!( + try_sapling_note_decryption(&TEST_NETWORK, height, &ivk, &output,), + None + ); + } + } + + #[test] + fn decryption_with_invalid_cmu() { + let mut rng = OsRng; + let heights = [TEST_NETWORK.activation_height(MASP).unwrap()]; + + for &height in heights.iter() { + let (_, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng); + output.cmu = bls12_381::Scalar::random(&mut rng); + + assert_eq!( + try_sapling_note_decryption(&TEST_NETWORK, height, &ivk, &output), + None + ); + } + } + + #[test] + fn decryption_with_invalid_tag() { + let mut rng = OsRng; + let heights = [TEST_NETWORK.activation_height(MASP).unwrap()]; + + for &height in heights.iter() { + let (_, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng); + output.enc_ciphertext[ENC_CIPHERTEXT_SIZE - 1] ^= 0xff; + + assert_eq!( + try_sapling_note_decryption(&TEST_NETWORK, height, &ivk, &output), + None + ); + } + } + + #[test] + fn decryption_with_invalid_version_byte() { + let mut rng = OsRng; + let masp_activation_height = TEST_NETWORK.activation_height(MASP).unwrap(); + let heights = [ + masp_activation_height, + masp_activation_height + ZIP212_GRACE_PERIOD, + ]; + let leadbytes = [0x01, 0x01]; + + for (&height, &leadbyte) in heights.iter().zip(leadbytes.iter()) { + let (ovk, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng); + + reencrypt_enc_ciphertext( + &ovk, + &output.cv, + &output.cmu, + &output.ephemeral_key, + &mut output.enc_ciphertext, + &output.out_ciphertext, + |pt| pt[0] = leadbyte, + ); + assert_eq!( + try_sapling_note_decryption(&TEST_NETWORK, height, &ivk, &output), + None + ); + } + } + + #[test] + fn decryption_with_invalid_diversifier() { + let mut rng = OsRng; + let heights = [TEST_NETWORK.activation_height(MASP).unwrap()]; + + for &height in heights.iter() { + let (ovk, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng); + + reencrypt_enc_ciphertext( + &ovk, + &output.cv, + &output.cmu, + &output.ephemeral_key, + &mut output.enc_ciphertext, + &output.out_ciphertext, + |pt| pt[1..12].copy_from_slice(&find_invalid_diversifier().0), + ); + assert_eq!( + try_sapling_note_decryption(&TEST_NETWORK, height, &ivk, &output), + None + ); + } + } + + #[test] + fn decryption_with_incorrect_diversifier() { + let mut rng = OsRng; + let heights = [TEST_NETWORK.activation_height(MASP).unwrap()]; + + for &height in heights.iter() { + let (ovk, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng); + + reencrypt_enc_ciphertext( + &ovk, + &output.cv, + &output.cmu, + &output.ephemeral_key, + &mut output.enc_ciphertext, + &output.out_ciphertext, + |pt| pt[1..12].copy_from_slice(&find_valid_diversifier().0), + ); + + assert_eq!( + try_sapling_note_decryption(&TEST_NETWORK, height, &ivk, &output), + None + ); + } + } + + #[test] + fn compact_decryption_with_invalid_ivk() { + let mut rng = OsRng; + let heights = [TEST_NETWORK.activation_height(MASP).unwrap()]; + + for &height in heights.iter() { + let (_, _, _, output) = random_enc_ciphertext(height, &mut rng); + + assert_eq!( + try_sapling_compact_note_decryption( + &TEST_NETWORK, + height, + &PreparedIncomingViewingKey::new(&SaplingIvk(jubjub::Fr::random(&mut rng))), + &CompactOutputDescription::from(output) + ), + None + ); + } + } + + #[test] + fn compact_decryption_with_invalid_epk() { + let mut rng = OsRng; + let heights = [TEST_NETWORK.activation_height(MASP).unwrap()]; + + for &height in heights.iter() { + let (_, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng); + output.ephemeral_key = jubjub::ExtendedPoint::random(&mut rng).to_bytes().into(); + + assert_eq!( + try_sapling_compact_note_decryption( + &TEST_NETWORK, + height, + &ivk, + &CompactOutputDescription::from(output) + ), + None + ); + } + } + + #[test] + fn compact_decryption_with_invalid_cmu() { + let mut rng = OsRng; + let heights = [TEST_NETWORK.activation_height(MASP).unwrap()]; + + for &height in heights.iter() { + let (_, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng); + output.cmu = bls12_381::Scalar::random(&mut rng); + + assert_eq!( + try_sapling_compact_note_decryption( + &TEST_NETWORK, + height, + &ivk, + &CompactOutputDescription::from(output) + ), + None + ); + } + } + + #[test] + fn compact_decryption_with_invalid_version_byte() { + let mut rng = OsRng; + let masp_activation_height = TEST_NETWORK.activation_height(MASP).unwrap(); + let heights = [ + masp_activation_height, + masp_activation_height + ZIP212_GRACE_PERIOD, + ]; + let leadbytes = [0x01, 0x01]; + + for (&height, &leadbyte) in heights.iter().zip(leadbytes.iter()) { + let (ovk, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng); + + reencrypt_enc_ciphertext( + &ovk, + &output.cv, + &output.cmu, + &output.ephemeral_key, + &mut output.enc_ciphertext, + &output.out_ciphertext, + |pt| pt[0] = leadbyte, + ); + assert_eq!( + try_sapling_compact_note_decryption( + &TEST_NETWORK, + height, + &ivk, + &CompactOutputDescription::from(output) + ), + None + ); + } + } + + #[test] + fn compact_decryption_with_invalid_diversifier() { + let mut rng = OsRng; + let heights = [TEST_NETWORK.activation_height(MASP).unwrap()]; + + for &height in heights.iter() { + let (ovk, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng); + + reencrypt_enc_ciphertext( + &ovk, + &output.cv, + &output.cmu, + &output.ephemeral_key, + &mut output.enc_ciphertext, + &output.out_ciphertext, + |pt| pt[1..12].copy_from_slice(&find_invalid_diversifier().0), + ); + assert_eq!( + try_sapling_compact_note_decryption( + &TEST_NETWORK, + height, + &ivk, + &CompactOutputDescription::from(output) + ), + None + ); + } + } + + #[test] + fn compact_decryption_with_incorrect_diversifier() { + let mut rng = OsRng; + let heights = [TEST_NETWORK.activation_height(MASP).unwrap()]; + + for &height in heights.iter() { + let (ovk, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng); + + reencrypt_enc_ciphertext( + &ovk, + &output.cv, + &output.cmu, + &output.ephemeral_key, + &mut output.enc_ciphertext, + &output.out_ciphertext, + |pt| pt[1..12].copy_from_slice(&find_valid_diversifier().0), + ); + assert_eq!( + try_sapling_compact_note_decryption( + &TEST_NETWORK, + height, + &ivk, + &CompactOutputDescription::from(output) + ), + None + ); + } + } + + #[test] + fn recovery_with_invalid_ovk() { + let mut rng = OsRng; + let heights = [TEST_NETWORK.activation_height(MASP).unwrap()]; + + for &height in heights.iter() { + let (mut ovk, _, _, output) = random_enc_ciphertext(height, &mut rng); + + ovk.0[0] ^= 0xff; + assert_eq!( + try_sapling_output_recovery(&TEST_NETWORK, height, &ovk, &output,), + None + ); + } + } + + #[test] + fn recovery_with_invalid_ock() { + let mut rng = OsRng; + let heights = [TEST_NETWORK.activation_height(MASP).unwrap()]; + + for &height in heights.iter() { + let (_, _, _, output) = random_enc_ciphertext(height, &mut rng); + + assert_eq!( + try_sapling_output_recovery_with_ock( + &TEST_NETWORK, + height, + &OutgoingCipherKey([0u8; 32]), + &output, + ), + None + ); + } + } + + #[test] + fn recovery_with_invalid_cv() { + let mut rng = OsRng; + let heights = [TEST_NETWORK.activation_height(MASP).unwrap()]; + + for &height in heights.iter() { + let (ovk, _, _, mut output) = random_enc_ciphertext(height, &mut rng); + output.cv = jubjub::ExtendedPoint::random(&mut rng); + + assert_eq!( + try_sapling_output_recovery(&TEST_NETWORK, height, &ovk, &output,), + None + ); + } + } + + #[test] + fn recovery_with_invalid_cmu() { + let mut rng = OsRng; + let heights = [TEST_NETWORK.activation_height(MASP).unwrap()]; + + for &height in heights.iter() { + let (ovk, ock, _, mut output) = random_enc_ciphertext(height, &mut rng); + output.cmu = bls12_381::Scalar::random(&mut rng); + + assert_eq!( + try_sapling_output_recovery(&TEST_NETWORK, height, &ovk, &output,), + None + ); + + assert_eq!( + try_sapling_output_recovery_with_ock(&TEST_NETWORK, height, &ock, &output,), + None + ); + } + } + + #[test] + fn recovery_with_invalid_epk() { + let mut rng = OsRng; + let heights = [TEST_NETWORK.activation_height(MASP).unwrap()]; + + for &height in heights.iter() { + let (ovk, ock, _, mut output) = random_enc_ciphertext(height, &mut rng); + output.ephemeral_key = jubjub::ExtendedPoint::random(&mut rng).to_bytes().into(); + + assert_eq!( + try_sapling_output_recovery(&TEST_NETWORK, height, &ovk, &output,), + None + ); + + assert_eq!( + try_sapling_output_recovery_with_ock(&TEST_NETWORK, height, &ock, &output,), + None + ); + } + } + + #[test] + fn recovery_with_invalid_enc_tag() { + let mut rng = OsRng; + let heights = [TEST_NETWORK.activation_height(MASP).unwrap()]; + + for &height in heights.iter() { + let (ovk, ock, _, mut output) = random_enc_ciphertext(height, &mut rng); + + output.enc_ciphertext[ENC_CIPHERTEXT_SIZE - 1] ^= 0xff; + assert_eq!( + try_sapling_output_recovery(&TEST_NETWORK, height, &ovk, &output,), + None + ); + assert_eq!( + try_sapling_output_recovery_with_ock(&TEST_NETWORK, height, &ock, &output,), + None + ); + } + } + + #[test] + fn recovery_with_invalid_out_tag() { + let mut rng = OsRng; + let heights = [TEST_NETWORK.activation_height(MASP).unwrap()]; + + for &height in heights.iter() { + let (ovk, ock, _, mut output) = random_enc_ciphertext(height, &mut rng); + + output.out_ciphertext[OUT_CIPHERTEXT_SIZE - 1] ^= 0xff; + assert_eq!( + try_sapling_output_recovery(&TEST_NETWORK, height, &ovk, &output,), + None + ); + assert_eq!( + try_sapling_output_recovery_with_ock(&TEST_NETWORK, height, &ock, &output,), + None + ); + } + } + + #[test] + fn recovery_with_invalid_version_byte() { + let mut rng = OsRng; + let masp_activation_height = TEST_NETWORK.activation_height(MASP).unwrap(); + let heights = [ + masp_activation_height, + masp_activation_height + ZIP212_GRACE_PERIOD, + ]; + let leadbytes = [0x01, 0x01]; + + for (&height, &leadbyte) in heights.iter().zip(leadbytes.iter()) { + let (ovk, ock, _, mut output) = random_enc_ciphertext(height, &mut rng); + + reencrypt_enc_ciphertext( + &ovk, + &output.cv, + &output.cmu, + &output.ephemeral_key, + &mut output.enc_ciphertext, + &output.out_ciphertext, + |pt| pt[0] = leadbyte, + ); + assert_eq!( + try_sapling_output_recovery(&TEST_NETWORK, height, &ovk, &output,), + None + ); + assert_eq!( + try_sapling_output_recovery_with_ock(&TEST_NETWORK, height, &ock, &output,), + None + ); + } + } + + #[test] + fn recovery_with_invalid_diversifier() { + let mut rng = OsRng; + let heights = [TEST_NETWORK.activation_height(MASP).unwrap()]; + + for &height in heights.iter() { + let (ovk, ock, _, mut output) = random_enc_ciphertext(height, &mut rng); + + reencrypt_enc_ciphertext( + &ovk, + &output.cv, + &output.cmu, + &output.ephemeral_key, + &mut output.enc_ciphertext, + &output.out_ciphertext, + |pt| pt[1..12].copy_from_slice(&find_invalid_diversifier().0), + ); + assert_eq!( + try_sapling_output_recovery(&TEST_NETWORK, height, &ovk, &output,), + None + ); + assert_eq!( + try_sapling_output_recovery_with_ock(&TEST_NETWORK, height, &ock, &output,), + None + ); + } + } + + #[test] + fn recovery_with_incorrect_diversifier() { + let mut rng = OsRng; + let heights = [TEST_NETWORK.activation_height(MASP).unwrap()]; + + for &height in heights.iter() { + let (ovk, ock, _, mut output) = random_enc_ciphertext(height, &mut rng); + + reencrypt_enc_ciphertext( + &ovk, + &output.cv, + &output.cmu, + &output.ephemeral_key, + &mut output.enc_ciphertext, + &output.out_ciphertext, + |pt| pt[1..12].copy_from_slice(&find_valid_diversifier().0), + ); + assert_eq!( + try_sapling_output_recovery(&TEST_NETWORK, height, &ovk, &output,), + None + ); + assert_eq!( + try_sapling_output_recovery_with_ock(&TEST_NETWORK, height, &ock, &output,), + None + ); + } + } + + #[test] + fn recovery_with_invalid_pk_d() { + let mut rng = OsRng; + let heights = [TEST_NETWORK.activation_height(MASP).unwrap()]; + + for &height in heights.iter() { + let ivk = SaplingIvk(jubjub::Fr::zero()); + let (ovk, ock, output) = random_enc_ciphertext_with(height, &ivk, &mut rng); + + assert_eq!( + try_sapling_output_recovery(&TEST_NETWORK, height, &ovk, &output,), + None + ); + assert_eq!( + try_sapling_output_recovery_with_ock(&TEST_NETWORK, height, &ock, &output,), + None + ); + } + } + + #[test] + fn test_vectors() { + let test_vectors = crate::test_vectors::note_encryption::make_test_vectors(); + + macro_rules! read_bls12_381_scalar { + ($field:expr) => {{ + bls12_381::Scalar::from_repr($field[..].try_into().unwrap()).unwrap() + }}; + } + + macro_rules! read_jubjub_scalar { + ($field:expr) => {{ + jubjub::Fr::from_repr($field[..].try_into().unwrap()).unwrap() + }}; + } + + macro_rules! read_point { + ($field:expr) => { + jubjub::ExtendedPoint::from_bytes(&$field).unwrap() + }; + } + // We must use height 0 here because the note encryption test vectors + // use pre-ZIP-212 rseed, while all MASP tx always use ZIP-212 + let height = crate::consensus::H0; + + let asset_type = AssetType::from_identifier(b"testtesttesttesttesttesttesttest").unwrap(); + + for tv in test_vectors { + // + // Load the test vector components + // + + let ivk = PreparedIncomingViewingKey::new(&SaplingIvk(read_jubjub_scalar!(tv.ivk))); + let pk_d = read_point!(tv.default_pk_d).into_subgroup().unwrap(); + let rcm = read_jubjub_scalar!(tv.rcm); + let cv = read_point!(tv.cv); + let cmu = read_bls12_381_scalar!(tv.cmu); + let esk = read_jubjub_scalar!(tv.esk); + let ephemeral_key = EphemeralKeyBytes(tv.epk); + + // + // Test the individual components + // + + let shared_secret = sapling_ka_agree(&esk, &pk_d.into()); + assert_eq!(shared_secret.to_bytes(), tv.shared_secret); + + let k_enc = kdf_sapling(shared_secret, &ephemeral_key); + assert_eq!(k_enc.as_bytes(), tv.k_enc); + + let ovk = OutgoingViewingKey(tv.ovk); + let ock = prf_ock(&ovk, &cv, &cmu.to_repr(), &ephemeral_key); + assert_eq!(ock.as_ref(), tv.ock); + + let to = PaymentAddress::from_parts(Diversifier(tv.default_d), pk_d).unwrap(); + let note = to + .create_note(asset_type, tv.v, Rseed::BeforeZip212(rcm)) + .unwrap(); + assert_eq!(note.cmu(), cmu); + + let output = OutputDescription { + cv, + cmu, + ephemeral_key, + enc_ciphertext: tv.c_enc, + out_ciphertext: tv.c_out, + zkproof: [0u8; GROTH_PROOF_SIZE], + }; + + // + // Test decryption + // (Tested first because it only requires immutable references.) + // + + match try_sapling_note_decryption(&TEST_NETWORK, height, &ivk, &output) { + Some((decrypted_note, decrypted_to, decrypted_memo)) => { + assert_eq!(decrypted_note, note); + assert_eq!(decrypted_to, to); + assert_eq!(&decrypted_memo.as_array()[..], &tv.memo[..]); + } + None => panic!("Note decryption failed"), + } + + match try_sapling_compact_note_decryption( + &TEST_NETWORK, + height, + &ivk, + &CompactOutputDescription::from(output.clone()), + ) { + Some((decrypted_note, decrypted_to)) => { + assert_eq!(decrypted_note, note); + assert_eq!(decrypted_to, to); + } + None => panic!("Compact note decryption failed"), + } + + match try_sapling_output_recovery(&TEST_NETWORK, height, &ovk, &output) { + Some((decrypted_note, decrypted_to, decrypted_memo)) => { + assert_eq!(decrypted_note, note); + assert_eq!(decrypted_to, to); + assert_eq!(&decrypted_memo.as_array()[..], &tv.memo[..]); + } + None => panic!("Output recovery failed"), + } + + match &batch::try_note_decryption( + &[ivk.clone()], + &[( + SaplingDomain::for_height(TEST_NETWORK, height), + output.clone(), + )], + )[..] + { + [Some(((decrypted_note, decrypted_to, decrypted_memo), i))] => { + assert_eq!(decrypted_note, ¬e); + assert_eq!(decrypted_to, &to); + assert_eq!(&decrypted_memo.as_array()[..], &tv.memo[..]); + assert_eq!(*i, 0); + } + _ => panic!("Note decryption failed"), + } + + match &batch::try_compact_note_decryption( + &[ivk.clone()], + &[( + SaplingDomain::for_height(TEST_NETWORK, height), + CompactOutputDescription::from(output.clone()), + )], + )[..] + { + [Some(((decrypted_note, decrypted_to), i))] => { + assert_eq!(decrypted_note, ¬e); + assert_eq!(decrypted_to, &to); + assert_eq!(*i, 0); + } + _ => panic!("Note decryption failed"), + } + + match try_sapling_output_recovery_with_ock(&TEST_NETWORK, height, &ock, &output) { + Some((decrypted_note, decrypted_to, decrypted_memo)) => { + assert_eq!(decrypted_note, note); + assert_eq!(decrypted_to, to); + assert_eq!(&decrypted_memo.as_array()[..], &tv.memo[..]); + } + None => panic!("Output recovery with ock failed"), + } + + // + // Test encryption + // + + let ne = NoteEncryption::>::new_with_esk( + esk, + Some(ovk), + note, + to, + MemoBytes::from_bytes(&tv.memo).unwrap(), + ); + + assert_eq!(ne.encrypt_note_plaintext().as_ref(), &tv.c_enc[..]); + assert_eq!( + &ne.encrypt_outgoing_plaintext(&cv, &cmu, &mut OsRng)[..], + &tv.c_out[..] + ); + } + } + + #[test] + fn batching() { + let mut rng = OsRng; + let height = TEST_NETWORK.activation_height(MASP).unwrap(); + + // Test batch trial-decryption with multiple IVKs and outputs. + let invalid_ivk = PreparedIncomingViewingKey::new(&SaplingIvk(jubjub::Fr::random(rng))); + let valid_ivk = SaplingIvk(jubjub::Fr::random(rng)); + let outputs: Vec<_> = (0..10) + .map(|_| { + ( + SaplingDomain::for_height(TEST_NETWORK, height), + random_enc_ciphertext_with(height, &valid_ivk, &mut rng).2, + ) + }) + .collect(); + let valid_ivk = PreparedIncomingViewingKey::new(&valid_ivk); + + // Check that batched trial decryptions with invalid_ivk fails. + let res = batch::try_note_decryption(&[invalid_ivk.clone()], &outputs); + assert_eq!(res.len(), 10); + assert_eq!(&res[..], &vec![None; 10][..]); + + // Check that batched trial decryptions with valid_ivk succeeds. + let res = batch::try_note_decryption(&[invalid_ivk, valid_ivk.clone()], &outputs); + assert_eq!(res.len(), 10); + for (result, (_, output)) in res.iter().zip(outputs.iter()) { + // Confirm the successful batched trial decryptions gave the same result. + // In all cases, the index of the valid ivk is returned. + assert!(result.is_some()); + assert_eq!( + result, + &try_sapling_note_decryption(&TEST_NETWORK, height, &valid_ivk, output) + .map(|r| (r, 1)) + ); + } + } +} diff --git a/masp_primitives/src/pedersen_hash.rs b/masp_primitives/src/sapling/pedersen_hash.rs similarity index 100% rename from masp_primitives/src/pedersen_hash.rs rename to masp_primitives/src/sapling/pedersen_hash.rs diff --git a/masp_primitives/src/prover.rs b/masp_primitives/src/sapling/prover.rs similarity index 85% rename from masp_primitives/src/prover.rs rename to masp_primitives/src/sapling/prover.rs index 53d888bf..03a442fb 100644 --- a/masp_primitives/src/prover.rs +++ b/masp_primitives/src/sapling/prover.rs @@ -1,16 +1,17 @@ //! Abstractions over the proving system and parameters. -use crate::primitives::{Diversifier, PaymentAddress, ProofGenerationKey}; - -use crate::convert::AllowedConversion; use crate::{ - redjubjub::{PublicKey, Signature}, - sapling::Node, + asset_type::AssetType, + convert::AllowedConversion, + merkle_tree::MerklePath, + sapling::{ + redjubjub::{PublicKey, Signature}, + Node, + }, + transaction::components::{Amount, GROTH_PROOF_SIZE}, }; -use zcash_primitives::transaction::components::GROTH_PROOF_SIZE; -use zcash_primitives::{merkle_tree::MerklePath, sapling::Rseed}; -use crate::asset_type::AssetType; +use super::{Diversifier, PaymentAddress, ProofGenerationKey, Rseed}; /// Interface for creating zero-knowledge proofs for shielded transactions. pub trait TxProver { @@ -21,9 +22,10 @@ pub trait TxProver { fn new_sapling_proving_context(&self) -> Self::SaplingProvingContext; /// Create the value commitment, re-randomized key, and proof for a MASP - /// SpendDescription, while accumulating its value commitment randomness inside + /// [`SpendDescription`], while accumulating its value commitment randomness inside /// the context for later use. - /// + /// + /// [`SpendDescription`]: crate::transaction::components::SpendDescription #[allow(clippy::too_many_arguments)] fn spend_proof( &self, @@ -71,12 +73,12 @@ pub trait TxProver { fn binding_sig( &self, ctx: &mut Self::SaplingProvingContext, - assets_and_values: &[(AssetType, i64)], + amount: &Amount, sighash: &[u8; 32], ) -> Result; } -#[cfg(test)] +#[cfg(any(test, feature = "test-dependencies"))] pub mod mock { use ff::Field; use rand_core::OsRng; @@ -84,23 +86,19 @@ pub mod mock { use crate::{ asset_type::AssetType, constants::SPENDING_KEY_GENERATOR, - primitives::{Diversifier, PaymentAddress, ProofGenerationKey}, - }; - - use crate::{ - redjubjub::{PublicKey, Signature}, - sapling::Node, + convert::AllowedConversion, + merkle_tree::MerklePath, + sapling::{ + redjubjub::{PublicKey, Signature}, + Diversifier, Node, PaymentAddress, ProofGenerationKey, Rseed, + }, + transaction::components::{Amount, GROTH_PROOF_SIZE}, }; - use zcash_primitives::transaction::components::GROTH_PROOF_SIZE; - use zcash_primitives::{merkle_tree::MerklePath, sapling::Rseed}; use super::TxProver; pub struct MockTxProver; - use crate::convert::AllowedConversion; - - #[cfg(test)] impl TxProver for MockTxProver { type SaplingProvingContext = (); @@ -171,7 +169,7 @@ pub mod mock { fn binding_sig( &self, _ctx: &mut Self::SaplingProvingContext, - _assets_and_values: &[(AssetType, i64)], + _value: &Amount, _sighash: &[u8; 32], ) -> Result { Err(()) diff --git a/masp_primitives/src/redjubjub.rs b/masp_primitives/src/sapling/redjubjub.rs similarity index 88% rename from masp_primitives/src/redjubjub.rs rename to masp_primitives/src/sapling/redjubjub.rs index 3b86144c..72c68b78 100644 --- a/masp_primitives/src/redjubjub.rs +++ b/masp_primitives/src/sapling/redjubjub.rs @@ -3,14 +3,20 @@ //! //! [RedJubjub]: https://zips.z.cash/protocol/protocol.pdf#concretereddsa +use crate::transaction::components::sapling::read_point; + +use super::util::hash_to_scalar; +use borsh::{BorshDeserialize, BorshSerialize}; use ff::{Field, PrimeField}; use group::GroupEncoding; use jubjub::{AffinePoint, ExtendedPoint, SubgroupPoint}; use rand_core::RngCore; -use std::io::{self, Read, Write}; -use std::ops::{AddAssign, MulAssign, Neg}; - -use zcash_primitives::sapling::util::hash_to_scalar; +use std::{ + cmp::Ordering, + hash::{Hash, Hasher}, + io::{self, Read, Write}, + ops::{AddAssign, MulAssign, Neg}, +}; fn read_scalar(mut reader: R) -> io::Result { let mut s_repr = [0u8; 32]; @@ -28,7 +34,7 @@ fn h_star(a: &[u8], b: &[u8]) -> jubjub::Fr { hash_to_scalar(b"MASP__RedJubjubH", a, b) } -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialOrd, PartialEq, Ord, Eq, Hash)] pub struct Signature { rbar: [u8; 32], sbar: [u8; 32], @@ -36,9 +42,45 @@ pub struct Signature { pub struct PrivateKey(pub jubjub::Fr); -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, Copy)] pub struct PublicKey(pub ExtendedPoint); +impl PartialOrd for PublicKey { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.to_bytes().partial_cmp(&other.0.to_bytes()) + } +} + +impl Hash for PublicKey { + fn hash(&self, state: &mut H) { + self.0.to_bytes().hash(state); + } +} + +impl BorshDeserialize for PublicKey { + fn deserialize(buf: &mut &[u8]) -> borsh::maybestd::io::Result { + Ok(Self(read_point(buf, "public key")?)) + } +} + +impl BorshSerialize for PublicKey { + fn serialize(&self, writer: &mut W) -> borsh::maybestd::io::Result<()> { + BorshSerialize::serialize(&self.0.to_bytes(), writer) + } +} + +impl BorshDeserialize for Signature { + fn deserialize(buf: &mut &[u8]) -> borsh::maybestd::io::Result { + Self::read(buf) + } +} + +impl BorshSerialize for Signature { + fn serialize(&self, writer: &mut W) -> borsh::maybestd::io::Result<()> { + self.write(writer) + } +} + impl Signature { pub fn read(mut reader: R) -> io::Result { let mut rbar = [0u8; 32]; @@ -55,6 +97,7 @@ impl Signature { } impl PrivateKey { + #[must_use] pub fn randomize(&self, alpha: jubjub::Fr) -> Self { let mut tmp = self.0; tmp.add_assign(&alpha); @@ -100,6 +143,7 @@ impl PublicKey { PublicKey((p_g * privkey.0).into()) } + #[must_use] pub fn randomize(&self, alpha: jubjub::Fr, p_g: SubgroupPoint) -> Self { PublicKey(ExtendedPoint::from(p_g * alpha) + self.0) } diff --git a/masp_primitives/src/sapling/util.rs b/masp_primitives/src/sapling/util.rs new file mode 100644 index 00000000..34b64bbe --- /dev/null +++ b/masp_primitives/src/sapling/util.rs @@ -0,0 +1,37 @@ +use blake2b_simd::Params; +use ff::Field; +use rand_core::{CryptoRng, RngCore}; + +use crate::consensus::{self, BlockHeight, NetworkUpgrade}; + +use super::Rseed; + +pub fn hash_to_scalar(persona: &[u8], a: &[u8], b: &[u8]) -> jubjub::Fr { + let mut hasher = Params::new().hash_length(64).personal(persona).to_state(); + hasher.update(a); + hasher.update(b); + let ret = hasher.finalize(); + jubjub::Fr::from_bytes_wide(ret.as_array()) +} + +pub fn generate_random_rseed( + params: &P, + height: BlockHeight, + rng: &mut R, +) -> Rseed { + generate_random_rseed_internal(params, height, rng) +} + +pub(crate) fn generate_random_rseed_internal( + params: &P, + height: BlockHeight, + rng: &mut R, +) -> Rseed { + if params.is_nu_active(NetworkUpgrade::MASP, height) { + let mut buffer = [0u8; 32]; + rng.fill_bytes(&mut buffer); + Rseed::AfterZip212(buffer) + } else { + Rseed::BeforeZip212(jubjub::Fr::random(rng)) + } +} diff --git a/masp_primitives/src/test_vectors/note_encryption.rs b/masp_primitives/src/test_vectors/note_encryption.rs index 17512b02..51ceefff 100644 --- a/masp_primitives/src/test_vectors/note_encryption.rs +++ b/masp_primitives/src/test_vectors/note_encryption.rs @@ -1,5 +1,4 @@ -#[allow(dead_code)] -pub(crate) struct TestVector { +pub struct TestVector { pub ovk: [u8; 32], pub ivk: [u8; 32], pub default_d: [u8; 11], @@ -13,35 +12,34 @@ pub(crate) struct TestVector { pub epk: [u8; 32], pub shared_secret: [u8; 32], pub k_enc: [u8; 32], - pub p_enc: [u8; 564], - pub c_enc: [u8; 580], + pub p_enc: [u8; 564 + 32], + pub c_enc: [u8; 580 + 32], pub ock: [u8; 32], pub op: [u8; 64], pub c_out: [u8; 80], } -#[allow(dead_code)] -pub(crate) fn make_test_vectors() -> Vec { +pub fn make_test_vectors() -> Vec { // From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/sapling_note_encryption.py vec![ TestVector { ovk: [ - 0x98, 0xd1, 0x69, 0x13, 0xd9, 0x9b, 0x04, 0x17, 0x7c, 0xab, 0xa4, 0x4f, 0x6e, 0x4d, - 0x22, 0x4e, 0x03, 0xb5, 0xac, 0x03, 0x1d, 0x7c, 0xe4, 0x5e, 0x86, 0x51, 0x38, 0xe1, - 0xb9, 0x96, 0xd6, 0x3b, + 0x52, 0xac, 0x15, 0x88, 0x17, 0x5f, 0x5a, 0x5e, 0x97, 0x94, 0xe6, 0xdd, 0xb8, 0x53, + 0x63, 0x61, 0x63, 0xc1, 0x3f, 0x91, 0x5d, 0x76, 0x08, 0x9d, 0x20, 0xde, 0x9f, 0x32, + 0x05, 0xf0, 0x18, 0x5c, ], ivk: [ - 0xb7, 0x0b, 0x7c, 0xd0, 0xed, 0x03, 0xcb, 0xdf, 0xd7, 0xad, 0xa9, 0x50, 0x2e, 0xe2, - 0x45, 0xb1, 0x3e, 0x56, 0x9d, 0x54, 0xa5, 0x71, 0x9d, 0x2d, 0xaa, 0x0f, 0x5f, 0x14, - 0x51, 0x47, 0x92, 0x04, + 0xbc, 0xc2, 0x5a, 0x9b, 0xf1, 0x42, 0xff, 0x98, 0x79, 0x67, 0x96, 0xea, 0xa1, 0xbc, + 0xd5, 0xff, 0xb7, 0x28, 0xb3, 0x65, 0x0f, 0xf0, 0x49, 0x4a, 0x17, 0x02, 0x7d, 0xee, + 0x93, 0x06, 0x6f, 0x02, ], default_d: [ - 0xf1, 0x9d, 0x9b, 0x79, 0x7e, 0x39, 0xf3, 0x37, 0x44, 0x58, 0x39, + 0x84, 0x4f, 0x9a, 0x4b, 0x31, 0x8b, 0x71, 0x39, 0xab, 0x60, 0x56, ], default_pk_d: [ - 0xdb, 0x4c, 0xd2, 0xb0, 0xaa, 0xc4, 0xf7, 0xeb, 0x8c, 0xa1, 0x31, 0xf1, 0x65, 0x67, - 0xc4, 0x45, 0xa9, 0x55, 0x51, 0x26, 0xd3, 0xc2, 0x9f, 0x14, 0xe3, 0xd7, 0x76, 0xe8, - 0x41, 0xae, 0x74, 0x15, + 0x47, 0xd2, 0x3d, 0x02, 0xa8, 0x0a, 0x28, 0xaf, 0xc0, 0xd2, 0xc5, 0xfc, 0x57, 0xbd, + 0x30, 0x1b, 0xb3, 0x99, 0xf8, 0x26, 0xb2, 0xca, 0xba, 0x6b, 0x62, 0x88, 0x36, 0x77, + 0x23, 0x29, 0x4a, 0x64, ], v: 100000000, rcm: [ @@ -89,14 +87,14 @@ pub(crate) fn make_test_vectors() -> Vec { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ], cv: [ - 0xa9, 0xcb, 0x0d, 0x13, 0x72, 0x32, 0xff, 0x84, 0x48, 0xd0, 0xf0, 0x78, 0xb6, 0x81, - 0x4c, 0x66, 0xcb, 0x33, 0x1b, 0x0f, 0x2d, 0x3d, 0x8a, 0x08, 0x5b, 0xed, 0xba, 0x81, - 0x5f, 0x00, 0xa8, 0xdb, + 0x9e, 0x1c, 0xf5, 0xc3, 0x2e, 0xdc, 0xd3, 0x30, 0x87, 0x11, 0x60, 0x06, 0x35, 0xad, + 0x95, 0xe1, 0x35, 0x46, 0xfb, 0xd9, 0xe0, 0x40, 0x70, 0x51, 0x79, 0x7d, 0x1f, 0x79, + 0xcb, 0xcc, 0x4a, 0x3e, ], cmu: [ - 0x63, 0x55, 0x72, 0xf5, 0x72, 0xa8, 0xa1, 0xa0, 0xb7, 0xac, 0xbc, 0x0a, 0xfc, 0x6d, - 0x66, 0xf1, 0x4a, 0x02, 0xef, 0xac, 0xde, 0x7b, 0xdf, 0x03, 0x44, 0x3e, 0xd4, 0xc3, - 0xe5, 0x51, 0xd4, 0x70, + 0x3c, 0x7d, 0x5a, 0xeb, 0xcf, 0x4d, 0xa7, 0x9c, 0xf9, 0x05, 0xa1, 0xd9, 0x8a, 0xa0, + 0x0b, 0xcf, 0x18, 0xee, 0x2a, 0x0a, 0xf2, 0xf8, 0x4a, 0x1a, 0x0f, 0x61, 0x20, 0x54, + 0x7e, 0xd5, 0x95, 0x1f, ], esk: [ 0x81, 0xc7, 0xb2, 0x17, 0x1f, 0xf4, 0x41, 0x52, 0x50, 0xca, 0xc0, 0x1f, 0x59, 0x82, @@ -104,26 +102,28 @@ pub(crate) fn make_test_vectors() -> Vec { 0x45, 0x96, 0x2a, 0x0e, ], epk: [ - 0xde, 0xd6, 0x8f, 0x05, 0xc6, 0x58, 0xfc, 0xae, 0x5a, 0xe2, 0x18, 0x64, 0x6f, 0xf8, - 0x44, 0x40, 0x6f, 0x84, 0x42, 0x67, 0x84, 0x04, 0x0d, 0x0b, 0xef, 0x2b, 0x09, 0xcb, - 0x38, 0x48, 0xc4, 0xdc, + 0x3e, 0x83, 0x55, 0x13, 0xb6, 0x77, 0xdb, 0x35, 0x95, 0x39, 0x67, 0xb0, 0x48, 0x1e, + 0xe6, 0xd7, 0x0d, 0x64, 0x7e, 0xb0, 0x00, 0x82, 0x0f, 0x9d, 0x1e, 0x52, 0x0c, 0x1c, + 0xde, 0x52, 0x42, 0xac, ], shared_secret: [ - 0x67, 0xf9, 0x61, 0x34, 0x04, 0xd9, 0xe9, 0x27, 0x1f, 0x16, 0x74, 0x01, 0x1b, 0x03, - 0x9b, 0x3d, 0x43, 0x81, 0xa4, 0xd7, 0x0c, 0x58, 0x6c, 0x8a, 0x13, 0x42, 0x28, 0x3f, - 0xd5, 0xfc, 0x3a, 0xde, + 0x0b, 0xe0, 0x22, 0x02, 0x17, 0x98, 0x36, 0x6c, 0xcd, 0x2d, 0x96, 0xf5, 0xe4, 0xb3, + 0x9b, 0xc9, 0x1a, 0x59, 0x88, 0x48, 0xab, 0x95, 0x54, 0x81, 0xf8, 0xa8, 0x72, 0x10, + 0x40, 0x0c, 0xef, 0x43, ], k_enc: [ - 0xe5, 0xbf, 0x8a, 0xb2, 0xf9, 0x41, 0xe9, 0xb9, 0xd2, 0xc7, 0x4a, 0xce, 0x2d, 0xf6, - 0xb3, 0x3c, 0x3c, 0x32, 0x29, 0xfa, 0x0b, 0x91, 0x26, 0xf9, 0xdd, 0xdb, 0x43, 0x29, - 0x66, 0x10, 0x00, 0x69, + 0x2c, 0xd7, 0x9c, 0x0f, 0x1d, 0x54, 0xb5, 0x94, 0x3c, 0x2a, 0x6a, 0x6d, 0x83, 0x4a, + 0x11, 0x98, 0xc9, 0xd6, 0xac, 0x12, 0xf0, 0x19, 0xc8, 0x92, 0x66, 0xd0, 0x3c, 0xc1, + 0x83, 0xb9, 0x3a, 0x93, ], p_enc: [ - 0x01, 0xf1, 0x9d, 0x9b, 0x79, 0x7e, 0x39, 0xf3, 0x37, 0x44, 0x58, 0x39, 0x00, 0xe1, - 0xf5, 0x05, 0x00, 0x00, 0x00, 0x00, 0x39, 0x17, 0x6d, 0xac, 0x39, 0xac, 0xe4, 0x98, - 0x0e, 0xcc, 0x8d, 0x77, 0x8e, 0x89, 0x86, 0x02, 0x55, 0xec, 0x36, 0x15, 0x06, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x84, 0x4f, 0x9a, 0x4b, 0x31, 0x8b, 0x71, 0x39, 0xab, 0x60, 0x56, 0x00, 0xe1, + 0xf5, 0x05, 0x00, 0x00, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, + 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, + 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x39, 0x17, 0x6d, 0xac, + 0x39, 0xac, 0xe4, 0x98, 0x0e, 0xcc, 0x8d, 0x77, 0x8e, 0x89, 0x86, 0x02, 0x55, 0xec, + 0x36, 0x15, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -159,91 +159,93 @@ pub(crate) fn make_test_vectors() -> Vec { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ], c_enc: [ - 0x8d, 0x6b, 0x27, 0xe7, 0xef, 0xf5, 0x9b, 0xfb, 0xa0, 0x1d, 0x65, 0x88, 0xba, 0xdd, - 0x36, 0x6c, 0xe5, 0x9b, 0x4d, 0x5b, 0x0e, 0xf9, 0x3b, 0xeb, 0xcb, 0xf2, 0x11, 0x41, - 0x7c, 0x56, 0xae, 0x70, 0x0a, 0xe1, 0x82, 0x44, 0xba, 0xc2, 0xfb, 0x64, 0x37, 0xdb, - 0x01, 0xf8, 0x3d, 0xc1, 0x49, 0xe2, 0x78, 0x6e, 0xc4, 0xec, 0x32, 0xc1, 0x1b, 0x05, - 0x4a, 0x4c, 0x0e, 0x2b, 0xdb, 0xe3, 0x43, 0x78, 0x8b, 0xb9, 0xc3, 0x3f, 0xf4, 0x2f, - 0xae, 0x99, 0x32, 0x32, 0x13, 0xe0, 0x96, 0x3e, 0x6f, 0x97, 0x6d, 0x6f, 0xff, 0xb8, - 0xc9, 0xfc, 0xf5, 0x21, 0x95, 0x74, 0xc7, 0xa9, 0x4c, 0x0e, 0x72, 0xf6, 0x09, 0x3a, - 0xed, 0xaf, 0xe3, 0x80, 0x62, 0x1b, 0x3b, 0xa8, 0x15, 0xd2, 0xb9, 0x72, 0x40, 0xf6, - 0x77, 0xd3, 0x90, 0xf5, 0xfc, 0x5d, 0x45, 0xee, 0xff, 0x16, 0x68, 0x8e, 0x40, 0xb9, - 0xee, 0xe8, 0xee, 0x1d, 0x39, 0x3b, 0x00, 0x97, 0x50, 0xcb, 0x73, 0xdf, 0x7a, 0x47, - 0xfd, 0x07, 0xa2, 0x81, 0x41, 0xdb, 0x49, 0xbd, 0x9c, 0xca, 0xb1, 0xf1, 0x8d, 0x0b, - 0x6a, 0x55, 0xed, 0x10, 0x1c, 0xa1, 0x6f, 0x73, 0x45, 0xbc, 0xb0, 0xbe, 0xaf, 0x7c, - 0xd7, 0x9a, 0x3d, 0x2b, 0xf2, 0x88, 0xf1, 0xd8, 0x8e, 0xbb, 0x1e, 0x4b, 0x74, 0x21, - 0x99, 0xd3, 0x30, 0xc3, 0x0a, 0x9f, 0xee, 0x1b, 0x44, 0xc6, 0x86, 0xa1, 0xff, 0x5c, - 0xc3, 0x3d, 0x46, 0x27, 0xf8, 0x3d, 0x61, 0xce, 0x34, 0xd6, 0xf1, 0x34, 0x4e, 0x2b, - 0x11, 0xa5, 0xf7, 0x17, 0x24, 0x42, 0x29, 0x60, 0x75, 0x91, 0x90, 0x05, 0x43, 0x4a, - 0x57, 0x4e, 0xd4, 0xe4, 0xc9, 0x8e, 0x23, 0x8e, 0xdd, 0x53, 0x67, 0xe8, 0xf5, 0x75, - 0x24, 0xb6, 0x38, 0xdd, 0x2d, 0x58, 0x30, 0xe8, 0x3f, 0x7f, 0x32, 0x08, 0x0d, 0x2d, - 0x51, 0xa0, 0x8a, 0xe8, 0x4e, 0x37, 0x42, 0x9c, 0x84, 0x38, 0xfa, 0xae, 0x15, 0x40, - 0x86, 0x7b, 0x12, 0xac, 0x2c, 0xf6, 0xa7, 0x7d, 0xa7, 0x80, 0xd9, 0x2c, 0xfa, 0x50, - 0x0c, 0x19, 0x5a, 0x07, 0x1c, 0xe8, 0xae, 0x3f, 0x10, 0x2c, 0xe0, 0x95, 0x01, 0xec, - 0xda, 0xc0, 0x8a, 0x79, 0x52, 0xa0, 0x8d, 0x53, 0xf3, 0x62, 0xd3, 0x7b, 0x64, 0x94, - 0x8c, 0x99, 0x15, 0xcb, 0xfc, 0x9f, 0x2d, 0x3c, 0x4e, 0x82, 0x22, 0xd3, 0x9a, 0x34, - 0x84, 0x21, 0x44, 0x7f, 0xab, 0xe4, 0xd5, 0xf0, 0x87, 0x80, 0x9a, 0x79, 0xe8, 0x49, - 0xb2, 0x8d, 0xff, 0xbc, 0x97, 0xfb, 0xbf, 0x64, 0x7f, 0xf3, 0x4f, 0x79, 0xff, 0x64, - 0xe7, 0x37, 0xeb, 0xf0, 0x3d, 0x8a, 0xdd, 0x44, 0xc1, 0x54, 0x32, 0x5f, 0x2b, 0xff, - 0x14, 0xc6, 0xe9, 0xe9, 0x0b, 0x0f, 0x98, 0x89, 0xf3, 0x25, 0xa9, 0x26, 0xa3, 0x68, - 0x56, 0x41, 0xa7, 0xa2, 0x19, 0xec, 0xe6, 0xfb, 0x2b, 0x4d, 0xee, 0xbf, 0x31, 0x09, - 0xd7, 0xee, 0x0f, 0x03, 0x9d, 0xac, 0x42, 0x74, 0x44, 0x99, 0x34, 0x85, 0x84, 0x84, - 0x44, 0xcc, 0xaf, 0xda, 0x5e, 0xa3, 0x28, 0x74, 0x06, 0x66, 0xdd, 0x75, 0xc3, 0x23, - 0xce, 0x7b, 0x92, 0x0e, 0xe0, 0xf3, 0xdc, 0x3a, 0xbc, 0xe6, 0xbd, 0x09, 0xc1, 0x3c, - 0x95, 0x7c, 0x5e, 0xa8, 0x95, 0x28, 0x27, 0x11, 0x6b, 0xb5, 0xbd, 0x0e, 0x5c, 0x27, - 0xf8, 0x20, 0xf2, 0xcf, 0x72, 0xa5, 0x10, 0x5d, 0x95, 0x55, 0xbe, 0x1e, 0x1e, 0x5e, - 0x68, 0xff, 0xfb, 0x71, 0x33, 0xdc, 0x39, 0x00, 0x19, 0x4e, 0x3b, 0x73, 0x1c, 0x7d, - 0x39, 0x11, 0x70, 0xad, 0x6d, 0x4a, 0xf1, 0x3a, 0x78, 0xa0, 0x6c, 0x25, 0xcf, 0xbb, - 0x0d, 0x09, 0x91, 0xd5, 0xa8, 0x83, 0xcf, 0xf5, 0x1c, 0xb6, 0xf5, 0x91, 0xc7, 0x92, - 0xd9, 0x9d, 0xcc, 0x55, 0x9c, 0xde, 0x9b, 0x7b, 0x39, 0xc4, 0xf5, 0x4a, 0x6b, 0xfb, - 0x29, 0xf1, 0xf8, 0x5e, 0x13, 0x5d, 0x17, 0x33, 0xb4, 0x9d, 0x5d, 0xd6, 0x70, 0x18, - 0xe6, 0x2e, 0x8c, 0x1a, 0xb0, 0xc1, 0x9a, 0x25, 0x41, 0x87, 0x26, 0xcc, 0xf2, 0xf5, - 0xe8, 0x8b, 0x97, 0x69, 0x21, 0x12, 0x92, 0x4b, 0xda, 0x2f, 0xde, 0x73, 0x48, 0xba, - 0xd7, 0x29, 0x52, 0x41, 0x72, 0x9d, 0xb4, 0xf3, 0x87, 0x11, 0xc7, 0xea, 0x98, 0xc5, - 0xd4, 0x19, 0x7c, 0x66, 0xfd, 0x23, + 0x95, 0x86, 0x5d, 0x04, 0x2a, 0x58, 0xdc, 0x2a, 0x2c, 0x34, 0x3e, 0x9b, 0xf1, 0x30, + 0xe8, 0x2b, 0x1d, 0x2e, 0xa2, 0x77, 0x2c, 0x42, 0xda, 0x53, 0x48, 0x09, 0xd0, 0x71, + 0x16, 0xa4, 0xeb, 0x28, 0xa7, 0x24, 0x9b, 0xf8, 0x19, 0x2f, 0xc0, 0xf5, 0xd7, 0xad, + 0xe8, 0xce, 0xc9, 0x4d, 0x6b, 0xa7, 0xc5, 0x12, 0x73, 0x12, 0xcd, 0xc6, 0xc7, 0x57, + 0x5b, 0x96, 0xea, 0xa8, 0x3f, 0x53, 0xd9, 0x3e, 0x32, 0x7b, 0xfe, 0x3f, 0x15, 0xc1, + 0x9e, 0x8c, 0x36, 0xe1, 0xc2, 0x89, 0xe4, 0x4a, 0xea, 0x66, 0x89, 0xc3, 0xc2, 0xe3, + 0xe9, 0x10, 0x02, 0x1e, 0xc3, 0xb4, 0x96, 0xee, 0x91, 0x37, 0xff, 0x1a, 0x28, 0x19, + 0x11, 0xc2, 0x44, 0xa5, 0x81, 0xd3, 0x7a, 0x67, 0x6f, 0xca, 0xf1, 0x85, 0x17, 0x88, + 0xe8, 0x43, 0xf2, 0xac, 0xd9, 0x25, 0x0c, 0x10, 0xf1, 0xd9, 0xac, 0xf1, 0x9c, 0x22, + 0xbb, 0xba, 0xcc, 0x53, 0x51, 0xc5, 0x9c, 0xca, 0x98, 0x8b, 0x9b, 0xd1, 0xd5, 0x06, + 0x76, 0xce, 0x78, 0x8a, 0xa6, 0xf8, 0x43, 0x8d, 0x95, 0x34, 0xcc, 0xf6, 0xa8, 0x34, + 0x0b, 0xc1, 0xb2, 0xbe, 0xf8, 0x55, 0xe0, 0xd9, 0x06, 0xa2, 0x17, 0x00, 0x4e, 0x84, + 0x18, 0x4f, 0x63, 0x2a, 0x07, 0xf6, 0xcd, 0x0e, 0x68, 0xf2, 0x86, 0xda, 0x3d, 0xdd, + 0xdd, 0x35, 0x42, 0x99, 0xb4, 0xb2, 0xbc, 0xb4, 0xfa, 0x41, 0x8d, 0x36, 0x61, 0x55, + 0x8e, 0x50, 0xf4, 0x85, 0xba, 0xe7, 0xf1, 0x88, 0xef, 0x4e, 0xd8, 0xbc, 0x62, 0xbe, + 0xa7, 0x32, 0xfb, 0xc8, 0x13, 0xb2, 0xfc, 0x2a, 0x94, 0xa0, 0xfe, 0x88, 0x2e, 0x80, + 0xe4, 0x1c, 0x92, 0x71, 0x2b, 0x05, 0x03, 0x64, 0x1a, 0xe4, 0xed, 0xba, 0xb6, 0x1d, + 0x54, 0x57, 0xfd, 0xd1, 0xe5, 0x68, 0x61, 0x97, 0x24, 0x70, 0xd9, 0xc6, 0x45, 0xad, + 0x7c, 0x26, 0x0b, 0x07, 0xd0, 0x2b, 0x17, 0xf9, 0xee, 0xe6, 0xe9, 0xc5, 0x8f, 0x15, + 0x95, 0xbf, 0x0d, 0x20, 0x5e, 0x21, 0xb5, 0x48, 0xd3, 0x8e, 0x61, 0xcf, 0x96, 0x6a, + 0x4c, 0x82, 0xb0, 0x45, 0x59, 0xb6, 0x47, 0x7a, 0x7e, 0x41, 0xf1, 0x5f, 0xe2, 0x6a, + 0xcc, 0x74, 0x54, 0xf1, 0x21, 0xb5, 0xa3, 0x96, 0x3c, 0x08, 0x65, 0x81, 0xa7, 0x8a, + 0x7e, 0xbb, 0x11, 0xe7, 0xa5, 0x18, 0x08, 0x60, 0x8d, 0x1e, 0x35, 0xe2, 0xd4, 0x03, + 0x64, 0xe8, 0x86, 0xe9, 0xb8, 0xea, 0x24, 0x6c, 0x59, 0xe3, 0x1e, 0xbf, 0x6f, 0x3f, + 0x2d, 0x48, 0x2d, 0x96, 0xb7, 0xb0, 0x89, 0x85, 0xbd, 0xec, 0xb9, 0x13, 0x49, 0x25, + 0xfe, 0x3a, 0xe2, 0x7a, 0x64, 0x34, 0x4c, 0x20, 0x09, 0xbd, 0x7f, 0x44, 0x22, 0xaa, + 0xbd, 0xcf, 0xa6, 0x1c, 0xb0, 0x58, 0xcf, 0x76, 0x31, 0x1e, 0x7e, 0x07, 0x7f, 0x55, + 0xd6, 0x2b, 0x86, 0xe9, 0x63, 0x8d, 0x93, 0x68, 0x71, 0x6a, 0x21, 0x68, 0x86, 0xe8, + 0x64, 0x22, 0x88, 0x0e, 0x4d, 0x21, 0xa9, 0xc1, 0x52, 0x22, 0xb3, 0x75, 0xe2, 0xb9, + 0x48, 0xc1, 0x68, 0xbc, 0x50, 0x56, 0xbe, 0xeb, 0x87, 0x20, 0xb6, 0x9b, 0x55, 0x3d, + 0xff, 0x20, 0x34, 0xcc, 0x2b, 0xc9, 0xc0, 0x55, 0x8a, 0xf0, 0x21, 0xdd, 0x93, 0xe2, + 0x03, 0x8d, 0x1a, 0x73, 0xf8, 0x61, 0xd1, 0xdd, 0xb6, 0xe3, 0x15, 0x23, 0xd8, 0x18, + 0x04, 0x4b, 0x43, 0x43, 0x21, 0x5d, 0x34, 0x04, 0x79, 0xed, 0xb4, 0x86, 0xe5, 0x0f, + 0x9f, 0x72, 0xcb, 0x1f, 0xd6, 0x96, 0xbf, 0xc7, 0xb4, 0xda, 0x8d, 0x8b, 0x0b, 0xc5, + 0xc9, 0x59, 0xf8, 0xf6, 0x96, 0xf5, 0x40, 0xd5, 0x5a, 0xce, 0x75, 0xd7, 0x47, 0x11, + 0x6f, 0x17, 0x83, 0x45, 0xee, 0x43, 0x3c, 0x06, 0xae, 0xef, 0x2a, 0x25, 0xd4, 0x3b, + 0xac, 0xe4, 0x83, 0x2b, 0xdb, 0x68, 0x19, 0xbc, 0x27, 0xed, 0x0e, 0x04, 0x9d, 0x98, + 0x95, 0x0b, 0x19, 0x7e, 0xeb, 0x44, 0x78, 0x73, 0x69, 0xc5, 0xbc, 0xb3, 0xe4, 0x65, + 0x96, 0x27, 0x21, 0x31, 0xa6, 0x23, 0x37, 0x3e, 0x25, 0x98, 0x76, 0xfa, 0xf2, 0xd9, + 0xe2, 0xa5, 0x6e, 0xc7, 0x2c, 0xe1, 0xb7, 0x44, 0x79, 0xb9, 0xef, 0x79, 0xe0, 0x31, + 0x41, 0x75, 0x8c, 0x1d, 0x8b, 0xe1, 0xfc, 0x5b, 0xbb, 0xb7, 0xe7, 0xb5, 0xd7, 0x46, + 0x58, 0xe6, 0x01, 0xc2, 0x75, 0x7d, 0x59, 0x9b, 0x3e, 0xd2, 0x42, 0xba, 0xb9, 0x86, + 0x46, 0xd3, 0xc8, 0x89, 0xce, 0xdf, 0x25, 0x11, 0x4e, 0x17, 0x66, 0x80, 0xd9, 0xa2, + 0x44, 0x54, 0x4e, 0x39, 0xdf, 0x61, 0x6c, 0xd8, 0xdd, 0x62, ], ock: [ - 0x6c, 0xe6, 0x1e, 0xad, 0x78, 0x49, 0x20, 0x42, 0x93, 0x34, 0x9e, 0x83, 0x2e, 0x95, - 0xca, 0x3a, 0xc6, 0x42, 0x2e, 0xc4, 0xfe, 0x21, 0xe5, 0xd1, 0x53, 0x86, 0x55, 0x8e, - 0x4d, 0x37, 0x79, 0x6d, + 0x37, 0xc9, 0xe1, 0x33, 0xc8, 0x08, 0x39, 0x66, 0xf6, 0xde, 0x35, 0x59, 0x01, 0xb2, + 0x29, 0xb3, 0xd7, 0x59, 0x7f, 0x08, 0x05, 0xae, 0x0b, 0xc4, 0xd2, 0xe1, 0x24, 0x17, + 0xbe, 0xcc, 0x95, 0xdb, ], op: [ - 0xdb, 0x4c, 0xd2, 0xb0, 0xaa, 0xc4, 0xf7, 0xeb, 0x8c, 0xa1, 0x31, 0xf1, 0x65, 0x67, - 0xc4, 0x45, 0xa9, 0x55, 0x51, 0x26, 0xd3, 0xc2, 0x9f, 0x14, 0xe3, 0xd7, 0x76, 0xe8, - 0x41, 0xae, 0x74, 0x15, 0x81, 0xc7, 0xb2, 0x17, 0x1f, 0xf4, 0x41, 0x52, 0x50, 0xca, + 0x47, 0xd2, 0x3d, 0x02, 0xa8, 0x0a, 0x28, 0xaf, 0xc0, 0xd2, 0xc5, 0xfc, 0x57, 0xbd, + 0x30, 0x1b, 0xb3, 0x99, 0xf8, 0x26, 0xb2, 0xca, 0xba, 0x6b, 0x62, 0x88, 0x36, 0x77, + 0x23, 0x29, 0x4a, 0x64, 0x81, 0xc7, 0xb2, 0x17, 0x1f, 0xf4, 0x41, 0x52, 0x50, 0xca, 0xc0, 0x1f, 0x59, 0x82, 0xfd, 0x8f, 0x49, 0x61, 0x9d, 0x61, 0xad, 0x78, 0xf6, 0x83, 0x0b, 0x3c, 0x60, 0x61, 0x45, 0x96, 0x2a, 0x0e, ], c_out: [ - 0x0e, 0xb2, 0xb0, 0x1b, 0xe8, 0x88, 0x0f, 0xc0, 0x46, 0x98, 0x42, 0x27, 0x14, 0x18, - 0xb5, 0x2b, 0xad, 0x40, 0x19, 0x89, 0x2c, 0xde, 0x53, 0xee, 0xca, 0xcd, 0xb2, 0xe4, - 0x5f, 0x5f, 0x33, 0x75, 0x85, 0xf7, 0xf6, 0x17, 0x5d, 0x88, 0x8f, 0x6e, 0x2c, 0x4e, - 0xd1, 0x35, 0x71, 0xcd, 0x96, 0xfd, 0x17, 0x7a, 0x01, 0xab, 0x10, 0x19, 0x08, 0xd7, - 0xca, 0x4a, 0x6d, 0x81, 0xd9, 0x16, 0x62, 0x2f, 0x5f, 0xf0, 0x77, 0xb1, 0x3f, 0x34, - 0x55, 0x90, 0xe2, 0x27, 0xc1, 0x0e, 0x08, 0x95, 0xe2, 0x04, + 0xbb, 0x71, 0x55, 0xbb, 0x76, 0x7f, 0x97, 0x35, 0x7d, 0x2b, 0xac, 0xa0, 0x4c, 0xe6, + 0xe0, 0x64, 0x41, 0x4f, 0x73, 0xd8, 0xdf, 0xb2, 0xb9, 0xac, 0x99, 0xd3, 0x24, 0xa8, + 0xbf, 0x2f, 0xae, 0x77, 0x8b, 0xcf, 0x47, 0xd3, 0xfa, 0xcb, 0x24, 0x1f, 0x4f, 0x9b, + 0xee, 0xdb, 0x0f, 0xbf, 0x7f, 0x7f, 0xf3, 0x5d, 0x9e, 0xe2, 0xb3, 0xe2, 0xd5, 0x75, + 0xa8, 0x9f, 0x5e, 0xd7, 0xe6, 0x35, 0xc1, 0xac, 0x32, 0x11, 0x62, 0xe7, 0x55, 0xcf, + 0xd0, 0x0e, 0x56, 0x4a, 0x6f, 0xb8, 0xbc, 0xee, 0x96, 0x31, ], }, TestVector { ovk: [ - 0x3b, 0x94, 0x62, 0x10, 0xce, 0x6d, 0x1b, 0x16, 0x92, 0xd7, 0x39, 0x2a, 0xc8, 0x4a, - 0x8b, 0xc8, 0xf0, 0x3b, 0x72, 0x72, 0x3c, 0x7d, 0x36, 0x72, 0x1b, 0x80, 0x9a, 0x79, - 0xc9, 0xd6, 0xe4, 0x5b, + 0x38, 0x2e, 0x85, 0xa6, 0x11, 0x09, 0xb0, 0x8a, 0x35, 0x88, 0xe0, 0x97, 0xa1, 0xe4, + 0x87, 0xbe, 0x9b, 0x49, 0xc1, 0x8c, 0x9d, 0x3b, 0x70, 0xb5, 0x57, 0xd3, 0x77, 0x8e, + 0xe3, 0xf1, 0x28, 0x44, ], ivk: [ - 0xc5, 0x18, 0x38, 0x44, 0x66, 0xb2, 0x69, 0x88, 0xb5, 0x10, 0x90, 0x67, 0x41, 0x8d, - 0x19, 0x2d, 0x9d, 0x6b, 0xd0, 0xd9, 0x23, 0x22, 0x05, 0xd7, 0x74, 0x18, 0xc2, 0x40, - 0xfc, 0x68, 0xa4, 0x06, + 0xb4, 0xed, 0xfb, 0x7c, 0x92, 0xb5, 0xef, 0xd2, 0x88, 0x7c, 0xb7, 0xce, 0x32, 0x0d, + 0xde, 0xc2, 0x85, 0xf6, 0xfd, 0xfb, 0xa2, 0xa9, 0x81, 0x3c, 0x2f, 0x16, 0x68, 0x0c, + 0x4e, 0x6b, 0x78, 0x01, ], default_d: [ - 0xae, 0xf1, 0x80, 0xf6, 0xe3, 0x4e, 0x35, 0x4b, 0x88, 0x8f, 0x81, + 0xe6, 0x77, 0xa4, 0xdd, 0x26, 0x76, 0xe0, 0x81, 0x88, 0xb9, 0x0f, ], default_pk_d: [ - 0xa6, 0xb1, 0x3e, 0xa3, 0x36, 0xdd, 0xb7, 0xa6, 0x7b, 0xb0, 0x9a, 0x0e, 0x68, 0xe9, - 0xd3, 0xcf, 0xb3, 0x92, 0x10, 0x83, 0x1e, 0xa3, 0xa2, 0x96, 0xba, 0x09, 0xa9, 0x22, - 0x06, 0x0f, 0xd3, 0x8b, + 0xdc, 0x53, 0x68, 0x2c, 0x0d, 0xd8, 0x90, 0x38, 0x2d, 0x89, 0x28, 0x30, 0xf6, 0xf3, + 0x7c, 0x80, 0x83, 0x87, 0x34, 0xa2, 0xaf, 0xaa, 0xc4, 0x0e, 0x8b, 0xee, 0xec, 0x09, + 0xa5, 0x7d, 0x24, 0xee, ], v: 200000000, rcm: [ @@ -291,14 +293,14 @@ pub(crate) fn make_test_vectors() -> Vec { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ], cv: [ - 0xfc, 0x54, 0x31, 0x9a, 0x39, 0xbe, 0x49, 0xc0, 0x48, 0x0c, 0x4d, 0xf3, 0x3b, 0x8f, - 0x77, 0xca, 0x67, 0x3a, 0x42, 0xbf, 0xde, 0xdf, 0xb8, 0x0e, 0xe4, 0x6b, 0x8f, 0x70, - 0xfc, 0x0d, 0xcd, 0x3d, + 0x3e, 0x71, 0x2f, 0xc2, 0x46, 0x65, 0xc4, 0x5e, 0x4f, 0xd8, 0x96, 0xa9, 0x00, 0xc8, + 0xc8, 0xb1, 0x74, 0x64, 0xa4, 0x21, 0x8a, 0xa2, 0xf9, 0xa5, 0x43, 0xfc, 0x2b, 0xbc, + 0xe4, 0x54, 0x29, 0x43, ], cmu: [ - 0x0c, 0x87, 0x41, 0x75, 0x77, 0x48, 0x0b, 0x69, 0x77, 0xba, 0x92, 0xc5, 0x54, 0x25, - 0xd6, 0x2b, 0x03, 0xb1, 0xe5, 0xf3, 0xc3, 0x82, 0x9c, 0xac, 0x49, 0xbf, 0xe5, 0x15, - 0xae, 0x72, 0x29, 0x45, + 0x5f, 0x2e, 0xab, 0x4e, 0xf9, 0x92, 0x6f, 0x60, 0x65, 0xe2, 0x1e, 0xca, 0x6e, 0x78, + 0x69, 0x32, 0x62, 0x90, 0xda, 0x1e, 0x68, 0xa1, 0x58, 0xda, 0x67, 0x96, 0xa0, 0x6b, + 0x6d, 0x9c, 0xb5, 0x54, ], esk: [ 0xad, 0x4a, 0xd6, 0x24, 0x77, 0xc2, 0xc8, 0x83, 0xc8, 0xba, 0xbf, 0xed, 0x5d, 0x38, @@ -306,26 +308,28 @@ pub(crate) fn make_test_vectors() -> Vec { 0x91, 0x55, 0x62, 0x0b, ], epk: [ - 0xf0, 0x6c, 0xba, 0xf8, 0xcb, 0x5c, 0x84, 0x82, 0x38, 0x47, 0xa1, 0x20, 0x10, 0x4c, - 0x85, 0xad, 0x70, 0x72, 0x28, 0xad, 0xba, 0x87, 0x6c, 0x6d, 0x83, 0x7e, 0xfd, 0x41, - 0x4e, 0x1c, 0x1d, 0xb4, + 0x11, 0xec, 0x16, 0x4b, 0x05, 0xbd, 0x16, 0x0f, 0x22, 0x76, 0x63, 0xe2, 0x90, 0x53, + 0xea, 0x8d, 0xce, 0x65, 0xe8, 0x16, 0x09, 0xf9, 0x75, 0x30, 0x90, 0xbd, 0xb5, 0x50, + 0x2b, 0x2e, 0xa4, 0x06, ], shared_secret: [ - 0xb9, 0x8a, 0x2c, 0x3b, 0xf0, 0xdc, 0x56, 0xb2, 0xbf, 0x65, 0xf5, 0xbd, 0x15, 0x25, - 0x05, 0x5e, 0xed, 0x22, 0xac, 0x0d, 0xcc, 0x2c, 0x11, 0xe3, 0x00, 0xc4, 0x67, 0x80, - 0x2b, 0x85, 0x88, 0x97, + 0x6b, 0x9a, 0xfe, 0xa9, 0x95, 0x58, 0x75, 0x0c, 0xc5, 0xd2, 0x28, 0x0b, 0x2b, 0xfb, + 0x6d, 0xd5, 0x40, 0xf7, 0x7f, 0x32, 0xa4, 0x13, 0x05, 0x4e, 0x31, 0x97, 0xb4, 0x4d, + 0x95, 0x24, 0xe2, 0x94, ], k_enc: [ - 0xb2, 0xef, 0x45, 0xb0, 0xf7, 0x25, 0x36, 0xa6, 0xc0, 0x22, 0xdd, 0xce, 0xe6, 0x2e, - 0xa7, 0x02, 0x7a, 0x49, 0x36, 0x2a, 0xa2, 0xdd, 0x3b, 0x54, 0x36, 0xd8, 0x89, 0x75, - 0xe0, 0x2a, 0xd0, 0xca, + 0x6a, 0xc9, 0xca, 0xdb, 0xae, 0x5c, 0x54, 0x65, 0x93, 0xbc, 0x09, 0x97, 0x4b, 0x8b, + 0xbf, 0x30, 0xa4, 0xa8, 0x1a, 0xe1, 0x21, 0x5f, 0x56, 0xf5, 0xbf, 0xdb, 0x83, 0x49, + 0x7c, 0x02, 0x24, 0xa1, ], p_enc: [ - 0x01, 0xae, 0xf1, 0x80, 0xf6, 0xe3, 0x4e, 0x35, 0x4b, 0x88, 0x8f, 0x81, 0x00, 0xc2, - 0xeb, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x47, 0x8b, 0xa0, 0xee, 0x6e, 0x1a, 0x75, 0xb6, - 0x00, 0x03, 0x6f, 0x26, 0xf1, 0x8b, 0x70, 0x15, 0xab, 0x55, 0x6b, 0xed, 0xdf, 0x8b, - 0x96, 0x02, 0x38, 0x86, 0x9f, 0x89, 0xdd, 0x80, 0x4e, 0x06, 0xf6, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0xe6, 0x77, 0xa4, 0xdd, 0x26, 0x76, 0xe0, 0x81, 0x88, 0xb9, 0x0f, 0x00, 0xc2, + 0xeb, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, + 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, + 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x47, 0x8b, 0xa0, 0xee, + 0x6e, 0x1a, 0x75, 0xb6, 0x00, 0x03, 0x6f, 0x26, 0xf1, 0x8b, 0x70, 0x15, 0xab, 0x55, + 0x6b, 0xed, 0xdf, 0x8b, 0x96, 0x02, 0x38, 0x86, 0x9f, 0x89, 0xdd, 0x80, 0x4e, 0x06, + 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -361,91 +365,93 @@ pub(crate) fn make_test_vectors() -> Vec { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ], c_enc: [ - 0x8a, 0x3f, 0x60, 0x25, 0x2f, 0x4d, 0xf9, 0x96, 0x39, 0x2e, 0x55, 0xaf, 0xee, 0x07, - 0x22, 0xf1, 0x24, 0xb1, 0xa1, 0x34, 0xe8, 0xa1, 0xfb, 0x1e, 0xaa, 0x88, 0x88, 0x9e, - 0x6a, 0xd4, 0x89, 0xcf, 0x1b, 0xa9, 0x12, 0x55, 0xee, 0x56, 0xfa, 0x1a, 0x09, 0xdb, - 0x71, 0x56, 0xc3, 0x55, 0x1a, 0xed, 0x29, 0x69, 0xa6, 0xff, 0x37, 0xf2, 0xa7, 0x7a, - 0x60, 0xb3, 0xea, 0x43, 0x75, 0xfa, 0xff, 0x04, 0x9e, 0x85, 0xc2, 0x72, 0x21, 0xcc, - 0x2b, 0xa9, 0x89, 0xbd, 0x18, 0xff, 0x96, 0x98, 0x00, 0x0a, 0xf1, 0xa7, 0x64, 0x3f, - 0x87, 0x85, 0xd6, 0x5e, 0xbb, 0x04, 0xc8, 0x5b, 0x24, 0x75, 0xdf, 0x62, 0x5b, 0x47, - 0xe3, 0xe9, 0xc7, 0xac, 0xa8, 0x4c, 0x13, 0x17, 0x23, 0x77, 0x6b, 0xd8, 0xc2, 0x9f, - 0x9d, 0x1f, 0x5f, 0xd2, 0x57, 0xe5, 0x8f, 0x72, 0xb6, 0x04, 0xf9, 0xb5, 0x7b, 0x1c, - 0x2d, 0x05, 0x31, 0xeb, 0xbb, 0x19, 0xcf, 0xc2, 0x73, 0x68, 0x89, 0x0d, 0x25, 0x6e, - 0x9a, 0xba, 0x30, 0x8d, 0xb9, 0xd8, 0x85, 0x6f, 0x49, 0xd4, 0x66, 0x3a, 0xfe, 0x55, - 0x50, 0x72, 0xed, 0x64, 0xc8, 0x19, 0x8e, 0x6a, 0xd1, 0x5c, 0x0c, 0x43, 0xbb, 0x16, - 0x85, 0x49, 0xa5, 0xbe, 0x38, 0xc5, 0xb4, 0x6d, 0xc1, 0x2f, 0x0c, 0x2a, 0x96, 0x1f, - 0xf3, 0xcf, 0xe3, 0x2a, 0x1c, 0x3e, 0xfe, 0x80, 0xb1, 0x5e, 0x37, 0xe4, 0xce, 0xbe, - 0x2a, 0x7a, 0xbe, 0x03, 0xeb, 0x17, 0xf4, 0xbb, 0xad, 0x22, 0x31, 0xcb, 0x52, 0x55, - 0xe2, 0x9c, 0xd0, 0x3c, 0xb9, 0x61, 0x33, 0x2c, 0xf5, 0xe5, 0x5e, 0x60, 0x53, 0xcd, - 0x40, 0x65, 0xc3, 0x78, 0x56, 0x06, 0xb2, 0x18, 0x5f, 0x18, 0xc4, 0xa3, 0xa2, 0x26, - 0x23, 0xd2, 0x59, 0xcd, 0x20, 0xdb, 0xe1, 0x54, 0xc4, 0xaf, 0x6b, 0x2b, 0xdc, 0xf3, - 0xb9, 0xc0, 0xff, 0x13, 0xce, 0x27, 0xe3, 0x95, 0x05, 0xa9, 0xf1, 0xb8, 0x2f, 0x6f, - 0xce, 0xea, 0xc0, 0x95, 0x38, 0x47, 0x17, 0xe8, 0x97, 0x0e, 0xe0, 0x29, 0xde, 0x96, - 0x4e, 0x80, 0x4a, 0xbd, 0x32, 0xd4, 0xda, 0x93, 0xbb, 0x8d, 0xc2, 0xb6, 0xbd, 0x60, - 0x44, 0xd8, 0xdf, 0xd7, 0x9d, 0xf7, 0x20, 0x7e, 0xa0, 0x3b, 0xdf, 0x03, 0x6f, 0xa6, - 0x26, 0x3f, 0x21, 0xbc, 0x1b, 0xfd, 0x4a, 0x6d, 0x9c, 0xb5, 0xf2, 0xd8, 0xbb, 0x6e, - 0x74, 0xb6, 0xdd, 0x04, 0x7a, 0xe1, 0xaa, 0xb8, 0xc1, 0xa7, 0x23, 0xb4, 0x78, 0x7c, - 0x54, 0xe2, 0x53, 0x96, 0x7f, 0xa9, 0x44, 0x0b, 0x73, 0x61, 0x83, 0x50, 0x65, 0x74, - 0x35, 0x03, 0x55, 0x26, 0x9b, 0x2b, 0x66, 0xb7, 0x48, 0xe8, 0x8f, 0xe9, 0xb8, 0xd1, - 0x23, 0xe9, 0x4b, 0x5f, 0xa5, 0xd0, 0x72, 0xb8, 0xc3, 0x96, 0x52, 0xe9, 0x20, 0x2b, - 0x16, 0xf1, 0x65, 0x46, 0x0e, 0x4b, 0x97, 0x0f, 0x63, 0xee, 0x7d, 0x63, 0x8f, 0x48, - 0xe4, 0x90, 0x17, 0xea, 0x64, 0x1c, 0xd3, 0x70, 0x09, 0xd4, 0x4b, 0x77, 0x24, 0x18, - 0x25, 0x44, 0xdb, 0x92, 0xbd, 0x0c, 0x4a, 0x7e, 0x9d, 0x93, 0x93, 0xd4, 0x6f, 0xcb, - 0x7b, 0xdd, 0xf9, 0x6f, 0x02, 0xcb, 0xf4, 0x7f, 0xa0, 0xf5, 0x28, 0x04, 0x09, 0x8e, - 0xcb, 0xbb, 0x7a, 0x13, 0xf3, 0xa2, 0xa5, 0xf1, 0x63, 0x8e, 0x77, 0xf8, 0xa8, 0x2f, - 0x6c, 0x3d, 0xec, 0xb7, 0x60, 0x7f, 0x09, 0x51, 0xc5, 0x7c, 0x7f, 0x27, 0x76, 0x04, - 0x22, 0x14, 0xf9, 0x0a, 0x3b, 0x6e, 0x00, 0xed, 0x16, 0x05, 0x9d, 0xff, 0x45, 0x55, - 0xbd, 0x47, 0x1d, 0x78, 0xaf, 0xe7, 0xaa, 0x3d, 0xc7, 0x91, 0x41, 0xa0, 0x87, 0x2d, - 0x19, 0xc8, 0x1c, 0x35, 0x1c, 0xaf, 0x54, 0xa2, 0xfc, 0x6d, 0xe8, 0xfd, 0x76, 0x86, - 0xc4, 0xf2, 0xc5, 0x34, 0xef, 0xac, 0x77, 0x51, 0x5e, 0x30, 0xf2, 0x50, 0x7b, 0xa0, - 0xb2, 0x3b, 0x1e, 0xe3, 0x7c, 0xa9, 0x08, 0x94, 0x3d, 0xfe, 0xf3, 0x80, 0x9a, 0x7e, - 0x9b, 0xec, 0xf1, 0xb9, 0x69, 0x10, 0x49, 0xf7, 0x87, 0x6a, 0x59, 0x2e, 0xe7, 0xed, - 0x64, 0x74, 0x0f, 0x1b, 0xe7, 0xe3, 0x06, 0x6e, 0xf7, 0x6f, 0x81, 0x47, 0x0f, 0x43, - 0x54, 0x33, 0x1a, 0xa1, 0xbc, 0x49, 0x57, 0x96, 0x99, 0x69, 0x77, 0x82, 0xbb, 0x07, - 0x5c, 0xbf, 0x82, 0xd3, 0xa8, 0xc0, + 0x70, 0xef, 0x0c, 0xd5, 0x09, 0x89, 0x0a, 0x02, 0x64, 0xc2, 0x5a, 0xdd, 0xcd, 0x73, + 0x3d, 0x24, 0xfd, 0x42, 0xef, 0xd0, 0xc8, 0xe8, 0x1d, 0xe7, 0x87, 0x0c, 0xbe, 0xa6, + 0x5d, 0x4e, 0x6f, 0x77, 0x6f, 0x45, 0x7f, 0xbd, 0x12, 0xd1, 0xf8, 0xfb, 0x26, 0xba, + 0x86, 0xf2, 0x48, 0x64, 0x53, 0x17, 0xbd, 0x6c, 0x6d, 0xb7, 0xe5, 0x46, 0xf0, 0x6e, + 0xb3, 0x02, 0xf7, 0x98, 0xd0, 0xdd, 0x4c, 0xd2, 0x65, 0xc4, 0x40, 0xea, 0x0f, 0xa0, + 0x98, 0x6a, 0x0d, 0x3f, 0x66, 0x9e, 0xe4, 0xb4, 0x44, 0x94, 0x08, 0x4f, 0x3a, 0x14, + 0x0c, 0x99, 0xd0, 0xb6, 0x37, 0x6d, 0xdd, 0xe0, 0xa8, 0x01, 0x90, 0x24, 0x42, 0xc0, + 0x47, 0x11, 0xff, 0x86, 0xfc, 0x80, 0xba, 0xb5, 0x74, 0xd2, 0xf4, 0xd9, 0x08, 0xdf, + 0x88, 0x49, 0x35, 0xdd, 0xfe, 0xce, 0x41, 0x1e, 0xef, 0x4c, 0xdd, 0x43, 0xf5, 0x0e, + 0x26, 0xe0, 0x02, 0x41, 0xcb, 0x46, 0xfd, 0x9f, 0x59, 0xed, 0x75, 0xe1, 0x1c, 0x1a, + 0x30, 0x90, 0x72, 0x50, 0x35, 0x3f, 0x1d, 0x2c, 0xdf, 0x8e, 0x02, 0x58, 0x5c, 0x4b, + 0xa2, 0xf7, 0x34, 0xb6, 0x82, 0xe7, 0xaf, 0xe9, 0xeb, 0xf5, 0x8b, 0x97, 0xa0, 0xde, + 0x8d, 0x0f, 0x06, 0xef, 0x83, 0xfc, 0x20, 0xe7, 0xe2, 0x1f, 0x16, 0x34, 0x7d, 0xb9, + 0x94, 0x48, 0x68, 0xd0, 0x16, 0xe9, 0x14, 0x9c, 0xb8, 0xcd, 0x2a, 0x3d, 0xc8, 0x31, + 0x72, 0x58, 0xd3, 0xb3, 0x8c, 0x48, 0x39, 0x82, 0x13, 0x6c, 0xa6, 0x5f, 0x43, 0x07, + 0x8a, 0xd0, 0x88, 0x53, 0x82, 0x40, 0x21, 0x44, 0xdf, 0x1b, 0x87, 0x89, 0x34, 0x63, + 0xdf, 0x45, 0xde, 0xae, 0xc8, 0x4f, 0x36, 0xfd, 0xe7, 0x52, 0xd9, 0x9e, 0x38, 0xc4, + 0x2e, 0xf3, 0x83, 0x08, 0xf7, 0xc5, 0x12, 0xbf, 0xfd, 0x11, 0x2a, 0xb3, 0x26, 0x65, + 0x6c, 0x6c, 0x4e, 0x5c, 0xad, 0x16, 0xe2, 0x37, 0x52, 0xbb, 0x95, 0xe1, 0x16, 0xb6, + 0x90, 0x9d, 0xeb, 0x52, 0x84, 0x32, 0x83, 0x4d, 0x5d, 0xd2, 0x09, 0x78, 0xfe, 0xa7, + 0x1e, 0x3e, 0xb2, 0x94, 0xc7, 0x4d, 0x49, 0xb0, 0x31, 0x0e, 0xb5, 0x65, 0x70, 0x93, + 0xec, 0x9f, 0xdc, 0x57, 0x86, 0xe7, 0xf7, 0xe9, 0xd7, 0x84, 0xc8, 0x69, 0x08, 0xb5, + 0xbb, 0x98, 0x04, 0x0a, 0x0a, 0x4b, 0xd1, 0x9d, 0xa8, 0xcd, 0x5e, 0x90, 0xa0, 0x7f, + 0xc7, 0x11, 0x20, 0x4b, 0x79, 0xf0, 0x70, 0xfb, 0x67, 0xf7, 0x72, 0xf3, 0x68, 0x98, + 0x49, 0x82, 0xaa, 0xb1, 0x27, 0xfc, 0x25, 0x78, 0xed, 0xd3, 0x27, 0xc4, 0x73, 0xc2, + 0x99, 0x20, 0xe9, 0xe6, 0x63, 0x24, 0xed, 0x70, 0xa8, 0x3b, 0x28, 0x66, 0x31, 0x88, + 0xb0, 0x94, 0xc8, 0x04, 0x2f, 0xf7, 0x27, 0x21, 0xb1, 0x9e, 0x54, 0xc0, 0xed, 0xe1, + 0x1d, 0xa5, 0x0c, 0x2a, 0xa4, 0xb1, 0xb8, 0x03, 0xdc, 0xfe, 0x52, 0x62, 0xc7, 0x61, + 0x26, 0x42, 0x3d, 0x4f, 0x30, 0xb5, 0x77, 0xa0, 0x94, 0x69, 0x99, 0x08, 0x43, 0xfa, + 0x92, 0xa9, 0xe1, 0xca, 0xed, 0x49, 0xc2, 0x4b, 0xcb, 0x2b, 0x7e, 0xa5, 0x93, 0xee, + 0xd8, 0xda, 0x12, 0xa7, 0x96, 0xa4, 0xd1, 0x77, 0x5e, 0x21, 0x71, 0x50, 0x1c, 0x04, + 0xd9, 0x79, 0x44, 0x7f, 0xf0, 0x52, 0x5c, 0x81, 0xa6, 0x09, 0xd1, 0x73, 0xb6, 0x1f, + 0x57, 0x6a, 0xbb, 0x7b, 0x38, 0xa1, 0x56, 0x68, 0x3b, 0xfb, 0xf0, 0xad, 0x4e, 0x6f, + 0xdf, 0x2f, 0xb9, 0xd3, 0x4f, 0x5c, 0xb9, 0x80, 0x63, 0xf3, 0xc6, 0xf2, 0x47, 0x62, + 0xee, 0x6a, 0x7e, 0x32, 0xe4, 0x0a, 0xe9, 0x26, 0x02, 0x9c, 0x7f, 0x2c, 0xd6, 0xa4, + 0xb0, 0xf5, 0x89, 0x0d, 0xad, 0x32, 0x20, 0xf4, 0x6f, 0xd5, 0x71, 0x0d, 0x32, 0x93, + 0x78, 0xd6, 0x5f, 0x1c, 0xea, 0x59, 0xb9, 0xb7, 0x12, 0xc1, 0x25, 0x26, 0xe6, 0x3e, + 0xf1, 0x95, 0x49, 0x63, 0x9d, 0x7f, 0x2b, 0x57, 0x2d, 0x08, 0xcd, 0x1b, 0x7f, 0x8c, + 0x25, 0x94, 0xff, 0xcf, 0x4a, 0x4f, 0xb6, 0x65, 0xfe, 0x9b, 0xcc, 0x9a, 0x4e, 0x57, + 0xe4, 0xad, 0x39, 0xfd, 0x96, 0xa8, 0xf5, 0x5f, 0x4e, 0x8a, 0x55, 0xd3, 0x55, 0x0b, + 0x22, 0x19, 0xf0, 0xce, 0xa6, 0x38, 0x85, 0x1d, 0xbe, 0x9a, 0x39, 0xdd, 0xec, 0xfc, + 0x82, 0xe7, 0xc4, 0x63, 0x4b, 0x6d, 0x77, 0x59, 0x69, 0x2f, 0xf0, 0xa3, 0x18, 0xff, + 0x47, 0x61, 0x6a, 0x79, 0xa0, 0x85, 0x24, 0x13, 0xdf, 0xbd, 0x26, 0x90, 0xf8, 0xed, + 0xd0, 0x85, 0x85, 0x30, 0x44, 0x18, 0x38, 0x39, 0x8b, 0x50, ], ock: [ - 0x6f, 0xce, 0x27, 0xbf, 0x1a, 0x62, 0xf0, 0x78, 0xe7, 0xe3, 0xcb, 0x5d, 0x8b, 0xf2, - 0x4c, 0xa7, 0xe4, 0xa5, 0x82, 0x1d, 0x45, 0x5f, 0x0f, 0xa8, 0x2c, 0xd5, 0x44, 0xec, - 0xb4, 0x20, 0x91, 0xfa, + 0xdd, 0x74, 0xd6, 0x97, 0xc7, 0x0a, 0x56, 0x41, 0xfa, 0xd2, 0x6c, 0x31, 0xc6, 0x69, + 0x8a, 0x59, 0x29, 0x8a, 0x44, 0xee, 0x9d, 0xaa, 0x27, 0x33, 0x5f, 0xac, 0x31, 0x0c, + 0x79, 0x2e, 0x6a, 0xf5, ], op: [ - 0xa6, 0xb1, 0x3e, 0xa3, 0x36, 0xdd, 0xb7, 0xa6, 0x7b, 0xb0, 0x9a, 0x0e, 0x68, 0xe9, - 0xd3, 0xcf, 0xb3, 0x92, 0x10, 0x83, 0x1e, 0xa3, 0xa2, 0x96, 0xba, 0x09, 0xa9, 0x22, - 0x06, 0x0f, 0xd3, 0x8b, 0xad, 0x4a, 0xd6, 0x24, 0x77, 0xc2, 0xc8, 0x83, 0xc8, 0xba, + 0xdc, 0x53, 0x68, 0x2c, 0x0d, 0xd8, 0x90, 0x38, 0x2d, 0x89, 0x28, 0x30, 0xf6, 0xf3, + 0x7c, 0x80, 0x83, 0x87, 0x34, 0xa2, 0xaf, 0xaa, 0xc4, 0x0e, 0x8b, 0xee, 0xec, 0x09, + 0xa5, 0x7d, 0x24, 0xee, 0xad, 0x4a, 0xd6, 0x24, 0x77, 0xc2, 0xc8, 0x83, 0xc8, 0xba, 0xbf, 0xed, 0x5d, 0x38, 0x5b, 0x51, 0xab, 0xdc, 0xc6, 0x98, 0xe9, 0x36, 0xe7, 0x8d, 0xc2, 0x26, 0x71, 0x72, 0x91, 0x55, 0x62, 0x0b, ], c_out: [ - 0x88, 0x24, 0x58, 0x30, 0x2c, 0x0a, 0xba, 0x55, 0xed, 0x8d, 0x67, 0x18, 0xca, 0x26, - 0xd8, 0xc2, 0x8a, 0x12, 0x7a, 0x01, 0xe7, 0x7c, 0x2a, 0xe5, 0xbf, 0x15, 0xc6, 0x96, - 0x73, 0x91, 0x81, 0x77, 0xf9, 0x24, 0x77, 0xa2, 0x18, 0xa7, 0xf6, 0xcf, 0x12, 0x17, - 0x80, 0x22, 0xc9, 0xdd, 0xc7, 0x18, 0x5c, 0x18, 0xd0, 0x87, 0x6c, 0x3c, 0x29, 0x65, - 0x83, 0xe0, 0xbc, 0x54, 0x79, 0x3b, 0xf1, 0xe2, 0x6a, 0x85, 0x4a, 0x41, 0xab, 0x61, - 0x7f, 0x20, 0x52, 0x71, 0xba, 0x6c, 0x14, 0x29, 0xbd, 0xf4, + 0x4e, 0x74, 0x7e, 0x34, 0xb4, 0x7a, 0x8e, 0xb5, 0x24, 0x88, 0x7b, 0xe9, 0xcb, 0xab, + 0x64, 0xad, 0x8d, 0x43, 0xf7, 0xe9, 0x46, 0xae, 0xe7, 0x2e, 0x89, 0x1d, 0xe6, 0xba, + 0x2b, 0x24, 0x3e, 0x60, 0x99, 0x87, 0xc5, 0xae, 0xe3, 0x6f, 0xe7, 0x3b, 0x59, 0xfa, + 0xa7, 0xb5, 0x9f, 0xf7, 0x98, 0xd8, 0xc3, 0x79, 0xdb, 0xbc, 0x95, 0x00, 0x1f, 0x24, + 0xca, 0x5d, 0xbc, 0xcd, 0xf9, 0x27, 0x56, 0x62, 0xa9, 0x05, 0x3e, 0x34, 0x36, 0xfd, + 0xd9, 0x8e, 0x40, 0x42, 0x3d, 0x43, 0x83, 0xb6, 0x1c, 0x04, ], }, TestVector { ovk: [ - 0x8b, 0xf4, 0x39, 0x0e, 0x28, 0xdd, 0xc9, 0x5b, 0x83, 0x02, 0xc3, 0x81, 0xd5, 0x81, - 0x0b, 0x84, 0xba, 0x8e, 0x60, 0x96, 0xe5, 0xa7, 0x68, 0x22, 0x77, 0x4f, 0xd4, 0x9f, - 0x49, 0x1e, 0x8f, 0x49, + 0x8d, 0xc3, 0x73, 0xff, 0xec, 0xa3, 0xd8, 0x57, 0x8d, 0x51, 0x6f, 0x35, 0x4a, 0xa8, + 0xa9, 0x73, 0x6f, 0x27, 0x8b, 0xee, 0xf1, 0x7a, 0x54, 0x4b, 0x16, 0xb3, 0x47, 0x8d, + 0xc5, 0x95, 0x46, 0xbd, ], ivk: [ - 0x47, 0x1c, 0x24, 0xa3, 0xdc, 0x87, 0x30, 0xe7, 0x50, 0x36, 0xc0, 0xa9, 0x5f, 0x3e, - 0x2f, 0x7d, 0xd1, 0xbe, 0x6f, 0xb9, 0x3a, 0xd2, 0x95, 0x92, 0x20, 0x3d, 0xef, 0x30, - 0x41, 0x95, 0x45, 0x05, + 0xdf, 0x4a, 0xfb, 0x34, 0x37, 0x3a, 0x88, 0x4f, 0x8d, 0x86, 0x53, 0x5a, 0x2c, 0x45, + 0xd6, 0xd3, 0x21, 0x66, 0x9e, 0xbf, 0xb8, 0x59, 0x99, 0x03, 0xa6, 0x40, 0x7d, 0xd3, + 0x82, 0x09, 0x76, 0x01, ], default_d: [ - 0x75, 0x99, 0xf0, 0xbf, 0x9b, 0x57, 0xcd, 0x2d, 0xc2, 0x99, 0xb6, + 0xa1, 0xe0, 0xf5, 0x3c, 0x47, 0x3e, 0xd9, 0x8c, 0x17, 0xb6, 0xd0, ], default_pk_d: [ - 0x66, 0x14, 0x17, 0x39, 0x51, 0x4b, 0x28, 0xf0, 0x5d, 0xef, 0x8a, 0x18, 0xee, 0xee, - 0x5e, 0xed, 0x4d, 0x44, 0xc6, 0x22, 0x5c, 0x3c, 0x65, 0xd8, 0x8d, 0xd9, 0x90, 0x77, - 0x08, 0x01, 0x2f, 0x5a, + 0xb3, 0x23, 0xbb, 0x8b, 0x98, 0x03, 0x11, 0x44, 0x88, 0x26, 0x0f, 0x9f, 0x51, 0xe5, + 0x46, 0xc2, 0xb4, 0x5f, 0x3d, 0x03, 0x6d, 0x03, 0x9b, 0x0f, 0x0c, 0xb2, 0x86, 0x13, + 0x9d, 0x4c, 0x25, 0xb5, ], v: 300000000, rcm: [ @@ -493,14 +499,14 @@ pub(crate) fn make_test_vectors() -> Vec { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ], cv: [ - 0x5c, 0xc9, 0xea, 0x16, 0x8e, 0x79, 0xff, 0x0d, 0x08, 0x3a, 0xf4, 0x21, 0xd3, 0x2d, - 0x27, 0xfb, 0xa1, 0xc8, 0xa6, 0x38, 0xc0, 0xc3, 0x52, 0xcf, 0x59, 0xdc, 0xb1, 0xca, - 0x84, 0xc3, 0xfb, 0x1b, + 0x48, 0x59, 0xd1, 0xb5, 0x37, 0x31, 0xfc, 0xb3, 0xe1, 0x50, 0x4a, 0xf5, 0x63, 0x94, + 0x32, 0x54, 0x7f, 0x30, 0x1c, 0x59, 0x8d, 0x41, 0x27, 0xd8, 0x0d, 0x87, 0xa4, 0xc0, + 0x3a, 0xad, 0x60, 0x1e, ], cmu: [ - 0xb3, 0xb4, 0xe7, 0xab, 0x08, 0x0b, 0x9b, 0x0f, 0xe4, 0x73, 0xcf, 0xc5, 0xa3, 0x10, - 0x5e, 0x9a, 0x06, 0x2a, 0x4e, 0xe4, 0x9e, 0xdd, 0x70, 0x95, 0xa6, 0x71, 0x63, 0x7e, - 0x00, 0x57, 0x24, 0x2b, + 0xdd, 0xdb, 0xb9, 0x62, 0x51, 0x66, 0x84, 0x26, 0x02, 0x2f, 0x25, 0x73, 0xd4, 0x44, + 0x2f, 0x0a, 0xbe, 0x2a, 0xea, 0xf6, 0x15, 0xb2, 0x6d, 0x1d, 0xeb, 0x2e, 0xa0, 0xe2, + 0xb8, 0xb3, 0x88, 0x23, ], esk: [ 0x99, 0xaa, 0x10, 0xc0, 0x57, 0x88, 0x08, 0x1c, 0x0d, 0xa7, 0xd8, 0x79, 0xcd, 0x95, @@ -508,26 +514,28 @@ pub(crate) fn make_test_vectors() -> Vec { 0x08, 0x96, 0x4c, 0x03, ], epk: [ - 0x6a, 0x92, 0x02, 0x60, 0x43, 0xfa, 0x93, 0x0e, 0xeb, 0x2b, 0x28, 0xfd, 0x7b, 0xbd, - 0xc5, 0xa7, 0x05, 0x00, 0xbe, 0xb8, 0x4c, 0x67, 0x11, 0x36, 0x23, 0x8e, 0x5e, 0xfd, - 0xb0, 0x17, 0xd9, 0x9c, + 0xbe, 0x1c, 0x98, 0xaa, 0xee, 0xf2, 0x4a, 0x12, 0xff, 0x3e, 0x3b, 0xe4, 0x2c, 0x92, + 0xda, 0x1f, 0xca, 0x4e, 0x26, 0x33, 0x38, 0x1c, 0xf6, 0x7d, 0x68, 0x36, 0xe0, 0x8c, + 0x76, 0x89, 0xac, 0x8e, ], shared_secret: [ - 0x50, 0x78, 0x28, 0x7f, 0xf1, 0x7b, 0x1d, 0x92, 0x9b, 0x6a, 0x99, 0xb5, 0xe2, 0x82, - 0x68, 0xa1, 0x92, 0x93, 0x95, 0x73, 0xda, 0xc4, 0xe8, 0x4d, 0x51, 0x1b, 0x53, 0x93, - 0xd7, 0x2a, 0x6d, 0x68, + 0xc4, 0x2e, 0xf4, 0x8e, 0x45, 0x53, 0xbb, 0xe5, 0xa2, 0x0b, 0x41, 0x78, 0x93, 0x82, + 0xd5, 0xa0, 0x40, 0x9f, 0x08, 0x28, 0x45, 0xd1, 0xda, 0x97, 0x1c, 0x01, 0xc4, 0x9d, + 0xb3, 0x93, 0xf9, 0xcc, ], k_enc: [ - 0xa4, 0x3c, 0xaa, 0xd6, 0x25, 0x30, 0xde, 0x86, 0xdf, 0x57, 0xe9, 0xde, 0x03, 0x47, - 0xa2, 0xd8, 0x06, 0x40, 0x53, 0x0a, 0x4c, 0xa9, 0x7b, 0x82, 0x92, 0xa5, 0xa5, 0x25, - 0x0f, 0x1b, 0xf2, 0x40, + 0x2f, 0x29, 0x71, 0xf4, 0xd0, 0x83, 0xb4, 0x9f, 0x26, 0x14, 0xa9, 0x8a, 0x29, 0x43, + 0xc6, 0xf1, 0x30, 0x9b, 0xf3, 0xfc, 0x2b, 0x07, 0x9e, 0x52, 0xf1, 0x68, 0x3f, 0xa6, + 0x75, 0x4b, 0x30, 0x6b, ], p_enc: [ - 0x01, 0x75, 0x99, 0xf0, 0xbf, 0x9b, 0x57, 0xcd, 0x2d, 0xc2, 0x99, 0xb6, 0x00, 0xa3, - 0xe1, 0x11, 0x00, 0x00, 0x00, 0x00, 0x14, 0x7c, 0xf2, 0xb5, 0x1b, 0x4c, 0x7c, 0x63, - 0xcb, 0x77, 0xb9, 0x9e, 0x8b, 0x78, 0x3e, 0x5b, 0x51, 0x11, 0xdb, 0x0a, 0x7c, 0xa0, - 0x4d, 0x6c, 0x01, 0x4a, 0x1d, 0x7d, 0xa8, 0x3b, 0xae, 0x0a, 0xf6, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0xa1, 0xe0, 0xf5, 0x3c, 0x47, 0x3e, 0xd9, 0x8c, 0x17, 0xb6, 0xd0, 0x00, 0xa3, + 0xe1, 0x11, 0x00, 0x00, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, + 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, + 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x14, 0x7c, 0xf2, 0xb5, + 0x1b, 0x4c, 0x7c, 0x63, 0xcb, 0x77, 0xb9, 0x9e, 0x8b, 0x78, 0x3e, 0x5b, 0x51, 0x11, + 0xdb, 0x0a, 0x7c, 0xa0, 0x4d, 0x6c, 0x01, 0x4a, 0x1d, 0x7d, 0xa8, 0x3b, 0xae, 0x0a, + 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -563,91 +571,93 @@ pub(crate) fn make_test_vectors() -> Vec { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ], c_enc: [ - 0x4c, 0xac, 0xe5, 0x2f, 0x2d, 0xa8, 0x2a, 0x34, 0xe3, 0x0d, 0xe8, 0xfb, 0x2e, 0x25, - 0x6b, 0xef, 0xd9, 0x2d, 0xd3, 0x0e, 0xf7, 0x86, 0x85, 0xa5, 0x08, 0xe4, 0x41, 0x0c, - 0x79, 0x33, 0x6f, 0x0a, 0xf1, 0xb2, 0x64, 0x84, 0x82, 0x33, 0x59, 0x24, 0x78, 0xd2, - 0x2d, 0xf7, 0x91, 0xab, 0x8d, 0x4c, 0x7d, 0x32, 0x3c, 0xd8, 0x4d, 0x6b, 0x2e, 0x4d, - 0xcf, 0x66, 0x49, 0x5b, 0x46, 0xc5, 0x31, 0xa3, 0x21, 0x67, 0x66, 0xfc, 0x8b, 0x6f, - 0x65, 0xfe, 0x57, 0x6c, 0x44, 0xef, 0x88, 0xc4, 0x44, 0xfa, 0x95, 0x7f, 0xbd, 0x87, - 0xaf, 0x7a, 0x30, 0xf5, 0x2b, 0xd3, 0xf2, 0x33, 0x8c, 0xbb, 0x0b, 0x7e, 0xe6, 0x68, - 0x5c, 0x51, 0xec, 0xef, 0xb5, 0xfd, 0x17, 0xd7, 0x53, 0x0b, 0xb6, 0x14, 0x52, 0x28, - 0xbb, 0x97, 0x6a, 0x56, 0xa1, 0xc9, 0xb2, 0xc8, 0xd2, 0x86, 0x4c, 0x43, 0xd3, 0xcd, - 0x64, 0x0b, 0xd7, 0xe0, 0x1f, 0x08, 0xaa, 0xc4, 0x16, 0xd2, 0x25, 0x0d, 0xf7, 0xf4, - 0xb1, 0xb9, 0xeb, 0xd9, 0xbd, 0x10, 0x3f, 0xd4, 0x17, 0xfd, 0xbe, 0x57, 0x13, 0x2e, - 0xab, 0xfc, 0x52, 0xc3, 0x79, 0x8e, 0x98, 0xc3, 0x7c, 0x1a, 0xf3, 0x4d, 0x28, 0x91, - 0x2c, 0x1d, 0x11, 0x64, 0xb5, 0x27, 0x71, 0x07, 0xc4, 0x7d, 0x6b, 0xd5, 0xf3, 0xc0, - 0xb3, 0x0f, 0x4e, 0xfa, 0xb7, 0xef, 0x04, 0x15, 0x8e, 0x11, 0x9d, 0x7c, 0x40, 0x79, - 0x4a, 0xb0, 0xd4, 0x23, 0x19, 0x49, 0xe7, 0xf8, 0x0f, 0x43, 0xd7, 0x63, 0x64, 0x56, - 0xfe, 0xe2, 0xe1, 0x27, 0x2e, 0xa1, 0xe2, 0xec, 0x3e, 0x8f, 0xf3, 0x06, 0x98, 0xb8, - 0x32, 0x64, 0x71, 0xeb, 0xa9, 0x40, 0x95, 0x0d, 0x55, 0x83, 0x62, 0x4d, 0xfd, 0xab, - 0xe8, 0x7d, 0x7c, 0x52, 0xa4, 0xd0, 0x0e, 0xf2, 0x00, 0x42, 0x38, 0x1c, 0x9e, 0x6f, - 0x03, 0xd3, 0x29, 0xbb, 0xf4, 0x20, 0x43, 0xf2, 0xf3, 0xb4, 0xfd, 0x77, 0x54, 0x16, - 0x32, 0x40, 0x2e, 0x06, 0x11, 0xb2, 0x44, 0xb0, 0xc2, 0x80, 0x3c, 0xd5, 0x12, 0x50, - 0x81, 0x4c, 0xff, 0xdd, 0x7e, 0xeb, 0x17, 0x35, 0xbe, 0xba, 0x8e, 0xa8, 0xa5, 0x8e, - 0xbc, 0xc3, 0x23, 0xf4, 0x24, 0xfc, 0xd5, 0xa7, 0x3d, 0xcc, 0xa2, 0xf5, 0x06, 0xfc, - 0xa4, 0x03, 0x19, 0x9f, 0x0c, 0xc7, 0xb1, 0xe9, 0x7b, 0x92, 0x0b, 0xa2, 0x72, 0x35, - 0xcd, 0x39, 0xe5, 0x27, 0x38, 0x2b, 0xad, 0x3a, 0x48, 0x3b, 0x9f, 0x1e, 0xbb, 0xf2, - 0x91, 0x77, 0xae, 0x94, 0xd8, 0xfa, 0x63, 0xbe, 0xeb, 0x45, 0x6d, 0x12, 0x78, 0xb9, - 0xd2, 0x28, 0x59, 0x44, 0x31, 0x99, 0x04, 0xdd, 0xe4, 0x2a, 0xdc, 0x70, 0x62, 0xb5, - 0x50, 0xb1, 0xff, 0x47, 0xb7, 0x0d, 0x3c, 0x78, 0xc2, 0x4c, 0x55, 0x06, 0x9f, 0x72, - 0x0f, 0xea, 0x60, 0x23, 0xf2, 0x19, 0x4a, 0x72, 0x91, 0xff, 0xb8, 0x11, 0xf6, 0x8a, - 0x16, 0xd6, 0xc1, 0x15, 0xf4, 0xd8, 0xc6, 0x85, 0xe0, 0x9a, 0x44, 0xda, 0x84, 0x11, - 0xe1, 0xb9, 0xb5, 0x3f, 0x39, 0xd5, 0x18, 0x46, 0x14, 0x7d, 0xdb, 0x62, 0x08, 0x98, - 0xe0, 0x80, 0xb7, 0xa6, 0x5f, 0xe8, 0xe2, 0xe1, 0x31, 0x2b, 0x0b, 0x81, 0x52, 0x13, - 0x8a, 0x8b, 0xa9, 0xe0, 0x86, 0x67, 0x90, 0x57, 0x17, 0x9f, 0xf0, 0x9f, 0x7b, 0x3c, - 0xbf, 0x58, 0xbf, 0x59, 0xe3, 0x3f, 0x83, 0xde, 0x2c, 0x70, 0x35, 0x0a, 0xb5, 0x7c, - 0x82, 0xbe, 0x9e, 0xc9, 0x5c, 0xcc, 0x95, 0xe2, 0xbe, 0x29, 0x4e, 0xc5, 0x38, 0x3f, - 0xa3, 0xbb, 0xd7, 0xa7, 0x59, 0x31, 0x5c, 0xc2, 0x5d, 0xea, 0x38, 0x53, 0xe7, 0xb5, - 0x36, 0x6b, 0xaa, 0xe0, 0x5a, 0xca, 0x8b, 0xc9, 0x56, 0xf1, 0xd5, 0xbd, 0xdc, 0xbd, - 0xa2, 0x95, 0xa5, 0xca, 0x7c, 0x2e, 0x26, 0xfb, 0x4e, 0x26, 0xf7, 0xeb, 0xdf, 0x62, - 0x44, 0xb7, 0x8a, 0x59, 0x1e, 0xfa, 0xa3, 0xa6, 0xf4, 0x8c, 0xc4, 0x10, 0x59, 0x78, - 0xc9, 0x68, 0xdd, 0x85, 0x88, 0x79, 0x5a, 0x9a, 0x65, 0x71, 0x17, 0x93, 0xf1, 0x98, - 0x04, 0xf8, 0x81, 0x4b, 0x4a, 0x9d, 0xb0, 0xbf, 0xa1, 0x57, 0x76, 0x9a, 0xaf, 0xda, - 0x2d, 0xb0, 0xee, 0xf0, 0x2b, 0x9a, 0x81, 0x16, 0x3b, 0x7c, 0x23, 0x56, 0x97, 0x62, - 0x0c, 0x72, 0xd8, 0x24, 0xe3, 0x2b, + 0x68, 0x36, 0x53, 0x04, 0x70, 0x77, 0xfe, 0x85, 0x30, 0xab, 0x62, 0xdf, 0xb5, 0x59, + 0xc9, 0x75, 0x42, 0x29, 0x57, 0x34, 0x4c, 0x3c, 0x9f, 0x19, 0x12, 0x00, 0x50, 0xe8, + 0x47, 0x2a, 0xa2, 0xe2, 0x18, 0x69, 0xee, 0x6c, 0x1a, 0xad, 0x56, 0x11, 0x0b, 0xf9, + 0x39, 0xcd, 0xa5, 0x2e, 0xab, 0x2c, 0xb2, 0x92, 0x84, 0xd7, 0xc2, 0x05, 0x7e, 0xbe, + 0xfd, 0x02, 0x7a, 0x8c, 0x71, 0x7f, 0x50, 0xc7, 0x89, 0x2a, 0xd6, 0xc3, 0x4b, 0xc9, + 0xdb, 0x71, 0xb1, 0x6e, 0x82, 0x33, 0xa6, 0xd8, 0x1c, 0x4d, 0x22, 0x84, 0x68, 0xc4, + 0x4d, 0x30, 0x32, 0x0e, 0x3b, 0xcb, 0x5b, 0x23, 0xcf, 0x8e, 0x0a, 0x47, 0x15, 0x44, + 0x9f, 0x61, 0x1d, 0xa4, 0xac, 0x22, 0xcc, 0xee, 0x1c, 0x1a, 0xa7, 0x47, 0xd0, 0xe4, + 0xc3, 0xe2, 0x4a, 0x27, 0x45, 0x89, 0x87, 0x76, 0x35, 0x5b, 0x5d, 0x1a, 0xac, 0xa5, + 0xa1, 0xd4, 0x27, 0x20, 0x4c, 0xbb, 0x13, 0x52, 0x3e, 0x58, 0xad, 0x54, 0xed, 0x1c, + 0x3f, 0x88, 0x7a, 0xbe, 0x29, 0x6e, 0xaf, 0xff, 0xe7, 0x2d, 0xd5, 0xe8, 0xe7, 0x82, + 0x3f, 0x22, 0xa8, 0xb0, 0x87, 0xa7, 0x84, 0xd7, 0x1a, 0x97, 0x6c, 0x3e, 0xb5, 0xbf, + 0xb7, 0x49, 0xaa, 0x1a, 0x49, 0x81, 0xc2, 0x2e, 0x4d, 0xb6, 0xd6, 0x4f, 0x69, 0xd3, + 0x83, 0xd8, 0xd2, 0x2d, 0x49, 0xb1, 0x06, 0xf1, 0x3b, 0xfc, 0xa0, 0xae, 0x67, 0x22, + 0x92, 0x66, 0x23, 0x12, 0x07, 0x89, 0x97, 0xdd, 0xdd, 0xd6, 0xb0, 0xb6, 0x80, 0x9f, + 0x69, 0x18, 0xbe, 0x81, 0x5a, 0x45, 0xcc, 0x2c, 0x7f, 0x2e, 0x0b, 0x0f, 0xcc, 0xd5, + 0xdc, 0x86, 0x25, 0x2c, 0xe6, 0xe9, 0x61, 0x9d, 0xf2, 0x38, 0x08, 0xb1, 0x12, 0xe4, + 0xa2, 0x6c, 0xe5, 0x80, 0x1e, 0x4b, 0xb1, 0x2a, 0xec, 0x68, 0xa3, 0x87, 0x2a, 0x85, + 0x6f, 0xcc, 0x2e, 0x20, 0xc2, 0x01, 0x65, 0x12, 0x81, 0x9c, 0x00, 0x89, 0xad, 0xb4, + 0xda, 0xbf, 0x9f, 0x44, 0x22, 0x4a, 0xf8, 0x8c, 0xcf, 0x36, 0x58, 0xec, 0x72, 0xa7, + 0xa3, 0x94, 0xfc, 0x84, 0xa2, 0xb6, 0xbb, 0xcf, 0x09, 0x5d, 0x88, 0x14, 0x72, 0x6a, + 0xd3, 0xbc, 0xf1, 0x2f, 0xb1, 0xa0, 0x4d, 0x7f, 0x7a, 0xfd, 0x08, 0x64, 0x73, 0x6b, + 0x4a, 0x64, 0x88, 0x95, 0xa7, 0x5e, 0x74, 0x3e, 0xa4, 0x67, 0x16, 0x0e, 0x4b, 0x7b, + 0x37, 0xd5, 0x91, 0x44, 0x99, 0x96, 0x97, 0x78, 0x75, 0xf0, 0xce, 0xc3, 0x61, 0x5b, + 0x1f, 0x53, 0xfe, 0xac, 0xca, 0x47, 0x2f, 0xb0, 0x52, 0x68, 0xb5, 0xfe, 0xc2, 0x08, + 0x72, 0xab, 0x27, 0xdc, 0x12, 0x65, 0xb6, 0xf4, 0x52, 0xdc, 0xff, 0x1f, 0xd9, 0x2a, + 0x91, 0x19, 0x8c, 0xbc, 0x92, 0xcb, 0x72, 0xd5, 0xce, 0xca, 0x18, 0xef, 0x7c, 0xdc, + 0x74, 0xf5, 0x12, 0xb2, 0x1d, 0x41, 0x45, 0xed, 0xaf, 0xdc, 0x51, 0xc9, 0x0a, 0x13, + 0xdb, 0x18, 0xc4, 0xe0, 0x51, 0x51, 0xc7, 0x0f, 0xac, 0x25, 0x27, 0x13, 0xdb, 0x67, + 0x6c, 0x71, 0x38, 0x01, 0x98, 0x4d, 0xb9, 0xa4, 0xca, 0xb7, 0xac, 0xf8, 0x9a, 0xb8, + 0xe1, 0x04, 0x72, 0x6d, 0xa4, 0x77, 0x68, 0xa5, 0x8e, 0x58, 0x33, 0x2c, 0x33, 0x07, + 0x96, 0x01, 0xea, 0x80, 0x44, 0x9e, 0x89, 0xe7, 0xba, 0x9e, 0x5c, 0x66, 0x19, 0xac, + 0x1e, 0xc5, 0xe8, 0x70, 0x18, 0x1f, 0xa3, 0x00, 0xee, 0x08, 0x60, 0xf9, 0x37, 0xe5, + 0x1c, 0x29, 0x98, 0xaa, 0xa7, 0x4b, 0xa2, 0x9c, 0x40, 0x0a, 0x89, 0x47, 0xed, 0xc0, + 0xf3, 0x49, 0x7e, 0xc6, 0xc4, 0xa8, 0x2b, 0x10, 0x45, 0x65, 0x32, 0xca, 0x08, 0x3f, + 0x77, 0xaf, 0x2a, 0x36, 0xc1, 0xab, 0xf3, 0x34, 0xf5, 0x7c, 0x69, 0x46, 0x58, 0xc5, + 0x82, 0xc6, 0xed, 0x30, 0x19, 0xb1, 0x50, 0x86, 0xed, 0x1e, 0x92, 0xb3, 0x8e, 0xdb, + 0xc2, 0xbc, 0xdc, 0xdd, 0x6a, 0x42, 0xa8, 0x79, 0x80, 0x36, 0x3b, 0xe2, 0x09, 0xd8, + 0xf3, 0x5a, 0x58, 0x9d, 0xc2, 0xb2, 0xcf, 0x1f, 0x94, 0x51, 0x81, 0x2b, 0xc2, 0xb4, + 0x8f, 0xae, 0x04, 0xa8, 0x1d, 0x29, 0x8b, 0x1a, 0x6a, 0x81, 0xc9, 0xa7, 0xa6, 0xaf, + 0xca, 0xd3, 0xae, 0x6b, 0x2f, 0xed, 0x57, 0x5b, 0xba, 0xc1, 0xdd, 0x37, 0xb1, 0x97, + 0x96, 0xe4, 0x9a, 0x54, 0x39, 0x64, 0x7c, 0xfa, 0xdf, 0xd2, 0x6d, 0x7b, 0x2d, 0x91, + 0x61, 0x7f, 0xb2, 0x6d, 0x36, 0x0b, 0xf0, 0x11, 0x1c, 0xe4, 0xec, 0xd5, 0xef, 0x32, + 0x83, 0x2b, 0xd0, 0x33, 0xf9, 0x5b, 0x98, 0x03, 0x1f, 0x9d, ], ock: [ - 0x24, 0x11, 0xa0, 0xf9, 0x31, 0xa8, 0xd3, 0x51, 0x6c, 0xdb, 0x71, 0x93, 0xc9, 0x41, - 0xcf, 0x0e, 0x49, 0xc3, 0x66, 0xae, 0x72, 0xc9, 0x79, 0xc4, 0x90, 0x49, 0xc9, 0x4b, - 0xd3, 0xc7, 0x5c, 0xf4, + 0x0a, 0xc3, 0x5f, 0x08, 0xac, 0xd8, 0x76, 0xdd, 0x7b, 0x9b, 0x4e, 0xf1, 0x9a, 0x33, + 0xb3, 0x6a, 0xa0, 0x90, 0xff, 0x62, 0x29, 0x7f, 0x96, 0x12, 0x98, 0x7e, 0x5e, 0x52, + 0x27, 0x5f, 0xd7, 0xa7, ], op: [ - 0x66, 0x14, 0x17, 0x39, 0x51, 0x4b, 0x28, 0xf0, 0x5d, 0xef, 0x8a, 0x18, 0xee, 0xee, - 0x5e, 0xed, 0x4d, 0x44, 0xc6, 0x22, 0x5c, 0x3c, 0x65, 0xd8, 0x8d, 0xd9, 0x90, 0x77, - 0x08, 0x01, 0x2f, 0x5a, 0x99, 0xaa, 0x10, 0xc0, 0x57, 0x88, 0x08, 0x1c, 0x0d, 0xa7, + 0xb3, 0x23, 0xbb, 0x8b, 0x98, 0x03, 0x11, 0x44, 0x88, 0x26, 0x0f, 0x9f, 0x51, 0xe5, + 0x46, 0xc2, 0xb4, 0x5f, 0x3d, 0x03, 0x6d, 0x03, 0x9b, 0x0f, 0x0c, 0xb2, 0x86, 0x13, + 0x9d, 0x4c, 0x25, 0xb5, 0x99, 0xaa, 0x10, 0xc0, 0x57, 0x88, 0x08, 0x1c, 0x0d, 0xa7, 0xd8, 0x79, 0xcd, 0x95, 0x43, 0xec, 0x18, 0x92, 0x15, 0x72, 0x92, 0x40, 0x2e, 0x96, 0x0b, 0x06, 0x99, 0x5a, 0x08, 0x96, 0x4c, 0x03, ], c_out: [ - 0x9d, 0xcf, 0xab, 0x0d, 0x20, 0x54, 0xd2, 0xbd, 0xf4, 0x06, 0xc3, 0x1b, 0x41, 0x78, - 0x46, 0x5d, 0xe6, 0x50, 0x5d, 0xb3, 0xbe, 0x9b, 0x69, 0x36, 0xf7, 0x8d, 0x2e, 0x29, - 0x37, 0x57, 0x9b, 0x58, 0x2e, 0x83, 0x28, 0x61, 0x92, 0x9a, 0x75, 0x17, 0x88, 0x04, - 0xb6, 0x57, 0x12, 0x6a, 0xdd, 0x74, 0x2e, 0x06, 0xcb, 0x84, 0x36, 0x86, 0x42, 0xdb, - 0x9b, 0xf4, 0x7a, 0xc6, 0xe4, 0xdc, 0x1a, 0xf1, 0x78, 0x19, 0x8b, 0x22, 0xd6, 0x26, - 0x23, 0x45, 0x37, 0x3b, 0x0f, 0x56, 0x2e, 0xf2, 0x7b, 0xb0, + 0x17, 0xbc, 0xa1, 0x5f, 0x91, 0xee, 0x2a, 0xd0, 0x1c, 0x42, 0xce, 0x89, 0x5e, 0x9c, + 0xa3, 0xd8, 0x7c, 0x04, 0x16, 0x4c, 0x29, 0xf7, 0x94, 0x52, 0xef, 0xcf, 0x8d, 0x32, + 0xd5, 0x35, 0x93, 0xbc, 0xc2, 0x02, 0xee, 0x96, 0xb7, 0x78, 0x22, 0x4c, 0xde, 0x87, + 0xbe, 0x07, 0x86, 0x71, 0x71, 0x0b, 0xbf, 0xea, 0xed, 0x7f, 0x61, 0x72, 0x62, 0x62, + 0xc4, 0xde, 0xc4, 0x95, 0x82, 0x72, 0x74, 0x4c, 0xf4, 0x04, 0xf3, 0xd4, 0xd9, 0xc8, + 0x1f, 0x2a, 0x24, 0x16, 0xe9, 0x00, 0xbc, 0x39, 0x20, 0x18, ], }, TestVector { ovk: [ - 0x14, 0x76, 0x78, 0xe0, 0x55, 0x3b, 0x97, 0x82, 0x93, 0x47, 0x64, 0x7c, 0x5b, 0xc7, - 0xda, 0xb4, 0xcc, 0x22, 0x02, 0xb5, 0x4e, 0xc2, 0x9f, 0xd3, 0x1a, 0x3d, 0xe6, 0xbe, - 0x08, 0x25, 0xfc, 0x5e, + 0x30, 0x2a, 0x78, 0xb5, 0xce, 0xe5, 0xd9, 0x84, 0x22, 0xf2, 0xdd, 0x13, 0xd8, 0xc4, + 0x6f, 0xe7, 0x27, 0x67, 0x25, 0x52, 0x23, 0x3c, 0xc8, 0x21, 0x7a, 0xe2, 0xf1, 0x44, + 0xb3, 0xd6, 0x0d, 0x04, ], ivk: [ - 0x63, 0x6a, 0xa9, 0x64, 0xbf, 0xc2, 0x3c, 0xe4, 0xb1, 0xfc, 0xf7, 0xdf, 0xc9, 0x91, - 0x79, 0xdd, 0xc4, 0x06, 0xff, 0x55, 0x40, 0x0c, 0x92, 0x95, 0xac, 0xfc, 0x14, 0xf0, - 0x31, 0xc7, 0x26, 0x00, + 0x2a, 0xa4, 0x0d, 0xd9, 0x3b, 0x51, 0xe7, 0xf6, 0x81, 0xb1, 0x1c, 0xdc, 0xde, 0x55, + 0xe3, 0x3a, 0xcb, 0x3c, 0x9d, 0xe6, 0x25, 0x9d, 0x78, 0xae, 0xa5, 0x39, 0xbf, 0x80, + 0xad, 0xfe, 0x67, 0x07, ], default_d: [ - 0x1b, 0x81, 0x61, 0x4f, 0x1d, 0xad, 0xea, 0x0f, 0x8d, 0x0a, 0x58, + 0x14, 0xbf, 0xe9, 0x79, 0x77, 0x94, 0x6a, 0x54, 0x7d, 0x5f, 0x3f, ], default_pk_d: [ - 0x25, 0xeb, 0x55, 0xfc, 0xcf, 0x76, 0x1f, 0xc6, 0x4e, 0x85, 0xa5, 0x88, 0xef, 0xe6, - 0xea, 0xd7, 0x83, 0x2f, 0xb1, 0xf0, 0xf7, 0xa8, 0x31, 0x65, 0x89, 0x5b, 0xdf, 0xf9, - 0x42, 0x92, 0x5f, 0x5c, + 0x65, 0xcd, 0x93, 0xd6, 0x16, 0xc2, 0x69, 0xae, 0x15, 0x7a, 0x0a, 0xaa, 0xbe, 0xfd, + 0xb6, 0xb3, 0x27, 0xbd, 0xb9, 0xaa, 0xba, 0xef, 0xa1, 0xb9, 0xc1, 0x70, 0x1b, 0x60, + 0x0e, 0x01, 0x08, 0xae, ], v: 400000000, rcm: [ @@ -695,14 +705,14 @@ pub(crate) fn make_test_vectors() -> Vec { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ], cv: [ - 0x6d, 0x6e, 0xf8, 0xce, 0x97, 0x92, 0x74, 0x09, 0x4f, 0x19, 0x1a, 0xef, 0x64, 0x3f, - 0x3f, 0xcb, 0xd1, 0xac, 0x9d, 0x98, 0xd6, 0x07, 0xe2, 0xbc, 0xfe, 0xf6, 0xfd, 0x51, - 0xba, 0x4b, 0xb4, 0xb9, + 0xb7, 0xf6, 0xc0, 0xc1, 0xbd, 0xf5, 0x53, 0x20, 0xbc, 0x11, 0xca, 0x32, 0x39, 0x9e, + 0x64, 0x01, 0x4c, 0x09, 0x49, 0xa4, 0x04, 0x11, 0xff, 0x59, 0xbc, 0x60, 0xee, 0x0b, + 0xda, 0x4c, 0xbd, 0xbc, ], cmu: [ - 0x51, 0xfd, 0xdd, 0x70, 0x8c, 0xd1, 0x51, 0xd3, 0xca, 0x47, 0x17, 0xe3, 0xc9, 0x9e, - 0xeb, 0x8f, 0x64, 0xf1, 0x04, 0x49, 0x5f, 0x26, 0xde, 0x05, 0x7b, 0x68, 0x10, 0x63, - 0xb9, 0xc9, 0x78, 0x2d, + 0x3b, 0x98, 0x2f, 0xa0, 0x4b, 0xc3, 0x4f, 0x4c, 0x42, 0x38, 0x9e, 0xc2, 0x0b, 0xa9, + 0xac, 0x79, 0x94, 0xa8, 0x70, 0x13, 0x60, 0x53, 0x8a, 0xfc, 0x6a, 0x01, 0x43, 0x06, + 0x3e, 0xdf, 0xd7, 0x5e, ], esk: [ 0xbd, 0xde, 0x13, 0x81, 0xec, 0x9f, 0xf4, 0x21, 0xca, 0xfd, 0x1e, 0x31, 0xcc, 0x5d, @@ -710,26 +720,28 @@ pub(crate) fn make_test_vectors() -> Vec { 0x59, 0x7e, 0xa1, 0x03, ], epk: [ - 0x04, 0xa1, 0x0a, 0x3e, 0xa0, 0xe4, 0xb1, 0xa1, 0xd1, 0x3a, 0x67, 0xbc, 0xb2, 0x7d, - 0xe6, 0x34, 0xe1, 0x94, 0xb2, 0x08, 0x01, 0x62, 0x61, 0x9f, 0xbc, 0xa7, 0x66, 0x2d, - 0x42, 0xb8, 0xa5, 0x5f, + 0x52, 0xf3, 0x51, 0xee, 0xe6, 0x34, 0x4a, 0x28, 0xfa, 0x35, 0x41, 0xac, 0xd6, 0xd1, + 0xb3, 0x8a, 0xd8, 0xdf, 0x27, 0xac, 0x30, 0x56, 0x5e, 0x9d, 0x62, 0xb3, 0xe7, 0x57, + 0xf7, 0xac, 0x95, 0x8f, ], shared_secret: [ - 0xdd, 0x88, 0x05, 0x9f, 0xd9, 0x05, 0x90, 0x13, 0xf2, 0xb9, 0xfa, 0xa2, 0x3a, 0x6b, - 0xa1, 0x49, 0xb2, 0xff, 0x0e, 0x37, 0x79, 0x3a, 0x3e, 0x8d, 0x92, 0x70, 0xff, 0x71, - 0x67, 0xfd, 0x7a, 0x8d, + 0x3c, 0x54, 0x29, 0xbb, 0x82, 0xbc, 0x1f, 0xbe, 0x2c, 0x63, 0xc0, 0x67, 0x94, 0x82, + 0x5c, 0x6f, 0xb9, 0x33, 0xf5, 0xc7, 0x81, 0xcf, 0x12, 0x77, 0x4a, 0x6e, 0x67, 0x3f, + 0x7b, 0xbf, 0x4f, 0x99, ], k_enc: [ - 0xab, 0xa4, 0xd4, 0xa5, 0xb5, 0x1a, 0x8b, 0xf5, 0x2e, 0x29, 0xd6, 0x80, 0x3a, 0xb9, - 0x33, 0x0c, 0xf9, 0xc8, 0x2b, 0x1e, 0xb1, 0xfe, 0xe6, 0xa1, 0xa5, 0x54, 0x4a, 0x82, - 0xc7, 0xb3, 0x16, 0x82, + 0x51, 0x95, 0x3c, 0x39, 0xb3, 0x18, 0xb3, 0x53, 0x15, 0x11, 0x69, 0x25, 0x06, 0x58, + 0x81, 0x39, 0x70, 0x1a, 0x76, 0x9a, 0xfd, 0xce, 0xae, 0xb9, 0x6b, 0xff, 0xd2, 0x9d, + 0xa3, 0x16, 0xa4, 0xc9, ], p_enc: [ - 0x01, 0x1b, 0x81, 0x61, 0x4f, 0x1d, 0xad, 0xea, 0x0f, 0x8d, 0x0a, 0x58, 0x00, 0x84, - 0xd7, 0x17, 0x00, 0x00, 0x00, 0x00, 0x34, 0xa4, 0xb2, 0xa9, 0x14, 0x4f, 0xf5, 0xea, - 0x54, 0xef, 0xee, 0x87, 0xcf, 0x90, 0x1b, 0x5b, 0xed, 0x5e, 0x35, 0xd2, 0x1f, 0xbb, - 0xd7, 0x88, 0xd5, 0xbd, 0x9d, 0x83, 0x3e, 0x11, 0x28, 0x04, 0xf6, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x14, 0xbf, 0xe9, 0x79, 0x77, 0x94, 0x6a, 0x54, 0x7d, 0x5f, 0x3f, 0x00, 0x84, + 0xd7, 0x17, 0x00, 0x00, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, + 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, + 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x34, 0xa4, 0xb2, 0xa9, + 0x14, 0x4f, 0xf5, 0xea, 0x54, 0xef, 0xee, 0x87, 0xcf, 0x90, 0x1b, 0x5b, 0xed, 0x5e, + 0x35, 0xd2, 0x1f, 0xbb, 0xd7, 0x88, 0xd5, 0xbd, 0x9d, 0x83, 0x3e, 0x11, 0x28, 0x04, + 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -765,91 +777,93 @@ pub(crate) fn make_test_vectors() -> Vec { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ], c_enc: [ - 0x9d, 0xb8, 0xb2, 0x4a, 0x05, 0x6f, 0x99, 0x6d, 0x39, 0x2d, 0x4d, 0x96, 0x3e, 0xa3, - 0x89, 0x76, 0xd0, 0xf3, 0x5e, 0x85, 0xd8, 0xaa, 0x84, 0x7a, 0x08, 0x96, 0x16, 0x4e, - 0x39, 0xd8, 0x69, 0x7a, 0xe1, 0x80, 0xc4, 0xdc, 0xc1, 0x70, 0x61, 0xd5, 0xf3, 0x99, - 0xe0, 0xac, 0x4e, 0xcb, 0x5f, 0x02, 0xd4, 0xd9, 0xa3, 0xca, 0x5b, 0x33, 0x51, 0x8c, - 0x58, 0xb1, 0xa0, 0x73, 0xbc, 0xa7, 0xee, 0x67, 0x41, 0x01, 0x03, 0x05, 0xdb, 0xb8, - 0xc7, 0x38, 0x38, 0x35, 0xb9, 0xc7, 0x80, 0xa9, 0x42, 0x78, 0x5c, 0x57, 0xa3, 0x09, - 0x8a, 0x81, 0xae, 0xf5, 0xd7, 0x06, 0x1f, 0xda, 0xba, 0xcf, 0x52, 0x72, 0x15, 0x30, - 0xef, 0x32, 0xdf, 0xfc, 0x01, 0x10, 0x19, 0xeb, 0xd3, 0x60, 0x97, 0xe8, 0x4d, 0xf2, - 0x03, 0x63, 0xcf, 0x18, 0x22, 0xb1, 0x15, 0x0c, 0x24, 0x73, 0x58, 0x2b, 0x01, 0xf8, - 0xd8, 0x67, 0x99, 0xc1, 0x73, 0xf7, 0xfe, 0xf8, 0xca, 0x93, 0x8e, 0x4c, 0xde, 0x71, - 0x85, 0xa1, 0x9d, 0x70, 0xad, 0x38, 0x61, 0x47, 0x9e, 0x7d, 0x43, 0x81, 0x0d, 0xc5, - 0x64, 0x24, 0x71, 0x03, 0x33, 0x49, 0x28, 0x6b, 0xaf, 0x71, 0x4f, 0x7f, 0xdc, 0x22, - 0xb3, 0x81, 0xd9, 0xe3, 0xad, 0xf3, 0xbc, 0x10, 0x49, 0x87, 0x8e, 0x18, 0x6d, 0x53, - 0x2d, 0x8c, 0x98, 0x70, 0xf6, 0x01, 0x80, 0xd6, 0x54, 0x72, 0x45, 0x5d, 0x22, 0xd2, - 0x59, 0x24, 0xb9, 0x92, 0xc0, 0x2f, 0x94, 0xea, 0x6e, 0xaf, 0x75, 0xb9, 0xdc, 0x88, - 0x3d, 0xe7, 0x37, 0x6d, 0xa6, 0x01, 0x8e, 0x55, 0x45, 0x1e, 0x23, 0xf2, 0x38, 0xe1, - 0x09, 0xa6, 0x40, 0x07, 0x89, 0xf9, 0x30, 0x52, 0x57, 0x9b, 0xbb, 0x18, 0x40, 0x19, - 0xf3, 0x09, 0xb3, 0xd0, 0x6d, 0x07, 0x67, 0xa1, 0x07, 0xe4, 0xb7, 0x9a, 0x2b, 0xfc, - 0x84, 0x25, 0xd8, 0xb0, 0x70, 0x62, 0x7f, 0x2d, 0x55, 0xc9, 0xa2, 0x6b, 0x22, 0x82, - 0x3a, 0x21, 0xe1, 0xca, 0xf6, 0xfb, 0xc2, 0xa5, 0x7d, 0xce, 0x78, 0x4b, 0x25, 0x30, - 0x34, 0x5a, 0x5f, 0x8b, 0x0c, 0xea, 0x3f, 0xce, 0x3b, 0x7f, 0xf4, 0xf5, 0xbb, 0x88, - 0x4f, 0x68, 0xb7, 0xd1, 0x36, 0x06, 0x92, 0x33, 0xad, 0xe4, 0xd6, 0xbd, 0xda, 0xf3, - 0x40, 0xde, 0xe1, 0x43, 0x72, 0x33, 0x2e, 0xc3, 0x76, 0xf5, 0x93, 0x5d, 0x62, 0x79, - 0xc3, 0x74, 0x91, 0x1d, 0x95, 0x40, 0xfa, 0xcc, 0x75, 0x11, 0x5b, 0x20, 0xc5, 0x53, - 0x32, 0x9b, 0x43, 0xee, 0x57, 0xa8, 0xbb, 0x58, 0xa3, 0xf7, 0x46, 0x06, 0xa7, 0xf3, - 0xfa, 0x87, 0xe4, 0x6a, 0xaf, 0x72, 0xad, 0xae, 0x90, 0x48, 0xb9, 0x43, 0xe4, 0x64, - 0x89, 0x85, 0xad, 0xaa, 0x99, 0x0d, 0x78, 0x20, 0xfb, 0xb2, 0xb1, 0x24, 0x65, 0xa1, - 0x61, 0x7d, 0x01, 0xca, 0xf4, 0x14, 0x36, 0xa4, 0x94, 0x6e, 0xa0, 0x95, 0x96, 0x23, - 0x96, 0x40, 0xdc, 0x95, 0xe5, 0x86, 0x81, 0x9e, 0x6c, 0x00, 0x69, 0xee, 0xe0, 0x7a, - 0x72, 0x42, 0xb9, 0x4a, 0xfd, 0x69, 0xce, 0x35, 0x43, 0xb8, 0x87, 0x7b, 0x31, 0x94, - 0xcd, 0xb9, 0xe7, 0x07, 0xc0, 0x83, 0x8b, 0x15, 0x43, 0x46, 0x03, 0x57, 0x50, 0x46, - 0x35, 0x2c, 0x1b, 0xf4, 0xcf, 0xc2, 0x7f, 0x4e, 0xdf, 0x61, 0x91, 0xd8, 0xec, 0xf5, - 0x52, 0xb8, 0xf6, 0x98, 0x70, 0x2d, 0x3a, 0x8f, 0x6f, 0xda, 0x58, 0xb5, 0xcf, 0x16, - 0x1f, 0xed, 0x6e, 0x6f, 0xdb, 0x14, 0x9a, 0x79, 0xdb, 0x0a, 0x6b, 0x02, 0xc3, 0x27, - 0xe9, 0x62, 0x9c, 0x94, 0x8f, 0x66, 0x5d, 0x13, 0x28, 0x3f, 0x65, 0xe5, 0x4b, 0xe5, - 0x5a, 0xc1, 0xae, 0x82, 0x75, 0x35, 0xff, 0x7a, 0xc1, 0x43, 0xcc, 0x72, 0xd9, 0x2b, - 0xc4, 0xf4, 0x6e, 0xf4, 0xad, 0x88, 0xc7, 0x66, 0xab, 0x4b, 0xff, 0x1e, 0x1d, 0x11, - 0x5c, 0x85, 0x1e, 0x59, 0x85, 0x41, 0x10, 0x5d, 0x6e, 0xbb, 0x36, 0x7c, 0xe0, 0x54, - 0x93, 0x20, 0xa2, 0x30, 0x83, 0x53, 0x11, 0x47, 0x8b, 0xdd, 0x9f, 0x6c, 0x53, 0x85, - 0x03, 0xf3, 0x62, 0xe5, 0xf6, 0xc2, 0x7d, 0x15, 0xb5, 0x6c, 0x41, 0x43, 0xd4, 0x57, - 0x69, 0xc2, 0x54, 0x6e, 0x53, 0xfb, 0x45, 0x01, 0xf9, 0xba, 0x5e, 0xd4, 0x55, 0xd2, - 0x49, 0x86, 0xb4, 0xdf, 0xf7, 0xcd, + 0x1d, 0xaa, 0x1e, 0x2f, 0xfc, 0xe5, 0x88, 0x4c, 0xe1, 0xae, 0x74, 0xfc, 0xfc, 0x28, + 0x21, 0xe7, 0x5f, 0x9e, 0xd1, 0xaf, 0x7c, 0x3b, 0xe0, 0x02, 0xc7, 0x2b, 0x5a, 0xe6, + 0xb7, 0x6b, 0xfd, 0x3e, 0x0f, 0xd7, 0x99, 0x57, 0x0b, 0xd5, 0x87, 0xdc, 0xfb, 0xd2, + 0x20, 0xb4, 0x36, 0xd6, 0xfd, 0x97, 0x38, 0xe5, 0x6f, 0x3f, 0x7c, 0x77, 0x2d, 0x0a, + 0x3c, 0xe2, 0x6a, 0xb7, 0x86, 0x0d, 0x5b, 0x15, 0x3d, 0xf0, 0xbe, 0x87, 0x14, 0xac, + 0x13, 0x72, 0x42, 0xc6, 0x3d, 0xf8, 0x51, 0x3d, 0x35, 0x68, 0x62, 0x4f, 0x3d, 0x58, + 0x2d, 0xe4, 0xec, 0x17, 0x8c, 0xdf, 0x3a, 0x6d, 0x7c, 0x8c, 0x10, 0xaf, 0xf6, 0x7f, + 0x55, 0xe0, 0xdc, 0x55, 0x1e, 0xf5, 0xe1, 0x0f, 0x37, 0x1c, 0x7d, 0xa2, 0xce, 0x9d, + 0x3b, 0xa3, 0x02, 0x16, 0xfb, 0xab, 0x42, 0xb9, 0xf7, 0x93, 0x32, 0x51, 0xa9, 0xe6, + 0x89, 0xee, 0x5b, 0x19, 0xc8, 0x26, 0xab, 0x3a, 0x2e, 0x5e, 0xdd, 0x9b, 0xf2, 0x74, + 0xe5, 0x9e, 0xcd, 0xcd, 0x81, 0x80, 0x21, 0xf9, 0xeb, 0x02, 0xab, 0xf0, 0xec, 0xef, + 0x0f, 0x3c, 0x1f, 0x53, 0xa3, 0x39, 0xb8, 0xdc, 0x54, 0xf1, 0xd9, 0xb6, 0x3d, 0x51, + 0x2a, 0x3e, 0xfc, 0x14, 0xa9, 0xcc, 0xb5, 0x9d, 0xee, 0xb3, 0xc6, 0xbc, 0xe2, 0x70, + 0xa4, 0x46, 0xe4, 0x1f, 0xad, 0xbb, 0xba, 0x8d, 0x85, 0xa5, 0x13, 0x65, 0x3d, 0x5e, + 0x44, 0x9d, 0x38, 0xab, 0x0b, 0xa7, 0x03, 0xd0, 0x1f, 0xbf, 0x0b, 0xa0, 0x64, 0x89, + 0x66, 0x06, 0x81, 0x1d, 0xb1, 0x27, 0x77, 0xdb, 0x0b, 0x8a, 0xac, 0xa5, 0x9b, 0x82, + 0x26, 0x38, 0x04, 0x87, 0x81, 0x45, 0x0c, 0x98, 0x18, 0xf8, 0x6e, 0x41, 0xe9, 0x44, + 0x9d, 0x09, 0x8a, 0x77, 0x16, 0x0b, 0x6a, 0x3f, 0x8a, 0xec, 0xa8, 0x60, 0x0a, 0x50, + 0xaa, 0xcd, 0x7d, 0xa7, 0x52, 0x99, 0x79, 0x2c, 0x3c, 0xc3, 0x4d, 0x8a, 0x12, 0xbd, + 0x39, 0x45, 0xb2, 0x66, 0x20, 0x6f, 0x1f, 0x4e, 0x24, 0xe5, 0x74, 0x96, 0x68, 0x2f, + 0xdf, 0x51, 0xd7, 0xef, 0x1c, 0x78, 0x5a, 0x92, 0x5a, 0x04, 0x02, 0x47, 0xc3, 0x91, + 0xa8, 0x4f, 0xfd, 0x47, 0x5f, 0x5d, 0xc7, 0xab, 0x96, 0x64, 0x14, 0x3b, 0x19, 0x5c, + 0x3e, 0x9b, 0x92, 0x04, 0x38, 0x62, 0x2d, 0xb2, 0xf2, 0xa1, 0x95, 0x48, 0x2c, 0xbe, + 0xa5, 0x27, 0xbb, 0x5a, 0xda, 0xcf, 0xec, 0xbf, 0xf7, 0xfa, 0xd4, 0x05, 0xdb, 0x07, + 0xd7, 0x12, 0xfc, 0xbc, 0x5e, 0x6a, 0xf8, 0xe5, 0xd2, 0x0e, 0x06, 0xfc, 0xad, 0x78, + 0x8a, 0x27, 0xb1, 0x5e, 0x18, 0x9d, 0x0a, 0x2f, 0xa6, 0x7b, 0x0a, 0x4c, 0x3e, 0x0c, + 0x07, 0x68, 0x09, 0x15, 0xbc, 0x5f, 0xfc, 0x08, 0x0b, 0x3a, 0x4d, 0xaf, 0x16, 0x38, + 0x0f, 0x76, 0xc6, 0x01, 0x65, 0xb4, 0xb5, 0x11, 0xb0, 0x02, 0xef, 0xbf, 0x1e, 0xac, + 0xe9, 0xb7, 0xb2, 0x71, 0xb7, 0x46, 0x58, 0xb0, 0x98, 0x43, 0x7a, 0x6e, 0x47, 0xca, + 0xb2, 0x5e, 0xe9, 0x3e, 0xfd, 0x09, 0xcf, 0x64, 0xd7, 0x73, 0xf5, 0xc7, 0x23, 0xd5, + 0x60, 0xba, 0xa1, 0xc3, 0x4a, 0x7e, 0x4a, 0xf8, 0x8f, 0x2a, 0x0c, 0xec, 0x3e, 0x54, + 0xd9, 0x26, 0x6b, 0x8f, 0xfe, 0x24, 0x80, 0x6c, 0xa7, 0x42, 0x00, 0x10, 0x51, 0xc9, + 0x2b, 0x0b, 0x4b, 0xf9, 0xa8, 0xaa, 0xd1, 0x5e, 0x26, 0xb6, 0x25, 0xd8, 0x1b, 0x39, + 0xba, 0x12, 0xe0, 0xcb, 0x9c, 0xe5, 0x1e, 0x73, 0x33, 0xf2, 0x68, 0xaf, 0x10, 0x1d, + 0xfd, 0x8a, 0xa5, 0xed, 0x83, 0xcf, 0x11, 0x0a, 0x95, 0xc5, 0xb8, 0xab, 0xbb, 0x52, + 0x20, 0xfe, 0xce, 0x76, 0xa8, 0x15, 0x03, 0x6f, 0x29, 0x6d, 0x63, 0x0b, 0x13, 0x8a, + 0x90, 0x27, 0x69, 0x87, 0xba, 0x4e, 0x36, 0xd9, 0x10, 0x0f, 0xc3, 0x16, 0x36, 0x0e, + 0xba, 0x7e, 0x25, 0x64, 0x6e, 0x6f, 0x38, 0xc1, 0x0e, 0xc4, 0x56, 0xb0, 0xfa, 0x91, + 0x1c, 0xb5, 0xe6, 0xe6, 0x10, 0xa9, 0xf0, 0xcf, 0x45, 0x3e, 0x5b, 0xec, 0x4d, 0xee, + 0xca, 0xdf, 0x1d, 0xcd, 0xf4, 0x62, 0x85, 0x2a, 0x67, 0x81, 0x10, 0x15, 0xe9, 0x54, + 0xca, 0xc9, 0xcf, 0xc1, 0x33, 0xb7, 0x97, 0xc1, 0x56, 0x14, 0x75, 0xa4, 0xc9, 0x02, + 0xe4, 0xba, 0xb8, 0x93, 0xf4, 0xc7, 0x7e, 0x36, 0xda, 0x37, 0x2c, 0x74, 0x57, 0x02, + 0x04, 0x83, 0x8c, 0x18, 0x92, 0x52, 0x9d, 0x16, 0x0c, 0x98, 0x2e, 0xa5, 0x68, 0x08, + 0xb0, 0x71, 0xd6, 0xd0, 0x02, 0x86, 0x9a, 0xb3, 0xd5, 0xf7, ], ock: [ - 0xf6, 0xbd, 0x5d, 0x10, 0x80, 0xfc, 0xa6, 0x46, 0x00, 0xee, 0x92, 0x17, 0xb0, 0x9e, - 0xf1, 0x98, 0x4c, 0x9a, 0x8b, 0x98, 0xe0, 0x6e, 0xe5, 0xd8, 0x36, 0xce, 0x0e, 0x6c, - 0x89, 0xab, 0x56, 0xfd, + 0xb3, 0xea, 0xdd, 0xd5, 0x97, 0x35, 0x5a, 0x33, 0xe4, 0x60, 0xb7, 0x9c, 0x90, 0xa7, + 0x86, 0x37, 0x67, 0x5d, 0x65, 0x03, 0x37, 0x03, 0x24, 0xd5, 0x02, 0x6c, 0x8a, 0xe9, + 0x58, 0xe1, 0xfb, 0xed, ], op: [ - 0x25, 0xeb, 0x55, 0xfc, 0xcf, 0x76, 0x1f, 0xc6, 0x4e, 0x85, 0xa5, 0x88, 0xef, 0xe6, - 0xea, 0xd7, 0x83, 0x2f, 0xb1, 0xf0, 0xf7, 0xa8, 0x31, 0x65, 0x89, 0x5b, 0xdf, 0xf9, - 0x42, 0x92, 0x5f, 0x5c, 0xbd, 0xde, 0x13, 0x81, 0xec, 0x9f, 0xf4, 0x21, 0xca, 0xfd, + 0x65, 0xcd, 0x93, 0xd6, 0x16, 0xc2, 0x69, 0xae, 0x15, 0x7a, 0x0a, 0xaa, 0xbe, 0xfd, + 0xb6, 0xb3, 0x27, 0xbd, 0xb9, 0xaa, 0xba, 0xef, 0xa1, 0xb9, 0xc1, 0x70, 0x1b, 0x60, + 0x0e, 0x01, 0x08, 0xae, 0xbd, 0xde, 0x13, 0x81, 0xec, 0x9f, 0xf4, 0x21, 0xca, 0xfd, 0x1e, 0x31, 0xcc, 0x5d, 0xe2, 0x55, 0x59, 0x88, 0x1f, 0x6b, 0x21, 0xb2, 0x17, 0x5d, 0x0d, 0xce, 0x94, 0x08, 0x59, 0x7e, 0xa1, 0x03, ], c_out: [ - 0x25, 0x4f, 0x12, 0x2c, 0xfe, 0x94, 0x98, 0xad, 0xd7, 0x57, 0xcf, 0x0b, 0x61, 0x0d, - 0xa8, 0xcb, 0xae, 0xda, 0x05, 0x3e, 0x26, 0xcb, 0x72, 0x30, 0x6f, 0x36, 0x23, 0x08, - 0x55, 0x28, 0x53, 0xff, 0x02, 0x3c, 0x23, 0xc2, 0x6f, 0x3a, 0xb4, 0x41, 0xb8, 0x1e, - 0xa2, 0x5c, 0xe0, 0xae, 0x57, 0xd1, 0xa9, 0x49, 0x83, 0xbb, 0x45, 0xab, 0x8a, 0x86, - 0xda, 0x68, 0xef, 0x63, 0xf1, 0x58, 0x16, 0xc1, 0x43, 0x32, 0x7a, 0x1e, 0x46, 0x0c, - 0x51, 0x0c, 0x63, 0x1c, 0xc6, 0x9f, 0x39, 0x60, 0xfb, 0x5a, + 0xb4, 0x7a, 0xc1, 0x19, 0x05, 0x2c, 0x88, 0x89, 0x52, 0x5a, 0xeb, 0xf8, 0x55, 0x21, + 0xd1, 0xad, 0x76, 0x54, 0xff, 0x0a, 0xfe, 0x98, 0xd8, 0x5d, 0xf2, 0x94, 0x89, 0xd0, + 0x6c, 0x28, 0x66, 0x71, 0x86, 0x85, 0x2e, 0x89, 0x55, 0x2f, 0x4f, 0xfa, 0xe1, 0xc2, + 0x74, 0x90, 0x1f, 0x52, 0x79, 0x45, 0xe8, 0x82, 0x40, 0x26, 0xb1, 0x68, 0xef, 0x8d, + 0x90, 0xcd, 0xc9, 0x1b, 0xb5, 0xc6, 0x02, 0x6a, 0x14, 0x58, 0xb3, 0xbc, 0xef, 0x3d, + 0x14, 0x3e, 0xb6, 0x42, 0xa4, 0xe0, 0x2a, 0x55, 0x21, 0xeb, ], }, TestVector { ovk: [ - 0x1b, 0x6e, 0x75, 0xec, 0xe3, 0xac, 0xe8, 0xdb, 0xa6, 0xa5, 0x41, 0x0d, 0x9a, 0xd4, - 0x75, 0x56, 0x68, 0xe4, 0xb3, 0x95, 0x85, 0xd6, 0x35, 0xec, 0x1d, 0xa7, 0xc8, 0xdc, - 0xfd, 0x5f, 0xc4, 0xed, + 0x82, 0x42, 0xe0, 0x59, 0xba, 0x92, 0x8e, 0xc6, 0xbe, 0x85, 0x65, 0x4a, 0x3d, 0xeb, + 0xa1, 0xbe, 0xe2, 0x47, 0xf1, 0x61, 0x84, 0x08, 0x0d, 0x69, 0xcf, 0x76, 0xa9, 0xc6, + 0x5e, 0x10, 0xf2, 0xc5, ], ivk: [ - 0x67, 0xfa, 0x2b, 0xf7, 0xc6, 0x7d, 0x46, 0x58, 0x24, 0x3c, 0x31, 0x7c, 0x0c, 0xb4, - 0x1f, 0xd3, 0x20, 0x64, 0xdf, 0xd3, 0x70, 0x9f, 0xe0, 0xdc, 0xb7, 0x24, 0xf1, 0x4b, - 0xb0, 0x1a, 0x1d, 0x04, + 0x38, 0x19, 0xa7, 0xdb, 0x66, 0xb7, 0x20, 0x61, 0x09, 0xdf, 0xee, 0xab, 0xc8, 0xe9, + 0x4e, 0xcd, 0xc8, 0xd7, 0xfe, 0x98, 0x3e, 0x48, 0x86, 0xde, 0xa6, 0x6e, 0xdc, 0xcd, + 0xeb, 0xcf, 0xbc, 0x00, ], default_d: [ - 0xfc, 0xfb, 0x68, 0xa4, 0x0d, 0x4b, 0xc6, 0xa0, 0x4b, 0x09, 0xc4, + 0xb8, 0xfd, 0x08, 0x5d, 0xf4, 0x66, 0x75, 0x8b, 0x8d, 0xef, 0x70, ], default_pk_d: [ - 0x8b, 0x2a, 0x33, 0x7f, 0x03, 0x62, 0x2c, 0x24, 0xff, 0x38, 0x1d, 0x4c, 0x54, 0x6f, - 0x69, 0x77, 0xf9, 0x05, 0x22, 0xe9, 0x2f, 0xde, 0x44, 0xc9, 0xd1, 0xbb, 0x09, 0x97, - 0x14, 0xb9, 0xdb, 0x2b, + 0xdf, 0x7d, 0xc9, 0xf5, 0x82, 0x3c, 0x2f, 0x25, 0x12, 0x07, 0xc3, 0xd0, 0x75, 0x47, + 0x8c, 0x54, 0x59, 0x8f, 0x2b, 0x59, 0x3e, 0xa1, 0x09, 0x31, 0x2d, 0xbd, 0x6e, 0x83, + 0x8b, 0x90, 0x2f, 0x2d, ], v: 500000000, rcm: [ @@ -897,14 +911,14 @@ pub(crate) fn make_test_vectors() -> Vec { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ], cv: [ - 0xce, 0x42, 0xf9, 0xd0, 0x89, 0xba, 0x9d, 0x9e, 0x62, 0xe3, 0xf6, 0x56, 0x33, 0x62, - 0xf0, 0xfd, 0xc7, 0xce, 0xde, 0x8a, 0xb3, 0x59, 0x43, 0x9e, 0x21, 0x4e, 0x26, 0x52, - 0xdb, 0xf0, 0x5a, 0x0c, + 0x39, 0xb2, 0x41, 0xc9, 0x65, 0xc2, 0x1e, 0x2a, 0xd8, 0xc2, 0x9a, 0x18, 0xe5, 0xb3, + 0x1e, 0xee, 0x9b, 0xad, 0x00, 0x63, 0x5f, 0x39, 0xc5, 0x85, 0x9f, 0x07, 0xa1, 0x78, + 0xfb, 0x88, 0x5a, 0x9a, ], cmu: [ - 0xc2, 0xb5, 0xf3, 0x57, 0x11, 0x7a, 0x40, 0x03, 0x62, 0x9e, 0x05, 0xca, 0x6f, 0x56, - 0xa6, 0x23, 0xa3, 0xc4, 0x8a, 0xa5, 0xeb, 0x79, 0x7c, 0xdd, 0x32, 0x2d, 0x48, 0x57, - 0xa0, 0xfb, 0xa4, 0x4e, + 0xbb, 0x75, 0xc1, 0x98, 0xe5, 0x69, 0x37, 0x93, 0xbd, 0xba, 0x6e, 0xcc, 0xe9, 0x56, + 0xb4, 0xb7, 0xd1, 0xa2, 0x46, 0xec, 0xee, 0x53, 0xb4, 0x91, 0x08, 0x36, 0xc4, 0x24, + 0x18, 0xad, 0x68, 0x59, ], esk: [ 0x3d, 0xc1, 0x66, 0xd5, 0x6a, 0x1d, 0x62, 0xf5, 0xa8, 0xd7, 0x55, 0x1d, 0xb5, 0xfd, @@ -912,26 +926,28 @@ pub(crate) fn make_test_vectors() -> Vec { 0xd5, 0x9a, 0xf8, 0x0d, ], epk: [ - 0x5b, 0x54, 0xe5, 0xd4, 0x13, 0xa8, 0x07, 0xdf, 0x36, 0x42, 0x6d, 0x5c, 0x8c, 0x09, - 0x81, 0x0a, 0xc2, 0x45, 0x95, 0xb1, 0x52, 0xcd, 0x89, 0x41, 0xa2, 0x34, 0x3c, 0x96, - 0x30, 0x3d, 0x24, 0x6b, + 0x54, 0x87, 0x9e, 0x32, 0x0d, 0x16, 0x8e, 0x81, 0xd6, 0xe3, 0x98, 0x87, 0x8f, 0x03, + 0x91, 0xaa, 0x90, 0xc0, 0xab, 0xa7, 0x3c, 0xde, 0xf8, 0xba, 0x74, 0x62, 0x67, 0x93, + 0x5a, 0x9f, 0xc8, 0x11, ], shared_secret: [ - 0x40, 0x64, 0xc2, 0xb7, 0xc1, 0x82, 0xd1, 0x80, 0x52, 0x50, 0xd3, 0x59, 0xfb, 0xa1, - 0xa5, 0x32, 0x54, 0x56, 0xb0, 0x12, 0x94, 0x4d, 0x7d, 0x92, 0x9f, 0x40, 0x9c, 0x6d, - 0xe5, 0x70, 0x5d, 0xc5, + 0x33, 0x5c, 0x01, 0x5a, 0x83, 0x38, 0xb0, 0x8e, 0x44, 0x51, 0x06, 0x49, 0xd8, 0x01, + 0x78, 0x03, 0xab, 0x9d, 0x26, 0x71, 0x32, 0xaf, 0xe5, 0xb1, 0x1f, 0x18, 0x1c, 0x69, + 0x42, 0x34, 0x7e, 0xe5, ], k_enc: [ - 0xc5, 0xfc, 0xf8, 0x13, 0xb1, 0xbb, 0xef, 0x20, 0xa6, 0x2a, 0xce, 0x7a, 0x47, 0xf3, - 0x7f, 0x26, 0x1f, 0xbb, 0x2d, 0xfa, 0xd8, 0x88, 0x66, 0xb4, 0x32, 0xff, 0x0d, 0xfa, - 0xee, 0xc5, 0xb2, 0xcf, + 0xce, 0xf1, 0x2f, 0xa6, 0xfa, 0x1f, 0xc2, 0xc2, 0x2c, 0x33, 0x87, 0x34, 0xac, 0x75, + 0x35, 0xb2, 0x7f, 0xe5, 0x19, 0xf4, 0xef, 0xc1, 0x31, 0x20, 0xb0, 0xe2, 0x3e, 0xf2, + 0x8f, 0xd6, 0x65, 0xcf, ], p_enc: [ - 0x01, 0xfc, 0xfb, 0x68, 0xa4, 0x0d, 0x4b, 0xc6, 0xa0, 0x4b, 0x09, 0xc4, 0x00, 0x65, - 0xcd, 0x1d, 0x00, 0x00, 0x00, 0x00, 0xe5, 0x57, 0x85, 0x13, 0x55, 0x74, 0x7c, 0x09, - 0xac, 0x59, 0x01, 0x3c, 0xbd, 0xe8, 0x59, 0x80, 0x96, 0x4e, 0xc1, 0x84, 0x4d, 0x9c, - 0x69, 0x67, 0xca, 0x0c, 0x02, 0x9c, 0x84, 0x57, 0xbb, 0x04, 0xf6, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0xb8, 0xfd, 0x08, 0x5d, 0xf4, 0x66, 0x75, 0x8b, 0x8d, 0xef, 0x70, 0x00, 0x65, + 0xcd, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, + 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, + 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0xe5, 0x57, 0x85, 0x13, + 0x55, 0x74, 0x7c, 0x09, 0xac, 0x59, 0x01, 0x3c, 0xbd, 0xe8, 0x59, 0x80, 0x96, 0x4e, + 0xc1, 0x84, 0x4d, 0x9c, 0x69, 0x67, 0xca, 0x0c, 0x02, 0x9c, 0x84, 0x57, 0xbb, 0x04, + 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -967,91 +983,93 @@ pub(crate) fn make_test_vectors() -> Vec { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ], c_enc: [ - 0xd7, 0xe7, 0x06, 0x31, 0x7c, 0x78, 0x95, 0x06, 0x2d, 0x89, 0xab, 0x5f, 0x10, 0x52, - 0x15, 0x5a, 0xc3, 0xd2, 0xa1, 0xe3, 0x43, 0x97, 0x3e, 0x5a, 0xab, 0x1c, 0xce, 0x53, - 0x59, 0xc6, 0xbc, 0x11, 0x1b, 0x9a, 0x7b, 0xb6, 0x68, 0xb6, 0xc7, 0xd0, 0x21, 0xb1, - 0x23, 0x35, 0x77, 0xe8, 0x2b, 0xaf, 0x33, 0x00, 0x5c, 0xd0, 0x34, 0xa9, 0x75, 0x4b, - 0x1e, 0x12, 0xdf, 0x03, 0x6b, 0x7b, 0xc7, 0x82, 0x98, 0x79, 0xca, 0x8c, 0x6b, 0x54, - 0x37, 0x8f, 0xcd, 0x5f, 0x18, 0x2f, 0x65, 0x16, 0x0e, 0xa7, 0x24, 0x3b, 0x7d, 0xfc, - 0xac, 0xfb, 0x6d, 0xac, 0xee, 0x02, 0x26, 0x34, 0x14, 0x9d, 0x8f, 0xb2, 0xf0, 0xca, - 0x51, 0xa8, 0x26, 0x72, 0xa5, 0x63, 0xd5, 0x36, 0xba, 0xf1, 0xaf, 0x88, 0x1a, 0x7a, - 0x8d, 0x25, 0xc5, 0xcf, 0x78, 0x61, 0x89, 0x53, 0x03, 0x2e, 0xf5, 0x65, 0xb0, 0xf3, - 0x98, 0xe3, 0x4b, 0xee, 0x2c, 0x30, 0x95, 0xa7, 0xbd, 0x0b, 0x7d, 0x09, 0x7a, 0x3d, - 0x26, 0x4d, 0x65, 0x46, 0xd0, 0x0c, 0x85, 0x83, 0x04, 0x43, 0x78, 0xd1, 0x48, 0x94, - 0x04, 0xa3, 0x1e, 0xec, 0xa8, 0x8f, 0x8f, 0x42, 0xeb, 0xfb, 0x82, 0x18, 0xd4, 0x9f, - 0xde, 0xd8, 0x2a, 0x9b, 0xa6, 0x23, 0x2c, 0xcc, 0x47, 0x94, 0x5d, 0x6f, 0x7d, 0x6e, - 0x39, 0xe0, 0xe8, 0x39, 0x29, 0x34, 0x1a, 0xcf, 0x88, 0xdb, 0x5a, 0x27, 0x73, 0xdc, - 0x55, 0x8a, 0x9d, 0xc1, 0x1d, 0xcd, 0xa1, 0xba, 0xb3, 0xcb, 0x21, 0xbf, 0x5c, 0x29, - 0x51, 0x83, 0xbf, 0x9a, 0x93, 0xee, 0x02, 0x5e, 0xb4, 0x60, 0xf7, 0xd7, 0x41, 0x20, - 0x42, 0xce, 0x5a, 0x84, 0x3a, 0x79, 0x0c, 0x3a, 0x94, 0xda, 0x2d, 0xb7, 0xf6, 0x12, - 0x03, 0x2f, 0xbf, 0x56, 0x4e, 0xfc, 0xf2, 0x04, 0xaf, 0xed, 0x0f, 0xf2, 0xab, 0x2b, - 0xc1, 0xb3, 0x77, 0xca, 0x41, 0x0f, 0x12, 0x7f, 0xaf, 0x98, 0x76, 0x62, 0x7f, 0xbd, - 0xb2, 0x26, 0x2a, 0xe6, 0x56, 0x23, 0x08, 0x84, 0x48, 0x00, 0xb5, 0xcd, 0x52, 0x74, - 0x3e, 0x7f, 0x7b, 0xca, 0xe3, 0xc7, 0xb2, 0x70, 0x34, 0xc5, 0xf2, 0x1d, 0x4f, 0xef, - 0xb5, 0x9b, 0xd2, 0x3b, 0xc6, 0xea, 0x0c, 0x39, 0x39, 0x87, 0x1a, 0xb4, 0x34, 0xb3, - 0xa5, 0xcb, 0x71, 0x03, 0x85, 0x1a, 0x24, 0x78, 0xc5, 0xf6, 0x13, 0x8f, 0x8f, 0xd9, - 0x91, 0x3f, 0xa7, 0xaf, 0x5a, 0x4a, 0xa2, 0x0e, 0xf9, 0x59, 0x40, 0x84, 0x0b, 0xcd, - 0x17, 0x4c, 0xa3, 0xe1, 0x06, 0x5a, 0xea, 0xee, 0x5f, 0x6c, 0x7d, 0x94, 0x34, 0x2c, - 0x68, 0x5f, 0x13, 0xa8, 0x1e, 0x7b, 0x53, 0xad, 0x42, 0x89, 0x0b, 0xa8, 0x10, 0x3a, - 0xc8, 0x34, 0xa4, 0xeb, 0x1f, 0x10, 0xb0, 0xa7, 0x0e, 0x76, 0x89, 0x1d, 0xbe, 0x18, - 0xf5, 0x80, 0x47, 0x2f, 0x5b, 0xdc, 0x3f, 0xc9, 0x55, 0x0f, 0x15, 0x6b, 0x31, 0x21, - 0xa8, 0x44, 0xd6, 0xc7, 0x7b, 0x22, 0x4b, 0x8d, 0x04, 0xf1, 0xfe, 0x8e, 0xa7, 0xb9, - 0x88, 0xd8, 0x78, 0xbf, 0xc0, 0x6d, 0xac, 0x33, 0x2a, 0x10, 0x6a, 0x6e, 0xad, 0x47, - 0xf8, 0x2b, 0xd8, 0xcb, 0x7c, 0x25, 0xae, 0x9e, 0x1d, 0x75, 0xbb, 0x76, 0x2a, 0xfe, - 0xe3, 0x49, 0x30, 0xf4, 0xa9, 0x98, 0xf2, 0x68, 0xd8, 0x76, 0x3c, 0xae, 0x7b, 0x32, - 0x15, 0x20, 0x5e, 0x58, 0x9c, 0x48, 0x11, 0x13, 0xb5, 0xa4, 0xcd, 0xb2, 0x09, 0xbe, - 0xce, 0x2f, 0x09, 0x4f, 0x33, 0x9f, 0x03, 0xfb, 0x39, 0xa1, 0x6e, 0xf1, 0x67, 0x2e, - 0x00, 0x89, 0x27, 0xfd, 0x97, 0x09, 0x8e, 0x00, 0x12, 0xbe, 0xca, 0xa0, 0x0f, 0x62, - 0xc6, 0xbf, 0xd9, 0x45, 0xa0, 0x16, 0xbe, 0x8b, 0x18, 0x66, 0xd9, 0x2b, 0x1d, 0x85, - 0x88, 0xae, 0x26, 0xc6, 0x35, 0x70, 0xd7, 0xe2, 0xa6, 0xb2, 0xee, 0x6e, 0xc2, 0xe6, - 0xb0, 0xbe, 0x22, 0x19, 0x38, 0x0e, 0x4e, 0xea, 0x6a, 0xf0, 0x9b, 0xf5, 0x85, 0xf2, - 0x85, 0x38, 0xd8, 0xb7, 0x89, 0x32, 0x6e, 0x6a, 0x3d, 0xe3, 0xbf, 0x45, 0x06, 0x80, - 0x28, 0xac, 0x80, 0xb1, 0x92, 0x25, 0x5f, 0x27, 0x33, 0x64, 0xda, 0x88, 0xdc, 0x1a, - 0x6f, 0x00, 0xe0, 0xcc, 0x32, 0xbb, 0x47, 0x5e, 0xcc, 0xbe, 0x09, 0x7a, 0x69, 0xf6, - 0x49, 0x2b, 0xdb, 0xa2, 0xad, 0xf0, + 0x10, 0xce, 0xec, 0x5a, 0x29, 0x10, 0x88, 0xd7, 0xc0, 0x86, 0x61, 0x9e, 0x42, 0xf8, + 0xd9, 0x6e, 0xf4, 0x93, 0xdf, 0x0f, 0xf5, 0xe5, 0x36, 0xf1, 0x64, 0x57, 0xf0, 0x28, + 0x04, 0xe9, 0xc3, 0xdc, 0x7e, 0x5f, 0x63, 0x8f, 0xc3, 0xe8, 0x0f, 0xb7, 0x12, 0x4b, + 0xdf, 0x1e, 0x2b, 0xf8, 0x2a, 0x8f, 0xb2, 0x0e, 0xa0, 0x52, 0x86, 0x5a, 0xc6, 0xfd, + 0x67, 0xb3, 0xd0, 0xa1, 0xe0, 0x15, 0xbc, 0x8b, 0x5d, 0xf1, 0x39, 0x38, 0x53, 0xc9, + 0x20, 0x39, 0x37, 0x2b, 0x7c, 0x20, 0x4b, 0x56, 0x11, 0x2b, 0x59, 0xb7, 0x4b, 0x49, + 0x70, 0x9b, 0x1b, 0x16, 0x10, 0x94, 0x9c, 0xd0, 0x2f, 0xc1, 0xb6, 0xa4, 0x4c, 0x07, + 0x82, 0xf3, 0xf0, 0x9c, 0x86, 0x17, 0x66, 0x03, 0xe4, 0x07, 0x2d, 0xf9, 0xdf, 0x9e, + 0x76, 0xcd, 0xbc, 0x5b, 0x98, 0x25, 0xb1, 0x9e, 0x49, 0x97, 0xd4, 0xa7, 0x77, 0x5f, + 0x14, 0x26, 0xed, 0x1a, 0x28, 0xb9, 0x8b, 0xe9, 0x51, 0x31, 0x87, 0x91, 0x42, 0x12, + 0xb8, 0xc2, 0x69, 0xa2, 0x9a, 0x58, 0xb3, 0xfc, 0x1a, 0x21, 0x7a, 0x95, 0x07, 0xe0, + 0xf3, 0x98, 0xa0, 0x7d, 0xd6, 0x46, 0x23, 0x99, 0xeb, 0x5a, 0xed, 0x9e, 0x13, 0x99, + 0xf3, 0x9b, 0x43, 0x4d, 0x23, 0xac, 0xd9, 0x55, 0x4f, 0xf3, 0x40, 0xa1, 0x33, 0x7e, + 0xe0, 0xf7, 0x03, 0x67, 0x44, 0xbc, 0x8e, 0x60, 0x97, 0xca, 0x5b, 0x45, 0xa2, 0xb0, + 0x88, 0x4b, 0xa0, 0x02, 0xbb, 0xb3, 0xc8, 0x38, 0x90, 0x0c, 0xfa, 0x9c, 0x88, 0x15, + 0x24, 0x48, 0xc1, 0x8e, 0x10, 0x30, 0x4b, 0xad, 0xbe, 0xca, 0xb6, 0x05, 0x3a, 0x13, + 0xe6, 0xf3, 0x45, 0x9e, 0xb1, 0x4c, 0x05, 0x8d, 0x19, 0xcb, 0x2e, 0xa1, 0x76, 0xe6, + 0xb8, 0x9f, 0x3f, 0x04, 0x8a, 0x8d, 0x4a, 0x81, 0x0b, 0xc0, 0x0a, 0xe5, 0xc1, 0xa9, + 0x08, 0xa6, 0x91, 0x5f, 0xb7, 0xe3, 0xb8, 0xd4, 0xd0, 0xbd, 0xe1, 0xed, 0x7f, 0x28, + 0xdd, 0x56, 0xf7, 0xb3, 0x39, 0xba, 0xc8, 0x39, 0xe7, 0x93, 0x8a, 0x05, 0xa6, 0x80, + 0x5c, 0x28, 0x7f, 0x2e, 0x42, 0x4a, 0x3c, 0x21, 0x90, 0x36, 0x77, 0xd9, 0x1a, 0x03, + 0x48, 0x3d, 0xc8, 0x69, 0x2a, 0xee, 0x97, 0xfa, 0x0c, 0xa9, 0x6c, 0x1e, 0xdb, 0xb7, + 0xf0, 0x14, 0x3d, 0xc7, 0x27, 0x53, 0xa2, 0x15, 0xc8, 0xc8, 0x05, 0x73, 0xe6, 0x17, + 0x19, 0xf0, 0x34, 0x3b, 0x3a, 0x7a, 0x7f, 0x55, 0x65, 0xb5, 0xae, 0x8c, 0xc7, 0x8f, + 0x0d, 0x8b, 0x2f, 0x62, 0xcb, 0x80, 0xa4, 0xe8, 0x49, 0x3d, 0x1e, 0xa8, 0xd2, 0x98, + 0xb8, 0xab, 0x8f, 0x52, 0xcb, 0x87, 0x18, 0x93, 0x4d, 0xc3, 0x15, 0x54, 0xf2, 0x79, + 0x14, 0x41, 0x5e, 0xd9, 0xd3, 0x66, 0x45, 0x55, 0x3e, 0xcb, 0x04, 0x26, 0xdf, 0x21, + 0xb6, 0xfd, 0x3a, 0xf9, 0x45, 0x67, 0x81, 0x06, 0xb1, 0xef, 0x8f, 0xdb, 0x89, 0xd6, + 0x9a, 0x60, 0x9e, 0xe4, 0x34, 0x50, 0xd7, 0x5a, 0xbf, 0x91, 0xf7, 0x49, 0x42, 0xdd, + 0x37, 0x1f, 0x2d, 0xac, 0xae, 0x43, 0x71, 0x45, 0x63, 0x8a, 0xbb, 0x11, 0x03, 0x99, + 0xce, 0x11, 0xe4, 0xa1, 0x0a, 0x44, 0x4f, 0x43, 0x95, 0x88, 0x00, 0x00, 0x1e, 0x72, + 0x71, 0xa0, 0xb7, 0xc5, 0x0c, 0x35, 0xc3, 0x38, 0x30, 0x21, 0x7d, 0xbd, 0xd9, 0xc5, + 0x9f, 0x0b, 0x2e, 0xa5, 0xdf, 0xd5, 0xf1, 0x3d, 0xc6, 0x5e, 0x96, 0x46, 0xdb, 0x28, + 0xd4, 0x35, 0x8e, 0xde, 0x29, 0x1a, 0xe1, 0xf7, 0xab, 0x79, 0x67, 0x02, 0x7b, 0xf2, + 0xa9, 0xcf, 0x02, 0x08, 0x51, 0x65, 0x00, 0x87, 0x8a, 0xa7, 0x6e, 0x12, 0x7f, 0x31, + 0x2e, 0x1d, 0xc7, 0x67, 0x09, 0x36, 0x6e, 0xe9, 0x0e, 0xfb, 0x18, 0x5b, 0x09, 0x05, + 0x56, 0x1c, 0x11, 0x8d, 0xa3, 0x28, 0x76, 0xc5, 0x2d, 0x86, 0x20, 0x65, 0xac, 0xa8, + 0x60, 0x5a, 0x6d, 0xd6, 0x50, 0x15, 0x25, 0x31, 0x4c, 0x16, 0x37, 0x1d, 0xdf, 0x69, + 0xbc, 0x2f, 0xc0, 0x97, 0xef, 0x27, 0xc8, 0x06, 0xe3, 0xcb, 0x26, 0x89, 0x4c, 0x44, + 0xf7, 0xe6, 0x17, 0xed, 0xb2, 0x78, 0xbd, 0x08, 0x88, 0x32, 0x72, 0x55, 0x16, 0x48, + 0xd8, 0xa1, 0xba, 0x7f, 0x26, 0x1e, 0x1a, 0x99, 0x02, 0xbf, 0xd0, 0xaf, 0x21, 0x27, + 0x9f, 0xdd, 0x13, 0x49, 0x5e, 0xdb, 0x68, 0xcd, 0xa6, 0x14, 0x70, 0x26, 0xab, 0x3f, + 0x9f, 0x38, 0x00, 0x16, 0x09, 0x2f, 0x23, 0x11, 0x5f, 0xdb, 0x24, 0x87, 0x21, 0xe8, + 0x97, 0x42, 0x99, 0x6d, 0xdd, 0xa1, 0x1c, 0xbb, 0x62, 0xe4, ], ock: [ - 0xf9, 0x8d, 0x6e, 0x55, 0xff, 0x78, 0x3a, 0x13, 0x13, 0x14, 0x0f, 0xb8, 0x8b, 0x7f, - 0x3a, 0x4d, 0xb2, 0x81, 0x86, 0x37, 0x86, 0x88, 0xbe, 0xc6, 0x19, 0x56, 0x23, 0x2e, - 0x42, 0xb7, 0x0a, 0xba, + 0x77, 0x2b, 0x15, 0x0a, 0xa0, 0x73, 0x60, 0xfe, 0x8b, 0xcb, 0x66, 0xc6, 0x0c, 0xf5, + 0x65, 0x0c, 0xec, 0xbb, 0x30, 0x43, 0xc4, 0x7d, 0xdc, 0x5e, 0xd4, 0x04, 0x02, 0x63, + 0x37, 0x88, 0x47, 0x31, ], op: [ - 0x8b, 0x2a, 0x33, 0x7f, 0x03, 0x62, 0x2c, 0x24, 0xff, 0x38, 0x1d, 0x4c, 0x54, 0x6f, - 0x69, 0x77, 0xf9, 0x05, 0x22, 0xe9, 0x2f, 0xde, 0x44, 0xc9, 0xd1, 0xbb, 0x09, 0x97, - 0x14, 0xb9, 0xdb, 0x2b, 0x3d, 0xc1, 0x66, 0xd5, 0x6a, 0x1d, 0x62, 0xf5, 0xa8, 0xd7, + 0xdf, 0x7d, 0xc9, 0xf5, 0x82, 0x3c, 0x2f, 0x25, 0x12, 0x07, 0xc3, 0xd0, 0x75, 0x47, + 0x8c, 0x54, 0x59, 0x8f, 0x2b, 0x59, 0x3e, 0xa1, 0x09, 0x31, 0x2d, 0xbd, 0x6e, 0x83, + 0x8b, 0x90, 0x2f, 0x2d, 0x3d, 0xc1, 0x66, 0xd5, 0x6a, 0x1d, 0x62, 0xf5, 0xa8, 0xd7, 0x55, 0x1d, 0xb5, 0xfd, 0x93, 0x13, 0xe8, 0xc7, 0x20, 0x3d, 0x99, 0x6a, 0xf7, 0xd4, 0x77, 0x08, 0x37, 0x56, 0xd5, 0x9a, 0xf8, 0x0d, ], c_out: [ - 0x3b, 0xfc, 0x13, 0x67, 0x3c, 0x24, 0xac, 0x5e, 0xaf, 0x0b, 0xc2, 0x44, 0x6c, 0x38, - 0xa7, 0x92, 0xae, 0x42, 0xd9, 0x6b, 0xaf, 0x05, 0x53, 0xce, 0xe4, 0x36, 0xb6, 0x34, - 0xb5, 0x73, 0x89, 0xb3, 0x62, 0x1d, 0xdb, 0xba, 0x22, 0xe6, 0x84, 0x89, 0x0a, 0x7b, - 0x64, 0x5d, 0x63, 0xc4, 0xbc, 0x8c, 0x26, 0xdb, 0x54, 0x62, 0x8c, 0xef, 0x4d, 0xed, - 0x98, 0x0f, 0x60, 0x8f, 0x00, 0x20, 0xbb, 0xb5, 0xa2, 0xf6, 0x55, 0x22, 0xa6, 0x1f, - 0x89, 0xdf, 0x82, 0x18, 0x18, 0x67, 0x04, 0x01, 0x1e, 0x91, + 0xe7, 0xce, 0x81, 0x95, 0xad, 0x76, 0xca, 0xb4, 0xc7, 0xde, 0x74, 0x9e, 0x65, 0x44, + 0x9e, 0x2f, 0xf2, 0x09, 0x90, 0xc3, 0x38, 0x54, 0x0a, 0xdd, 0x42, 0x55, 0x6e, 0xbd, + 0x92, 0x9a, 0x5e, 0xff, 0x86, 0x1a, 0x62, 0x2e, 0xac, 0x03, 0x3b, 0x7e, 0x67, 0xea, + 0x90, 0x86, 0x6e, 0xd1, 0xd1, 0x5c, 0x04, 0xc0, 0x24, 0x3f, 0x14, 0x80, 0x8c, 0x60, + 0x56, 0x8a, 0x9e, 0xe2, 0x2d, 0x66, 0x62, 0x02, 0x51, 0x52, 0xbe, 0x07, 0xdd, 0xc8, + 0x8b, 0x2b, 0x89, 0x23, 0x5b, 0xf5, 0xaa, 0x9e, 0xdf, 0xa3, ], }, TestVector { ovk: [ - 0xc6, 0xbc, 0x1f, 0x39, 0xf0, 0xd7, 0x86, 0x31, 0x4c, 0xb2, 0x0b, 0xf9, 0xab, 0x22, - 0x85, 0x40, 0x91, 0x35, 0x55, 0xf9, 0x70, 0x69, 0x6b, 0x6d, 0x7c, 0x77, 0xbb, 0x33, - 0x23, 0x28, 0x37, 0x2a, + 0x61, 0x1f, 0x89, 0x77, 0x5b, 0x10, 0x86, 0xbc, 0x30, 0xc1, 0x97, 0xa4, 0x3b, 0xbb, + 0x3a, 0x55, 0xd3, 0xfd, 0x4a, 0xac, 0x41, 0x82, 0x68, 0xbd, 0x8e, 0x6c, 0x9d, 0xe8, + 0xe9, 0x32, 0xe2, 0x3c, ], ivk: [ - 0xea, 0x3f, 0x1d, 0x80, 0xe4, 0x30, 0x7c, 0xa7, 0x3b, 0x9f, 0x37, 0x80, 0x1f, 0x91, - 0xfb, 0xa8, 0x10, 0xcc, 0x41, 0xd2, 0x79, 0xfc, 0x29, 0xf5, 0x64, 0x23, 0x56, 0x54, - 0xa2, 0x17, 0x8e, 0x03, + 0xd0, 0xc2, 0xfb, 0xf8, 0x7a, 0x28, 0xcd, 0xce, 0x8e, 0x22, 0x98, 0x96, 0xa9, 0x44, + 0xd0, 0x74, 0x2f, 0xe1, 0x5c, 0x61, 0x88, 0x8f, 0x10, 0x39, 0x18, 0x9a, 0x8e, 0x80, + 0x5c, 0x6c, 0xdf, 0x06, ], default_d: [ - 0xeb, 0x51, 0x98, 0x82, 0xad, 0x1e, 0x5c, 0xc6, 0x54, 0xcd, 0x59, + 0x91, 0xd8, 0x0c, 0x32, 0x53, 0x00, 0xd9, 0x7e, 0x0c, 0x3b, 0x05, ], default_pk_d: [ - 0x6b, 0x27, 0xda, 0xcc, 0xb5, 0xa8, 0x20, 0x7f, 0x53, 0x2d, 0x10, 0xca, 0x23, 0x8f, - 0x97, 0x86, 0x64, 0x8a, 0x11, 0xb5, 0x96, 0x6e, 0x51, 0xa2, 0xf7, 0xd8, 0x9e, 0x15, - 0xd2, 0x9b, 0x8f, 0xdf, + 0xe7, 0x25, 0xc0, 0x2a, 0xc9, 0x18, 0x84, 0xe1, 0x45, 0x2e, 0x5b, 0xbe, 0x8d, 0xbf, + 0xb1, 0xe0, 0xcd, 0xee, 0x00, 0x56, 0xdc, 0x2f, 0x5f, 0xc1, 0x92, 0x39, 0xb2, 0x0b, + 0x7b, 0xe7, 0x62, 0xe0, ], v: 600000000, rcm: [ @@ -1099,14 +1117,14 @@ pub(crate) fn make_test_vectors() -> Vec { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ], cv: [ - 0x30, 0x27, 0xd7, 0xb7, 0x47, 0x64, 0xca, 0xf7, 0x2b, 0x73, 0x87, 0x28, 0x9b, 0x12, - 0x8f, 0x43, 0x9f, 0xd0, 0x42, 0xc2, 0x1d, 0x81, 0x36, 0x4b, 0xc2, 0xae, 0x7b, 0xd2, - 0x9e, 0xab, 0x51, 0x23, + 0x61, 0x01, 0x36, 0xc4, 0x2a, 0x72, 0x80, 0xc0, 0x3c, 0xa8, 0xa3, 0x19, 0x4d, 0x39, + 0x6f, 0x28, 0x1d, 0x2c, 0xa3, 0x75, 0xfa, 0xa2, 0x31, 0xfe, 0x43, 0x39, 0x88, 0x96, + 0x0b, 0x28, 0xdf, 0xad, ], cmu: [ - 0x38, 0x2c, 0x7d, 0x68, 0x8b, 0xdf, 0x34, 0xb9, 0x4d, 0x40, 0x1c, 0x41, 0x22, 0x79, - 0x52, 0xa2, 0xb9, 0x31, 0xc5, 0x7b, 0x00, 0x5c, 0x82, 0xf2, 0xc3, 0x63, 0x15, 0xf6, - 0x1c, 0x35, 0x02, 0x4e, + 0x7c, 0x4c, 0x61, 0x63, 0xa4, 0x0a, 0xc2, 0x3a, 0x3c, 0x70, 0xab, 0x61, 0xfb, 0x79, + 0xbf, 0x75, 0xdc, 0x4c, 0xc9, 0x86, 0xf7, 0x7b, 0xcb, 0x6b, 0x28, 0x3a, 0xb7, 0xc0, + 0x98, 0x8c, 0x97, 0x23, ], esk: [ 0x4e, 0x41, 0x8c, 0x3c, 0x54, 0x3d, 0x6b, 0xf0, 0x15, 0x31, 0x74, 0xa0, 0x4e, 0x85, @@ -1114,26 +1132,28 @@ pub(crate) fn make_test_vectors() -> Vec { 0x79, 0x11, 0x09, 0x03, ], epk: [ - 0xe0, 0xc2, 0x9b, 0x43, 0x5d, 0xae, 0xdb, 0xc9, 0x8d, 0x46, 0x5f, 0x38, 0x9b, 0x1b, - 0x60, 0xd7, 0xdf, 0xac, 0x0e, 0x45, 0x9b, 0x1e, 0x62, 0x8f, 0xa0, 0x18, 0x4e, 0x92, - 0xf2, 0x64, 0x79, 0xca, + 0x7a, 0x9e, 0xd4, 0xc1, 0xfb, 0xe3, 0x39, 0xd8, 0xbf, 0xab, 0x2d, 0xca, 0x72, 0xde, + 0x75, 0xe5, 0x7b, 0x9a, 0x8f, 0x79, 0x52, 0xb3, 0x8f, 0xb1, 0xc6, 0x02, 0x76, 0xa7, + 0xf8, 0xd9, 0x61, 0x6b, ], shared_secret: [ - 0x34, 0xdd, 0x16, 0x13, 0xa8, 0x57, 0x75, 0x2a, 0xa9, 0x07, 0x26, 0xff, 0xf0, 0x7d, - 0x42, 0x9d, 0xcb, 0x52, 0xd2, 0xca, 0x27, 0x7d, 0x84, 0xeb, 0x7a, 0x12, 0xfa, 0x9a, - 0xfc, 0x99, 0xa7, 0x35, + 0x5c, 0x9b, 0x08, 0xfd, 0xd7, 0x81, 0x70, 0x9f, 0xeb, 0x1a, 0x0b, 0xa1, 0xc0, 0x6a, + 0x1d, 0x1a, 0x9c, 0xb3, 0xc6, 0x95, 0x9e, 0xbf, 0x88, 0x4f, 0x32, 0xb3, 0x5e, 0x06, + 0x8d, 0x98, 0xc0, 0xaf, ], k_enc: [ - 0x03, 0x25, 0xb3, 0x12, 0x63, 0x58, 0x57, 0x3c, 0x09, 0x90, 0xa3, 0x62, 0xb8, 0xf2, - 0x7c, 0xd0, 0x0c, 0xe0, 0xdc, 0x4b, 0x4d, 0x00, 0xcc, 0x8d, 0x8d, 0x3b, 0xa2, 0xce, - 0x6e, 0xa9, 0xc2, 0x97, + 0x68, 0x55, 0x03, 0x94, 0x2d, 0x0d, 0xf2, 0xc7, 0xf5, 0x8f, 0x89, 0x68, 0xac, 0x6b, + 0x56, 0xa3, 0x90, 0x3d, 0x7c, 0x9e, 0x7d, 0xea, 0xb7, 0x4f, 0x1d, 0xe9, 0xf6, 0x69, + 0x40, 0x1e, 0x6a, 0xcb, ], p_enc: [ - 0x01, 0xeb, 0x51, 0x98, 0x82, 0xad, 0x1e, 0x5c, 0xc6, 0x54, 0xcd, 0x59, 0x00, 0x46, - 0xc3, 0x23, 0x00, 0x00, 0x00, 0x00, 0x68, 0xf0, 0x61, 0x04, 0x60, 0x6b, 0x0c, 0x54, - 0x49, 0x84, 0x5f, 0xf4, 0xc6, 0x5f, 0x73, 0xe9, 0x0f, 0x45, 0xef, 0x5a, 0x43, 0xc9, - 0xd7, 0x4c, 0xb2, 0xc8, 0x5c, 0xf5, 0x6c, 0x94, 0xc0, 0x02, 0xf6, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x91, 0xd8, 0x0c, 0x32, 0x53, 0x00, 0xd9, 0x7e, 0x0c, 0x3b, 0x05, 0x00, 0x46, + 0xc3, 0x23, 0x00, 0x00, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, + 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, + 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x68, 0xf0, 0x61, 0x04, + 0x60, 0x6b, 0x0c, 0x54, 0x49, 0x84, 0x5f, 0xf4, 0xc6, 0x5f, 0x73, 0xe9, 0x0f, 0x45, + 0xef, 0x5a, 0x43, 0xc9, 0xd7, 0x4c, 0xb2, 0xc8, 0x5c, 0xf5, 0x6c, 0x94, 0xc0, 0x02, + 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -1169,91 +1189,93 @@ pub(crate) fn make_test_vectors() -> Vec { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ], c_enc: [ - 0x3f, 0x92, 0x6f, 0x4c, 0x93, 0xff, 0x12, 0x5b, 0xd1, 0xfa, 0x04, 0xc9, 0x1e, 0xf5, - 0x9e, 0x07, 0x14, 0x33, 0xf5, 0x7c, 0x60, 0x6e, 0xe1, 0xbc, 0x91, 0x2d, 0x54, 0x62, - 0x8d, 0x14, 0x07, 0x40, 0xa1, 0xab, 0x8a, 0x34, 0x51, 0x6a, 0xde, 0xfb, 0xe6, 0x48, - 0x00, 0x7f, 0x86, 0xf1, 0x31, 0xf4, 0x99, 0x3b, 0x99, 0xae, 0xbd, 0x18, 0x99, 0x63, - 0x48, 0xf4, 0xec, 0x85, 0x34, 0x1d, 0xf3, 0x35, 0x42, 0x2b, 0x12, 0x61, 0x8f, 0x63, - 0xaa, 0x80, 0x4b, 0x30, 0x6c, 0x6c, 0x6b, 0x23, 0x7a, 0x0c, 0x04, 0x4f, 0x79, 0x03, - 0x3d, 0x02, 0x8d, 0x13, 0xcf, 0x1f, 0x3d, 0x6e, 0x38, 0xac, 0xf3, 0x90, 0xf5, 0x54, - 0xa8, 0xd4, 0xe4, 0x64, 0x94, 0x8f, 0xb5, 0xa7, 0xf9, 0x8d, 0x16, 0x1e, 0x3a, 0x8a, - 0x15, 0x7a, 0xf4, 0xc8, 0x94, 0xca, 0x2d, 0xa4, 0x64, 0x7c, 0x53, 0x22, 0x35, 0x4f, - 0x26, 0x19, 0xfd, 0x6c, 0xcc, 0x3c, 0xab, 0xef, 0x03, 0x71, 0xba, 0x42, 0x2f, 0x3d, - 0x6d, 0x92, 0x16, 0x99, 0x6e, 0x49, 0xe6, 0x93, 0x87, 0x1c, 0x56, 0x3f, 0xfb, 0xf4, - 0xc6, 0xd1, 0xd1, 0xc4, 0x73, 0x9f, 0x73, 0x26, 0xda, 0x4c, 0x66, 0x97, 0x61, 0x84, - 0xf0, 0x13, 0x64, 0x96, 0x71, 0x2a, 0x7e, 0xed, 0x56, 0xea, 0x4c, 0xa1, 0xd0, 0x78, - 0x4c, 0x7f, 0xa2, 0xc5, 0x56, 0xd6, 0xa9, 0x64, 0x0b, 0x55, 0x45, 0xd2, 0x14, 0x0a, - 0xd7, 0x45, 0xf1, 0xfc, 0xda, 0xb6, 0xb1, 0xf9, 0xee, 0x59, 0x35, 0x6b, 0xed, 0x24, - 0x93, 0x38, 0xa5, 0xc6, 0xc1, 0xc6, 0x37, 0xea, 0x9b, 0x77, 0x9b, 0x83, 0x11, 0xa5, - 0x32, 0x3a, 0x15, 0xd6, 0x1f, 0x1a, 0x0f, 0xfc, 0x7b, 0x2f, 0xc9, 0xe0, 0xbe, 0x58, - 0xc5, 0xfc, 0xbd, 0xbe, 0x57, 0xa2, 0xe4, 0xd3, 0xbf, 0x21, 0x84, 0x5b, 0x90, 0x16, - 0x54, 0x1c, 0x8c, 0xb4, 0x4a, 0x59, 0xec, 0xa7, 0xf2, 0xb4, 0x18, 0x3b, 0xfb, 0xbc, - 0xda, 0x57, 0xeb, 0x54, 0x24, 0xe8, 0x9d, 0xc3, 0xb0, 0x67, 0x14, 0xe2, 0x0e, 0xdf, - 0x78, 0x46, 0xd6, 0x8a, 0x5f, 0x8a, 0x18, 0x4a, 0x7f, 0x7c, 0x5a, 0x08, 0xfc, 0xcc, - 0x79, 0x84, 0x12, 0x2e, 0x8c, 0x63, 0x63, 0x03, 0xd0, 0x3b, 0x52, 0xb5, 0x1e, 0xc8, - 0xcd, 0x97, 0x68, 0x88, 0x97, 0x6a, 0xc5, 0x9f, 0xe4, 0xeb, 0xda, 0x53, 0x95, 0x53, - 0x8d, 0xbe, 0xa3, 0xd0, 0x09, 0x7b, 0xe5, 0x54, 0x6e, 0x1e, 0x0a, 0xb1, 0xba, 0x4c, - 0xbb, 0x47, 0xf6, 0x20, 0x3d, 0xca, 0xb8, 0x4b, 0x12, 0x9c, 0x52, 0x99, 0xe3, 0xe9, - 0x9d, 0x65, 0xeb, 0xcb, 0xe4, 0x0f, 0xd0, 0x5b, 0x87, 0x36, 0x9c, 0x30, 0xdb, 0x29, - 0x38, 0x37, 0xdb, 0xd0, 0x4e, 0x7a, 0x71, 0x08, 0xab, 0x74, 0x4b, 0x4f, 0xb3, 0xda, - 0x1f, 0x8a, 0x7d, 0x2c, 0xba, 0x6a, 0x5f, 0x01, 0x4f, 0x0d, 0x70, 0x5e, 0xce, 0x11, - 0x9a, 0xe9, 0x80, 0xe9, 0x99, 0x3d, 0xa3, 0xdd, 0xaa, 0x3b, 0xf1, 0x89, 0x9a, 0x74, - 0x74, 0xd6, 0x0b, 0x72, 0xed, 0x1e, 0x39, 0x0d, 0xfe, 0x4a, 0x3a, 0x07, 0x1a, 0xce, - 0xfb, 0x02, 0xcc, 0xca, 0x0b, 0xa9, 0x39, 0x8c, 0x86, 0x1b, 0xed, 0x45, 0x21, 0x61, - 0x79, 0xee, 0x2a, 0x08, 0x53, 0x36, 0x1c, 0x7d, 0xea, 0x89, 0xac, 0x1c, 0xd7, 0xe2, - 0xb4, 0xef, 0xa6, 0xad, 0x82, 0x15, 0xf5, 0xf7, 0x6a, 0xc2, 0x8a, 0x73, 0x1d, 0x27, - 0x79, 0xc1, 0xff, 0xeb, 0xe9, 0xab, 0x6f, 0x51, 0x3d, 0x9b, 0x5e, 0xe0, 0x08, 0x13, - 0x5f, 0xf6, 0x0b, 0xb8, 0x6f, 0x8e, 0x13, 0x97, 0x87, 0xc6, 0xc3, 0x46, 0x8d, 0x31, - 0x29, 0x8f, 0x25, 0x91, 0x76, 0x48, 0xf0, 0x72, 0xa1, 0x1c, 0x0b, 0x8a, 0xf4, 0x0f, - 0x92, 0xa8, 0xb5, 0x04, 0x2c, 0xd4, 0xaf, 0x4f, 0x5a, 0x2a, 0x55, 0x27, 0x31, 0x54, - 0x61, 0x90, 0x44, 0x8d, 0xf1, 0x07, 0x86, 0x37, 0xf4, 0x2e, 0x97, 0x54, 0x5a, 0x86, - 0x64, 0x3a, 0xa4, 0x10, 0x37, 0xc5, 0x34, 0xbc, 0x3e, 0x2e, 0x44, 0xa8, 0x85, 0x34, - 0x10, 0xa0, 0x6e, 0x91, 0x25, 0x31, 0x8a, 0x96, 0x56, 0x55, 0xf3, 0x3f, 0xed, 0x8e, - 0xba, 0x35, 0x62, 0x93, 0xd7, 0xcc, 0xfb, 0x97, 0xa2, 0x33, 0x20, 0xbc, 0x35, 0x39, - 0x70, 0xaa, 0xa1, 0x18, 0xe7, 0x43, + 0x90, 0x12, 0xc1, 0x3c, 0x23, 0x2d, 0x33, 0x9f, 0x6f, 0x63, 0x38, 0x13, 0xa1, 0xc3, + 0xf0, 0x44, 0xcb, 0xf3, 0x94, 0x24, 0x67, 0x01, 0x0f, 0x5a, 0x28, 0xc2, 0xa0, 0x51, + 0x74, 0xf5, 0x4a, 0xc1, 0xbf, 0x9e, 0x56, 0x71, 0x2d, 0x7e, 0xe8, 0xf6, 0x92, 0xa4, + 0x0e, 0x8f, 0x26, 0x8b, 0xbb, 0x55, 0x74, 0xdf, 0x0f, 0x08, 0x04, 0x62, 0x98, 0xcd, + 0x41, 0xb8, 0xea, 0xb6, 0xac, 0xa9, 0x15, 0x60, 0x32, 0xf4, 0xfa, 0x68, 0xbc, 0x6c, + 0xc0, 0xa1, 0x02, 0xb5, 0xb8, 0x48, 0x8d, 0xea, 0xa7, 0x2d, 0x65, 0xac, 0x47, 0xe9, + 0x7d, 0xf6, 0x26, 0xce, 0x7a, 0x97, 0x4e, 0xd0, 0x04, 0x0d, 0x69, 0x4e, 0x85, 0x9d, + 0x4b, 0xd5, 0x35, 0x16, 0xb0, 0xc2, 0xa9, 0x84, 0x9d, 0x4d, 0xc7, 0xc8, 0x1c, 0x49, + 0x56, 0xf6, 0xb2, 0xdd, 0xf3, 0xf3, 0xb7, 0x40, 0x5b, 0x71, 0x61, 0xaf, 0xbc, 0x7d, + 0x28, 0x12, 0x0e, 0xd7, 0xb0, 0x2d, 0xa1, 0x2d, 0x13, 0xc8, 0x0d, 0x29, 0xb3, 0xda, + 0x16, 0x3b, 0x09, 0x4c, 0xcb, 0xb8, 0xd9, 0x13, 0xa3, 0xa3, 0xa1, 0x13, 0xd6, 0x72, + 0x24, 0x5f, 0xdf, 0xbb, 0x72, 0xc6, 0xea, 0xaa, 0x15, 0xe1, 0x6b, 0xfd, 0xdc, 0xcb, + 0x61, 0x1b, 0xf2, 0xaf, 0x92, 0x59, 0xbb, 0x0e, 0x12, 0x89, 0x31, 0xa6, 0xa3, 0x45, + 0x03, 0xf6, 0xfd, 0x77, 0xbf, 0xb0, 0x9c, 0x22, 0xd5, 0x86, 0x6f, 0xc4, 0x80, 0x26, + 0xf8, 0x14, 0xc2, 0x61, 0xfd, 0x74, 0x41, 0xe8, 0xe2, 0x43, 0x81, 0x7c, 0xc9, 0xd4, + 0x52, 0x69, 0x05, 0x54, 0xbd, 0x94, 0x82, 0x57, 0x76, 0x26, 0xfe, 0xf7, 0x60, 0x6c, + 0x39, 0x09, 0xb3, 0x14, 0x86, 0x89, 0x22, 0x2a, 0x36, 0x56, 0x59, 0x2c, 0xfe, 0x37, + 0xbb, 0xbc, 0xf0, 0x74, 0x91, 0xbb, 0xa1, 0x19, 0xe0, 0x92, 0x93, 0x81, 0x98, 0xcc, + 0xa0, 0xab, 0x9b, 0xa3, 0xae, 0x4f, 0x27, 0x06, 0xa3, 0xaa, 0x90, 0x1b, 0xa0, 0x3d, + 0xda, 0x1e, 0x09, 0xe1, 0xfb, 0x26, 0xa5, 0xd7, 0x0b, 0x30, 0x30, 0x90, 0x9d, 0x9d, + 0x32, 0x03, 0xc1, 0x4b, 0x68, 0xfb, 0xec, 0x9d, 0xf7, 0xb9, 0x78, 0xa3, 0x2f, 0x4e, + 0xaf, 0x4a, 0x80, 0x85, 0xe7, 0x20, 0xd9, 0x37, 0xac, 0xf7, 0x3a, 0xca, 0x66, 0x3d, + 0x21, 0xde, 0x88, 0xc7, 0xd6, 0xc3, 0xb3, 0xac, 0xab, 0x9d, 0xb9, 0x31, 0xc9, 0xc9, + 0x6e, 0x56, 0xda, 0x1b, 0x8e, 0x06, 0x6d, 0xd8, 0x6b, 0x58, 0x78, 0x19, 0xf9, 0xef, + 0xa2, 0x4d, 0x34, 0x29, 0xe5, 0x6d, 0x98, 0x1c, 0x68, 0xfe, 0x49, 0xaa, 0x1f, 0xfa, + 0x1e, 0xc1, 0xd3, 0x18, 0xc8, 0x6c, 0x60, 0x28, 0xbf, 0xa2, 0x92, 0xfa, 0x6a, 0x6d, + 0xf1, 0x1f, 0x8e, 0x9a, 0x75, 0x31, 0x1e, 0x0e, 0x73, 0xfd, 0xc8, 0x1b, 0xd5, 0xf9, + 0x85, 0x25, 0x97, 0x01, 0x9f, 0x1b, 0x55, 0xf0, 0x97, 0x33, 0xee, 0x51, 0x29, 0xb5, + 0x0c, 0xb2, 0x05, 0xb6, 0xe0, 0x06, 0xda, 0x67, 0x73, 0x3d, 0x52, 0xb8, 0x4d, 0xae, + 0xe2, 0x6e, 0xf6, 0x0e, 0xf0, 0x99, 0x20, 0xe3, 0x5e, 0xa1, 0x7a, 0xa1, 0x6a, 0x95, + 0xf5, 0x41, 0x9c, 0x3e, 0x23, 0x7a, 0xd2, 0xb5, 0x9d, 0xc8, 0xb6, 0xd7, 0xc3, 0x2a, + 0xbb, 0xfa, 0x8e, 0xaf, 0xb7, 0x43, 0xda, 0x87, 0x6b, 0x18, 0x59, 0x78, 0xe4, 0xf4, + 0x43, 0x2a, 0x44, 0xb4, 0x63, 0x21, 0x53, 0x09, 0xb9, 0x6a, 0xa5, 0x74, 0x6a, 0x4a, + 0xa6, 0xe9, 0x34, 0xd2, 0xf3, 0x4d, 0xed, 0x72, 0xbf, 0xa4, 0x00, 0x08, 0x8b, 0xd4, + 0x97, 0x0e, 0xa7, 0x74, 0x01, 0x99, 0x8e, 0xd3, 0xf8, 0xa4, 0xd0, 0x74, 0x5b, 0x4b, + 0xe2, 0xb0, 0xef, 0x7f, 0x91, 0x23, 0xde, 0x06, 0x4c, 0xa8, 0x98, 0x6e, 0xde, 0xf6, + 0x49, 0xbb, 0x01, 0x13, 0x71, 0x71, 0x96, 0x0b, 0x97, 0x00, 0x4d, 0x8c, 0xa5, 0x95, + 0xea, 0x60, 0xfa, 0x97, 0xd9, 0x2a, 0x03, 0x54, 0xe5, 0x25, 0xdf, 0x7e, 0xc1, 0x5f, + 0xa5, 0x61, 0x46, 0x18, 0x77, 0xdb, 0xb1, 0xab, 0x36, 0x2e, 0x1f, 0x4a, 0x22, 0x95, + 0x2c, 0x97, 0x5c, 0x5a, 0xfa, 0x4b, 0x26, 0xe1, 0xf0, 0x53, 0x8a, 0x53, 0x47, 0x25, + 0x59, 0x73, 0x0a, 0x1a, 0x85, 0xac, 0xeb, 0x7e, 0x08, 0x1b, 0x91, 0xc4, 0xb1, 0xc2, + 0x0b, 0x4e, 0x89, 0x00, 0x80, 0xdc, 0xc3, 0xd9, 0xde, 0x84, 0xfc, 0xb5, 0x1e, 0x76, + 0xe1, 0x52, 0x7f, 0xef, 0x0f, 0x3e, 0x7c, 0x53, 0x77, 0x57, 0x23, 0x85, 0x83, 0x33, + 0x3e, 0x3f, 0x2d, 0x9a, 0x70, 0x6c, 0x14, 0xce, 0x6b, 0x64, ], ock: [ - 0x95, 0x9a, 0x28, 0x02, 0x17, 0xb9, 0xef, 0x54, 0xab, 0x44, 0x3b, 0x8d, 0x0f, 0xea, - 0x5a, 0x11, 0x75, 0x86, 0xae, 0x8a, 0xdd, 0x64, 0x99, 0x7d, 0x02, 0xec, 0xb8, 0xb5, - 0xcb, 0xac, 0x14, 0x87, + 0xef, 0x11, 0x62, 0x27, 0x4d, 0xde, 0x33, 0xcd, 0x97, 0x69, 0xbc, 0x8e, 0xcf, 0x52, + 0x52, 0x10, 0x83, 0xe2, 0x46, 0xfd, 0xeb, 0x50, 0xce, 0x36, 0xb3, 0x34, 0xc4, 0x7f, + 0x98, 0x52, 0x19, 0x0e, ], op: [ - 0x6b, 0x27, 0xda, 0xcc, 0xb5, 0xa8, 0x20, 0x7f, 0x53, 0x2d, 0x10, 0xca, 0x23, 0x8f, - 0x97, 0x86, 0x64, 0x8a, 0x11, 0xb5, 0x96, 0x6e, 0x51, 0xa2, 0xf7, 0xd8, 0x9e, 0x15, - 0xd2, 0x9b, 0x8f, 0xdf, 0x4e, 0x41, 0x8c, 0x3c, 0x54, 0x3d, 0x6b, 0xf0, 0x15, 0x31, + 0xe7, 0x25, 0xc0, 0x2a, 0xc9, 0x18, 0x84, 0xe1, 0x45, 0x2e, 0x5b, 0xbe, 0x8d, 0xbf, + 0xb1, 0xe0, 0xcd, 0xee, 0x00, 0x56, 0xdc, 0x2f, 0x5f, 0xc1, 0x92, 0x39, 0xb2, 0x0b, + 0x7b, 0xe7, 0x62, 0xe0, 0x4e, 0x41, 0x8c, 0x3c, 0x54, 0x3d, 0x6b, 0xf0, 0x15, 0x31, 0x74, 0xa0, 0x4e, 0x85, 0x44, 0xae, 0x7c, 0x58, 0x09, 0x2a, 0x2e, 0x4e, 0x5d, 0x7d, 0x9c, 0x67, 0x2a, 0x3a, 0x79, 0x11, 0x09, 0x03, ], c_out: [ - 0x65, 0x9d, 0xef, 0x25, 0x08, 0x34, 0x84, 0x6f, 0x85, 0xeb, 0x9e, 0x39, 0x5b, 0xef, - 0xe1, 0x5e, 0x1d, 0x4d, 0x2a, 0xb4, 0x36, 0x2d, 0x1a, 0xa7, 0xde, 0x84, 0x24, 0x3f, - 0x74, 0x45, 0xd5, 0xd2, 0x8f, 0x47, 0x92, 0x92, 0x4d, 0x60, 0xc7, 0x60, 0x53, 0x3c, - 0xef, 0x05, 0x10, 0x47, 0xe5, 0x4d, 0x52, 0x1e, 0x2b, 0x07, 0x2d, 0x13, 0x30, 0xb2, - 0x68, 0x5e, 0xb8, 0x70, 0x10, 0x6c, 0x66, 0x1f, 0x1f, 0x07, 0xb7, 0x6f, 0xdb, 0xb5, - 0x14, 0xaa, 0x9b, 0x94, 0xad, 0x41, 0x91, 0xbc, 0x0d, 0x2d, + 0x26, 0x2e, 0x7e, 0x3a, 0x8e, 0x00, 0x17, 0x2c, 0xad, 0xf8, 0x7a, 0x68, 0x17, 0x26, + 0x29, 0xce, 0x5c, 0x46, 0xb8, 0x6a, 0xe6, 0x77, 0x98, 0xb8, 0xe1, 0xf7, 0xbe, 0xf8, + 0x0f, 0x1b, 0x55, 0x5b, 0xee, 0x61, 0xb1, 0x74, 0x0c, 0xa6, 0xfd, 0x65, 0x20, 0x5a, + 0x66, 0xa5, 0x81, 0x36, 0x76, 0xdc, 0x4b, 0xf3, 0xb7, 0x86, 0x86, 0x43, 0x87, 0xb9, + 0xbe, 0x99, 0xae, 0x6c, 0x15, 0xb2, 0xfc, 0xf1, 0x86, 0x05, 0xfd, 0x1d, 0xfd, 0x12, + 0xa5, 0xd1, 0x0a, 0x43, 0xec, 0xa6, 0xb0, 0xed, 0xfc, 0xb5, ], }, TestVector { ovk: [ - 0xf6, 0x2c, 0x05, 0xe8, 0x48, 0xa8, 0x73, 0xef, 0x88, 0x5e, 0x12, 0xb0, 0x8c, 0x5e, - 0x7c, 0xa2, 0xf3, 0x24, 0x24, 0xba, 0xcc, 0x75, 0x4c, 0xb6, 0x97, 0x50, 0x44, 0x4d, - 0x35, 0x5f, 0x51, 0x06, + 0xda, 0x02, 0x6d, 0x6e, 0x32, 0xc4, 0xc9, 0xf4, 0x5a, 0x79, 0x3d, 0xb3, 0x23, 0xf8, + 0x2b, 0xe1, 0xec, 0xcd, 0x30, 0x49, 0x3d, 0xc0, 0x70, 0x89, 0x35, 0xe0, 0xb4, 0x2e, + 0x12, 0x5b, 0xfe, 0xd5, ], ivk: [ - 0xb5, 0xc5, 0x89, 0x49, 0x43, 0x95, 0x69, 0x33, 0xc0, 0xe5, 0xc1, 0x2d, 0x31, 0x1f, - 0xc1, 0x2c, 0xba, 0x58, 0x35, 0x4b, 0x5c, 0x38, 0x9e, 0xdc, 0x03, 0xda, 0x55, 0x08, - 0x4f, 0x74, 0xc2, 0x05, + 0xff, 0x6a, 0xc3, 0x6c, 0x82, 0xc1, 0x94, 0x57, 0x1e, 0x3f, 0xba, 0x8b, 0xdb, 0x26, + 0x7a, 0xc9, 0x0e, 0xdb, 0x6c, 0xa7, 0xb4, 0x21, 0x60, 0x60, 0xe8, 0x26, 0xa4, 0x3a, + 0x93, 0xde, 0x3c, 0x07, ], default_d: [ - 0xbe, 0xbb, 0x0f, 0xb4, 0x6b, 0x8a, 0xaf, 0xf8, 0x90, 0x40, 0xf6, + 0xce, 0x57, 0xe4, 0x2f, 0x7d, 0x60, 0x05, 0xd0, 0xc3, 0x28, 0xb2, ], default_pk_d: [ - 0xd1, 0x1d, 0xa0, 0x1f, 0x0b, 0x43, 0xbd, 0xd5, 0x28, 0x8d, 0x32, 0x38, 0x5b, 0x87, - 0x71, 0xd2, 0x23, 0x49, 0x3c, 0x69, 0x80, 0x25, 0x44, 0x04, 0x3f, 0x77, 0xcf, 0x1d, - 0x71, 0xc1, 0xcb, 0x8c, + 0x75, 0x8a, 0x9a, 0xd6, 0x6f, 0xe3, 0x1a, 0x08, 0x30, 0xcb, 0x5d, 0x39, 0x89, 0x4d, + 0x62, 0x23, 0xad, 0xaa, 0x11, 0x08, 0xc0, 0xae, 0xcd, 0x54, 0xcc, 0xd9, 0xfd, 0x1c, + 0x1e, 0xd5, 0xe7, 0x62, ], v: 700000000, rcm: [ @@ -1301,14 +1323,14 @@ pub(crate) fn make_test_vectors() -> Vec { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ], cv: [ - 0x77, 0x08, 0x94, 0xc7, 0xa5, 0x45, 0x8b, 0x16, 0x7d, 0x85, 0x18, 0xa5, 0x47, 0xbc, - 0x62, 0xb4, 0x6b, 0xa1, 0x89, 0x80, 0x7e, 0xb9, 0x7c, 0x08, 0x28, 0x4e, 0x1b, 0x92, - 0xb6, 0xda, 0x35, 0x2a, + 0x51, 0x98, 0xc0, 0xe1, 0x10, 0x6d, 0x59, 0xab, 0x12, 0x9e, 0xb1, 0x86, 0xab, 0x4a, + 0xa4, 0x05, 0x74, 0x87, 0x6e, 0xa0, 0x8f, 0x3f, 0xb5, 0x7f, 0xf6, 0x68, 0x42, 0x63, + 0x20, 0x0a, 0x75, 0xe0, ], cmu: [ - 0x0d, 0xd4, 0x2d, 0x63, 0xff, 0x38, 0xee, 0x4c, 0x46, 0x65, 0x1e, 0x4d, 0x1d, 0xd5, - 0x22, 0x7d, 0xc5, 0x97, 0x33, 0x9f, 0x7d, 0x70, 0x4c, 0x51, 0x8e, 0xf4, 0x02, 0xf8, - 0xcd, 0x6f, 0x37, 0x44, + 0x0a, 0x2d, 0x48, 0xf2, 0xc9, 0x82, 0x8a, 0xb5, 0x0b, 0x36, 0xed, 0x04, 0xb9, 0x76, + 0x13, 0xcd, 0x90, 0x83, 0xe1, 0x85, 0xb9, 0xce, 0xe2, 0xf1, 0xcf, 0xb1, 0xd6, 0x2d, + 0x93, 0x7d, 0x99, 0x2f, ], esk: [ 0x6d, 0xa9, 0x45, 0xd3, 0x03, 0x81, 0xc2, 0xee, 0xd2, 0xb8, 0x1d, 0x27, 0x08, 0x6d, @@ -1316,26 +1338,28 @@ pub(crate) fn make_test_vectors() -> Vec { 0xea, 0xbc, 0x46, 0x02, ], epk: [ - 0xa5, 0x2f, 0x0b, 0x5a, 0xe4, 0xa9, 0x4f, 0xa8, 0x8a, 0xa7, 0xcb, 0x7e, 0x5f, 0x0f, - 0x34, 0x3c, 0xa2, 0xfa, 0x66, 0xb3, 0x94, 0x41, 0xba, 0x66, 0x28, 0x20, 0xe4, 0x6a, - 0x9b, 0xbb, 0xa3, 0xb5, + 0x31, 0x60, 0xb3, 0xab, 0x25, 0x2e, 0xbd, 0x6b, 0xfb, 0x7f, 0x14, 0x8c, 0x14, 0x0d, + 0xf5, 0x90, 0xb3, 0x29, 0x9e, 0xc4, 0x1f, 0xf4, 0xe2, 0xc4, 0x48, 0x2d, 0x8b, 0x15, + 0x00, 0xe2, 0x4d, 0xec, ], shared_secret: [ - 0x81, 0xc7, 0xc5, 0xd5, 0xff, 0x63, 0xe9, 0xe6, 0x1f, 0xe3, 0x5a, 0x4b, 0x39, 0x6e, - 0xa7, 0xf1, 0x9e, 0x48, 0x07, 0x6f, 0x22, 0x09, 0x0a, 0xe7, 0x29, 0xa4, 0x11, 0x79, - 0x2f, 0x08, 0x58, 0x4a, + 0x0c, 0x77, 0x10, 0x2f, 0x32, 0x04, 0x37, 0x25, 0x66, 0x0d, 0xf2, 0x41, 0x48, 0xf0, + 0x60, 0x6d, 0xe6, 0x37, 0xd5, 0x92, 0xb6, 0xf6, 0x4e, 0x1f, 0xe3, 0x17, 0x90, 0x83, + 0x0f, 0x4c, 0xdc, 0x5a, ], k_enc: [ - 0xb4, 0xf9, 0xa7, 0xff, 0x9c, 0x60, 0x80, 0x6e, 0xc7, 0xf5, 0x5c, 0xee, 0xbe, 0xc2, - 0xba, 0x54, 0x76, 0x19, 0x8e, 0x29, 0x1d, 0xf7, 0x57, 0x8c, 0x2b, 0xef, 0x87, 0xe6, - 0x4a, 0x71, 0x6a, 0xe7, + 0x25, 0x8d, 0x67, 0x9b, 0x11, 0x4a, 0x9e, 0x1d, 0x2b, 0x29, 0xfc, 0x6b, 0xbc, 0x0e, + 0xde, 0x59, 0x70, 0x13, 0x9d, 0x0a, 0x21, 0x79, 0x54, 0x8d, 0xd5, 0x7a, 0xb7, 0x63, + 0x46, 0x0b, 0xb5, 0x94, ], p_enc: [ - 0x01, 0xbe, 0xbb, 0x0f, 0xb4, 0x6b, 0x8a, 0xaf, 0xf8, 0x90, 0x40, 0xf6, 0x00, 0x27, - 0xb9, 0x29, 0x00, 0x00, 0x00, 0x00, 0x49, 0xf9, 0x0b, 0x47, 0xfd, 0x52, 0xfe, 0xe7, - 0xc1, 0xc8, 0x1f, 0x0d, 0xcb, 0x5b, 0x74, 0xc3, 0xfb, 0x9b, 0x3e, 0x03, 0x97, 0x6f, - 0x8b, 0x75, 0x24, 0xea, 0xba, 0xd0, 0x08, 0x89, 0x21, 0x07, 0xf6, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0xce, 0x57, 0xe4, 0x2f, 0x7d, 0x60, 0x05, 0xd0, 0xc3, 0x28, 0xb2, 0x00, 0x27, + 0xb9, 0x29, 0x00, 0x00, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, + 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, + 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x49, 0xf9, 0x0b, 0x47, + 0xfd, 0x52, 0xfe, 0xe7, 0xc1, 0xc8, 0x1f, 0x0d, 0xcb, 0x5b, 0x74, 0xc3, 0xfb, 0x9b, + 0x3e, 0x03, 0x97, 0x6f, 0x8b, 0x75, 0x24, 0xea, 0xba, 0xd0, 0x08, 0x89, 0x21, 0x07, + 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -1371,91 +1395,93 @@ pub(crate) fn make_test_vectors() -> Vec { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ], c_enc: [ - 0x20, 0x77, 0xdf, 0x43, 0x25, 0x24, 0x61, 0x4c, 0x07, 0x6e, 0x77, 0x93, 0x02, 0x41, - 0x91, 0xaa, 0xc9, 0xe4, 0x93, 0xf5, 0xc8, 0xa9, 0x87, 0x45, 0xae, 0x65, 0x31, 0x0c, - 0xfc, 0xb5, 0x75, 0x56, 0x4a, 0x93, 0xf1, 0x27, 0x2b, 0xce, 0x90, 0x07, 0x77, 0xb8, - 0x50, 0x49, 0x7e, 0x84, 0x54, 0x0c, 0xb1, 0x92, 0x03, 0x85, 0x65, 0x88, 0x2f, 0xa4, - 0xf3, 0x71, 0x21, 0x3e, 0xb5, 0x09, 0x00, 0x41, 0xff, 0xd9, 0x24, 0x7b, 0xee, 0x2b, - 0xb1, 0x53, 0x21, 0x22, 0x83, 0xb2, 0x7e, 0x36, 0xe2, 0x84, 0x60, 0x3c, 0x0b, 0xc4, - 0x0c, 0x46, 0x5f, 0xc6, 0xab, 0x8f, 0x88, 0x98, 0x5e, 0xf5, 0x0e, 0x2a, 0xb0, 0xeb, - 0x66, 0xa6, 0x34, 0x30, 0x9b, 0xb9, 0x02, 0xc6, 0xcd, 0xd6, 0xa5, 0x55, 0xb8, 0xc3, - 0x71, 0x48, 0x9f, 0x57, 0xc7, 0xea, 0x3b, 0x54, 0x37, 0xf2, 0x87, 0xc7, 0x4e, 0x35, - 0xe0, 0x34, 0xcc, 0x68, 0x08, 0xe2, 0xc9, 0xf2, 0xc9, 0x73, 0xfa, 0xc9, 0x6e, 0x84, - 0x9d, 0x31, 0xde, 0x76, 0xf8, 0x06, 0x63, 0xa5, 0x82, 0xb2, 0x3a, 0xfc, 0x36, 0x45, - 0x5e, 0xc4, 0x6e, 0x23, 0x8c, 0xb2, 0x84, 0xda, 0xf1, 0x11, 0x4a, 0x6e, 0x5b, 0xd0, - 0x28, 0x9a, 0xef, 0xb7, 0x46, 0x94, 0x31, 0xb8, 0xb8, 0x60, 0x89, 0xb9, 0xd3, 0x6f, - 0xfd, 0x67, 0x45, 0xbd, 0x86, 0x7b, 0xaa, 0x6b, 0x58, 0xfb, 0x30, 0xaf, 0xa0, 0x97, - 0xab, 0x9e, 0x57, 0x38, 0x8f, 0x4f, 0xdf, 0xc0, 0xfd, 0x48, 0x3d, 0xc6, 0x7f, 0x02, - 0xbc, 0x07, 0x99, 0x0e, 0x1a, 0x39, 0x7b, 0x11, 0x2d, 0x5d, 0xbc, 0xf2, 0x2f, 0x9b, - 0x64, 0xf5, 0xf5, 0x43, 0x10, 0x24, 0x63, 0xe3, 0x0f, 0x46, 0x81, 0x72, 0x85, 0x39, - 0xc0, 0xc5, 0xc5, 0xe0, 0x0a, 0x25, 0x35, 0xae, 0xf7, 0x68, 0xe3, 0xaf, 0x7d, 0x47, - 0xa0, 0x8d, 0xdb, 0x99, 0xea, 0x2e, 0xd0, 0x0c, 0x52, 0xbf, 0x4b, 0x5e, 0xb3, 0x14, - 0x05, 0x85, 0xb0, 0xf9, 0x0e, 0xcf, 0x7d, 0x21, 0x5b, 0x4c, 0xc1, 0x8a, 0xf9, 0xae, - 0xc8, 0x17, 0x0c, 0x6d, 0xb6, 0xc6, 0x69, 0x98, 0xb8, 0xda, 0x0f, 0x09, 0x17, 0xf1, - 0x38, 0x0c, 0x87, 0xa4, 0x18, 0x1b, 0x86, 0xc6, 0xcd, 0xfe, 0x6f, 0x2d, 0xb2, 0x21, - 0x41, 0xe7, 0x98, 0x4b, 0x1a, 0xac, 0xf7, 0xce, 0xc5, 0xe7, 0xd0, 0x76, 0xaa, 0xc5, - 0x47, 0x9e, 0xd7, 0x14, 0x40, 0xb2, 0xd4, 0x60, 0x18, 0x5b, 0xa3, 0xdb, 0xea, 0x03, - 0xc8, 0xfc, 0xca, 0xc0, 0x9a, 0xec, 0xd3, 0x3a, 0x3f, 0xdd, 0xa9, 0xa1, 0x34, 0xea, - 0x42, 0xa1, 0xa9, 0x78, 0xc4, 0x05, 0x17, 0x99, 0xe6, 0xcc, 0x69, 0x6f, 0x8a, 0x49, - 0x40, 0x0a, 0xea, 0xd6, 0x65, 0x2f, 0x93, 0xa2, 0x58, 0x22, 0x0c, 0x63, 0x38, 0xb9, - 0xe7, 0x3b, 0x10, 0xa0, 0x1c, 0xd2, 0xec, 0x39, 0x72, 0x86, 0x1c, 0x7b, 0x62, 0x69, - 0x5a, 0xda, 0xa5, 0x41, 0x4a, 0x78, 0x74, 0x50, 0xe7, 0xa5, 0xf8, 0x21, 0xe4, 0xf2, - 0x45, 0xdd, 0x97, 0x2c, 0x08, 0x92, 0xe8, 0x6f, 0xa1, 0x26, 0xba, 0x59, 0x5c, 0x12, - 0x25, 0x73, 0x8e, 0x2f, 0x8b, 0xe3, 0x6f, 0x11, 0xdc, 0xc5, 0x2c, 0xed, 0x4f, 0x78, - 0x75, 0xdf, 0x5b, 0xbb, 0xd8, 0x3a, 0xec, 0x8d, 0x43, 0x13, 0x07, 0x2d, 0x7e, 0xc9, - 0x47, 0xaf, 0x86, 0xb5, 0x6b, 0x65, 0xfc, 0xb1, 0xbd, 0x32, 0xf0, 0xdb, 0x0c, 0xb3, - 0x7d, 0xea, 0xa6, 0xcd, 0xe0, 0xdf, 0xe4, 0xbd, 0xb8, 0x09, 0x16, 0x1e, 0xda, 0x03, - 0x4a, 0x94, 0x9a, 0x3a, 0x03, 0x9a, 0xf9, 0xbb, 0xe0, 0x9e, 0xaf, 0xb3, 0x5b, 0x7c, - 0xd8, 0xb5, 0x32, 0x83, 0x42, 0xc3, 0x93, 0x22, 0x1a, 0x4f, 0x13, 0x4b, 0x15, 0xa4, - 0x16, 0x3c, 0x05, 0x3b, 0x32, 0xeb, 0xa8, 0x5e, 0x59, 0x36, 0x06, 0xda, 0x67, 0xa1, - 0x1c, 0xe1, 0x74, 0xb7, 0x7b, 0xbe, 0xfd, 0x50, 0xef, 0x10, 0x25, 0xe9, 0x4a, 0x06, - 0xc5, 0xe0, 0x98, 0x8d, 0xb7, 0xf9, 0xda, 0x54, 0x0a, 0xa3, 0xb1, 0xc0, 0x33, 0x09, - 0xb4, 0xb1, 0x40, 0x01, 0xe2, 0xc4, 0x5a, 0xa9, 0x99, 0x65, 0x0b, 0x01, 0xaa, 0x3b, - 0xef, 0x5f, 0xb2, 0xd3, 0x38, 0x0c, 0xbf, 0x33, 0xc5, 0x5d, 0x45, 0x70, 0x25, 0x9f, - 0x1e, 0x3e, 0xd7, 0xe0, 0x0c, 0xa9, + 0x8e, 0xf1, 0xd9, 0x6b, 0x3b, 0x0d, 0x11, 0x45, 0xa8, 0x4b, 0x9c, 0xe9, 0x0a, 0x24, + 0xf9, 0xa2, 0x4c, 0x29, 0xbe, 0x05, 0x54, 0x54, 0x80, 0xf4, 0x43, 0xae, 0xb4, 0xe1, + 0x62, 0x41, 0x28, 0xe0, 0x7d, 0x61, 0x56, 0x14, 0x06, 0xea, 0x85, 0x28, 0xb0, 0xc6, + 0x82, 0xec, 0xdf, 0x19, 0x4b, 0x77, 0x90, 0x06, 0x32, 0x3d, 0x72, 0xce, 0x21, 0xda, + 0x9c, 0xaf, 0x5f, 0x71, 0x05, 0x9f, 0x94, 0xfe, 0x08, 0xbb, 0xea, 0x1c, 0x3c, 0xb8, + 0x20, 0x81, 0x9b, 0x94, 0x44, 0x15, 0xfb, 0x86, 0x91, 0x76, 0xaa, 0x54, 0x43, 0xda, + 0x0e, 0xc6, 0xf1, 0xf1, 0x3b, 0x68, 0x9b, 0x4a, 0xe1, 0x9f, 0xfc, 0x6c, 0xed, 0xa3, + 0xfd, 0x92, 0xd8, 0x33, 0x43, 0x4d, 0x99, 0x87, 0x2f, 0xed, 0x90, 0xb7, 0xd4, 0xc9, + 0x85, 0xc1, 0x95, 0x6d, 0xe6, 0x59, 0x1f, 0x80, 0x24, 0xbb, 0x4e, 0x6f, 0x9d, 0x41, + 0xf8, 0x7c, 0x6e, 0xe9, 0x69, 0xf5, 0xfd, 0x91, 0x81, 0x59, 0x57, 0x1c, 0xd8, 0x59, + 0xc7, 0x90, 0x53, 0x03, 0x0c, 0x61, 0xc6, 0x32, 0x67, 0x34, 0xce, 0xba, 0x98, 0x04, + 0xcc, 0x2b, 0x3d, 0x09, 0x98, 0xad, 0x99, 0xda, 0x2e, 0xb1, 0xa9, 0x8b, 0xea, 0x62, + 0x65, 0xce, 0x10, 0xcb, 0xfa, 0xdd, 0xb5, 0x8e, 0x17, 0x33, 0x73, 0x4c, 0xcb, 0xd4, + 0xd7, 0x27, 0x18, 0x06, 0xdf, 0xfa, 0xd1, 0x6e, 0xb7, 0xca, 0x90, 0x69, 0xea, 0x8e, + 0xbd, 0xb7, 0x16, 0xed, 0xf7, 0x6f, 0x40, 0x87, 0xee, 0xc6, 0xf8, 0xbe, 0x47, 0xa6, + 0x6c, 0xad, 0x2f, 0x4e, 0xc3, 0x8f, 0xc9, 0x0f, 0x0d, 0xa2, 0x06, 0xd5, 0x8b, 0xf1, + 0x72, 0x7f, 0x0e, 0x31, 0xe2, 0xe2, 0xb6, 0xb0, 0xe5, 0xd0, 0x23, 0x1e, 0x4f, 0xab, + 0x59, 0x5e, 0xbd, 0x27, 0x1a, 0x37, 0x93, 0x74, 0xbb, 0x9b, 0xee, 0xab, 0x9e, 0xa7, + 0xf8, 0xe7, 0xe3, 0x89, 0x28, 0x36, 0x83, 0x13, 0x4d, 0x88, 0x68, 0xa5, 0xb6, 0x61, + 0x24, 0xe5, 0x55, 0xdc, 0x26, 0x9d, 0x5e, 0x11, 0x44, 0xb3, 0xb8, 0x58, 0xf8, 0x36, + 0x6a, 0xa3, 0xcb, 0x70, 0x1f, 0x2a, 0xb3, 0x9f, 0x79, 0xfb, 0x45, 0x0f, 0x9d, 0x29, + 0x21, 0xfd, 0x51, 0xbf, 0x2f, 0xf2, 0xb1, 0x17, 0xb4, 0x13, 0x1b, 0xd4, 0xe7, 0x01, + 0x38, 0x13, 0x8f, 0xa5, 0x3b, 0x0f, 0xba, 0x1e, 0xa0, 0x9e, 0xf7, 0x7e, 0x1e, 0x28, + 0x74, 0x2b, 0xa8, 0x8d, 0x62, 0x0d, 0x04, 0x78, 0xe0, 0x1e, 0x8d, 0x96, 0xb5, 0x20, + 0x6f, 0x35, 0xf6, 0x01, 0x92, 0xc6, 0x80, 0xb4, 0x6c, 0x69, 0xa8, 0xe0, 0x87, 0x9d, + 0xe3, 0x09, 0xd5, 0xf6, 0x1b, 0xee, 0xa5, 0xfb, 0x1c, 0xcc, 0x0d, 0xa1, 0x6c, 0x13, + 0xd8, 0xaf, 0xba, 0xb6, 0xb7, 0x0f, 0xa6, 0x9b, 0x1c, 0x15, 0x3d, 0x74, 0x30, 0x7e, + 0x58, 0xd8, 0xac, 0xa7, 0x3e, 0x81, 0x94, 0x4d, 0xda, 0xdd, 0xae, 0x48, 0xc7, 0x15, + 0x40, 0xf4, 0xc8, 0x2e, 0xb2, 0xb3, 0xae, 0x26, 0x87, 0x91, 0x3d, 0x93, 0xb3, 0xb1, + 0x6d, 0x32, 0x91, 0x88, 0x0e, 0x7a, 0x31, 0x99, 0x0e, 0xc4, 0x0f, 0x97, 0x3d, 0x34, + 0xf2, 0x25, 0x40, 0x0c, 0xdd, 0x86, 0x72, 0x66, 0x55, 0x8f, 0xba, 0xff, 0x91, 0x5c, + 0xd5, 0xe5, 0x2e, 0x0e, 0x9a, 0xcf, 0x56, 0xb7, 0x83, 0x33, 0x23, 0x18, 0x3d, 0xc7, + 0x76, 0x57, 0x40, 0x2d, 0x53, 0x44, 0x5d, 0xe2, 0x73, 0xce, 0x89, 0x66, 0x09, 0x86, + 0xbb, 0x7c, 0x91, 0x5f, 0x1f, 0x79, 0xf8, 0x9e, 0x99, 0x9b, 0x01, 0x23, 0xfd, 0xea, + 0x8f, 0xb0, 0x5e, 0x8e, 0x81, 0xd5, 0xfc, 0xe9, 0xb0, 0x7b, 0xbe, 0xe0, 0x69, 0x5c, + 0xbb, 0x29, 0x99, 0x34, 0xdd, 0x6f, 0x0d, 0x0d, 0xe5, 0xc6, 0x9e, 0xd6, 0x18, 0x45, + 0xcb, 0x1f, 0xf7, 0x21, 0xd3, 0xf2, 0xa9, 0x9b, 0x7a, 0xed, 0xe8, 0x63, 0x61, 0x55, + 0xf7, 0x17, 0x89, 0x9a, 0x66, 0x8e, 0xb9, 0xf3, 0x38, 0x17, 0xca, 0x02, 0x3b, 0x8c, + 0x23, 0x71, 0x36, 0x25, 0x8e, 0x71, 0xff, 0xd7, 0x79, 0x4d, 0xa9, 0x34, 0xa1, 0x8c, + 0xec, 0xbc, 0xbd, 0x0f, 0x55, 0x71, 0xd6, 0x97, 0x73, 0xd0, 0xb8, 0x1a, 0x8c, 0x1b, + 0xa6, 0x61, 0xe0, 0xc4, 0xcf, 0xdb, 0xa5, 0x23, 0xa3, 0xab, 0x5a, 0x40, 0x0c, 0x0c, + 0x61, 0xe2, 0x16, 0xb0, 0x3d, 0x8e, 0xb5, 0x3b, 0xe6, 0x58, 0x13, 0xa5, 0x51, 0x9d, + 0x7c, 0xd3, 0xe4, 0xb3, 0xbd, 0x0f, 0x33, 0x9b, 0xfd, 0x53, 0x0b, 0xea, 0xe7, 0x3c, + 0xa7, 0x98, 0x01, 0xf9, 0x71, 0x49, 0x6e, 0x28, 0x71, 0xb0, ], ock: [ - 0x54, 0xce, 0xb1, 0x1b, 0xb0, 0xe8, 0xf8, 0x54, 0x86, 0x10, 0xd1, 0x1f, 0xf1, 0xab, - 0x14, 0x92, 0xd1, 0x8d, 0x5c, 0x85, 0x3c, 0x8f, 0x2f, 0x0c, 0xd5, 0xd1, 0x9d, 0x6d, - 0x34, 0xcf, 0x7c, 0x2d, + 0xd5, 0x81, 0x15, 0xd7, 0x77, 0x91, 0xe6, 0x4c, 0x68, 0x2a, 0x55, 0x95, 0x73, 0x54, + 0x96, 0x56, 0x95, 0x9d, 0xe4, 0xee, 0xa0, 0xc0, 0x71, 0xc7, 0x80, 0x55, 0x0d, 0x60, + 0xc5, 0xf1, 0x61, 0xfb, ], op: [ - 0xd1, 0x1d, 0xa0, 0x1f, 0x0b, 0x43, 0xbd, 0xd5, 0x28, 0x8d, 0x32, 0x38, 0x5b, 0x87, - 0x71, 0xd2, 0x23, 0x49, 0x3c, 0x69, 0x80, 0x25, 0x44, 0x04, 0x3f, 0x77, 0xcf, 0x1d, - 0x71, 0xc1, 0xcb, 0x8c, 0x6d, 0xa9, 0x45, 0xd3, 0x03, 0x81, 0xc2, 0xee, 0xd2, 0xb8, + 0x75, 0x8a, 0x9a, 0xd6, 0x6f, 0xe3, 0x1a, 0x08, 0x30, 0xcb, 0x5d, 0x39, 0x89, 0x4d, + 0x62, 0x23, 0xad, 0xaa, 0x11, 0x08, 0xc0, 0xae, 0xcd, 0x54, 0xcc, 0xd9, 0xfd, 0x1c, + 0x1e, 0xd5, 0xe7, 0x62, 0x6d, 0xa9, 0x45, 0xd3, 0x03, 0x81, 0xc2, 0xee, 0xd2, 0xb8, 0x1d, 0x27, 0x08, 0x6d, 0x22, 0x48, 0xe7, 0xc4, 0x49, 0xfe, 0x50, 0x9b, 0x38, 0xe2, 0x76, 0x79, 0x11, 0x89, 0xea, 0xbc, 0x46, 0x02, ], c_out: [ - 0xe7, 0x72, 0xe0, 0x1d, 0x61, 0x09, 0xb6, 0xf9, 0x85, 0xb1, 0x77, 0x2e, 0xd1, 0x55, - 0x0a, 0x94, 0x7b, 0x35, 0xa8, 0x4b, 0x3e, 0x71, 0x12, 0x33, 0x31, 0xa3, 0xd6, 0x1f, - 0x1b, 0xf5, 0x96, 0x4e, 0x97, 0x42, 0x54, 0x42, 0xe5, 0xc8, 0xef, 0x2b, 0x9d, 0x84, - 0xab, 0x3d, 0xcb, 0xab, 0x9c, 0x96, 0xfe, 0x6a, 0x89, 0xce, 0x1d, 0x5e, 0x8a, 0x9b, - 0x83, 0xb5, 0x09, 0x0b, 0xb0, 0x7c, 0x50, 0x45, 0x0b, 0xbb, 0xfc, 0x8a, 0x74, 0x64, - 0xa7, 0x7c, 0x33, 0x97, 0x16, 0x33, 0xb2, 0x13, 0x68, 0xf0, + 0x6c, 0x37, 0xf0, 0xd7, 0xe0, 0xab, 0x23, 0xd5, 0x17, 0x11, 0x33, 0xa0, 0x02, 0x30, + 0xf4, 0x05, 0xdc, 0x36, 0x94, 0x6e, 0xa6, 0x74, 0xc8, 0xb5, 0xf9, 0x9c, 0x07, 0x85, + 0x86, 0xf8, 0xf4, 0x0c, 0xf9, 0xc3, 0x7f, 0xf7, 0x99, 0x86, 0xc6, 0x1b, 0xbd, 0xec, + 0x30, 0x92, 0x86, 0x96, 0xb9, 0x3f, 0xa2, 0x53, 0xf6, 0x80, 0xdc, 0x61, 0x66, 0x58, + 0xe0, 0xa8, 0x26, 0x45, 0x0d, 0x0e, 0xd9, 0x76, 0xfe, 0x52, 0xf5, 0x97, 0x94, 0x63, + 0x9c, 0xf5, 0x85, 0x40, 0xa1, 0x1c, 0x07, 0x8f, 0x4d, 0xb9, ], }, TestVector { ovk: [ - 0xe9, 0xe0, 0xdc, 0x1e, 0xd3, 0x11, 0xda, 0xed, 0x64, 0xbd, 0x74, 0xda, 0x5d, 0x94, - 0xfe, 0x88, 0xa6, 0xea, 0x41, 0x4b, 0x73, 0x12, 0xde, 0x3d, 0x2a, 0x78, 0xf6, 0x46, - 0x32, 0xbb, 0xe3, 0x73, + 0xbd, 0x39, 0x7c, 0x76, 0x26, 0xdf, 0x00, 0xc4, 0x06, 0x78, 0xa4, 0xca, 0x22, 0x64, + 0x6a, 0xd2, 0x13, 0x6b, 0xd4, 0xb0, 0xac, 0x55, 0x11, 0x53, 0x76, 0x03, 0x75, 0x75, + 0x24, 0xee, 0x11, 0x4e, ], ivk: [ - 0x87, 0x16, 0xc8, 0x28, 0x80, 0xe1, 0x36, 0x83, 0xe1, 0xbb, 0x05, 0x9d, 0xd0, 0x6c, - 0x80, 0xc9, 0x01, 0x34, 0xa9, 0x6d, 0x5a, 0xfc, 0xa8, 0xaa, 0xc2, 0xbb, 0xf6, 0x8b, - 0xb0, 0x5f, 0x84, 0x02, + 0xd0, 0xb0, 0x3d, 0x9d, 0x8a, 0xd5, 0x3a, 0xe7, 0x70, 0xc0, 0xc8, 0x70, 0x8e, 0xc9, + 0x20, 0xff, 0xd6, 0x01, 0x6c, 0x89, 0x97, 0xeb, 0x3b, 0x24, 0x13, 0xad, 0x17, 0xb8, + 0xcd, 0xfd, 0xec, 0x05, ], default_d: [ - 0xad, 0x6e, 0x2e, 0x18, 0x5a, 0x31, 0x00, 0xe3, 0xa6, 0xa8, 0xb3, + 0xef, 0x83, 0x2c, 0xf6, 0x6f, 0x46, 0xd8, 0x3e, 0x97, 0xbd, 0x79, ], default_pk_d: [ - 0x32, 0xcb, 0x28, 0x06, 0xb8, 0x82, 0xf1, 0x36, 0x8b, 0x0d, 0x4a, 0x89, 0x8f, 0x72, - 0xc4, 0xc8, 0xf7, 0x28, 0x13, 0x2c, 0xc1, 0x24, 0x56, 0x94, 0x6e, 0x7f, 0x4c, 0xb0, - 0xfb, 0x05, 0x8d, 0xa9, + 0x34, 0xfe, 0x17, 0xbd, 0x7f, 0xbd, 0x16, 0xa1, 0x69, 0x06, 0xd7, 0xfe, 0x2e, 0x62, + 0x60, 0xe8, 0xb7, 0x25, 0x9b, 0x7c, 0x6e, 0xa3, 0x45, 0x64, 0x6f, 0x7b, 0x28, 0xf0, + 0xb7, 0xe3, 0xc6, 0xce, ], v: 800000000, rcm: [ @@ -1503,14 +1529,14 @@ pub(crate) fn make_test_vectors() -> Vec { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ], cv: [ - 0x29, 0x54, 0xcc, 0x7f, 0x9f, 0x9d, 0xfe, 0xb1, 0x4f, 0x02, 0xee, 0xbf, 0xf3, 0xf8, - 0x48, 0xd5, 0xd0, 0xe3, 0xd2, 0xe0, 0x1f, 0xeb, 0xc9, 0x16, 0x41, 0xf4, 0x12, 0x6c, - 0x60, 0x34, 0x33, 0x0c, + 0xe1, 0x58, 0xf7, 0x46, 0xdb, 0x3d, 0xcc, 0xfa, 0x41, 0x7a, 0x50, 0xd0, 0xc3, 0x46, + 0xcc, 0x6e, 0x6a, 0xad, 0x39, 0x5c, 0x34, 0xeb, 0x49, 0x76, 0x2a, 0xe6, 0x45, 0x3c, + 0xbd, 0xe6, 0x97, 0xa8, ], cmu: [ - 0x09, 0x90, 0xcd, 0xb9, 0xa5, 0x2e, 0x5c, 0xd1, 0xba, 0x54, 0xd9, 0x20, 0x4c, 0x26, - 0x69, 0x1c, 0xb0, 0x36, 0xb1, 0x30, 0x12, 0x21, 0x26, 0xeb, 0x14, 0x12, 0x9c, 0xdf, - 0x0f, 0xc5, 0x18, 0x3c, + 0xd5, 0x5d, 0x9d, 0xcd, 0xc7, 0x8f, 0x1e, 0x20, 0x78, 0x03, 0xee, 0xb4, 0x17, 0x81, + 0xb5, 0x6e, 0xd3, 0xa3, 0x7f, 0x76, 0xe0, 0xa7, 0x1d, 0xc0, 0x9c, 0xda, 0x97, 0x0d, + 0x2c, 0x4d, 0x75, 0x46, ], esk: [ 0xab, 0x2a, 0xff, 0x03, 0x32, 0xd5, 0x43, 0xfd, 0x1d, 0x80, 0x23, 0x18, 0x5b, 0x8e, @@ -1518,26 +1544,28 @@ pub(crate) fn make_test_vectors() -> Vec { 0x4f, 0xc2, 0x23, 0x09, ], epk: [ - 0xd0, 0x04, 0x99, 0x7c, 0x79, 0xd0, 0x07, 0xa5, 0x3b, 0xf2, 0xfd, 0x2f, 0x6a, 0x66, - 0xc0, 0xaf, 0xd9, 0xf8, 0x79, 0xb5, 0x5f, 0xec, 0xdc, 0x15, 0x8a, 0x90, 0x12, 0x32, - 0xb7, 0x88, 0x48, 0x09, + 0xa6, 0xdb, 0x6b, 0xc5, 0x3e, 0x74, 0x7f, 0x5d, 0x20, 0xb6, 0xdd, 0x39, 0xae, 0x37, + 0x29, 0x67, 0x9a, 0xf3, 0x0a, 0xc8, 0xde, 0x4d, 0x27, 0x09, 0xd3, 0x3e, 0x3b, 0xb9, + 0x5a, 0xef, 0x28, 0x9a, ], shared_secret: [ - 0xa8, 0xde, 0xa9, 0xbe, 0x94, 0xdc, 0xca, 0xc8, 0x15, 0x75, 0xb4, 0x4f, 0x4b, 0xe8, - 0x53, 0xe8, 0xc0, 0xf7, 0xe6, 0xba, 0x7f, 0x0b, 0xf8, 0xf2, 0xb3, 0xa1, 0xb8, 0x9c, - 0x6a, 0xc8, 0x92, 0x39, + 0x91, 0x06, 0x03, 0x61, 0xc3, 0x12, 0x7e, 0x46, 0xf4, 0xfa, 0xe5, 0x05, 0x78, 0xd1, + 0x63, 0x39, 0x12, 0x9f, 0x14, 0x64, 0xe5, 0x21, 0xb5, 0x64, 0x15, 0xfd, 0xe7, 0x52, + 0x04, 0xc8, 0xd1, 0x93, ], k_enc: [ - 0x14, 0x1b, 0x55, 0x0a, 0xd3, 0xc2, 0xe7, 0xdf, 0xdc, 0xd4, 0x2d, 0x4a, 0xba, 0x31, - 0x39, 0x97, 0x42, 0xa9, 0x29, 0xbb, 0x23, 0x10, 0x0a, 0x7c, 0x51, 0xed, 0x32, 0xf9, - 0xcb, 0x45, 0x96, 0xc6, + 0x16, 0xcd, 0xc1, 0xbb, 0x53, 0x0c, 0x0a, 0x0c, 0xc2, 0x03, 0x41, 0x1b, 0x83, 0xa9, + 0x17, 0x1c, 0xb7, 0x44, 0xd2, 0x53, 0xdb, 0x99, 0xd2, 0xbb, 0xd6, 0xbc, 0x0b, 0xb2, + 0xae, 0x55, 0x9f, 0x32, ], p_enc: [ - 0x01, 0xad, 0x6e, 0x2e, 0x18, 0x5a, 0x31, 0x00, 0xe3, 0xa6, 0xa8, 0xb3, 0x00, 0x08, - 0xaf, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x51, 0x65, 0xaf, 0xf2, 0x2d, 0xd4, 0xed, 0x56, - 0xb4, 0xd8, 0x1d, 0x1f, 0x17, 0x1c, 0xc3, 0xd6, 0x43, 0x2f, 0xed, 0x1b, 0xeb, 0xf2, - 0x0a, 0x7b, 0xea, 0xb1, 0x2d, 0xb1, 0x42, 0xf9, 0x4a, 0x0c, 0xf6, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0xef, 0x83, 0x2c, 0xf6, 0x6f, 0x46, 0xd8, 0x3e, 0x97, 0xbd, 0x79, 0x00, 0x08, + 0xaf, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, + 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, + 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x51, 0x65, 0xaf, 0xf2, + 0x2d, 0xd4, 0xed, 0x56, 0xb4, 0xd8, 0x1d, 0x1f, 0x17, 0x1c, 0xc3, 0xd6, 0x43, 0x2f, + 0xed, 0x1b, 0xeb, 0xf2, 0x0a, 0x7b, 0xea, 0xb1, 0x2d, 0xb1, 0x42, 0xf9, 0x4a, 0x0c, + 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -1573,91 +1601,93 @@ pub(crate) fn make_test_vectors() -> Vec { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ], c_enc: [ - 0x6d, 0x3e, 0xff, 0x72, 0x8a, 0x28, 0x8e, 0x35, 0x59, 0xd8, 0x96, 0x06, 0xa4, 0x50, - 0xce, 0x14, 0x86, 0x4d, 0xf9, 0x03, 0x23, 0xcb, 0x2f, 0x41, 0xfb, 0xa2, 0x68, 0x84, - 0x3c, 0xec, 0x77, 0x75, 0x48, 0xbc, 0xc4, 0x25, 0xf5, 0xed, 0x1e, 0x6e, 0x8c, 0x75, - 0xe2, 0xda, 0xe3, 0x56, 0x16, 0x84, 0x56, 0x39, 0x1b, 0x87, 0xb5, 0xc6, 0xcd, 0x55, - 0x50, 0x3f, 0x12, 0xc3, 0x4f, 0x94, 0xb0, 0xd8, 0x24, 0xa7, 0x7a, 0xe6, 0x21, 0x3f, - 0xf4, 0x3f, 0x12, 0xa3, 0x4f, 0x2c, 0x66, 0x8e, 0xa1, 0x6b, 0xd1, 0xf0, 0x4a, 0x91, - 0xd3, 0x9a, 0x7b, 0x60, 0x19, 0x7c, 0x7b, 0x58, 0x62, 0x90, 0x36, 0xa8, 0x8f, 0xa7, - 0x0a, 0x8d, 0x5b, 0xf8, 0x3e, 0xd4, 0xdb, 0x40, 0x63, 0xb1, 0xea, 0xce, 0x10, 0x95, - 0xf9, 0x06, 0x62, 0xce, 0x9f, 0x6a, 0xc0, 0x26, 0x73, 0xf7, 0xb9, 0xa3, 0x6e, 0xbc, - 0x52, 0xf4, 0x98, 0x4b, 0xd7, 0x11, 0x53, 0xb3, 0xe2, 0xed, 0xca, 0x80, 0x3d, 0x86, - 0x90, 0x26, 0xee, 0x2f, 0xf0, 0x22, 0x8a, 0xfa, 0x7b, 0x61, 0xd0, 0xd3, 0x8c, 0x9b, - 0xcc, 0xb3, 0x00, 0x8b, 0x32, 0xc6, 0xa0, 0x59, 0x84, 0x2e, 0xe8, 0xa0, 0x7b, 0xa1, - 0x2c, 0x63, 0x08, 0x43, 0x6b, 0x64, 0x89, 0x85, 0x35, 0x3d, 0x7d, 0xd5, 0x8b, 0x20, - 0x92, 0xb5, 0xac, 0x2e, 0xd7, 0xe7, 0x20, 0x65, 0xec, 0xad, 0xa6, 0x50, 0xae, 0xe6, - 0xcd, 0x00, 0xfd, 0x34, 0xd5, 0x8c, 0x2b, 0x58, 0xd4, 0x1a, 0x48, 0xaa, 0xc7, 0xbf, - 0x4b, 0x45, 0xc9, 0x6c, 0x53, 0xa1, 0x0b, 0x04, 0xdb, 0x73, 0xcc, 0x83, 0x27, 0x1b, - 0xa6, 0x71, 0x17, 0xd6, 0x42, 0xe4, 0xd8, 0x19, 0xc3, 0x02, 0xd7, 0x18, 0x5e, 0xcc, - 0xbf, 0xa5, 0x40, 0x5b, 0x80, 0xc5, 0xb3, 0xe4, 0xb2, 0xc5, 0x52, 0x43, 0x28, 0x60, - 0x80, 0x81, 0x78, 0xcb, 0x8f, 0xce, 0x40, 0x5b, 0x73, 0xfe, 0xf2, 0xb3, 0x46, 0xc4, - 0x1b, 0xb2, 0xb2, 0xfa, 0xd7, 0x1a, 0x80, 0x31, 0x3b, 0xe3, 0xcf, 0x01, 0xec, 0xfd, - 0x88, 0x8f, 0x25, 0x72, 0xed, 0xcf, 0x57, 0xe4, 0xd7, 0x1e, 0x47, 0xcf, 0x8d, 0x52, - 0xdb, 0xa4, 0xc6, 0x44, 0x0d, 0x0d, 0x4a, 0x9b, 0x19, 0x3f, 0x57, 0x74, 0x8d, 0x20, - 0xf8, 0x9a, 0xb5, 0xd6, 0xda, 0x16, 0x14, 0x36, 0x2a, 0x5f, 0xb8, 0x5f, 0x6a, 0xb2, - 0xbe, 0x35, 0xc7, 0x2f, 0xd6, 0x28, 0x7a, 0xe5, 0x5c, 0xd2, 0x77, 0x79, 0x19, 0x44, - 0xdf, 0x24, 0xa3, 0x76, 0x46, 0x71, 0xdd, 0xd4, 0x06, 0x0a, 0x9b, 0x9c, 0xab, 0x01, - 0x4a, 0xbe, 0x14, 0x35, 0x09, 0x31, 0x64, 0xa6, 0x9f, 0x61, 0xbf, 0x29, 0x24, 0x8c, - 0x35, 0x9c, 0xb6, 0x90, 0xab, 0x25, 0xe9, 0x93, 0xce, 0x39, 0x72, 0xd6, 0xee, 0x36, - 0x78, 0x5e, 0xf0, 0x61, 0x87, 0x20, 0x50, 0xf5, 0x26, 0xf7, 0xdb, 0x7f, 0xf1, 0x98, - 0xfb, 0xac, 0xff, 0x29, 0x85, 0x81, 0xb7, 0x33, 0x06, 0xef, 0xc0, 0x2b, 0xb9, 0xd4, - 0xab, 0x32, 0xdf, 0x26, 0x4f, 0x14, 0xa8, 0x0e, 0x7f, 0x0c, 0x76, 0xe5, 0xf1, 0x4d, - 0xa2, 0x9a, 0xb1, 0xea, 0x04, 0xa3, 0xe3, 0xf5, 0xba, 0x5e, 0x35, 0x05, 0x5d, 0xba, - 0xd2, 0x76, 0xe1, 0x20, 0x1c, 0xce, 0x0a, 0xec, 0x14, 0x82, 0xcb, 0xec, 0x1d, 0x3f, - 0xa4, 0xa1, 0x3d, 0x3e, 0x16, 0x51, 0x1b, 0x0d, 0xee, 0x35, 0x58, 0xc5, 0xae, 0xef, - 0x27, 0xe3, 0xe6, 0x1b, 0x91, 0x51, 0xe5, 0x5a, 0x5a, 0xe1, 0x57, 0x03, 0x0c, 0xe5, - 0x97, 0xf8, 0x21, 0x82, 0x89, 0x3e, 0xe4, 0xd6, 0xbd, 0x4f, 0xb0, 0x87, 0x29, 0xbb, - 0xc3, 0x01, 0x41, 0x9c, 0xe0, 0x66, 0x41, 0x45, 0xba, 0x7a, 0xb8, 0xcb, 0xc0, 0x65, - 0x48, 0xe1, 0xf7, 0xfd, 0xf5, 0x3d, 0x06, 0x05, 0xa7, 0x7b, 0xe6, 0xe4, 0x0c, 0x54, - 0x00, 0x90, 0xf9, 0x8c, 0x25, 0xb1, 0x25, 0xbe, 0x74, 0x99, 0xf1, 0x76, 0xbb, 0x85, - 0x01, 0x49, 0x33, 0x53, 0xcf, 0x90, 0x5f, 0x72, 0x25, 0x00, 0x62, 0xd6, 0xcf, 0x01, - 0x88, 0x14, 0x82, 0x46, 0xee, 0x94, 0xef, 0x9b, 0x21, 0xad, 0xb7, 0xae, 0x1a, 0xe7, - 0x3b, 0xb6, 0xe6, 0x8f, 0xa9, 0x1d, 0x7f, 0xb4, 0x98, 0x28, 0xd6, 0x57, 0xd8, 0x19, - 0x5f, 0x6e, 0x95, 0x08, 0x2f, 0xad, + 0xf8, 0x73, 0x30, 0x70, 0xc8, 0xfe, 0xe7, 0x86, 0x53, 0x05, 0xc0, 0xa9, 0xed, 0x7a, + 0x96, 0x78, 0xf1, 0x0c, 0xae, 0x4b, 0x61, 0xe3, 0x28, 0x52, 0x53, 0x46, 0x54, 0xe5, + 0x2e, 0xb0, 0x53, 0xe9, 0x71, 0x60, 0xd9, 0x6f, 0xad, 0x66, 0x7e, 0xe3, 0x89, 0xe0, + 0xf2, 0x70, 0x24, 0x89, 0xd9, 0x0d, 0x92, 0x55, 0x58, 0xfa, 0x1a, 0xd7, 0x30, 0xda, + 0x94, 0xd0, 0xab, 0x21, 0xaa, 0xd8, 0x2c, 0x06, 0xde, 0xca, 0xed, 0xe0, 0xd7, 0xcf, + 0x44, 0xce, 0x85, 0x3c, 0x21, 0x28, 0x73, 0x43, 0x4b, 0x7d, 0x6e, 0x88, 0xd4, 0x1d, + 0xee, 0x30, 0xe9, 0x73, 0x51, 0x44, 0xf6, 0xba, 0x5a, 0x29, 0xf3, 0x6d, 0xd8, 0x47, + 0xeb, 0x46, 0x9f, 0xf3, 0x13, 0xd3, 0xce, 0x7e, 0x97, 0x57, 0x7e, 0x00, 0x40, 0xc7, + 0x5f, 0x9d, 0xf2, 0x85, 0x3f, 0x62, 0xc4, 0xd9, 0x77, 0x78, 0xef, 0xd9, 0xd3, 0x39, + 0xcb, 0x8e, 0x19, 0x8b, 0x3f, 0x44, 0xc7, 0x60, 0x8f, 0x4d, 0x42, 0x35, 0xdc, 0x24, + 0x5e, 0xb2, 0x62, 0xe9, 0x49, 0xe0, 0x56, 0xe4, 0x33, 0x2c, 0x58, 0x9d, 0x8a, 0xc9, + 0xe4, 0xb4, 0x40, 0xa0, 0x20, 0xfe, 0x64, 0x01, 0x7c, 0x68, 0xb9, 0x03, 0x0a, 0x52, + 0x9a, 0xa4, 0x84, 0xab, 0x62, 0xf4, 0xee, 0x32, 0x41, 0xc5, 0x35, 0xc6, 0xb2, 0x97, + 0x5b, 0xfe, 0xa8, 0x9f, 0x48, 0x41, 0x81, 0x0b, 0x7a, 0x46, 0xb3, 0xfe, 0xf4, 0x1d, + 0x59, 0x2d, 0xb0, 0x12, 0xfd, 0x0d, 0x9c, 0x16, 0xb9, 0x58, 0xac, 0x2b, 0xb4, 0xf0, + 0x44, 0x6e, 0x19, 0x8f, 0xd7, 0x58, 0x42, 0x66, 0xb5, 0xcb, 0xe3, 0x22, 0x78, 0x6c, + 0xc1, 0x46, 0x27, 0x77, 0xf6, 0x9f, 0xe9, 0x85, 0xe5, 0xab, 0x95, 0x65, 0xca, 0x01, + 0xff, 0xf8, 0x98, 0x24, 0xb2, 0x0d, 0x87, 0xb8, 0xaf, 0x70, 0x63, 0xf5, 0xbe, 0xbb, + 0x13, 0x55, 0x7d, 0x7d, 0x3f, 0x08, 0xdb, 0x57, 0xab, 0xd6, 0x59, 0xd9, 0xf0, 0x40, + 0x4d, 0xbf, 0xa4, 0x54, 0xba, 0x74, 0x39, 0xef, 0x8b, 0x93, 0xfb, 0x0d, 0x3a, 0xac, + 0x2f, 0x88, 0x51, 0xab, 0x62, 0x8a, 0x3a, 0xf5, 0xa5, 0x04, 0xc6, 0x60, 0x5b, 0xfb, + 0xcb, 0x8a, 0x8c, 0xce, 0xa8, 0x73, 0xa7, 0x5d, 0x76, 0x93, 0x8a, 0x13, 0x43, 0x97, + 0x73, 0x69, 0xed, 0x39, 0xf3, 0x2a, 0x35, 0xb6, 0xf6, 0x5b, 0xda, 0xce, 0xf4, 0xba, + 0xfb, 0x4a, 0x2b, 0x46, 0xbb, 0xde, 0xa7, 0xc8, 0xe5, 0xeb, 0x69, 0x0b, 0x07, 0x60, + 0x9e, 0xdb, 0xe2, 0xfe, 0x28, 0xa6, 0x73, 0x4d, 0x3a, 0xf6, 0x74, 0x73, 0x74, 0xcf, + 0x84, 0x21, 0x1c, 0x26, 0xdc, 0xb6, 0x37, 0x86, 0xc0, 0x59, 0xee, 0xa6, 0xf4, 0xe9, + 0x1f, 0x5e, 0x67, 0x10, 0x3d, 0xd5, 0x36, 0xe0, 0x20, 0xa4, 0x75, 0x98, 0x0d, 0xd4, + 0xf5, 0x07, 0xd4, 0x66, 0xb5, 0xc2, 0x87, 0xbe, 0x3b, 0x9a, 0x56, 0x17, 0x78, 0x8f, + 0x20, 0xe8, 0x74, 0xa9, 0x9b, 0x2c, 0xe3, 0x7c, 0x02, 0x1d, 0x0b, 0xc8, 0xf5, 0xbf, + 0x5b, 0x02, 0x32, 0xec, 0xd2, 0xc9, 0x9f, 0x27, 0x69, 0x82, 0xa6, 0xe5, 0x02, 0x5c, + 0x65, 0x44, 0xe1, 0xe6, 0x62, 0x16, 0xd0, 0x47, 0x17, 0xb5, 0x96, 0x7a, 0xec, 0x14, + 0xc1, 0x81, 0xe0, 0xe9, 0x12, 0xa9, 0xc9, 0x87, 0xf3, 0x16, 0x67, 0xed, 0x58, 0x07, + 0x92, 0xc4, 0xc4, 0x6d, 0x94, 0x7a, 0x28, 0x75, 0x72, 0x07, 0xa7, 0x5f, 0x65, 0xe6, + 0x6c, 0x16, 0xa5, 0x5f, 0x44, 0x41, 0x5c, 0xa2, 0x83, 0x25, 0x4c, 0x8f, 0xbf, 0xc4, + 0x8c, 0x3d, 0x03, 0xfd, 0x47, 0xee, 0xd0, 0x5c, 0x36, 0x38, 0x29, 0xce, 0x89, 0x1f, + 0x6c, 0xfa, 0xb3, 0x15, 0xc8, 0xbd, 0xa0, 0xa8, 0xa4, 0x62, 0xb6, 0xf9, 0x8d, 0x3d, + 0x74, 0x81, 0x90, 0x0b, 0x04, 0x2c, 0x98, 0x80, 0x44, 0x88, 0x12, 0x0e, 0xb4, 0x46, + 0x70, 0xbf, 0x37, 0xa1, 0xab, 0x37, 0x69, 0xb9, 0xe6, 0x43, 0xb8, 0xd6, 0xb3, 0x76, + 0xf9, 0x68, 0xde, 0xda, 0x10, 0x08, 0x26, 0xff, 0x5c, 0xbe, 0x4d, 0x07, 0x4c, 0xe9, + 0x04, 0x1c, 0x46, 0x08, 0x79, 0x2a, 0x27, 0x9c, 0xed, 0x9f, 0x65, 0xd2, 0xa0, 0x88, + 0x1f, 0x1c, 0xa1, 0xa5, 0x90, 0x74, 0x47, 0x3b, 0xc4, 0xb7, 0x0a, 0xed, 0x9e, 0x0e, + 0xda, 0xcf, 0xc6, 0xc3, 0xda, 0xd3, 0x13, 0x81, 0x3f, 0x38, 0x5b, 0x45, 0xb6, 0x9b, + 0xba, 0x95, 0x54, 0x3f, 0x97, 0xab, 0x01, 0x54, 0xc5, 0x2a, 0xa8, 0x9a, 0xe4, 0xe3, + 0x3d, 0x2c, 0x9f, 0x24, 0x2e, 0x86, 0x18, 0xd5, 0xe1, 0xc1, ], ock: [ - 0xda, 0xb4, 0x26, 0x26, 0x9e, 0x8d, 0x33, 0x09, 0x55, 0x23, 0x7a, 0x9f, 0xed, 0x86, - 0x83, 0xa9, 0x27, 0x7c, 0x61, 0x82, 0xa8, 0x08, 0xcc, 0x53, 0xa1, 0xbe, 0xdd, 0xd2, - 0x03, 0x68, 0xb1, 0x0a, + 0x72, 0x7b, 0xf1, 0x55, 0xdc, 0xdf, 0x0d, 0x05, 0x13, 0x2b, 0x77, 0x89, 0xaf, 0xd7, + 0xfc, 0x42, 0x23, 0xf4, 0x55, 0x84, 0x3b, 0xec, 0xc1, 0xdf, 0x5f, 0xd7, 0x6f, 0xc9, + 0x0f, 0x59, 0x41, 0xf0, ], op: [ - 0x32, 0xcb, 0x28, 0x06, 0xb8, 0x82, 0xf1, 0x36, 0x8b, 0x0d, 0x4a, 0x89, 0x8f, 0x72, - 0xc4, 0xc8, 0xf7, 0x28, 0x13, 0x2c, 0xc1, 0x24, 0x56, 0x94, 0x6e, 0x7f, 0x4c, 0xb0, - 0xfb, 0x05, 0x8d, 0xa9, 0xab, 0x2a, 0xff, 0x03, 0x32, 0xd5, 0x43, 0xfd, 0x1d, 0x80, + 0x34, 0xfe, 0x17, 0xbd, 0x7f, 0xbd, 0x16, 0xa1, 0x69, 0x06, 0xd7, 0xfe, 0x2e, 0x62, + 0x60, 0xe8, 0xb7, 0x25, 0x9b, 0x7c, 0x6e, 0xa3, 0x45, 0x64, 0x6f, 0x7b, 0x28, 0xf0, + 0xb7, 0xe3, 0xc6, 0xce, 0xab, 0x2a, 0xff, 0x03, 0x32, 0xd5, 0x43, 0xfd, 0x1d, 0x80, 0x23, 0x18, 0x5b, 0x8e, 0xcb, 0x5f, 0x22, 0xa2, 0x9c, 0x32, 0xef, 0x74, 0x16, 0x33, 0x31, 0x6e, 0xee, 0x51, 0x4f, 0xc2, 0x23, 0x09, ], c_out: [ - 0xaf, 0x4d, 0x97, 0xfb, 0x72, 0x28, 0xf0, 0x1f, 0x6d, 0x9e, 0x2f, 0x79, 0xa1, 0xa1, - 0xba, 0x45, 0xa2, 0x3d, 0x60, 0x90, 0x59, 0x78, 0x4e, 0xa9, 0x35, 0x0f, 0x1e, 0xb0, - 0x92, 0xb0, 0x54, 0xa3, 0x26, 0x8c, 0xc0, 0x26, 0xd3, 0xd7, 0x37, 0xef, 0x35, 0xad, - 0xc2, 0x86, 0xd1, 0x95, 0xea, 0xa4, 0x14, 0x49, 0x3e, 0xd2, 0xa5, 0x1f, 0x2f, 0x61, - 0x09, 0x9a, 0x34, 0x51, 0xf9, 0x55, 0x5b, 0xab, 0x1a, 0x5e, 0xf3, 0xe3, 0xfb, 0xbe, - 0x8e, 0xc6, 0x41, 0x6b, 0xd3, 0x3d, 0x50, 0xdf, 0xf9, 0x8f, + 0xf5, 0xda, 0x0d, 0x00, 0x1e, 0x23, 0x66, 0x3b, 0x55, 0xc8, 0x1e, 0x46, 0x81, 0x98, + 0x87, 0x6f, 0x1b, 0xb0, 0x76, 0x99, 0x95, 0xe3, 0xcf, 0xa4, 0x00, 0x60, 0xe8, 0xd9, + 0xf5, 0x00, 0x5c, 0xf9, 0xa4, 0xde, 0x9c, 0x0c, 0xb3, 0xb5, 0x4e, 0x8c, 0xd2, 0x10, + 0x38, 0x8f, 0xac, 0x28, 0x5c, 0xd7, 0x26, 0xac, 0x13, 0x0c, 0x5f, 0xac, 0x03, 0xb4, + 0xca, 0x16, 0xdf, 0xe7, 0xc3, 0x22, 0x32, 0x5d, 0x21, 0x46, 0x7a, 0xb4, 0xdf, 0xd2, + 0x0f, 0xee, 0xb4, 0x15, 0xed, 0x1b, 0x2c, 0x45, 0xb7, 0x1e, ], }, TestVector { ovk: [ - 0x14, 0x7d, 0xd1, 0x1d, 0x77, 0xeb, 0xa1, 0xb1, 0x63, 0x6f, 0xd6, 0x19, 0x0c, 0x62, - 0xb9, 0xa5, 0xd0, 0x48, 0x1b, 0xee, 0x7e, 0x91, 0x7f, 0xab, 0x02, 0xe2, 0x18, 0x58, - 0x06, 0x3a, 0xb5, 0x04, + 0x21, 0x15, 0x33, 0xa6, 0x4b, 0xc1, 0x87, 0xb9, 0x93, 0x35, 0x99, 0xb4, 0x10, 0x12, + 0x37, 0xe5, 0x05, 0x8d, 0x67, 0x7e, 0xb0, 0xa8, 0xb8, 0xdb, 0x91, 0x88, 0x67, 0x55, + 0x71, 0x2f, 0xfb, 0x54, ], ivk: [ - 0x99, 0xc9, 0xb4, 0xb8, 0x4f, 0x4b, 0x4e, 0x35, 0x0f, 0x78, 0x7d, 0x1c, 0xf7, 0x05, - 0x1d, 0x50, 0xec, 0xc3, 0x4b, 0x1a, 0x5b, 0x20, 0xd2, 0xd2, 0x13, 0x9b, 0x4a, 0xf1, - 0xf1, 0x60, 0xe0, 0x01, + 0xc4, 0x86, 0x04, 0x3b, 0x54, 0xf2, 0x1f, 0x93, 0xbf, 0x29, 0xe7, 0x0d, 0x38, 0xae, + 0x9a, 0x2d, 0xa7, 0xfc, 0x48, 0x23, 0x35, 0xc9, 0x39, 0xc3, 0xbd, 0x86, 0xdb, 0xe3, + 0xa6, 0x6e, 0x6e, 0x03, ], default_d: [ - 0x21, 0xc9, 0x0e, 0x1c, 0x65, 0x8b, 0x3e, 0xfe, 0x86, 0xaf, 0x58, + 0x55, 0x63, 0x11, 0xd5, 0x93, 0xaf, 0x50, 0xe3, 0x1c, 0x4d, 0x1e, ], default_pk_d: [ - 0x9e, 0x64, 0x17, 0x4b, 0x4a, 0xb9, 0x81, 0x40, 0x5c, 0x32, 0x3b, 0x5e, 0x12, 0x47, - 0x59, 0x45, 0xa4, 0x6d, 0x4f, 0xed, 0xf8, 0x06, 0x08, 0x28, 0x04, 0x1c, 0xd2, 0x0e, - 0x62, 0xfd, 0x2c, 0xef, + 0x44, 0x7e, 0xfa, 0x0f, 0x22, 0x05, 0x00, 0x44, 0x26, 0x3d, 0x7d, 0x98, 0xd4, 0x75, + 0xc2, 0x60, 0x14, 0x26, 0xf1, 0xae, 0xa1, 0x9e, 0xd5, 0xaf, 0xe3, 0xb5, 0xfc, 0x75, + 0xd0, 0x81, 0x24, 0xa4, ], v: 900000000, rcm: [ @@ -1705,14 +1735,14 @@ pub(crate) fn make_test_vectors() -> Vec { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ], cv: [ - 0x4a, 0x85, 0xeb, 0x3f, 0x25, 0x3f, 0x3b, 0xaa, 0xf6, 0xb5, 0x5a, 0x99, 0x49, 0x51, - 0xb2, 0xca, 0x82, 0x48, 0xcb, 0xd6, 0x79, 0xf7, 0xa5, 0x77, 0xe3, 0x3b, 0xcd, 0x66, - 0x46, 0xb2, 0x13, 0x51, + 0xd7, 0x89, 0x19, 0x2e, 0x90, 0xde, 0x28, 0xc3, 0x7f, 0x58, 0xfe, 0x46, 0xdb, 0x58, + 0xdc, 0x20, 0xc5, 0x74, 0xd5, 0x97, 0x62, 0x85, 0x25, 0x30, 0xd3, 0xe9, 0x3f, 0x07, + 0xfa, 0x15, 0x99, 0x1c, ], cmu: [ - 0x56, 0x90, 0xcd, 0x51, 0xa4, 0x5c, 0xe8, 0x9a, 0x51, 0xac, 0xbe, 0x01, 0x60, 0x60, - 0xf0, 0xdf, 0xee, 0x0d, 0x2f, 0xc9, 0xb8, 0x97, 0x58, 0x5f, 0x97, 0x4a, 0x40, 0x2e, - 0x53, 0x7f, 0xe2, 0x18, + 0xa3, 0x64, 0xc4, 0x9c, 0x48, 0x4b, 0xc6, 0xc8, 0x2a, 0x7b, 0x84, 0xf4, 0x31, 0xfe, + 0x56, 0xe1, 0x9b, 0x40, 0x2f, 0x74, 0x14, 0x1d, 0x47, 0x1a, 0xe0, 0x30, 0x16, 0x5f, + 0xde, 0xb5, 0x82, 0x16, ], esk: [ 0xa5, 0x3d, 0x19, 0xf5, 0x69, 0x45, 0x95, 0xd5, 0xae, 0x63, 0x02, 0x27, 0x67, 0x3c, @@ -1720,26 +1750,28 @@ pub(crate) fn make_test_vectors() -> Vec { 0x61, 0x28, 0xcd, 0x0b, ], epk: [ - 0x4d, 0xfc, 0x8a, 0x70, 0xb2, 0x10, 0xdf, 0xd4, 0x48, 0x37, 0xaa, 0x52, 0xd6, 0x3b, - 0xd5, 0xd8, 0x1a, 0x5e, 0x40, 0xd8, 0xb4, 0xc1, 0x7a, 0x2d, 0xca, 0x25, 0xa5, 0xf7, - 0x5f, 0xe5, 0x20, 0x2e, + 0x28, 0xb2, 0xea, 0x96, 0x7e, 0xf2, 0xc2, 0x0e, 0xe4, 0xad, 0x38, 0x6e, 0x7d, 0xcc, + 0xe1, 0x2f, 0x25, 0x20, 0x23, 0xb7, 0xa6, 0x8c, 0xc5, 0x2a, 0x8f, 0xd3, 0x87, 0x38, + 0xcc, 0x36, 0x87, 0xcb, ], shared_secret: [ - 0x1f, 0xf7, 0x5f, 0x5e, 0x7a, 0x51, 0x4b, 0x3c, 0xf5, 0xb3, 0x3c, 0xa3, 0x1a, 0x67, - 0x1f, 0xc5, 0x0c, 0x26, 0x8c, 0xf1, 0xa3, 0x16, 0xb2, 0x1b, 0x98, 0x67, 0x4b, 0xaa, - 0x45, 0x00, 0x85, 0xcf, + 0xec, 0xf0, 0x6e, 0x8e, 0xc5, 0xe5, 0x27, 0xe5, 0xdf, 0x71, 0x82, 0xc5, 0xdb, 0x78, + 0x41, 0xf9, 0x18, 0xe2, 0xe2, 0xb3, 0xad, 0x9f, 0x4a, 0xfd, 0xce, 0xf0, 0xed, 0x48, + 0xca, 0xc3, 0x67, 0x3b, ], k_enc: [ - 0x3c, 0x52, 0xd9, 0xc8, 0x32, 0x07, 0xee, 0x14, 0xf5, 0x62, 0x0d, 0x16, 0x21, 0x82, - 0xa6, 0xb9, 0xca, 0xbe, 0xfd, 0xba, 0x9e, 0x7a, 0x74, 0xf5, 0xba, 0x2f, 0x81, 0xb8, - 0x71, 0x40, 0x1f, 0x08, + 0x01, 0x61, 0x89, 0xc3, 0x24, 0x24, 0xbd, 0x33, 0xb7, 0x77, 0x1f, 0x51, 0x00, 0x98, + 0x66, 0xf8, 0x8b, 0xa8, 0xd5, 0x19, 0x5e, 0x4f, 0x05, 0x93, 0xec, 0x53, 0xdb, 0xfa, + 0x90, 0x9a, 0x86, 0x73, ], p_enc: [ - 0x01, 0x21, 0xc9, 0x0e, 0x1c, 0x65, 0x8b, 0x3e, 0xfe, 0x86, 0xaf, 0x58, 0x00, 0xe9, - 0xa4, 0x35, 0x00, 0x00, 0x00, 0x00, 0x8c, 0x3e, 0x56, 0x44, 0x9d, 0xc8, 0x63, 0x54, - 0xd3, 0x3b, 0x02, 0x5e, 0xf2, 0x79, 0x34, 0x60, 0xbc, 0xb1, 0x69, 0xf3, 0x32, 0x4e, - 0x4a, 0x6b, 0x64, 0xba, 0xa6, 0x08, 0x32, 0x31, 0x57, 0x04, 0xf6, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x55, 0x63, 0x11, 0xd5, 0x93, 0xaf, 0x50, 0xe3, 0x1c, 0x4d, 0x1e, 0x00, 0xe9, + 0xa4, 0x35, 0x00, 0x00, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, + 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, + 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x8c, 0x3e, 0x56, 0x44, + 0x9d, 0xc8, 0x63, 0x54, 0xd3, 0x3b, 0x02, 0x5e, 0xf2, 0x79, 0x34, 0x60, 0xbc, 0xb1, + 0x69, 0xf3, 0x32, 0x4e, 0x4a, 0x6b, 0x64, 0xba, 0xa6, 0x08, 0x32, 0x31, 0x57, 0x04, + 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -1775,91 +1807,93 @@ pub(crate) fn make_test_vectors() -> Vec { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ], c_enc: [ - 0x84, 0xd3, 0x61, 0x09, 0xbd, 0xd2, 0x1c, 0x67, 0x8e, 0x84, 0x47, 0xf8, 0x89, 0xe5, - 0x60, 0xef, 0x6d, 0x07, 0xa8, 0x27, 0xaa, 0xab, 0x78, 0x9b, 0x46, 0xc3, 0xf9, 0xeb, - 0x32, 0x2e, 0xea, 0x21, 0x4c, 0x20, 0xf7, 0xe9, 0xfa, 0x7f, 0x7a, 0xa5, 0xe0, 0x44, - 0xa4, 0xed, 0x4c, 0xb1, 0x5d, 0xa9, 0xc5, 0x6c, 0x32, 0xf3, 0x7e, 0x4c, 0xbe, 0x7d, - 0x1e, 0xd1, 0xf6, 0x85, 0xa8, 0x74, 0x8d, 0xbf, 0x78, 0x12, 0x90, 0xf9, 0x7a, 0xc1, - 0x41, 0x40, 0xaa, 0x8b, 0x50, 0x93, 0x2a, 0x3f, 0x66, 0xc2, 0x08, 0x22, 0x6f, 0x8d, - 0x8e, 0xc0, 0xde, 0xb7, 0xbb, 0x58, 0x35, 0x72, 0xc9, 0xe9, 0x70, 0xbc, 0xd0, 0xc6, - 0x44, 0x67, 0x26, 0xaa, 0x5b, 0x6a, 0x5f, 0x81, 0xcf, 0x18, 0xc6, 0x7a, 0x99, 0x2d, - 0x6c, 0x86, 0x03, 0x86, 0xab, 0xbb, 0x5b, 0x90, 0xbe, 0x58, 0x64, 0x34, 0x4f, 0xc8, - 0xbf, 0x3e, 0xbb, 0x75, 0x41, 0xaa, 0x9b, 0x9e, 0x1e, 0x3f, 0x96, 0x25, 0xac, 0xce, - 0x7f, 0x4b, 0xf1, 0x58, 0x39, 0xa0, 0x81, 0x70, 0x68, 0xe9, 0x15, 0x1b, 0x63, 0x7f, - 0xa2, 0xa2, 0xca, 0x09, 0xb9, 0xbe, 0x28, 0x5f, 0xea, 0x7e, 0x0a, 0x03, 0x31, 0x7c, - 0x29, 0x8a, 0xd7, 0xff, 0xfe, 0x40, 0xc5, 0xf0, 0xf6, 0xe9, 0xfb, 0x44, 0xe8, 0xf0, - 0x6e, 0x19, 0x2f, 0x1a, 0xc2, 0x10, 0x8f, 0x3f, 0x11, 0xf7, 0x76, 0x3c, 0xf2, 0x1e, - 0x96, 0x62, 0x4d, 0x52, 0xf3, 0xe7, 0x2a, 0xaf, 0x15, 0x7f, 0x3b, 0xc7, 0xc5, 0xd1, - 0x8f, 0x1e, 0xba, 0x3d, 0x82, 0x7f, 0x71, 0x9c, 0x27, 0x9f, 0xd9, 0x66, 0xc2, 0x7d, - 0x94, 0xd7, 0x47, 0x23, 0xc5, 0x31, 0x1b, 0x86, 0x65, 0xbd, 0x29, 0xb3, 0xa1, 0x00, - 0xbb, 0x21, 0x11, 0xaa, 0x42, 0x16, 0xf0, 0x66, 0x5b, 0x16, 0x9e, 0xc0, 0x94, 0x17, - 0x68, 0xa9, 0x57, 0x4a, 0xe5, 0x0c, 0x2b, 0xc7, 0x90, 0x05, 0x53, 0xf5, 0xc4, 0x50, - 0xee, 0x98, 0x82, 0xaf, 0x44, 0x55, 0xd1, 0xd8, 0xce, 0x35, 0x18, 0x49, 0xd7, 0x8d, - 0xbb, 0xe6, 0x1e, 0xd1, 0xdb, 0x7a, 0x2f, 0xd6, 0x57, 0x75, 0xd5, 0x50, 0x6d, 0xfd, - 0x02, 0xa9, 0x4d, 0x9d, 0x42, 0x85, 0xa2, 0x3a, 0x3c, 0xab, 0x8a, 0xa3, 0x32, 0x14, - 0x22, 0xa4, 0xaa, 0xa5, 0x49, 0x27, 0x4a, 0x25, 0xf7, 0xf1, 0x2f, 0xf7, 0xa5, 0x19, - 0x5e, 0x51, 0x55, 0x73, 0x9f, 0x31, 0x8c, 0x30, 0xc0, 0x24, 0x8c, 0x3a, 0x21, 0x9a, - 0x7a, 0xde, 0x72, 0x98, 0x38, 0x0a, 0x59, 0x5c, 0x5c, 0x88, 0x5b, 0x42, 0x06, 0x69, - 0xcd, 0x6d, 0xeb, 0x2e, 0x5c, 0x80, 0x49, 0x78, 0xcb, 0x42, 0xd2, 0x06, 0x02, 0x74, - 0x57, 0x33, 0x60, 0x7c, 0xef, 0x4e, 0x26, 0xa5, 0xc9, 0x7c, 0xca, 0x1c, 0xc5, 0x2b, - 0x7f, 0xdc, 0x10, 0x69, 0x01, 0x70, 0x18, 0x07, 0x6c, 0xac, 0x62, 0xe5, 0xc4, 0xdb, - 0xf9, 0x07, 0x48, 0x72, 0x05, 0x0a, 0x42, 0x22, 0x19, 0x51, 0x3b, 0xca, 0x27, 0xa8, - 0x35, 0xf4, 0x82, 0x4f, 0x47, 0xba, 0x33, 0x7d, 0xeb, 0x74, 0x40, 0xf3, 0xf2, 0xca, - 0xce, 0x9e, 0x33, 0x16, 0x70, 0xdd, 0x98, 0xe3, 0x28, 0xab, 0x0a, 0x16, 0xac, 0x4a, - 0xb6, 0x62, 0x76, 0xd1, 0xe1, 0x01, 0x8b, 0x2c, 0xf1, 0x79, 0x43, 0x62, 0x66, 0xa4, - 0x08, 0xda, 0x8d, 0xda, 0xfc, 0x44, 0xb2, 0x27, 0x6b, 0x11, 0x68, 0x52, 0xd4, 0xcc, - 0xb3, 0x52, 0x89, 0xb4, 0x21, 0x30, 0x09, 0x12, 0x5d, 0x2d, 0x87, 0x84, 0x5d, 0x6e, - 0xb7, 0x8e, 0x55, 0x03, 0x15, 0x3d, 0x92, 0xfb, 0xd4, 0x93, 0xd1, 0x9e, 0xf0, 0x1f, - 0x37, 0x00, 0x26, 0xba, 0xf1, 0x72, 0x30, 0x7b, 0x3f, 0xe2, 0xc4, 0x56, 0x96, 0xfb, - 0xce, 0xda, 0x3b, 0x6e, 0xab, 0x05, 0xe2, 0xb0, 0x68, 0x5c, 0x72, 0x79, 0x04, 0x98, - 0x23, 0x3a, 0xbb, 0xbd, 0x6e, 0x05, 0xb0, 0xf4, 0x4a, 0x72, 0x98, 0xae, 0x0a, 0x25, - 0xaf, 0x08, 0xd7, 0x95, 0x74, 0x61, 0x4c, 0xf2, 0xd8, 0x3e, 0xa7, 0x9c, 0x2b, 0x79, - 0x53, 0xf8, 0x6c, 0xf5, 0xd0, 0x49, 0x27, 0xf0, 0x9c, 0x0d, 0x7d, 0xf8, 0x12, 0xf1, - 0xcf, 0x18, 0xa4, 0x53, 0xa0, 0x49, 0x70, 0xaf, 0x0d, 0x72, 0x9c, 0xe7, 0xd9, 0xc8, - 0xd6, 0xa2, 0x4d, 0x7e, 0xed, 0x3d, + 0x89, 0xb5, 0x33, 0xe1, 0x20, 0x13, 0x2b, 0x5c, 0x16, 0x40, 0x3b, 0x83, 0x5f, 0x79, + 0x14, 0x59, 0xd3, 0xa9, 0xaf, 0x90, 0x0d, 0xda, 0x75, 0xaa, 0xa5, 0x3c, 0x8d, 0x21, + 0xa9, 0x51, 0x92, 0xa4, 0x2d, 0x97, 0x27, 0xf5, 0x48, 0x5f, 0xbf, 0x41, 0x67, 0x41, + 0x1c, 0xf2, 0xcd, 0x6c, 0x65, 0xfb, 0xbb, 0x2d, 0x9b, 0x5a, 0xa0, 0xaa, 0x29, 0x4a, + 0x41, 0x69, 0xe7, 0x65, 0x83, 0x19, 0x67, 0xb4, 0xa9, 0x0a, 0xca, 0xd5, 0xcf, 0x4e, + 0xa9, 0xa1, 0x0b, 0xc4, 0xdd, 0x6e, 0x9e, 0x91, 0x4f, 0xa6, 0x9d, 0x63, 0x94, 0x9d, + 0xdc, 0xc2, 0x79, 0xa2, 0xfa, 0xb6, 0x6f, 0x77, 0x1b, 0x8b, 0xe5, 0xf0, 0xe7, 0xf6, + 0x09, 0x93, 0xc3, 0xcf, 0xe5, 0x2d, 0x90, 0x08, 0x23, 0x60, 0xf0, 0x7a, 0x05, 0x82, + 0x44, 0x9e, 0x15, 0x1a, 0xe3, 0x9d, 0x51, 0x5e, 0xff, 0xea, 0x06, 0xa6, 0x5d, 0x1a, + 0x14, 0xee, 0xff, 0xca, 0x8b, 0xa2, 0xec, 0x48, 0xf6, 0xbf, 0xfd, 0x17, 0x1f, 0x5e, + 0x6a, 0xd1, 0x63, 0xc5, 0xe4, 0x9d, 0x89, 0x28, 0xb0, 0x94, 0x83, 0x92, 0x01, 0xbd, + 0x1f, 0x3d, 0xe7, 0xb8, 0x2e, 0x9c, 0xa5, 0x6f, 0xaa, 0xa4, 0x9c, 0x78, 0x56, 0x88, + 0xfb, 0x1e, 0xe7, 0xd9, 0xc1, 0xca, 0xe9, 0x63, 0x2a, 0x11, 0x88, 0x2f, 0xa3, 0x7d, + 0x3c, 0xc2, 0xa9, 0xe2, 0x7c, 0x60, 0x38, 0x84, 0x4b, 0xb1, 0xc6, 0x9c, 0x3b, 0xc2, + 0xc9, 0xcb, 0x5a, 0xa3, 0x90, 0xd6, 0x99, 0x97, 0x9c, 0xd9, 0x41, 0xc7, 0xca, 0x0b, + 0x3c, 0xcd, 0x89, 0xc9, 0xcc, 0x42, 0x99, 0x35, 0x26, 0xd7, 0x37, 0xf7, 0x7e, 0x34, + 0xd7, 0x78, 0x80, 0xd5, 0xdb, 0x4d, 0x39, 0x29, 0x0a, 0xd1, 0xbf, 0xcc, 0x55, 0xa3, + 0x32, 0x35, 0xb3, 0x0c, 0xbb, 0x88, 0x3e, 0xec, 0xb2, 0x55, 0x17, 0x3a, 0x09, 0xb8, + 0x2d, 0x5a, 0x7f, 0x64, 0x26, 0xb7, 0xd7, 0xaf, 0xcc, 0xf6, 0x95, 0xf1, 0xf1, 0x4a, + 0xfa, 0xf4, 0x73, 0xfa, 0x65, 0xf7, 0x69, 0xe7, 0x1d, 0x84, 0xcd, 0x5e, 0x71, 0x66, + 0x24, 0xa3, 0xff, 0x0c, 0xda, 0x04, 0x7f, 0x88, 0x87, 0xeb, 0x89, 0xfc, 0x7f, 0xae, + 0xc5, 0x90, 0x70, 0x43, 0x30, 0x78, 0x8e, 0xe1, 0x77, 0x6c, 0xa0, 0x73, 0x47, 0x21, + 0x6a, 0xe6, 0x34, 0xc7, 0x63, 0xfa, 0xf4, 0x10, 0xe4, 0xc6, 0xee, 0xcb, 0x99, 0x66, + 0xf6, 0x32, 0x4d, 0x80, 0x90, 0x5a, 0x9d, 0x34, 0x5c, 0x46, 0x2c, 0xa0, 0x58, 0x18, + 0xcf, 0x34, 0x23, 0x78, 0x1b, 0x89, 0x04, 0xe2, 0x42, 0xec, 0xfa, 0x10, 0x66, 0x88, + 0xd4, 0xb4, 0x4b, 0xb1, 0x3a, 0xd3, 0xd9, 0x91, 0x99, 0x80, 0xf6, 0x6f, 0x81, 0xd9, + 0xc4, 0x94, 0xc1, 0xd2, 0x42, 0x8f, 0x08, 0xac, 0xa0, 0x2e, 0x7a, 0x1a, 0xd0, 0xc7, + 0xc8, 0x11, 0x6b, 0x9a, 0x25, 0x07, 0x04, 0xc0, 0x86, 0xdf, 0x3f, 0xbc, 0x0d, 0x80, + 0x27, 0x10, 0x38, 0xf2, 0xce, 0xb8, 0xfd, 0x52, 0x07, 0xb0, 0x65, 0x98, 0xfb, 0x84, + 0xd6, 0x6f, 0x3d, 0x9a, 0xdc, 0x4f, 0xeb, 0x21, 0xd7, 0x71, 0x31, 0x21, 0x57, 0x5d, + 0x79, 0xc2, 0xad, 0xf5, 0x9a, 0xf7, 0xb9, 0x1d, 0x70, 0x4a, 0xcb, 0xb5, 0xb6, 0x49, + 0x05, 0xac, 0x7b, 0x8d, 0x8c, 0xd6, 0xd0, 0x8c, 0xeb, 0x3c, 0x1f, 0x70, 0x68, 0x50, + 0xf3, 0x21, 0x6c, 0xed, 0xc4, 0x93, 0xb4, 0xf8, 0x20, 0x9a, 0x8a, 0x58, 0x82, 0x3b, + 0x45, 0x19, 0x06, 0x60, 0x94, 0x6c, 0x1c, 0xa8, 0xbf, 0x27, 0xda, 0xaf, 0xf2, 0xfc, + 0x3a, 0x1f, 0xe4, 0x1c, 0xd2, 0xb3, 0x3c, 0x55, 0xbe, 0x24, 0xee, 0xa4, 0xb6, 0xf0, + 0xbd, 0xb5, 0xe5, 0xa1, 0x7c, 0x42, 0xbc, 0x18, 0x3e, 0x88, 0xd6, 0x8d, 0x92, 0xc6, + 0x61, 0xc2, 0xe7, 0x2a, 0x24, 0xff, 0xf1, 0x28, 0xcb, 0xcb, 0x4c, 0xad, 0xeb, 0x44, + 0x3e, 0x8f, 0xcf, 0x1d, 0x8e, 0x2c, 0x4b, 0x64, 0xef, 0x57, 0x58, 0xc2, 0xde, 0x99, + 0xb9, 0x0c, 0x5e, 0xfd, 0xe6, 0xa9, 0x1e, 0x7c, 0x4d, 0x2d, 0x3e, 0xed, 0x9e, 0xe8, + 0x1d, 0x9f, 0x0c, 0xcf, 0x7d, 0xb7, 0x45, 0x9d, 0xf6, 0x9c, 0x93, 0x24, 0x9a, 0xa9, + 0xcc, 0xef, 0xb2, 0x2d, 0x7e, 0xa0, 0x83, 0x37, 0xec, 0xfa, 0x2f, 0xd6, 0xca, 0xf5, + 0xa7, 0x42, 0xf5, 0x70, 0x25, 0x2b, 0x76, 0x06, 0x7c, 0xed, 0x01, 0x56, 0x68, 0x5d, + 0xd6, 0x41, 0xe6, 0xc2, 0x8a, 0xb0, 0xc0, 0x74, 0xba, 0x91, 0xd9, 0x17, 0xf2, 0x99, + 0x73, 0x44, 0x2b, 0xe9, 0x60, 0xde, 0x31, 0x7f, 0x9f, 0xfb, ], ock: [ - 0xc9, 0x72, 0x1e, 0x9e, 0x65, 0xa2, 0x61, 0x85, 0x10, 0x07, 0xcd, 0x81, 0x46, 0x7b, - 0xa5, 0xf3, 0x58, 0x05, 0xba, 0x78, 0x5a, 0x2c, 0x92, 0xa9, 0xaa, 0x62, 0x32, 0xb0, - 0x55, 0x1c, 0xf3, 0xf4, + 0x27, 0x6a, 0x0b, 0x0f, 0xd0, 0x38, 0xbb, 0x1f, 0xca, 0x8a, 0xf0, 0x2d, 0x37, 0x8e, + 0x1e, 0x79, 0xfd, 0x45, 0xa4, 0x53, 0xa8, 0xd9, 0x8f, 0xbe, 0x13, 0xc1, 0xc3, 0x53, + 0x83, 0xc8, 0x96, 0xb6, ], op: [ - 0x9e, 0x64, 0x17, 0x4b, 0x4a, 0xb9, 0x81, 0x40, 0x5c, 0x32, 0x3b, 0x5e, 0x12, 0x47, - 0x59, 0x45, 0xa4, 0x6d, 0x4f, 0xed, 0xf8, 0x06, 0x08, 0x28, 0x04, 0x1c, 0xd2, 0x0e, - 0x62, 0xfd, 0x2c, 0xef, 0xa5, 0x3d, 0x19, 0xf5, 0x69, 0x45, 0x95, 0xd5, 0xae, 0x63, + 0x44, 0x7e, 0xfa, 0x0f, 0x22, 0x05, 0x00, 0x44, 0x26, 0x3d, 0x7d, 0x98, 0xd4, 0x75, + 0xc2, 0x60, 0x14, 0x26, 0xf1, 0xae, 0xa1, 0x9e, 0xd5, 0xaf, 0xe3, 0xb5, 0xfc, 0x75, + 0xd0, 0x81, 0x24, 0xa4, 0xa5, 0x3d, 0x19, 0xf5, 0x69, 0x45, 0x95, 0xd5, 0xae, 0x63, 0x02, 0x27, 0x67, 0x3c, 0x80, 0x24, 0x9c, 0xe1, 0x24, 0x41, 0x9f, 0x46, 0xdf, 0x4e, 0x7b, 0x3f, 0xc1, 0x04, 0x61, 0x28, 0xcd, 0x0b, ], c_out: [ - 0xbc, 0x16, 0xaf, 0xa8, 0xaa, 0xb2, 0x38, 0x06, 0x26, 0x01, 0x8c, 0xe2, 0x75, 0x58, - 0x67, 0x55, 0x8f, 0x9d, 0x59, 0x85, 0x73, 0x93, 0xa1, 0xf3, 0x48, 0xb2, 0x1c, 0xb5, - 0x0f, 0x53, 0xea, 0xba, 0xe7, 0xf6, 0xe4, 0x7b, 0x45, 0x24, 0x1f, 0x6b, 0x7b, 0x3d, - 0x68, 0x94, 0x5d, 0xd4, 0x0c, 0xad, 0xc5, 0x7a, 0x9a, 0xde, 0x6a, 0xf9, 0x69, 0xae, - 0x07, 0x4f, 0xf2, 0x89, 0xbc, 0xb6, 0x61, 0x0a, 0xe3, 0x8c, 0x82, 0x10, 0xa5, 0xcb, - 0xd7, 0x47, 0xb8, 0x31, 0x15, 0x1c, 0x56, 0xef, 0x02, 0xc9, + 0x64, 0xd3, 0x78, 0x4a, 0x8c, 0x25, 0xf8, 0x11, 0x64, 0x81, 0xe6, 0xc6, 0xd7, 0x07, + 0x22, 0xb3, 0xec, 0xe7, 0x37, 0x3b, 0x27, 0x09, 0x27, 0xea, 0x69, 0xe2, 0x17, 0x18, + 0xd4, 0x30, 0xad, 0xc7, 0xea, 0x1f, 0xb1, 0x9e, 0x3a, 0x80, 0xe5, 0x55, 0xc7, 0x22, + 0x6c, 0x47, 0xab, 0x06, 0x9f, 0x51, 0x32, 0x0d, 0x94, 0x4b, 0x66, 0x7b, 0x85, 0x9f, + 0xb6, 0x7e, 0xd0, 0x1c, 0xbf, 0xa6, 0xf3, 0x5a, 0xac, 0x38, 0x72, 0x6c, 0x40, 0x40, + 0x4c, 0xf6, 0x0b, 0x02, 0x3b, 0xa3, 0x26, 0x31, 0x50, 0x8f, ], }, TestVector { ovk: [ - 0x57, 0x34, 0x67, 0xa7, 0xb3, 0x0e, 0xad, 0x6c, 0xcc, 0x50, 0x47, 0x44, 0xca, 0x9e, - 0x1a, 0x28, 0x1a, 0x0d, 0x1a, 0x08, 0x73, 0x8b, 0x06, 0xa0, 0x68, 0x4f, 0xea, 0xcd, - 0x1e, 0x9d, 0x12, 0x6d, + 0xa5, 0xaf, 0x3b, 0xdc, 0x0c, 0x32, 0xb6, 0x51, 0x85, 0x90, 0xce, 0x04, 0x9a, 0x3d, + 0x7b, 0xb2, 0x35, 0x7b, 0x0f, 0x24, 0x58, 0x4e, 0xd7, 0x8d, 0x36, 0xde, 0x49, 0xbe, + 0x7c, 0xed, 0xb2, 0x84, ], ivk: [ - 0xdb, 0x95, 0xea, 0x8b, 0xd9, 0xf9, 0x3d, 0x41, 0xb5, 0xab, 0x2b, 0xeb, 0xc9, 0x1a, - 0x38, 0xed, 0xd5, 0x27, 0x08, 0x3e, 0x2a, 0x6e, 0xf9, 0xf3, 0xc2, 0x97, 0x02, 0xd5, - 0xff, 0x89, 0xed, 0x00, + 0x43, 0x98, 0x29, 0xc6, 0x7e, 0x0b, 0x12, 0xd6, 0xb5, 0x8d, 0x03, 0x17, 0x7e, 0x7b, + 0x98, 0xf2, 0x01, 0x78, 0x9c, 0x43, 0xfc, 0x76, 0x6e, 0x41, 0xd8, 0x8a, 0x49, 0x40, + 0x4d, 0x6b, 0x88, 0x07, ], default_d: [ - 0x23, 0x3c, 0x4a, 0xb8, 0x86, 0xa5, 0x5e, 0x3b, 0xa3, 0x74, 0xc0, + 0x0d, 0xaa, 0x3c, 0x1b, 0xcf, 0xda, 0xda, 0x95, 0x7e, 0x46, 0xb6, ], default_pk_d: [ - 0xb6, 0x8e, 0x9e, 0xe0, 0xc0, 0x67, 0x8d, 0x7b, 0x30, 0x36, 0x93, 0x1c, 0x83, 0x1a, - 0x25, 0x25, 0x5f, 0x7e, 0xe4, 0x87, 0x38, 0x5a, 0x30, 0x31, 0x6e, 0x15, 0xf6, 0x48, - 0x2b, 0x87, 0x4f, 0xda, + 0x2a, 0x9f, 0xbb, 0x3b, 0xac, 0xd1, 0x7c, 0x47, 0xa8, 0xe1, 0x57, 0x2f, 0xc5, 0x1b, + 0xa4, 0x9e, 0xb4, 0x65, 0x1c, 0x6d, 0x90, 0xb0, 0x4a, 0x27, 0x4c, 0xe1, 0xb4, 0xaf, + 0xc8, 0x93, 0x29, 0xcb, ], v: 1000000000, rcm: [ @@ -1907,14 +1941,14 @@ pub(crate) fn make_test_vectors() -> Vec { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ], cv: [ - 0x2a, 0x54, 0x7d, 0x97, 0x8c, 0x7c, 0x90, 0xa8, 0xd0, 0xa5, 0x47, 0x4e, 0x29, 0xdb, - 0xff, 0xf3, 0x4b, 0xae, 0x81, 0xe6, 0x40, 0x8e, 0xc1, 0xfe, 0x2d, 0x56, 0xa2, 0x52, - 0x41, 0xa8, 0xe3, 0x29, + 0x6b, 0x29, 0x10, 0x37, 0xf1, 0x4d, 0x5f, 0xc1, 0x2e, 0xe9, 0x91, 0xe4, 0xe2, 0x67, + 0x36, 0xe3, 0xb6, 0x57, 0x4d, 0x1b, 0x49, 0xf7, 0x07, 0x8b, 0x85, 0x34, 0x0a, 0x82, + 0xff, 0xb6, 0xb5, 0x4f, ], cmu: [ - 0xf4, 0xba, 0x4e, 0xf0, 0x40, 0xf8, 0x0d, 0x00, 0x08, 0x0d, 0x29, 0xa6, 0xb3, 0x99, - 0xdc, 0x40, 0x32, 0x40, 0x33, 0x61, 0xe0, 0x59, 0x1e, 0xd6, 0x14, 0x99, 0xbc, 0x06, - 0x8e, 0x41, 0xed, 0x38, + 0x7a, 0x9b, 0xdf, 0xcc, 0xbb, 0xdc, 0x2a, 0x26, 0xa3, 0x58, 0x37, 0x4e, 0x09, 0xcd, + 0x6e, 0xb6, 0x16, 0x63, 0xa6, 0x2b, 0xae, 0x15, 0x7f, 0x42, 0xa2, 0x42, 0x79, 0xba, + 0xb8, 0xbc, 0x5c, 0x34, ], esk: [ 0x29, 0x95, 0x89, 0x80, 0x69, 0x4f, 0x7f, 0x67, 0x08, 0x09, 0x97, 0xc2, 0x66, 0x47, @@ -1922,26 +1956,28 @@ pub(crate) fn make_test_vectors() -> Vec { 0x1f, 0xe0, 0xf8, 0x00, ], epk: [ - 0xea, 0x6b, 0x3c, 0x98, 0x5f, 0x33, 0xb2, 0xa2, 0x2d, 0x0d, 0xbf, 0x7c, 0xd9, 0x30, - 0x19, 0xfd, 0x9e, 0x57, 0x31, 0x6c, 0x85, 0xb7, 0x67, 0x49, 0x54, 0x62, 0x9c, 0x77, - 0xdf, 0xae, 0xc0, 0x66, + 0x70, 0x99, 0xe7, 0x89, 0x48, 0x89, 0x08, 0x2f, 0xb8, 0x52, 0x8b, 0xd9, 0xa9, 0x77, + 0x8a, 0x4a, 0x68, 0x1a, 0x07, 0x65, 0xcf, 0x2e, 0xe3, 0x02, 0xf8, 0x9f, 0xbd, 0x14, + 0xa1, 0x3f, 0x4c, 0x56, ], shared_secret: [ - 0xc0, 0x64, 0x58, 0x25, 0xdf, 0xc4, 0x4d, 0x54, 0x82, 0x83, 0xf6, 0xe8, 0x88, 0x25, - 0x3b, 0xf5, 0xc3, 0x2a, 0x90, 0xde, 0xbb, 0x92, 0x8e, 0x89, 0x67, 0x86, 0xac, 0x0b, - 0x16, 0xd5, 0xf6, 0x56, + 0x0b, 0x1a, 0x29, 0x4c, 0x04, 0x58, 0x61, 0xdf, 0x76, 0x54, 0x28, 0x19, 0xea, 0x3c, + 0x96, 0xc8, 0x30, 0xcf, 0x26, 0x97, 0x16, 0x82, 0xc9, 0xa9, 0x0b, 0x36, 0xb8, 0xdc, + 0x6d, 0x1b, 0x0f, 0x0f, ], k_enc: [ - 0x33, 0xd2, 0xda, 0x8d, 0x80, 0xe0, 0xce, 0xd8, 0xb4, 0xbe, 0xec, 0x94, 0x3a, 0x0f, - 0xc9, 0xc9, 0x60, 0xad, 0x7c, 0xcc, 0x59, 0x77, 0x43, 0x74, 0x4c, 0x18, 0xc9, 0xc2, - 0xa5, 0x62, 0xf6, 0x3a, + 0x19, 0x0b, 0xaf, 0x71, 0x67, 0x47, 0x82, 0xb8, 0x11, 0x14, 0x5c, 0x7a, 0xb1, 0xf4, + 0x5b, 0xe9, 0x95, 0xad, 0xc8, 0xc8, 0xed, 0xff, 0xc7, 0xd9, 0x9f, 0x17, 0x8e, 0x60, + 0xb8, 0xe7, 0xe2, 0x9a, ], p_enc: [ - 0x01, 0x23, 0x3c, 0x4a, 0xb8, 0x86, 0xa5, 0x5e, 0x3b, 0xa3, 0x74, 0xc0, 0x00, 0xca, - 0x9a, 0x3b, 0x00, 0x00, 0x00, 0x00, 0x6e, 0xbb, 0xed, 0x74, 0x36, 0x19, 0xa2, 0x56, - 0xf9, 0xad, 0x2e, 0x85, 0x88, 0x0c, 0xfa, 0xa9, 0x09, 0x8a, 0x5f, 0xdb, 0x16, 0x29, - 0x99, 0x0d, 0x9a, 0x7d, 0x3b, 0xb9, 0x3f, 0xc9, 0x00, 0x03, 0xf6, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x0d, 0xaa, 0x3c, 0x1b, 0xcf, 0xda, 0xda, 0x95, 0x7e, 0x46, 0xb6, 0x00, 0xca, + 0x9a, 0x3b, 0x00, 0x00, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, + 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, + 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x6e, 0xbb, 0xed, 0x74, + 0x36, 0x19, 0xa2, 0x56, 0xf9, 0xad, 0x2e, 0x85, 0x88, 0x0c, 0xfa, 0xa9, 0x09, 0x8a, + 0x5f, 0xdb, 0x16, 0x29, 0x99, 0x0d, 0x9a, 0x7d, 0x3b, 0xb9, 0x3f, 0xc9, 0x00, 0x03, + 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -1977,71 +2013,73 @@ pub(crate) fn make_test_vectors() -> Vec { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ], c_enc: [ - 0x14, 0x9a, 0x52, 0xf8, 0xf5, 0x34, 0x2b, 0x44, 0x84, 0x88, 0x91, 0xf8, 0x85, 0xd3, - 0xcd, 0x09, 0x9a, 0xbe, 0x80, 0x5a, 0xa5, 0x09, 0x1f, 0xe1, 0x71, 0x0e, 0xb7, 0x35, - 0x02, 0xde, 0x38, 0x7d, 0xf3, 0xf9, 0x64, 0x67, 0x22, 0xe8, 0xb8, 0x5c, 0x37, 0x7c, - 0x82, 0x2a, 0x71, 0x03, 0x34, 0x7c, 0x81, 0x01, 0xe9, 0xae, 0x8c, 0x31, 0x82, 0xca, - 0x36, 0xda, 0xfd, 0x75, 0x8d, 0x96, 0xce, 0xba, 0x48, 0x32, 0x7a, 0x09, 0x82, 0x86, - 0xa4, 0xe8, 0x32, 0x1d, 0x1e, 0x74, 0xfe, 0x3d, 0x61, 0x59, 0xc0, 0x29, 0x48, 0x3d, - 0xe9, 0xee, 0xf3, 0xb2, 0x4d, 0x85, 0xe4, 0xd5, 0x16, 0xb8, 0x70, 0x4f, 0x8e, 0x7d, - 0x93, 0xe7, 0x44, 0x42, 0xed, 0x00, 0x7a, 0xd7, 0x9a, 0x61, 0x52, 0xf2, 0xb6, 0x64, - 0x2f, 0xbe, 0xe6, 0x04, 0x35, 0xe1, 0x92, 0x09, 0xd8, 0x11, 0xc6, 0x6c, 0x17, 0xb7, - 0xdf, 0x3d, 0xfd, 0x76, 0x9f, 0xb5, 0xc7, 0xd0, 0x06, 0xb3, 0x67, 0x42, 0xbb, 0xe7, - 0x26, 0x92, 0x9e, 0x87, 0x9b, 0x11, 0x6d, 0x36, 0x13, 0x57, 0x1a, 0xa6, 0x3a, 0xc2, - 0xcc, 0xca, 0x43, 0xf8, 0x90, 0x0b, 0x89, 0x3e, 0x64, 0xdd, 0x0b, 0x8f, 0xf9, 0x1e, - 0xc5, 0x11, 0x40, 0x82, 0xe6, 0xd0, 0x0c, 0xf9, 0x3a, 0x7c, 0xfa, 0x75, 0x18, 0xbb, - 0x7f, 0xb6, 0x4a, 0x7f, 0x34, 0x64, 0x20, 0xb6, 0x44, 0x78, 0xd7, 0x18, 0x69, 0xe9, - 0x1d, 0x47, 0x97, 0x90, 0x1f, 0xa8, 0x6e, 0x70, 0xb2, 0x20, 0x1a, 0xfe, 0x4b, 0xd3, - 0xea, 0x55, 0x03, 0x81, 0x6f, 0xac, 0x68, 0x7d, 0x81, 0x25, 0x2f, 0x65, 0x61, 0x6e, - 0x7f, 0xb2, 0x68, 0x46, 0x52, 0x1e, 0x39, 0xff, 0x94, 0xbe, 0x73, 0xb8, 0xac, 0xa8, - 0x04, 0xc6, 0x5c, 0xf9, 0x4e, 0x32, 0x56, 0xbd, 0x3c, 0x69, 0xad, 0x31, 0x8e, 0x6b, - 0x28, 0x55, 0x19, 0x48, 0x77, 0x93, 0xee, 0x29, 0x88, 0x51, 0x40, 0xf0, 0xbc, 0x00, - 0x84, 0x5f, 0x67, 0x41, 0x5f, 0x67, 0x0f, 0x04, 0xca, 0x81, 0x8c, 0x5f, 0x32, 0x49, - 0xd3, 0xfb, 0x70, 0xbf, 0xea, 0x10, 0xc6, 0x25, 0xeb, 0x8c, 0xf2, 0xca, 0xb3, 0xf5, - 0x83, 0x62, 0x2a, 0x21, 0xa3, 0x8b, 0x8f, 0xe5, 0x1a, 0x5f, 0xf2, 0x91, 0x9e, 0xf4, - 0xc1, 0xbd, 0x98, 0x30, 0xa9, 0xf2, 0x48, 0x6a, 0xbd, 0x88, 0x5d, 0xd9, 0x43, 0xb9, - 0x4e, 0xdc, 0x8f, 0x88, 0xc8, 0xb7, 0x8a, 0x5e, 0xb0, 0x31, 0xf3, 0x4b, 0x7d, 0x93, - 0x1c, 0x87, 0x53, 0xaf, 0xd9, 0x76, 0x8d, 0x0f, 0xa8, 0xd2, 0x6e, 0x88, 0xc9, 0x56, - 0x7a, 0xd5, 0x89, 0x23, 0xe7, 0xb0, 0xaf, 0xbd, 0xaa, 0xdf, 0x47, 0x7b, 0xd1, 0xd2, - 0x3f, 0xc4, 0x0a, 0x42, 0xc2, 0x9b, 0x4d, 0x5f, 0xe1, 0x08, 0x76, 0x45, 0xdd, 0xfd, - 0xeb, 0xa0, 0xc7, 0xd5, 0x67, 0x15, 0xcd, 0x57, 0xf0, 0xd1, 0x74, 0x1a, 0x3d, 0x9c, - 0xb3, 0x8d, 0x88, 0xd6, 0x47, 0xb1, 0xc5, 0xb2, 0x4a, 0xdd, 0xba, 0xd1, 0xac, 0xfa, - 0x3a, 0x8d, 0xa3, 0x7a, 0x74, 0x26, 0x05, 0x55, 0xec, 0x0d, 0xea, 0x88, 0xed, 0x2c, - 0x7f, 0x46, 0xdd, 0x87, 0xb3, 0xf2, 0x79, 0xa9, 0x6a, 0x0e, 0x78, 0x54, 0xec, 0x4a, - 0x79, 0xce, 0xad, 0xc7, 0x4a, 0x68, 0x0f, 0xc8, 0x2d, 0x75, 0xae, 0xc7, 0xf2, 0xd1, - 0x3d, 0xfb, 0x62, 0x23, 0x50, 0x57, 0xe4, 0xf7, 0xdc, 0x5b, 0x07, 0xc6, 0xba, 0xba, - 0x82, 0xb3, 0x2f, 0xe9, 0x0b, 0x5c, 0x6e, 0x9d, 0xc6, 0xb2, 0xfb, 0x33, 0xbe, 0xac, - 0x88, 0x0d, 0x3a, 0x60, 0xba, 0x08, 0x48, 0xfa, 0xc6, 0x61, 0x9d, 0xa8, 0xca, 0x33, - 0xa6, 0x32, 0x94, 0xeb, 0x63, 0xd0, 0xf2, 0x4c, 0xbb, 0x1e, 0x03, 0x17, 0x82, 0x88, - 0x0f, 0xfa, 0x18, 0x35, 0x6c, 0x98, 0x76, 0x2c, 0xcd, 0xd3, 0xaf, 0xab, 0x81, 0xf1, - 0x9a, 0xbf, 0x3b, 0xdd, 0x2b, 0xc4, 0x3c, 0xb1, 0xf2, 0x15, 0x5c, 0xaf, 0x64, 0x98, - 0x89, 0x4e, 0x06, 0x8b, 0xa7, 0x49, 0xc9, 0x76, 0xec, 0x23, 0xf2, 0x11, 0x62, 0x26, - 0x14, 0x60, 0x78, 0x56, 0xd8, 0x7b, 0x74, 0x16, 0x24, 0xf7, 0xf8, 0x34, 0x95, 0xd7, - 0xde, 0x4d, 0x6d, 0xe2, 0x08, 0xe1, 0x35, 0x74, 0xc8, 0x2a, 0x1b, 0x8b, 0x1c, 0xfe, - 0x87, 0xe9, 0x18, 0xe7, 0xb3, 0x96, + 0x3c, 0x09, 0xa0, 0x14, 0x0a, 0x8e, 0x3c, 0x91, 0xdc, 0x8e, 0x9d, 0x47, 0xa8, 0x52, + 0x00, 0x9d, 0xc7, 0x5a, 0x88, 0x32, 0x72, 0x0c, 0xf5, 0xa8, 0xd7, 0x38, 0x20, 0xe1, + 0x13, 0xdd, 0xb4, 0x5f, 0x9e, 0x96, 0x49, 0x8b, 0xc3, 0xe9, 0xe5, 0xd4, 0xed, 0x07, + 0x1d, 0x08, 0x42, 0xb9, 0x86, 0x5c, 0x84, 0x53, 0xd5, 0xdc, 0x39, 0x5a, 0x50, 0xd7, + 0xbf, 0x03, 0x0e, 0xc9, 0x86, 0x5c, 0xc6, 0xa2, 0x5e, 0x78, 0x65, 0xab, 0x43, 0x47, + 0xa1, 0xf5, 0x24, 0xdd, 0x9e, 0xcb, 0x8f, 0x92, 0xcd, 0xb4, 0xc4, 0x21, 0x46, 0x1c, + 0xee, 0x5c, 0x19, 0xab, 0x03, 0x1d, 0x68, 0x9a, 0x2b, 0xd5, 0x96, 0xcf, 0x52, 0xd4, + 0x03, 0xeb, 0x5f, 0x6d, 0x31, 0x38, 0xb3, 0xd7, 0x6c, 0x5e, 0xb8, 0x66, 0x40, 0x8c, + 0x7f, 0xcc, 0x82, 0xee, 0xa2, 0x31, 0x57, 0x7d, 0x99, 0x09, 0xc7, 0xe8, 0xed, 0x59, + 0x8c, 0x7a, 0xad, 0x85, 0xbc, 0x07, 0xd1, 0xce, 0x06, 0xa9, 0xaf, 0x30, 0x49, 0xd9, + 0x3d, 0x09, 0xef, 0x2e, 0xdb, 0xf3, 0xb5, 0x66, 0xf0, 0x17, 0xaf, 0xb5, 0x09, 0xc4, + 0x1f, 0x39, 0xf6, 0x30, 0x8c, 0xf7, 0xc8, 0x8c, 0x67, 0x14, 0x1a, 0x7e, 0xf6, 0xc4, + 0xa5, 0xfa, 0x68, 0x2b, 0x97, 0xae, 0x72, 0x66, 0xe4, 0x7a, 0x0e, 0x0b, 0x94, 0x86, + 0xad, 0x62, 0x96, 0x0f, 0x06, 0x48, 0x7c, 0x37, 0x24, 0x0d, 0x68, 0xd5, 0x81, 0x5f, + 0x4b, 0x83, 0xaf, 0xfd, 0xac, 0x53, 0x4a, 0x5b, 0xc4, 0xaf, 0xf5, 0x6b, 0x2b, 0xd9, + 0xed, 0xa9, 0x10, 0xac, 0x32, 0x73, 0x6f, 0x79, 0x1d, 0x79, 0xdf, 0xdd, 0x67, 0x3f, + 0xa9, 0x85, 0x0a, 0x5e, 0x1e, 0x16, 0x6a, 0x53, 0x80, 0x81, 0x7f, 0x42, 0x73, 0x26, + 0x81, 0x66, 0x7f, 0xe9, 0xfa, 0x53, 0xeb, 0xff, 0x8a, 0x67, 0x92, 0x24, 0xc5, 0x65, + 0x2a, 0x1f, 0x2a, 0xe6, 0x55, 0xf2, 0x22, 0xc2, 0x25, 0xdc, 0x8b, 0x6e, 0xeb, 0xb3, + 0x8e, 0xf0, 0x49, 0x29, 0xd6, 0xb3, 0x9c, 0x1b, 0x0e, 0xa7, 0xea, 0x5f, 0x9d, 0x06, + 0xb8, 0x59, 0x76, 0x27, 0xac, 0x62, 0x9b, 0x62, 0xaf, 0x81, 0x78, 0x24, 0x05, 0x96, + 0x42, 0xa9, 0xb1, 0xe0, 0x4a, 0x27, 0x20, 0x98, 0x74, 0xc9, 0x56, 0x2a, 0xf5, 0x5d, + 0x49, 0x37, 0x53, 0xa8, 0x37, 0xc6, 0x4b, 0x1e, 0xae, 0xc6, 0x7b, 0xcd, 0x58, 0x07, + 0xf7, 0x1f, 0x4f, 0xb1, 0x66, 0xd9, 0x6b, 0xbf, 0x24, 0xdc, 0xc5, 0x69, 0x88, 0xbf, + 0x31, 0x01, 0x36, 0xaf, 0x79, 0xd1, 0xe3, 0x2c, 0x8b, 0x13, 0x7d, 0x49, 0xe0, 0x98, + 0x60, 0x5b, 0xe2, 0x31, 0x6b, 0x38, 0xbb, 0xba, 0xc6, 0x5f, 0x36, 0x68, 0x25, 0x52, + 0x1d, 0x44, 0x46, 0x2f, 0x05, 0x2a, 0x86, 0xab, 0xea, 0xab, 0xf7, 0x9c, 0x4f, 0xc9, + 0xff, 0xd6, 0x94, 0x38, 0x28, 0x69, 0x29, 0xf7, 0x99, 0x90, 0xcc, 0xfe, 0xa8, 0x38, + 0x61, 0xd8, 0x46, 0xcf, 0xd4, 0x9e, 0xa9, 0x95, 0x34, 0x16, 0x5e, 0x1f, 0xfa, 0x7c, + 0xc9, 0xea, 0xa9, 0xff, 0xa9, 0xad, 0x13, 0x1b, 0xe3, 0xd1, 0xf4, 0xd1, 0xa6, 0xcc, + 0x45, 0xad, 0x00, 0x11, 0xcc, 0xfd, 0xbe, 0x50, 0x11, 0x7f, 0x80, 0x14, 0xe7, 0xf3, + 0x7f, 0x3b, 0x99, 0x14, 0x5b, 0x2f, 0xb8, 0xb0, 0x5e, 0x66, 0xe3, 0x1b, 0x06, 0x1e, + 0x3f, 0xf3, 0xc7, 0x43, 0xd3, 0x9b, 0x93, 0x8e, 0x14, 0x68, 0xf0, 0x83, 0xe1, 0xde, + 0x0e, 0xae, 0xb3, 0x2b, 0x05, 0x9f, 0x78, 0x9c, 0x09, 0x7f, 0x82, 0xd4, 0xc3, 0x77, + 0xff, 0xb6, 0x59, 0x23, 0xf3, 0x96, 0xa0, 0xd2, 0x00, 0xc4, 0xcb, 0xe4, 0x8f, 0x84, + 0x0b, 0x36, 0x70, 0xe4, 0x76, 0xe9, 0xa0, 0x09, 0x3f, 0x06, 0xfe, 0x4f, 0x7c, 0x7a, + 0x58, 0x40, 0xc2, 0xc4, 0xc0, 0x87, 0xb5, 0xef, 0x05, 0xdd, 0x92, 0x7f, 0x2c, 0x0b, + 0x57, 0x8b, 0x13, 0xb2, 0xf8, 0xa5, 0x92, 0x97, 0xc0, 0x5b, 0x8f, 0xe0, 0x4f, 0x27, + 0xee, 0xd3, 0x2a, 0xe4, 0x6b, 0xab, 0xff, 0x3b, 0xe5, 0x62, 0x01, 0xe7, 0x15, 0x7a, + 0x54, 0x4f, 0x56, 0xcd, 0x7b, 0x5e, 0xbf, 0xe7, 0xe1, 0xe1, 0x51, 0xb2, 0xf3, 0x79, + 0x9e, 0x16, 0x05, 0xdc, 0x39, 0x19, 0x37, 0x19, 0xa8, 0x81, 0xc6, 0x0e, 0xa4, 0xc8, + 0xd0, 0x88, 0xcc, 0x30, 0xfd, 0x30, 0x03, 0x92, 0xa3, 0x33, 0xfd, 0x9c, 0x64, 0xb8, + 0x32, 0x53, 0x41, 0x3d, 0x75, 0x99, 0x4e, 0x19, 0x6f, 0x31, 0xa8, 0xf4, 0xb0, 0xe9, + 0x8b, 0x89, 0x2b, 0xe9, 0x13, 0xe5, 0x53, 0x1c, 0x76, 0xc1, ], ock: [ - 0xdb, 0x5b, 0xa6, 0xb9, 0xdb, 0xb1, 0x1f, 0x7c, 0xe8, 0x12, 0xeb, 0x1b, 0xf3, 0x29, - 0x8c, 0xca, 0x55, 0x71, 0xee, 0xcc, 0x69, 0xb7, 0x22, 0xa0, 0xa3, 0xb8, 0x67, 0x50, - 0x72, 0x92, 0x99, 0xa0, + 0x35, 0xc4, 0x5c, 0x7a, 0xad, 0x47, 0xc6, 0x08, 0x16, 0xf6, 0x51, 0xb2, 0x0e, 0xaf, + 0xdd, 0xe1, 0xe7, 0xe4, 0xd5, 0xbc, 0x61, 0xfb, 0x60, 0x12, 0x17, 0x03, 0x02, 0x68, + 0x13, 0xd8, 0xb4, 0x0b, ], op: [ - 0xb6, 0x8e, 0x9e, 0xe0, 0xc0, 0x67, 0x8d, 0x7b, 0x30, 0x36, 0x93, 0x1c, 0x83, 0x1a, - 0x25, 0x25, 0x5f, 0x7e, 0xe4, 0x87, 0x38, 0x5a, 0x30, 0x31, 0x6e, 0x15, 0xf6, 0x48, - 0x2b, 0x87, 0x4f, 0xda, 0x29, 0x95, 0x89, 0x80, 0x69, 0x4f, 0x7f, 0x67, 0x08, 0x09, + 0x2a, 0x9f, 0xbb, 0x3b, 0xac, 0xd1, 0x7c, 0x47, 0xa8, 0xe1, 0x57, 0x2f, 0xc5, 0x1b, + 0xa4, 0x9e, 0xb4, 0x65, 0x1c, 0x6d, 0x90, 0xb0, 0x4a, 0x27, 0x4c, 0xe1, 0xb4, 0xaf, + 0xc8, 0x93, 0x29, 0xcb, 0x29, 0x95, 0x89, 0x80, 0x69, 0x4f, 0x7f, 0x67, 0x08, 0x09, 0x97, 0xc2, 0x66, 0x47, 0x02, 0x89, 0x0c, 0xd1, 0xb5, 0x03, 0xdd, 0xa4, 0x2d, 0x33, 0xa8, 0x99, 0xce, 0x99, 0x1f, 0xe0, 0xf8, 0x00, ], c_out: [ - 0xe2, 0x7a, 0x46, 0x4d, 0x6f, 0x44, 0xcc, 0x44, 0xf6, 0x17, 0xe2, 0x3c, 0x9f, 0xb1, - 0xb7, 0x1f, 0xff, 0xd4, 0x6a, 0xeb, 0xf0, 0x36, 0x77, 0xcf, 0x7d, 0xd2, 0x4d, 0x71, - 0x1b, 0xa0, 0xc6, 0xca, 0x38, 0x53, 0x09, 0x7b, 0x24, 0x7a, 0xb7, 0x4c, 0x15, 0xbb, - 0x93, 0x8e, 0xd6, 0x02, 0xfb, 0xcd, 0x30, 0xf4, 0xa6, 0x59, 0x56, 0x43, 0x0f, 0x47, - 0xa0, 0xfb, 0xcb, 0xe8, 0xe0, 0x8a, 0xad, 0xa3, 0x86, 0x30, 0x78, 0x5a, 0x80, 0x57, - 0x53, 0xba, 0x33, 0xb3, 0x34, 0xcd, 0x2a, 0x4b, 0xfc, 0x3d, + 0x01, 0x01, 0x97, 0xe7, 0x6d, 0x28, 0x38, 0xcb, 0xb7, 0x4b, 0x7c, 0xcd, 0xf5, 0x8e, + 0x20, 0x3c, 0x73, 0x5a, 0xbe, 0x4d, 0xb7, 0xb5, 0xe8, 0x74, 0x1b, 0x39, 0xc5, 0xfd, + 0x2c, 0xab, 0xd3, 0x7e, 0xc0, 0xd2, 0x77, 0x6a, 0xd0, 0x6a, 0x6a, 0xfc, 0x1e, 0x73, + 0x4a, 0x38, 0x36, 0x15, 0xb9, 0xbd, 0x9f, 0xf4, 0x22, 0x26, 0xe6, 0xd9, 0xb4, 0x6c, + 0xeb, 0xba, 0x59, 0xb9, 0xfb, 0xb5, 0xc9, 0x8c, 0x86, 0xa1, 0xac, 0x22, 0x60, 0x42, + 0xd8, 0x86, 0x03, 0xa6, 0x52, 0x8c, 0xec, 0x74, 0x7e, 0x25, ], }, ] diff --git a/masp_primitives/src/test_vectors/note_encryption_new.rs b/masp_primitives/src/test_vectors/note_encryption_new.rs new file mode 100644 index 00000000..e2783c94 --- /dev/null +++ b/masp_primitives/src/test_vectors/note_encryption_new.rs @@ -0,0 +1,564 @@ + struct TestVector { + ovk: [u8; 32], + ivk: [u8; 32], + default_d: [u8; 11], + default_pk_d: [u8; 32], + v: u64, + rcm: [u8; 32], + memo: [u8; 512], + cv: [u8; 32], + cmu: [u8; 32], + esk: [u8; 32], + epk: [u8; 32], + shared_secret: [u8; 32], + k_enc: [u8; 32], + p_enc: [u8; 564], + c_enc: [u8; 580], + ock: [u8; 32], + op: [u8; 64], + c_out: [u8; 80], + }; + + // From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/sapling_note_encryption.py + let test_vectors = vec![ + TestVector { + ovk: [ + 0x52, 0xac, 0x15, 0x88, 0x17, 0x5f, 0x5a, 0x5e, 0x97, 0x94, 0xe6, 0xdd, 0xb8, 0x53, 0x63, 0x61, 0x63, 0xc1, 0x3f, 0x91, 0x5d, 0x76, 0x08, 0x9d, 0x20, 0xde, 0x9f, 0x32, 0x05, 0xf0, 0x18, 0x5c + ], + ivk: [ + 0xbc, 0xc2, 0x5a, 0x9b, 0xf1, 0x42, 0xff, 0x98, 0x79, 0x67, 0x96, 0xea, 0xa1, 0xbc, 0xd5, 0xff, 0xb7, 0x28, 0xb3, 0x65, 0x0f, 0xf0, 0x49, 0x4a, 0x17, 0x02, 0x7d, 0xee, 0x93, 0x06, 0x6f, 0x02 + ], + default_d: [ + 0x84, 0x4f, 0x9a, 0x4b, 0x31, 0x8b, 0x71, 0x39, 0xab, 0x60, 0x56 + ], + default_pk_d: [ + 0x47, 0xd2, 0x3d, 0x02, 0xa8, 0x0a, 0x28, 0xaf, 0xc0, 0xd2, 0xc5, 0xfc, 0x57, 0xbd, 0x30, 0x1b, 0xb3, 0x99, 0xf8, 0x26, 0xb2, 0xca, 0xba, 0x6b, 0x62, 0x88, 0x36, 0x77, 0x23, 0x29, 0x4a, 0x64 + ], + v: 100000000, + rcm: [ + 0x39, 0x17, 0x6d, 0xac, 0x39, 0xac, 0xe4, 0x98, 0x0e, 0xcc, 0x8d, 0x77, 0x8e, 0x89, 0x86, 0x02, 0x55, 0xec, 0x36, 0x15, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + ], + memo: [ + 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + ], + cv: [ + 0x9e, 0x1c, 0xf5, 0xc3, 0x2e, 0xdc, 0xd3, 0x30, 0x87, 0x11, 0x60, 0x06, 0x35, 0xad, 0x95, 0xe1, 0x35, 0x46, 0xfb, 0xd9, 0xe0, 0x40, 0x70, 0x51, 0x79, 0x7d, 0x1f, 0x79, 0xcb, 0xcc, 0x4a, 0x3e + ], + cmu: [ + 0x3c, 0x7d, 0x5a, 0xeb, 0xcf, 0x4d, 0xa7, 0x9c, 0xf9, 0x05, 0xa1, 0xd9, 0x8a, 0xa0, 0x0b, 0xcf, 0x18, 0xee, 0x2a, 0x0a, 0xf2, 0xf8, 0x4a, 0x1a, 0x0f, 0x61, 0x20, 0x54, 0x7e, 0xd5, 0x95, 0x1f + ], + esk: [ + 0x81, 0xc7, 0xb2, 0x17, 0x1f, 0xf4, 0x41, 0x52, 0x50, 0xca, 0xc0, 0x1f, 0x59, 0x82, 0xfd, 0x8f, 0x49, 0x61, 0x9d, 0x61, 0xad, 0x78, 0xf6, 0x83, 0x0b, 0x3c, 0x60, 0x61, 0x45, 0x96, 0x2a, 0x0e + ], + epk: [ + 0x3e, 0x83, 0x55, 0x13, 0xb6, 0x77, 0xdb, 0x35, 0x95, 0x39, 0x67, 0xb0, 0x48, 0x1e, 0xe6, 0xd7, 0x0d, 0x64, 0x7e, 0xb0, 0x00, 0x82, 0x0f, 0x9d, 0x1e, 0x52, 0x0c, 0x1c, 0xde, 0x52, 0x42, 0xac + ], + shared_secret: [ + 0x0b, 0xe0, 0x22, 0x02, 0x17, 0x98, 0x36, 0x6c, 0xcd, 0x2d, 0x96, 0xf5, 0xe4, 0xb3, 0x9b, 0xc9, 0x1a, 0x59, 0x88, 0x48, 0xab, 0x95, 0x54, 0x81, 0xf8, 0xa8, 0x72, 0x10, 0x40, 0x0c, 0xef, 0x43 + ], + k_enc: [ + 0x2c, 0xd7, 0x9c, 0x0f, 0x1d, 0x54, 0xb5, 0x94, 0x3c, 0x2a, 0x6a, 0x6d, 0x83, 0x4a, 0x11, 0x98, 0xc9, 0xd6, 0xac, 0x12, 0xf0, 0x19, 0xc8, 0x92, 0x66, 0xd0, 0x3c, 0xc1, 0x83, 0xb9, 0x3a, 0x93 + ], + p_enc: [ + 0x02, 0x84, 0x4f, 0x9a, 0x4b, 0x31, 0x8b, 0x71, 0x39, 0xab, 0x60, 0x56, 0x00, 0xe1, 0xf5, 0x05, 0x00, 0x00, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x39, 0x17, 0x6d, 0xac, 0x39, 0xac, 0xe4, 0x98, 0x0e, 0xcc, 0x8d, 0x77, 0x8e, 0x89, 0x86, 0x02, 0x55, 0xec, 0x36, 0x15, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + ], + c_enc: [ + 0x96, 0x86, 0x5d, 0x04, 0x2a, 0x58, 0xdc, 0x2a, 0x2c, 0x34, 0x3e, 0x9b, 0xf1, 0x30, 0xe8, 0x2b, 0x1d, 0x2e, 0xa2, 0x77, 0x2c, 0x42, 0xda, 0x53, 0x48, 0x09, 0xd0, 0x71, 0x16, 0xa4, 0xeb, 0x28, 0xa7, 0x24, 0x9b, 0xf8, 0x19, 0x2f, 0xc0, 0xf5, 0xd7, 0xad, 0xe8, 0xce, 0xc9, 0x4d, 0x6b, 0xa7, 0xc5, 0x12, 0x73, 0x12, 0xcd, 0xc6, 0xc7, 0x57, 0x5b, 0x96, 0xea, 0xa8, 0x3f, 0x53, 0xd9, 0x3e, 0x32, 0x7b, 0xfe, 0x3f, 0x15, 0xc1, 0x9e, 0x8c, 0x36, 0xe1, 0xc2, 0x89, 0xe4, 0x4a, 0xea, 0x66, 0x89, 0xc3, 0xc2, 0xe3, 0xe9, 0x10, 0x02, 0x1e, 0xc3, 0xb4, 0x96, 0xee, 0x91, 0x37, 0xff, 0x1a, 0x28, 0x19, 0x11, 0xc2, 0x44, 0xa5, 0x81, 0xd3, 0x7a, 0x67, 0x6f, 0xca, 0xf1, 0x85, 0x17, 0x88, 0xe8, 0x43, 0xf2, 0xac, 0xd9, 0x25, 0x0c, 0x10, 0xf1, 0xd9, 0xac, 0xf1, 0x9c, 0x22, 0xbb, 0xba, 0xcc, 0x53, 0x51, 0xc5, 0x9c, 0xca, 0x98, 0x8b, 0x9b, 0xd1, 0xd5, 0x06, 0x76, 0xce, 0x78, 0x8a, 0xa6, 0xf8, 0x43, 0x8d, 0x95, 0x34, 0xcc, 0xf6, 0xa8, 0x34, 0x0b, 0xc1, 0xb2, 0xbe, 0xf8, 0x55, 0xe0, 0xd9, 0x06, 0xa2, 0x17, 0x00, 0x4e, 0x84, 0x18, 0x4f, 0x63, 0x2a, 0x07, 0xf6, 0xcd, 0x0e, 0x68, 0xf2, 0x86, 0xda, 0x3d, 0xdd, 0xdd, 0x35, 0x42, 0x99, 0xb4, 0xb2, 0xbc, 0xb4, 0xfa, 0x41, 0x8d, 0x36, 0x61, 0x55, 0x8e, 0x50, 0xf4, 0x85, 0xba, 0xe7, 0xf1, 0x88, 0xef, 0x4e, 0xd8, 0xbc, 0x62, 0xbe, 0xa7, 0x32, 0xfb, 0xc8, 0x13, 0xb2, 0xfc, 0x2a, 0x94, 0xa0, 0xfe, 0x88, 0x2e, 0x80, 0xe4, 0x1c, 0x92, 0x71, 0x2b, 0x05, 0x03, 0x64, 0x1a, 0xe4, 0xed, 0xba, 0xb6, 0x1d, 0x54, 0x57, 0xfd, 0xd1, 0xe5, 0x68, 0x61, 0x97, 0x24, 0x70, 0xd9, 0xc6, 0x45, 0xad, 0x7c, 0x26, 0x0b, 0x07, 0xd0, 0x2b, 0x17, 0xf9, 0xee, 0xe6, 0xe9, 0xc5, 0x8f, 0x15, 0x95, 0xbf, 0x0d, 0x20, 0x5e, 0x21, 0xb5, 0x48, 0xd3, 0x8e, 0x61, 0xcf, 0x96, 0x6a, 0x4c, 0x82, 0xb0, 0x45, 0x59, 0xb6, 0x47, 0x7a, 0x7e, 0x41, 0xf1, 0x5f, 0xe2, 0x6a, 0xcc, 0x74, 0x54, 0xf1, 0x21, 0xb5, 0xa3, 0x96, 0x3c, 0x08, 0x65, 0x81, 0xa7, 0x8a, 0x7e, 0xbb, 0x11, 0xe7, 0xa5, 0x18, 0x08, 0x60, 0x8d, 0x1e, 0x35, 0xe2, 0xd4, 0x03, 0x64, 0xe8, 0x86, 0xe9, 0xb8, 0xea, 0x24, 0x6c, 0x59, 0xe3, 0x1e, 0xbf, 0x6f, 0x3f, 0x2d, 0x48, 0x2d, 0x96, 0xb7, 0xb0, 0x89, 0x85, 0xbd, 0xec, 0xb9, 0x13, 0x49, 0x25, 0xfe, 0x3a, 0xe2, 0x7a, 0x64, 0x34, 0x4c, 0x20, 0x09, 0xbd, 0x7f, 0x44, 0x22, 0xaa, 0xbd, 0xcf, 0xa6, 0x1c, 0xb0, 0x58, 0xcf, 0x76, 0x31, 0x1e, 0x7e, 0x07, 0x7f, 0x55, 0xd6, 0x2b, 0x86, 0xe9, 0x63, 0x8d, 0x93, 0x68, 0x71, 0x6a, 0x21, 0x68, 0x86, 0xe8, 0x64, 0x22, 0x88, 0x0e, 0x4d, 0x21, 0xa9, 0xc1, 0x52, 0x22, 0xb3, 0x75, 0xe2, 0xb9, 0x48, 0xc1, 0x68, 0xbc, 0x50, 0x56, 0xbe, 0xeb, 0x87, 0x20, 0xb6, 0x9b, 0x55, 0x3d, 0xff, 0x20, 0x34, 0xcc, 0x2b, 0xc9, 0xc0, 0x55, 0x8a, 0xf0, 0x21, 0xdd, 0x93, 0xe2, 0x03, 0x8d, 0x1a, 0x73, 0xf8, 0x61, 0xd1, 0xdd, 0xb6, 0xe3, 0x15, 0x23, 0xd8, 0x18, 0x04, 0x4b, 0x43, 0x43, 0x21, 0x5d, 0x34, 0x04, 0x79, 0xed, 0xb4, 0x86, 0xe5, 0x0f, 0x9f, 0x72, 0xcb, 0x1f, 0xd6, 0x96, 0xbf, 0xc7, 0xb4, 0xda, 0x8d, 0x8b, 0x0b, 0xc5, 0xc9, 0x59, 0xf8, 0xf6, 0x96, 0xf5, 0x40, 0xd5, 0x5a, 0xce, 0x75, 0xd7, 0x47, 0x11, 0x6f, 0x17, 0x83, 0x45, 0xee, 0x43, 0x3c, 0x06, 0xae, 0xef, 0x2a, 0x25, 0xd4, 0x3b, 0xac, 0xe4, 0x83, 0x2b, 0xdb, 0x68, 0x19, 0xbc, 0x27, 0xed, 0x0e, 0x04, 0x9d, 0x98, 0x95, 0x0b, 0x19, 0x7e, 0xeb, 0x44, 0x78, 0x73, 0x69, 0xc5, 0xbc, 0xb3, 0xe4, 0x65, 0x96, 0x27, 0x21, 0x31, 0xa6, 0x23, 0x37, 0x3e, 0x25, 0x98, 0x76, 0xfa, 0xf2, 0xd9, 0xe2, 0xa5, 0x6e, 0xc7, 0x2c, 0xe1, 0xb7, 0x44, 0x79, 0xb9, 0xef, 0x79, 0xe0, 0x31, 0x41, 0x75, 0x8c, 0x1d, 0x8b, 0xe1, 0xfc, 0x5b, 0xbb, 0xb7, 0xe7, 0xb5, 0xd7, 0x46, 0x58, 0xe6, 0x01, 0xc2, 0x75, 0x7d, 0x59, 0x9b, 0x3e, 0xd2, 0x42, 0xba, 0xb9, 0x86, 0x46, 0xd3, 0xc8, 0x89, 0xce, 0xdf, 0x25, 0x11, 0x7e, 0x0c, 0x9a, 0xec, 0x3f, 0xfc, 0xbf, 0xd7, 0x55, 0x8c, 0x4f, 0xbf, 0xcf, 0xe1, 0x1b, 0xe0 + ], + ock: [ + 0x37, 0xc9, 0xe1, 0x33, 0xc8, 0x08, 0x39, 0x66, 0xf6, 0xde, 0x35, 0x59, 0x01, 0xb2, 0x29, 0xb3, 0xd7, 0x59, 0x7f, 0x08, 0x05, 0xae, 0x0b, 0xc4, 0xd2, 0xe1, 0x24, 0x17, 0xbe, 0xcc, 0x95, 0xdb + ], + op: [ + 0x47, 0xd2, 0x3d, 0x02, 0xa8, 0x0a, 0x28, 0xaf, 0xc0, 0xd2, 0xc5, 0xfc, 0x57, 0xbd, 0x30, 0x1b, 0xb3, 0x99, 0xf8, 0x26, 0xb2, 0xca, 0xba, 0x6b, 0x62, 0x88, 0x36, 0x77, 0x23, 0x29, 0x4a, 0x64, 0x81, 0xc7, 0xb2, 0x17, 0x1f, 0xf4, 0x41, 0x52, 0x50, 0xca, 0xc0, 0x1f, 0x59, 0x82, 0xfd, 0x8f, 0x49, 0x61, 0x9d, 0x61, 0xad, 0x78, 0xf6, 0x83, 0x0b, 0x3c, 0x60, 0x61, 0x45, 0x96, 0x2a, 0x0e + ], + c_out: [ + 0xbb, 0x71, 0x55, 0xbb, 0x76, 0x7f, 0x97, 0x35, 0x7d, 0x2b, 0xac, 0xa0, 0x4c, 0xe6, 0xe0, 0x64, 0x41, 0x4f, 0x73, 0xd8, 0xdf, 0xb2, 0xb9, 0xac, 0x99, 0xd3, 0x24, 0xa8, 0xbf, 0x2f, 0xae, 0x77, 0x8b, 0xcf, 0x47, 0xd3, 0xfa, 0xcb, 0x24, 0x1f, 0x4f, 0x9b, 0xee, 0xdb, 0x0f, 0xbf, 0x7f, 0x7f, 0xf3, 0x5d, 0x9e, 0xe2, 0xb3, 0xe2, 0xd5, 0x75, 0xa8, 0x9f, 0x5e, 0xd7, 0xe6, 0x35, 0xc1, 0xac, 0x32, 0x11, 0x62, 0xe7, 0x55, 0xcf, 0xd0, 0x0e, 0x56, 0x4a, 0x6f, 0xb8, 0xbc, 0xee, 0x96, 0x31 + ], + }, + TestVector { + ovk: [ + 0x38, 0x2e, 0x85, 0xa6, 0x11, 0x09, 0xb0, 0x8a, 0x35, 0x88, 0xe0, 0x97, 0xa1, 0xe4, 0x87, 0xbe, 0x9b, 0x49, 0xc1, 0x8c, 0x9d, 0x3b, 0x70, 0xb5, 0x57, 0xd3, 0x77, 0x8e, 0xe3, 0xf1, 0x28, 0x44 + ], + ivk: [ + 0xb4, 0xed, 0xfb, 0x7c, 0x92, 0xb5, 0xef, 0xd2, 0x88, 0x7c, 0xb7, 0xce, 0x32, 0x0d, 0xde, 0xc2, 0x85, 0xf6, 0xfd, 0xfb, 0xa2, 0xa9, 0x81, 0x3c, 0x2f, 0x16, 0x68, 0x0c, 0x4e, 0x6b, 0x78, 0x01 + ], + default_d: [ + 0xe6, 0x77, 0xa4, 0xdd, 0x26, 0x76, 0xe0, 0x81, 0x88, 0xb9, 0x0f + ], + default_pk_d: [ + 0xdc, 0x53, 0x68, 0x2c, 0x0d, 0xd8, 0x90, 0x38, 0x2d, 0x89, 0x28, 0x30, 0xf6, 0xf3, 0x7c, 0x80, 0x83, 0x87, 0x34, 0xa2, 0xaf, 0xaa, 0xc4, 0x0e, 0x8b, 0xee, 0xec, 0x09, 0xa5, 0x7d, 0x24, 0xee + ], + v: 200000000, + rcm: [ + 0x47, 0x8b, 0xa0, 0xee, 0x6e, 0x1a, 0x75, 0xb6, 0x00, 0x03, 0x6f, 0x26, 0xf1, 0x8b, 0x70, 0x15, 0xab, 0x55, 0x6b, 0xed, 0xdf, 0x8b, 0x96, 0x02, 0x38, 0x86, 0x9f, 0x89, 0xdd, 0x80, 0x4e, 0x06 + ], + memo: [ + 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + ], + cv: [ + 0x3e, 0x71, 0x2f, 0xc2, 0x46, 0x65, 0xc4, 0x5e, 0x4f, 0xd8, 0x96, 0xa9, 0x00, 0xc8, 0xc8, 0xb1, 0x74, 0x64, 0xa4, 0x21, 0x8a, 0xa2, 0xf9, 0xa5, 0x43, 0xfc, 0x2b, 0xbc, 0xe4, 0x54, 0x29, 0x43 + ], + cmu: [ + 0x5f, 0x2e, 0xab, 0x4e, 0xf9, 0x92, 0x6f, 0x60, 0x65, 0xe2, 0x1e, 0xca, 0x6e, 0x78, 0x69, 0x32, 0x62, 0x90, 0xda, 0x1e, 0x68, 0xa1, 0x58, 0xda, 0x67, 0x96, 0xa0, 0x6b, 0x6d, 0x9c, 0xb5, 0x54 + ], + esk: [ + 0xad, 0x4a, 0xd6, 0x24, 0x77, 0xc2, 0xc8, 0x83, 0xc8, 0xba, 0xbf, 0xed, 0x5d, 0x38, 0x5b, 0x51, 0xab, 0xdc, 0xc6, 0x98, 0xe9, 0x36, 0xe7, 0x8d, 0xc2, 0x26, 0x71, 0x72, 0x91, 0x55, 0x62, 0x0b + ], + epk: [ + 0x11, 0xec, 0x16, 0x4b, 0x05, 0xbd, 0x16, 0x0f, 0x22, 0x76, 0x63, 0xe2, 0x90, 0x53, 0xea, 0x8d, 0xce, 0x65, 0xe8, 0x16, 0x09, 0xf9, 0x75, 0x30, 0x90, 0xbd, 0xb5, 0x50, 0x2b, 0x2e, 0xa4, 0x06 + ], + shared_secret: [ + 0x6b, 0x9a, 0xfe, 0xa9, 0x95, 0x58, 0x75, 0x0c, 0xc5, 0xd2, 0x28, 0x0b, 0x2b, 0xfb, 0x6d, 0xd5, 0x40, 0xf7, 0x7f, 0x32, 0xa4, 0x13, 0x05, 0x4e, 0x31, 0x97, 0xb4, 0x4d, 0x95, 0x24, 0xe2, 0x94 + ], + k_enc: [ + 0x6a, 0xc9, 0xca, 0xdb, 0xae, 0x5c, 0x54, 0x65, 0x93, 0xbc, 0x09, 0x97, 0x4b, 0x8b, 0xbf, 0x30, 0xa4, 0xa8, 0x1a, 0xe1, 0x21, 0x5f, 0x56, 0xf5, 0xbf, 0xdb, 0x83, 0x49, 0x7c, 0x02, 0x24, 0xa1 + ], + p_enc: [ + 0x02, 0xe6, 0x77, 0xa4, 0xdd, 0x26, 0x76, 0xe0, 0x81, 0x88, 0xb9, 0x0f, 0x00, 0xc2, 0xeb, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x47, 0x8b, 0xa0, 0xee, 0x6e, 0x1a, 0x75, 0xb6, 0x00, 0x03, 0x6f, 0x26, 0xf1, 0x8b, 0x70, 0x15, 0xab, 0x55, 0x6b, 0xed, 0xdf, 0x8b, 0x96, 0x02, 0x38, 0x86, 0x9f, 0x89, 0xdd, 0x80, 0x4e, 0x06, 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + ], + c_enc: [ + 0x73, 0xef, 0x0c, 0xd5, 0x09, 0x89, 0x0a, 0x02, 0x64, 0xc2, 0x5a, 0xdd, 0xcd, 0x73, 0x3d, 0x24, 0xfd, 0x42, 0xef, 0xd0, 0xc8, 0xe8, 0x1d, 0xe7, 0x87, 0x0c, 0xbe, 0xa6, 0x5d, 0x4e, 0x6f, 0x77, 0x6f, 0x45, 0x7f, 0xbd, 0x12, 0xd1, 0xf8, 0xfb, 0x26, 0xba, 0x86, 0xf2, 0x48, 0x64, 0x53, 0x17, 0xbd, 0x6c, 0x6d, 0xb7, 0xe5, 0x46, 0xf0, 0x6e, 0xb3, 0x02, 0xf7, 0x98, 0xd0, 0xdd, 0x4c, 0xd2, 0x65, 0xc4, 0x40, 0xea, 0x0f, 0xa0, 0x98, 0x6a, 0x0d, 0x3f, 0x66, 0x9e, 0xe4, 0xb4, 0x44, 0x94, 0x08, 0x4f, 0x3a, 0x14, 0x0c, 0x99, 0xd0, 0xb6, 0x37, 0x6d, 0xdd, 0xe0, 0xa8, 0x01, 0x90, 0x24, 0x42, 0xc0, 0x47, 0x11, 0xff, 0x86, 0xfc, 0x80, 0xba, 0xb5, 0x74, 0xd2, 0xf4, 0xd9, 0x08, 0xdf, 0x88, 0x49, 0x35, 0xdd, 0xfe, 0xce, 0x41, 0x1e, 0xef, 0x4c, 0xdd, 0x43, 0xf5, 0x0e, 0x26, 0xe0, 0x02, 0x41, 0xcb, 0x46, 0xfd, 0x9f, 0x59, 0xed, 0x75, 0xe1, 0x1c, 0x1a, 0x30, 0x90, 0x72, 0x50, 0x35, 0x3f, 0x1d, 0x2c, 0xdf, 0x8e, 0x02, 0x58, 0x5c, 0x4b, 0xa2, 0xf7, 0x34, 0xb6, 0x82, 0xe7, 0xaf, 0xe9, 0xeb, 0xf5, 0x8b, 0x97, 0xa0, 0xde, 0x8d, 0x0f, 0x06, 0xef, 0x83, 0xfc, 0x20, 0xe7, 0xe2, 0x1f, 0x16, 0x34, 0x7d, 0xb9, 0x94, 0x48, 0x68, 0xd0, 0x16, 0xe9, 0x14, 0x9c, 0xb8, 0xcd, 0x2a, 0x3d, 0xc8, 0x31, 0x72, 0x58, 0xd3, 0xb3, 0x8c, 0x48, 0x39, 0x82, 0x13, 0x6c, 0xa6, 0x5f, 0x43, 0x07, 0x8a, 0xd0, 0x88, 0x53, 0x82, 0x40, 0x21, 0x44, 0xdf, 0x1b, 0x87, 0x89, 0x34, 0x63, 0xdf, 0x45, 0xde, 0xae, 0xc8, 0x4f, 0x36, 0xfd, 0xe7, 0x52, 0xd9, 0x9e, 0x38, 0xc4, 0x2e, 0xf3, 0x83, 0x08, 0xf7, 0xc5, 0x12, 0xbf, 0xfd, 0x11, 0x2a, 0xb3, 0x26, 0x65, 0x6c, 0x6c, 0x4e, 0x5c, 0xad, 0x16, 0xe2, 0x37, 0x52, 0xbb, 0x95, 0xe1, 0x16, 0xb6, 0x90, 0x9d, 0xeb, 0x52, 0x84, 0x32, 0x83, 0x4d, 0x5d, 0xd2, 0x09, 0x78, 0xfe, 0xa7, 0x1e, 0x3e, 0xb2, 0x94, 0xc7, 0x4d, 0x49, 0xb0, 0x31, 0x0e, 0xb5, 0x65, 0x70, 0x93, 0xec, 0x9f, 0xdc, 0x57, 0x86, 0xe7, 0xf7, 0xe9, 0xd7, 0x84, 0xc8, 0x69, 0x08, 0xb5, 0xbb, 0x98, 0x04, 0x0a, 0x0a, 0x4b, 0xd1, 0x9d, 0xa8, 0xcd, 0x5e, 0x90, 0xa0, 0x7f, 0xc7, 0x11, 0x20, 0x4b, 0x79, 0xf0, 0x70, 0xfb, 0x67, 0xf7, 0x72, 0xf3, 0x68, 0x98, 0x49, 0x82, 0xaa, 0xb1, 0x27, 0xfc, 0x25, 0x78, 0xed, 0xd3, 0x27, 0xc4, 0x73, 0xc2, 0x99, 0x20, 0xe9, 0xe6, 0x63, 0x24, 0xed, 0x70, 0xa8, 0x3b, 0x28, 0x66, 0x31, 0x88, 0xb0, 0x94, 0xc8, 0x04, 0x2f, 0xf7, 0x27, 0x21, 0xb1, 0x9e, 0x54, 0xc0, 0xed, 0xe1, 0x1d, 0xa5, 0x0c, 0x2a, 0xa4, 0xb1, 0xb8, 0x03, 0xdc, 0xfe, 0x52, 0x62, 0xc7, 0x61, 0x26, 0x42, 0x3d, 0x4f, 0x30, 0xb5, 0x77, 0xa0, 0x94, 0x69, 0x99, 0x08, 0x43, 0xfa, 0x92, 0xa9, 0xe1, 0xca, 0xed, 0x49, 0xc2, 0x4b, 0xcb, 0x2b, 0x7e, 0xa5, 0x93, 0xee, 0xd8, 0xda, 0x12, 0xa7, 0x96, 0xa4, 0xd1, 0x77, 0x5e, 0x21, 0x71, 0x50, 0x1c, 0x04, 0xd9, 0x79, 0x44, 0x7f, 0xf0, 0x52, 0x5c, 0x81, 0xa6, 0x09, 0xd1, 0x73, 0xb6, 0x1f, 0x57, 0x6a, 0xbb, 0x7b, 0x38, 0xa1, 0x56, 0x68, 0x3b, 0xfb, 0xf0, 0xad, 0x4e, 0x6f, 0xdf, 0x2f, 0xb9, 0xd3, 0x4f, 0x5c, 0xb9, 0x80, 0x63, 0xf3, 0xc6, 0xf2, 0x47, 0x62, 0xee, 0x6a, 0x7e, 0x32, 0xe4, 0x0a, 0xe9, 0x26, 0x02, 0x9c, 0x7f, 0x2c, 0xd6, 0xa4, 0xb0, 0xf5, 0x89, 0x0d, 0xad, 0x32, 0x20, 0xf4, 0x6f, 0xd5, 0x71, 0x0d, 0x32, 0x93, 0x78, 0xd6, 0x5f, 0x1c, 0xea, 0x59, 0xb9, 0xb7, 0x12, 0xc1, 0x25, 0x26, 0xe6, 0x3e, 0xf1, 0x95, 0x49, 0x63, 0x9d, 0x7f, 0x2b, 0x57, 0x2d, 0x08, 0xcd, 0x1b, 0x7f, 0x8c, 0x25, 0x94, 0xff, 0xcf, 0x4a, 0x4f, 0xb6, 0x65, 0xfe, 0x9b, 0xcc, 0x9a, 0x4e, 0x57, 0xe4, 0xad, 0x39, 0xfd, 0x96, 0xa8, 0xf5, 0x5f, 0x4e, 0x8a, 0x55, 0xd3, 0x55, 0x0b, 0x22, 0x19, 0xf0, 0xce, 0xa6, 0x38, 0x85, 0x1d, 0xbe, 0x9a, 0x39, 0xdd, 0xec, 0xfc, 0x82, 0xe7, 0xc4, 0x63, 0x4b, 0x6d, 0x77, 0x59, 0x69, 0x2f, 0xf0, 0xa3, 0x18, 0xff, 0x47, 0x61, 0x6a, 0x79, 0xa0, 0x85, 0x24, 0x13, 0x02, 0x59, 0xb7, 0xb3, 0x92, 0x64, 0xd4, 0xc3, 0x3b, 0xc1, 0xa2, 0xcf, 0x88, 0x26, 0x29, 0x43 + ], + ock: [ + 0xdd, 0x74, 0xd6, 0x97, 0xc7, 0x0a, 0x56, 0x41, 0xfa, 0xd2, 0x6c, 0x31, 0xc6, 0x69, 0x8a, 0x59, 0x29, 0x8a, 0x44, 0xee, 0x9d, 0xaa, 0x27, 0x33, 0x5f, 0xac, 0x31, 0x0c, 0x79, 0x2e, 0x6a, 0xf5 + ], + op: [ + 0xdc, 0x53, 0x68, 0x2c, 0x0d, 0xd8, 0x90, 0x38, 0x2d, 0x89, 0x28, 0x30, 0xf6, 0xf3, 0x7c, 0x80, 0x83, 0x87, 0x34, 0xa2, 0xaf, 0xaa, 0xc4, 0x0e, 0x8b, 0xee, 0xec, 0x09, 0xa5, 0x7d, 0x24, 0xee, 0xad, 0x4a, 0xd6, 0x24, 0x77, 0xc2, 0xc8, 0x83, 0xc8, 0xba, 0xbf, 0xed, 0x5d, 0x38, 0x5b, 0x51, 0xab, 0xdc, 0xc6, 0x98, 0xe9, 0x36, 0xe7, 0x8d, 0xc2, 0x26, 0x71, 0x72, 0x91, 0x55, 0x62, 0x0b + ], + c_out: [ + 0x4e, 0x74, 0x7e, 0x34, 0xb4, 0x7a, 0x8e, 0xb5, 0x24, 0x88, 0x7b, 0xe9, 0xcb, 0xab, 0x64, 0xad, 0x8d, 0x43, 0xf7, 0xe9, 0x46, 0xae, 0xe7, 0x2e, 0x89, 0x1d, 0xe6, 0xba, 0x2b, 0x24, 0x3e, 0x60, 0x99, 0x87, 0xc5, 0xae, 0xe3, 0x6f, 0xe7, 0x3b, 0x59, 0xfa, 0xa7, 0xb5, 0x9f, 0xf7, 0x98, 0xd8, 0xc3, 0x79, 0xdb, 0xbc, 0x95, 0x00, 0x1f, 0x24, 0xca, 0x5d, 0xbc, 0xcd, 0xf9, 0x27, 0x56, 0x62, 0xa9, 0x05, 0x3e, 0x34, 0x36, 0xfd, 0xd9, 0x8e, 0x40, 0x42, 0x3d, 0x43, 0x83, 0xb6, 0x1c, 0x04 + ], + }, + TestVector { + ovk: [ + 0x8d, 0xc3, 0x73, 0xff, 0xec, 0xa3, 0xd8, 0x57, 0x8d, 0x51, 0x6f, 0x35, 0x4a, 0xa8, 0xa9, 0x73, 0x6f, 0x27, 0x8b, 0xee, 0xf1, 0x7a, 0x54, 0x4b, 0x16, 0xb3, 0x47, 0x8d, 0xc5, 0x95, 0x46, 0xbd + ], + ivk: [ + 0xdf, 0x4a, 0xfb, 0x34, 0x37, 0x3a, 0x88, 0x4f, 0x8d, 0x86, 0x53, 0x5a, 0x2c, 0x45, 0xd6, 0xd3, 0x21, 0x66, 0x9e, 0xbf, 0xb8, 0x59, 0x99, 0x03, 0xa6, 0x40, 0x7d, 0xd3, 0x82, 0x09, 0x76, 0x01 + ], + default_d: [ + 0xa1, 0xe0, 0xf5, 0x3c, 0x47, 0x3e, 0xd9, 0x8c, 0x17, 0xb6, 0xd0 + ], + default_pk_d: [ + 0xb3, 0x23, 0xbb, 0x8b, 0x98, 0x03, 0x11, 0x44, 0x88, 0x26, 0x0f, 0x9f, 0x51, 0xe5, 0x46, 0xc2, 0xb4, 0x5f, 0x3d, 0x03, 0x6d, 0x03, 0x9b, 0x0f, 0x0c, 0xb2, 0x86, 0x13, 0x9d, 0x4c, 0x25, 0xb5 + ], + v: 300000000, + rcm: [ + 0x14, 0x7c, 0xf2, 0xb5, 0x1b, 0x4c, 0x7c, 0x63, 0xcb, 0x77, 0xb9, 0x9e, 0x8b, 0x78, 0x3e, 0x5b, 0x51, 0x11, 0xdb, 0x0a, 0x7c, 0xa0, 0x4d, 0x6c, 0x01, 0x4a, 0x1d, 0x7d, 0xa8, 0x3b, 0xae, 0x0a + ], + memo: [ + 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + ], + cv: [ + 0x48, 0x59, 0xd1, 0xb5, 0x37, 0x31, 0xfc, 0xb3, 0xe1, 0x50, 0x4a, 0xf5, 0x63, 0x94, 0x32, 0x54, 0x7f, 0x30, 0x1c, 0x59, 0x8d, 0x41, 0x27, 0xd8, 0x0d, 0x87, 0xa4, 0xc0, 0x3a, 0xad, 0x60, 0x1e + ], + cmu: [ + 0xdd, 0xdb, 0xb9, 0x62, 0x51, 0x66, 0x84, 0x26, 0x02, 0x2f, 0x25, 0x73, 0xd4, 0x44, 0x2f, 0x0a, 0xbe, 0x2a, 0xea, 0xf6, 0x15, 0xb2, 0x6d, 0x1d, 0xeb, 0x2e, 0xa0, 0xe2, 0xb8, 0xb3, 0x88, 0x23 + ], + esk: [ + 0x99, 0xaa, 0x10, 0xc0, 0x57, 0x88, 0x08, 0x1c, 0x0d, 0xa7, 0xd8, 0x79, 0xcd, 0x95, 0x43, 0xec, 0x18, 0x92, 0x15, 0x72, 0x92, 0x40, 0x2e, 0x96, 0x0b, 0x06, 0x99, 0x5a, 0x08, 0x96, 0x4c, 0x03 + ], + epk: [ + 0xbe, 0x1c, 0x98, 0xaa, 0xee, 0xf2, 0x4a, 0x12, 0xff, 0x3e, 0x3b, 0xe4, 0x2c, 0x92, 0xda, 0x1f, 0xca, 0x4e, 0x26, 0x33, 0x38, 0x1c, 0xf6, 0x7d, 0x68, 0x36, 0xe0, 0x8c, 0x76, 0x89, 0xac, 0x8e + ], + shared_secret: [ + 0xc4, 0x2e, 0xf4, 0x8e, 0x45, 0x53, 0xbb, 0xe5, 0xa2, 0x0b, 0x41, 0x78, 0x93, 0x82, 0xd5, 0xa0, 0x40, 0x9f, 0x08, 0x28, 0x45, 0xd1, 0xda, 0x97, 0x1c, 0x01, 0xc4, 0x9d, 0xb3, 0x93, 0xf9, 0xcc + ], + k_enc: [ + 0x2f, 0x29, 0x71, 0xf4, 0xd0, 0x83, 0xb4, 0x9f, 0x26, 0x14, 0xa9, 0x8a, 0x29, 0x43, 0xc6, 0xf1, 0x30, 0x9b, 0xf3, 0xfc, 0x2b, 0x07, 0x9e, 0x52, 0xf1, 0x68, 0x3f, 0xa6, 0x75, 0x4b, 0x30, 0x6b + ], + p_enc: [ + 0x02, 0xa1, 0xe0, 0xf5, 0x3c, 0x47, 0x3e, 0xd9, 0x8c, 0x17, 0xb6, 0xd0, 0x00, 0xa3, 0xe1, 0x11, 0x00, 0x00, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x14, 0x7c, 0xf2, 0xb5, 0x1b, 0x4c, 0x7c, 0x63, 0xcb, 0x77, 0xb9, 0x9e, 0x8b, 0x78, 0x3e, 0x5b, 0x51, 0x11, 0xdb, 0x0a, 0x7c, 0xa0, 0x4d, 0x6c, 0x01, 0x4a, 0x1d, 0x7d, 0xa8, 0x3b, 0xae, 0x0a, 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + ], + c_enc: [ + 0x6b, 0x36, 0x53, 0x04, 0x70, 0x77, 0xfe, 0x85, 0x30, 0xab, 0x62, 0xdf, 0xb5, 0x59, 0xc9, 0x75, 0x42, 0x29, 0x57, 0x34, 0x4c, 0x3c, 0x9f, 0x19, 0x12, 0x00, 0x50, 0xe8, 0x47, 0x2a, 0xa2, 0xe2, 0x18, 0x69, 0xee, 0x6c, 0x1a, 0xad, 0x56, 0x11, 0x0b, 0xf9, 0x39, 0xcd, 0xa5, 0x2e, 0xab, 0x2c, 0xb2, 0x92, 0x84, 0xd7, 0xc2, 0x05, 0x7e, 0xbe, 0xfd, 0x02, 0x7a, 0x8c, 0x71, 0x7f, 0x50, 0xc7, 0x89, 0x2a, 0xd6, 0xc3, 0x4b, 0xc9, 0xdb, 0x71, 0xb1, 0x6e, 0x82, 0x33, 0xa6, 0xd8, 0x1c, 0x4d, 0x22, 0x84, 0x68, 0xc4, 0x4d, 0x30, 0x32, 0x0e, 0x3b, 0xcb, 0x5b, 0x23, 0xcf, 0x8e, 0x0a, 0x47, 0x15, 0x44, 0x9f, 0x61, 0x1d, 0xa4, 0xac, 0x22, 0xcc, 0xee, 0x1c, 0x1a, 0xa7, 0x47, 0xd0, 0xe4, 0xc3, 0xe2, 0x4a, 0x27, 0x45, 0x89, 0x87, 0x76, 0x35, 0x5b, 0x5d, 0x1a, 0xac, 0xa5, 0xa1, 0xd4, 0x27, 0x20, 0x4c, 0xbb, 0x13, 0x52, 0x3e, 0x58, 0xad, 0x54, 0xed, 0x1c, 0x3f, 0x88, 0x7a, 0xbe, 0x29, 0x6e, 0xaf, 0xff, 0xe7, 0x2d, 0xd5, 0xe8, 0xe7, 0x82, 0x3f, 0x22, 0xa8, 0xb0, 0x87, 0xa7, 0x84, 0xd7, 0x1a, 0x97, 0x6c, 0x3e, 0xb5, 0xbf, 0xb7, 0x49, 0xaa, 0x1a, 0x49, 0x81, 0xc2, 0x2e, 0x4d, 0xb6, 0xd6, 0x4f, 0x69, 0xd3, 0x83, 0xd8, 0xd2, 0x2d, 0x49, 0xb1, 0x06, 0xf1, 0x3b, 0xfc, 0xa0, 0xae, 0x67, 0x22, 0x92, 0x66, 0x23, 0x12, 0x07, 0x89, 0x97, 0xdd, 0xdd, 0xd6, 0xb0, 0xb6, 0x80, 0x9f, 0x69, 0x18, 0xbe, 0x81, 0x5a, 0x45, 0xcc, 0x2c, 0x7f, 0x2e, 0x0b, 0x0f, 0xcc, 0xd5, 0xdc, 0x86, 0x25, 0x2c, 0xe6, 0xe9, 0x61, 0x9d, 0xf2, 0x38, 0x08, 0xb1, 0x12, 0xe4, 0xa2, 0x6c, 0xe5, 0x80, 0x1e, 0x4b, 0xb1, 0x2a, 0xec, 0x68, 0xa3, 0x87, 0x2a, 0x85, 0x6f, 0xcc, 0x2e, 0x20, 0xc2, 0x01, 0x65, 0x12, 0x81, 0x9c, 0x00, 0x89, 0xad, 0xb4, 0xda, 0xbf, 0x9f, 0x44, 0x22, 0x4a, 0xf8, 0x8c, 0xcf, 0x36, 0x58, 0xec, 0x72, 0xa7, 0xa3, 0x94, 0xfc, 0x84, 0xa2, 0xb6, 0xbb, 0xcf, 0x09, 0x5d, 0x88, 0x14, 0x72, 0x6a, 0xd3, 0xbc, 0xf1, 0x2f, 0xb1, 0xa0, 0x4d, 0x7f, 0x7a, 0xfd, 0x08, 0x64, 0x73, 0x6b, 0x4a, 0x64, 0x88, 0x95, 0xa7, 0x5e, 0x74, 0x3e, 0xa4, 0x67, 0x16, 0x0e, 0x4b, 0x7b, 0x37, 0xd5, 0x91, 0x44, 0x99, 0x96, 0x97, 0x78, 0x75, 0xf0, 0xce, 0xc3, 0x61, 0x5b, 0x1f, 0x53, 0xfe, 0xac, 0xca, 0x47, 0x2f, 0xb0, 0x52, 0x68, 0xb5, 0xfe, 0xc2, 0x08, 0x72, 0xab, 0x27, 0xdc, 0x12, 0x65, 0xb6, 0xf4, 0x52, 0xdc, 0xff, 0x1f, 0xd9, 0x2a, 0x91, 0x19, 0x8c, 0xbc, 0x92, 0xcb, 0x72, 0xd5, 0xce, 0xca, 0x18, 0xef, 0x7c, 0xdc, 0x74, 0xf5, 0x12, 0xb2, 0x1d, 0x41, 0x45, 0xed, 0xaf, 0xdc, 0x51, 0xc9, 0x0a, 0x13, 0xdb, 0x18, 0xc4, 0xe0, 0x51, 0x51, 0xc7, 0x0f, 0xac, 0x25, 0x27, 0x13, 0xdb, 0x67, 0x6c, 0x71, 0x38, 0x01, 0x98, 0x4d, 0xb9, 0xa4, 0xca, 0xb7, 0xac, 0xf8, 0x9a, 0xb8, 0xe1, 0x04, 0x72, 0x6d, 0xa4, 0x77, 0x68, 0xa5, 0x8e, 0x58, 0x33, 0x2c, 0x33, 0x07, 0x96, 0x01, 0xea, 0x80, 0x44, 0x9e, 0x89, 0xe7, 0xba, 0x9e, 0x5c, 0x66, 0x19, 0xac, 0x1e, 0xc5, 0xe8, 0x70, 0x18, 0x1f, 0xa3, 0x00, 0xee, 0x08, 0x60, 0xf9, 0x37, 0xe5, 0x1c, 0x29, 0x98, 0xaa, 0xa7, 0x4b, 0xa2, 0x9c, 0x40, 0x0a, 0x89, 0x47, 0xed, 0xc0, 0xf3, 0x49, 0x7e, 0xc6, 0xc4, 0xa8, 0x2b, 0x10, 0x45, 0x65, 0x32, 0xca, 0x08, 0x3f, 0x77, 0xaf, 0x2a, 0x36, 0xc1, 0xab, 0xf3, 0x34, 0xf5, 0x7c, 0x69, 0x46, 0x58, 0xc5, 0x82, 0xc6, 0xed, 0x30, 0x19, 0xb1, 0x50, 0x86, 0xed, 0x1e, 0x92, 0xb3, 0x8e, 0xdb, 0xc2, 0xbc, 0xdc, 0xdd, 0x6a, 0x42, 0xa8, 0x79, 0x80, 0x36, 0x3b, 0xe2, 0x09, 0xd8, 0xf3, 0x5a, 0x58, 0x9d, 0xc2, 0xb2, 0xcf, 0x1f, 0x94, 0x51, 0x81, 0x2b, 0xc2, 0xb4, 0x8f, 0xae, 0x04, 0xa8, 0x1d, 0x29, 0x8b, 0x1a, 0x6a, 0x81, 0xc9, 0xa7, 0xa6, 0xaf, 0xca, 0xd3, 0xae, 0x6b, 0x2f, 0xed, 0x57, 0x5b, 0xba, 0xc1, 0xdd, 0x37, 0xb1, 0x97, 0x96, 0xe4, 0x9a, 0x54, 0x39, 0x64, 0x7c, 0xfa, 0xdf, 0xd2, 0x6d, 0x7b, 0x2d, 0x91, 0x61, 0x7f, 0xb2, 0x6d, 0x36, 0x0b, 0xf0, 0x11, 0xf2, 0x5e, 0xcb, 0x6e, 0x3d, 0x3f, 0x41, 0xf1, 0xa5, 0x73, 0xc0, 0xf1, 0x04, 0x63, 0x32, 0x13 + ], + ock: [ + 0x0a, 0xc3, 0x5f, 0x08, 0xac, 0xd8, 0x76, 0xdd, 0x7b, 0x9b, 0x4e, 0xf1, 0x9a, 0x33, 0xb3, 0x6a, 0xa0, 0x90, 0xff, 0x62, 0x29, 0x7f, 0x96, 0x12, 0x98, 0x7e, 0x5e, 0x52, 0x27, 0x5f, 0xd7, 0xa7 + ], + op: [ + 0xb3, 0x23, 0xbb, 0x8b, 0x98, 0x03, 0x11, 0x44, 0x88, 0x26, 0x0f, 0x9f, 0x51, 0xe5, 0x46, 0xc2, 0xb4, 0x5f, 0x3d, 0x03, 0x6d, 0x03, 0x9b, 0x0f, 0x0c, 0xb2, 0x86, 0x13, 0x9d, 0x4c, 0x25, 0xb5, 0x99, 0xaa, 0x10, 0xc0, 0x57, 0x88, 0x08, 0x1c, 0x0d, 0xa7, 0xd8, 0x79, 0xcd, 0x95, 0x43, 0xec, 0x18, 0x92, 0x15, 0x72, 0x92, 0x40, 0x2e, 0x96, 0x0b, 0x06, 0x99, 0x5a, 0x08, 0x96, 0x4c, 0x03 + ], + c_out: [ + 0x17, 0xbc, 0xa1, 0x5f, 0x91, 0xee, 0x2a, 0xd0, 0x1c, 0x42, 0xce, 0x89, 0x5e, 0x9c, 0xa3, 0xd8, 0x7c, 0x04, 0x16, 0x4c, 0x29, 0xf7, 0x94, 0x52, 0xef, 0xcf, 0x8d, 0x32, 0xd5, 0x35, 0x93, 0xbc, 0xc2, 0x02, 0xee, 0x96, 0xb7, 0x78, 0x22, 0x4c, 0xde, 0x87, 0xbe, 0x07, 0x86, 0x71, 0x71, 0x0b, 0xbf, 0xea, 0xed, 0x7f, 0x61, 0x72, 0x62, 0x62, 0xc4, 0xde, 0xc4, 0x95, 0x82, 0x72, 0x74, 0x4c, 0xf4, 0x04, 0xf3, 0xd4, 0xd9, 0xc8, 0x1f, 0x2a, 0x24, 0x16, 0xe9, 0x00, 0xbc, 0x39, 0x20, 0x18 + ], + }, + TestVector { + ovk: [ + 0x30, 0x2a, 0x78, 0xb5, 0xce, 0xe5, 0xd9, 0x84, 0x22, 0xf2, 0xdd, 0x13, 0xd8, 0xc4, 0x6f, 0xe7, 0x27, 0x67, 0x25, 0x52, 0x23, 0x3c, 0xc8, 0x21, 0x7a, 0xe2, 0xf1, 0x44, 0xb3, 0xd6, 0x0d, 0x04 + ], + ivk: [ + 0x2a, 0xa4, 0x0d, 0xd9, 0x3b, 0x51, 0xe7, 0xf6, 0x81, 0xb1, 0x1c, 0xdc, 0xde, 0x55, 0xe3, 0x3a, 0xcb, 0x3c, 0x9d, 0xe6, 0x25, 0x9d, 0x78, 0xae, 0xa5, 0x39, 0xbf, 0x80, 0xad, 0xfe, 0x67, 0x07 + ], + default_d: [ + 0x14, 0xbf, 0xe9, 0x79, 0x77, 0x94, 0x6a, 0x54, 0x7d, 0x5f, 0x3f + ], + default_pk_d: [ + 0x65, 0xcd, 0x93, 0xd6, 0x16, 0xc2, 0x69, 0xae, 0x15, 0x7a, 0x0a, 0xaa, 0xbe, 0xfd, 0xb6, 0xb3, 0x27, 0xbd, 0xb9, 0xaa, 0xba, 0xef, 0xa1, 0xb9, 0xc1, 0x70, 0x1b, 0x60, 0x0e, 0x01, 0x08, 0xae + ], + v: 400000000, + rcm: [ + 0x34, 0xa4, 0xb2, 0xa9, 0x14, 0x4f, 0xf5, 0xea, 0x54, 0xef, 0xee, 0x87, 0xcf, 0x90, 0x1b, 0x5b, 0xed, 0x5e, 0x35, 0xd2, 0x1f, 0xbb, 0xd7, 0x88, 0xd5, 0xbd, 0x9d, 0x83, 0x3e, 0x11, 0x28, 0x04 + ], + memo: [ + 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + ], + cv: [ + 0xb7, 0xf6, 0xc0, 0xc1, 0xbd, 0xf5, 0x53, 0x20, 0xbc, 0x11, 0xca, 0x32, 0x39, 0x9e, 0x64, 0x01, 0x4c, 0x09, 0x49, 0xa4, 0x04, 0x11, 0xff, 0x59, 0xbc, 0x60, 0xee, 0x0b, 0xda, 0x4c, 0xbd, 0xbc + ], + cmu: [ + 0x3b, 0x98, 0x2f, 0xa0, 0x4b, 0xc3, 0x4f, 0x4c, 0x42, 0x38, 0x9e, 0xc2, 0x0b, 0xa9, 0xac, 0x79, 0x94, 0xa8, 0x70, 0x13, 0x60, 0x53, 0x8a, 0xfc, 0x6a, 0x01, 0x43, 0x06, 0x3e, 0xdf, 0xd7, 0x5e + ], + esk: [ + 0xbd, 0xde, 0x13, 0x81, 0xec, 0x9f, 0xf4, 0x21, 0xca, 0xfd, 0x1e, 0x31, 0xcc, 0x5d, 0xe2, 0x55, 0x59, 0x88, 0x1f, 0x6b, 0x21, 0xb2, 0x17, 0x5d, 0x0d, 0xce, 0x94, 0x08, 0x59, 0x7e, 0xa1, 0x03 + ], + epk: [ + 0x52, 0xf3, 0x51, 0xee, 0xe6, 0x34, 0x4a, 0x28, 0xfa, 0x35, 0x41, 0xac, 0xd6, 0xd1, 0xb3, 0x8a, 0xd8, 0xdf, 0x27, 0xac, 0x30, 0x56, 0x5e, 0x9d, 0x62, 0xb3, 0xe7, 0x57, 0xf7, 0xac, 0x95, 0x8f + ], + shared_secret: [ + 0x3c, 0x54, 0x29, 0xbb, 0x82, 0xbc, 0x1f, 0xbe, 0x2c, 0x63, 0xc0, 0x67, 0x94, 0x82, 0x5c, 0x6f, 0xb9, 0x33, 0xf5, 0xc7, 0x81, 0xcf, 0x12, 0x77, 0x4a, 0x6e, 0x67, 0x3f, 0x7b, 0xbf, 0x4f, 0x99 + ], + k_enc: [ + 0x51, 0x95, 0x3c, 0x39, 0xb3, 0x18, 0xb3, 0x53, 0x15, 0x11, 0x69, 0x25, 0x06, 0x58, 0x81, 0x39, 0x70, 0x1a, 0x76, 0x9a, 0xfd, 0xce, 0xae, 0xb9, 0x6b, 0xff, 0xd2, 0x9d, 0xa3, 0x16, 0xa4, 0xc9 + ], + p_enc: [ + 0x02, 0x14, 0xbf, 0xe9, 0x79, 0x77, 0x94, 0x6a, 0x54, 0x7d, 0x5f, 0x3f, 0x00, 0x84, 0xd7, 0x17, 0x00, 0x00, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x34, 0xa4, 0xb2, 0xa9, 0x14, 0x4f, 0xf5, 0xea, 0x54, 0xef, 0xee, 0x87, 0xcf, 0x90, 0x1b, 0x5b, 0xed, 0x5e, 0x35, 0xd2, 0x1f, 0xbb, 0xd7, 0x88, 0xd5, 0xbd, 0x9d, 0x83, 0x3e, 0x11, 0x28, 0x04, 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + ], + c_enc: [ + 0x1e, 0xaa, 0x1e, 0x2f, 0xfc, 0xe5, 0x88, 0x4c, 0xe1, 0xae, 0x74, 0xfc, 0xfc, 0x28, 0x21, 0xe7, 0x5f, 0x9e, 0xd1, 0xaf, 0x7c, 0x3b, 0xe0, 0x02, 0xc7, 0x2b, 0x5a, 0xe6, 0xb7, 0x6b, 0xfd, 0x3e, 0x0f, 0xd7, 0x99, 0x57, 0x0b, 0xd5, 0x87, 0xdc, 0xfb, 0xd2, 0x20, 0xb4, 0x36, 0xd6, 0xfd, 0x97, 0x38, 0xe5, 0x6f, 0x3f, 0x7c, 0x77, 0x2d, 0x0a, 0x3c, 0xe2, 0x6a, 0xb7, 0x86, 0x0d, 0x5b, 0x15, 0x3d, 0xf0, 0xbe, 0x87, 0x14, 0xac, 0x13, 0x72, 0x42, 0xc6, 0x3d, 0xf8, 0x51, 0x3d, 0x35, 0x68, 0x62, 0x4f, 0x3d, 0x58, 0x2d, 0xe4, 0xec, 0x17, 0x8c, 0xdf, 0x3a, 0x6d, 0x7c, 0x8c, 0x10, 0xaf, 0xf6, 0x7f, 0x55, 0xe0, 0xdc, 0x55, 0x1e, 0xf5, 0xe1, 0x0f, 0x37, 0x1c, 0x7d, 0xa2, 0xce, 0x9d, 0x3b, 0xa3, 0x02, 0x16, 0xfb, 0xab, 0x42, 0xb9, 0xf7, 0x93, 0x32, 0x51, 0xa9, 0xe6, 0x89, 0xee, 0x5b, 0x19, 0xc8, 0x26, 0xab, 0x3a, 0x2e, 0x5e, 0xdd, 0x9b, 0xf2, 0x74, 0xe5, 0x9e, 0xcd, 0xcd, 0x81, 0x80, 0x21, 0xf9, 0xeb, 0x02, 0xab, 0xf0, 0xec, 0xef, 0x0f, 0x3c, 0x1f, 0x53, 0xa3, 0x39, 0xb8, 0xdc, 0x54, 0xf1, 0xd9, 0xb6, 0x3d, 0x51, 0x2a, 0x3e, 0xfc, 0x14, 0xa9, 0xcc, 0xb5, 0x9d, 0xee, 0xb3, 0xc6, 0xbc, 0xe2, 0x70, 0xa4, 0x46, 0xe4, 0x1f, 0xad, 0xbb, 0xba, 0x8d, 0x85, 0xa5, 0x13, 0x65, 0x3d, 0x5e, 0x44, 0x9d, 0x38, 0xab, 0x0b, 0xa7, 0x03, 0xd0, 0x1f, 0xbf, 0x0b, 0xa0, 0x64, 0x89, 0x66, 0x06, 0x81, 0x1d, 0xb1, 0x27, 0x77, 0xdb, 0x0b, 0x8a, 0xac, 0xa5, 0x9b, 0x82, 0x26, 0x38, 0x04, 0x87, 0x81, 0x45, 0x0c, 0x98, 0x18, 0xf8, 0x6e, 0x41, 0xe9, 0x44, 0x9d, 0x09, 0x8a, 0x77, 0x16, 0x0b, 0x6a, 0x3f, 0x8a, 0xec, 0xa8, 0x60, 0x0a, 0x50, 0xaa, 0xcd, 0x7d, 0xa7, 0x52, 0x99, 0x79, 0x2c, 0x3c, 0xc3, 0x4d, 0x8a, 0x12, 0xbd, 0x39, 0x45, 0xb2, 0x66, 0x20, 0x6f, 0x1f, 0x4e, 0x24, 0xe5, 0x74, 0x96, 0x68, 0x2f, 0xdf, 0x51, 0xd7, 0xef, 0x1c, 0x78, 0x5a, 0x92, 0x5a, 0x04, 0x02, 0x47, 0xc3, 0x91, 0xa8, 0x4f, 0xfd, 0x47, 0x5f, 0x5d, 0xc7, 0xab, 0x96, 0x64, 0x14, 0x3b, 0x19, 0x5c, 0x3e, 0x9b, 0x92, 0x04, 0x38, 0x62, 0x2d, 0xb2, 0xf2, 0xa1, 0x95, 0x48, 0x2c, 0xbe, 0xa5, 0x27, 0xbb, 0x5a, 0xda, 0xcf, 0xec, 0xbf, 0xf7, 0xfa, 0xd4, 0x05, 0xdb, 0x07, 0xd7, 0x12, 0xfc, 0xbc, 0x5e, 0x6a, 0xf8, 0xe5, 0xd2, 0x0e, 0x06, 0xfc, 0xad, 0x78, 0x8a, 0x27, 0xb1, 0x5e, 0x18, 0x9d, 0x0a, 0x2f, 0xa6, 0x7b, 0x0a, 0x4c, 0x3e, 0x0c, 0x07, 0x68, 0x09, 0x15, 0xbc, 0x5f, 0xfc, 0x08, 0x0b, 0x3a, 0x4d, 0xaf, 0x16, 0x38, 0x0f, 0x76, 0xc6, 0x01, 0x65, 0xb4, 0xb5, 0x11, 0xb0, 0x02, 0xef, 0xbf, 0x1e, 0xac, 0xe9, 0xb7, 0xb2, 0x71, 0xb7, 0x46, 0x58, 0xb0, 0x98, 0x43, 0x7a, 0x6e, 0x47, 0xca, 0xb2, 0x5e, 0xe9, 0x3e, 0xfd, 0x09, 0xcf, 0x64, 0xd7, 0x73, 0xf5, 0xc7, 0x23, 0xd5, 0x60, 0xba, 0xa1, 0xc3, 0x4a, 0x7e, 0x4a, 0xf8, 0x8f, 0x2a, 0x0c, 0xec, 0x3e, 0x54, 0xd9, 0x26, 0x6b, 0x8f, 0xfe, 0x24, 0x80, 0x6c, 0xa7, 0x42, 0x00, 0x10, 0x51, 0xc9, 0x2b, 0x0b, 0x4b, 0xf9, 0xa8, 0xaa, 0xd1, 0x5e, 0x26, 0xb6, 0x25, 0xd8, 0x1b, 0x39, 0xba, 0x12, 0xe0, 0xcb, 0x9c, 0xe5, 0x1e, 0x73, 0x33, 0xf2, 0x68, 0xaf, 0x10, 0x1d, 0xfd, 0x8a, 0xa5, 0xed, 0x83, 0xcf, 0x11, 0x0a, 0x95, 0xc5, 0xb8, 0xab, 0xbb, 0x52, 0x20, 0xfe, 0xce, 0x76, 0xa8, 0x15, 0x03, 0x6f, 0x29, 0x6d, 0x63, 0x0b, 0x13, 0x8a, 0x90, 0x27, 0x69, 0x87, 0xba, 0x4e, 0x36, 0xd9, 0x10, 0x0f, 0xc3, 0x16, 0x36, 0x0e, 0xba, 0x7e, 0x25, 0x64, 0x6e, 0x6f, 0x38, 0xc1, 0x0e, 0xc4, 0x56, 0xb0, 0xfa, 0x91, 0x1c, 0xb5, 0xe6, 0xe6, 0x10, 0xa9, 0xf0, 0xcf, 0x45, 0x3e, 0x5b, 0xec, 0x4d, 0xee, 0xca, 0xdf, 0x1d, 0xcd, 0xf4, 0x62, 0x85, 0x2a, 0x67, 0x81, 0x10, 0x15, 0xe9, 0x54, 0xca, 0xc9, 0xcf, 0xc1, 0x33, 0xb7, 0x97, 0xc1, 0x56, 0x14, 0x75, 0xa4, 0xc9, 0x02, 0xe4, 0xba, 0xb8, 0x93, 0xf4, 0xc7, 0x7e, 0x36, 0xda, 0x37, 0x2c, 0x74, 0x57, 0x02, 0x04, 0x83, 0x8c, 0x18, 0x92, 0x52, 0x9d, 0x16, 0xb5, 0xc3, 0xbb, 0x0f, 0xef, 0x5f, 0xd6, 0x75, 0x4d, 0x75, 0x8a, 0x29, 0x61, 0x8d, 0x2c, 0x54 + ], + ock: [ + 0xb3, 0xea, 0xdd, 0xd5, 0x97, 0x35, 0x5a, 0x33, 0xe4, 0x60, 0xb7, 0x9c, 0x90, 0xa7, 0x86, 0x37, 0x67, 0x5d, 0x65, 0x03, 0x37, 0x03, 0x24, 0xd5, 0x02, 0x6c, 0x8a, 0xe9, 0x58, 0xe1, 0xfb, 0xed + ], + op: [ + 0x65, 0xcd, 0x93, 0xd6, 0x16, 0xc2, 0x69, 0xae, 0x15, 0x7a, 0x0a, 0xaa, 0xbe, 0xfd, 0xb6, 0xb3, 0x27, 0xbd, 0xb9, 0xaa, 0xba, 0xef, 0xa1, 0xb9, 0xc1, 0x70, 0x1b, 0x60, 0x0e, 0x01, 0x08, 0xae, 0xbd, 0xde, 0x13, 0x81, 0xec, 0x9f, 0xf4, 0x21, 0xca, 0xfd, 0x1e, 0x31, 0xcc, 0x5d, 0xe2, 0x55, 0x59, 0x88, 0x1f, 0x6b, 0x21, 0xb2, 0x17, 0x5d, 0x0d, 0xce, 0x94, 0x08, 0x59, 0x7e, 0xa1, 0x03 + ], + c_out: [ + 0xb4, 0x7a, 0xc1, 0x19, 0x05, 0x2c, 0x88, 0x89, 0x52, 0x5a, 0xeb, 0xf8, 0x55, 0x21, 0xd1, 0xad, 0x76, 0x54, 0xff, 0x0a, 0xfe, 0x98, 0xd8, 0x5d, 0xf2, 0x94, 0x89, 0xd0, 0x6c, 0x28, 0x66, 0x71, 0x86, 0x85, 0x2e, 0x89, 0x55, 0x2f, 0x4f, 0xfa, 0xe1, 0xc2, 0x74, 0x90, 0x1f, 0x52, 0x79, 0x45, 0xe8, 0x82, 0x40, 0x26, 0xb1, 0x68, 0xef, 0x8d, 0x90, 0xcd, 0xc9, 0x1b, 0xb5, 0xc6, 0x02, 0x6a, 0x14, 0x58, 0xb3, 0xbc, 0xef, 0x3d, 0x14, 0x3e, 0xb6, 0x42, 0xa4, 0xe0, 0x2a, 0x55, 0x21, 0xeb + ], + }, + TestVector { + ovk: [ + 0x82, 0x42, 0xe0, 0x59, 0xba, 0x92, 0x8e, 0xc6, 0xbe, 0x85, 0x65, 0x4a, 0x3d, 0xeb, 0xa1, 0xbe, 0xe2, 0x47, 0xf1, 0x61, 0x84, 0x08, 0x0d, 0x69, 0xcf, 0x76, 0xa9, 0xc6, 0x5e, 0x10, 0xf2, 0xc5 + ], + ivk: [ + 0x38, 0x19, 0xa7, 0xdb, 0x66, 0xb7, 0x20, 0x61, 0x09, 0xdf, 0xee, 0xab, 0xc8, 0xe9, 0x4e, 0xcd, 0xc8, 0xd7, 0xfe, 0x98, 0x3e, 0x48, 0x86, 0xde, 0xa6, 0x6e, 0xdc, 0xcd, 0xeb, 0xcf, 0xbc, 0x00 + ], + default_d: [ + 0xb8, 0xfd, 0x08, 0x5d, 0xf4, 0x66, 0x75, 0x8b, 0x8d, 0xef, 0x70 + ], + default_pk_d: [ + 0xdf, 0x7d, 0xc9, 0xf5, 0x82, 0x3c, 0x2f, 0x25, 0x12, 0x07, 0xc3, 0xd0, 0x75, 0x47, 0x8c, 0x54, 0x59, 0x8f, 0x2b, 0x59, 0x3e, 0xa1, 0x09, 0x31, 0x2d, 0xbd, 0x6e, 0x83, 0x8b, 0x90, 0x2f, 0x2d + ], + v: 500000000, + rcm: [ + 0xe5, 0x57, 0x85, 0x13, 0x55, 0x74, 0x7c, 0x09, 0xac, 0x59, 0x01, 0x3c, 0xbd, 0xe8, 0x59, 0x80, 0x96, 0x4e, 0xc1, 0x84, 0x4d, 0x9c, 0x69, 0x67, 0xca, 0x0c, 0x02, 0x9c, 0x84, 0x57, 0xbb, 0x04 + ], + memo: [ + 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + ], + cv: [ + 0x39, 0xb2, 0x41, 0xc9, 0x65, 0xc2, 0x1e, 0x2a, 0xd8, 0xc2, 0x9a, 0x18, 0xe5, 0xb3, 0x1e, 0xee, 0x9b, 0xad, 0x00, 0x63, 0x5f, 0x39, 0xc5, 0x85, 0x9f, 0x07, 0xa1, 0x78, 0xfb, 0x88, 0x5a, 0x9a + ], + cmu: [ + 0xbb, 0x75, 0xc1, 0x98, 0xe5, 0x69, 0x37, 0x93, 0xbd, 0xba, 0x6e, 0xcc, 0xe9, 0x56, 0xb4, 0xb7, 0xd1, 0xa2, 0x46, 0xec, 0xee, 0x53, 0xb4, 0x91, 0x08, 0x36, 0xc4, 0x24, 0x18, 0xad, 0x68, 0x59 + ], + esk: [ + 0x3d, 0xc1, 0x66, 0xd5, 0x6a, 0x1d, 0x62, 0xf5, 0xa8, 0xd7, 0x55, 0x1d, 0xb5, 0xfd, 0x93, 0x13, 0xe8, 0xc7, 0x20, 0x3d, 0x99, 0x6a, 0xf7, 0xd4, 0x77, 0x08, 0x37, 0x56, 0xd5, 0x9a, 0xf8, 0x0d + ], + epk: [ + 0x54, 0x87, 0x9e, 0x32, 0x0d, 0x16, 0x8e, 0x81, 0xd6, 0xe3, 0x98, 0x87, 0x8f, 0x03, 0x91, 0xaa, 0x90, 0xc0, 0xab, 0xa7, 0x3c, 0xde, 0xf8, 0xba, 0x74, 0x62, 0x67, 0x93, 0x5a, 0x9f, 0xc8, 0x11 + ], + shared_secret: [ + 0x33, 0x5c, 0x01, 0x5a, 0x83, 0x38, 0xb0, 0x8e, 0x44, 0x51, 0x06, 0x49, 0xd8, 0x01, 0x78, 0x03, 0xab, 0x9d, 0x26, 0x71, 0x32, 0xaf, 0xe5, 0xb1, 0x1f, 0x18, 0x1c, 0x69, 0x42, 0x34, 0x7e, 0xe5 + ], + k_enc: [ + 0xce, 0xf1, 0x2f, 0xa6, 0xfa, 0x1f, 0xc2, 0xc2, 0x2c, 0x33, 0x87, 0x34, 0xac, 0x75, 0x35, 0xb2, 0x7f, 0xe5, 0x19, 0xf4, 0xef, 0xc1, 0x31, 0x20, 0xb0, 0xe2, 0x3e, 0xf2, 0x8f, 0xd6, 0x65, 0xcf + ], + p_enc: [ + 0x02, 0xb8, 0xfd, 0x08, 0x5d, 0xf4, 0x66, 0x75, 0x8b, 0x8d, 0xef, 0x70, 0x00, 0x65, 0xcd, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0xe5, 0x57, 0x85, 0x13, 0x55, 0x74, 0x7c, 0x09, 0xac, 0x59, 0x01, 0x3c, 0xbd, 0xe8, 0x59, 0x80, 0x96, 0x4e, 0xc1, 0x84, 0x4d, 0x9c, 0x69, 0x67, 0xca, 0x0c, 0x02, 0x9c, 0x84, 0x57, 0xbb, 0x04, 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + ], + c_enc: [ + 0x13, 0xce, 0xec, 0x5a, 0x29, 0x10, 0x88, 0xd7, 0xc0, 0x86, 0x61, 0x9e, 0x42, 0xf8, 0xd9, 0x6e, 0xf4, 0x93, 0xdf, 0x0f, 0xf5, 0xe5, 0x36, 0xf1, 0x64, 0x57, 0xf0, 0x28, 0x04, 0xe9, 0xc3, 0xdc, 0x7e, 0x5f, 0x63, 0x8f, 0xc3, 0xe8, 0x0f, 0xb7, 0x12, 0x4b, 0xdf, 0x1e, 0x2b, 0xf8, 0x2a, 0x8f, 0xb2, 0x0e, 0xa0, 0x52, 0x86, 0x5a, 0xc6, 0xfd, 0x67, 0xb3, 0xd0, 0xa1, 0xe0, 0x15, 0xbc, 0x8b, 0x5d, 0xf1, 0x39, 0x38, 0x53, 0xc9, 0x20, 0x39, 0x37, 0x2b, 0x7c, 0x20, 0x4b, 0x56, 0x11, 0x2b, 0x59, 0xb7, 0x4b, 0x49, 0x70, 0x9b, 0x1b, 0x16, 0x10, 0x94, 0x9c, 0xd0, 0x2f, 0xc1, 0xb6, 0xa4, 0x4c, 0x07, 0x82, 0xf3, 0xf0, 0x9c, 0x86, 0x17, 0x66, 0x03, 0xe4, 0x07, 0x2d, 0xf9, 0xdf, 0x9e, 0x76, 0xcd, 0xbc, 0x5b, 0x98, 0x25, 0xb1, 0x9e, 0x49, 0x97, 0xd4, 0xa7, 0x77, 0x5f, 0x14, 0x26, 0xed, 0x1a, 0x28, 0xb9, 0x8b, 0xe9, 0x51, 0x31, 0x87, 0x91, 0x42, 0x12, 0xb8, 0xc2, 0x69, 0xa2, 0x9a, 0x58, 0xb3, 0xfc, 0x1a, 0x21, 0x7a, 0x95, 0x07, 0xe0, 0xf3, 0x98, 0xa0, 0x7d, 0xd6, 0x46, 0x23, 0x99, 0xeb, 0x5a, 0xed, 0x9e, 0x13, 0x99, 0xf3, 0x9b, 0x43, 0x4d, 0x23, 0xac, 0xd9, 0x55, 0x4f, 0xf3, 0x40, 0xa1, 0x33, 0x7e, 0xe0, 0xf7, 0x03, 0x67, 0x44, 0xbc, 0x8e, 0x60, 0x97, 0xca, 0x5b, 0x45, 0xa2, 0xb0, 0x88, 0x4b, 0xa0, 0x02, 0xbb, 0xb3, 0xc8, 0x38, 0x90, 0x0c, 0xfa, 0x9c, 0x88, 0x15, 0x24, 0x48, 0xc1, 0x8e, 0x10, 0x30, 0x4b, 0xad, 0xbe, 0xca, 0xb6, 0x05, 0x3a, 0x13, 0xe6, 0xf3, 0x45, 0x9e, 0xb1, 0x4c, 0x05, 0x8d, 0x19, 0xcb, 0x2e, 0xa1, 0x76, 0xe6, 0xb8, 0x9f, 0x3f, 0x04, 0x8a, 0x8d, 0x4a, 0x81, 0x0b, 0xc0, 0x0a, 0xe5, 0xc1, 0xa9, 0x08, 0xa6, 0x91, 0x5f, 0xb7, 0xe3, 0xb8, 0xd4, 0xd0, 0xbd, 0xe1, 0xed, 0x7f, 0x28, 0xdd, 0x56, 0xf7, 0xb3, 0x39, 0xba, 0xc8, 0x39, 0xe7, 0x93, 0x8a, 0x05, 0xa6, 0x80, 0x5c, 0x28, 0x7f, 0x2e, 0x42, 0x4a, 0x3c, 0x21, 0x90, 0x36, 0x77, 0xd9, 0x1a, 0x03, 0x48, 0x3d, 0xc8, 0x69, 0x2a, 0xee, 0x97, 0xfa, 0x0c, 0xa9, 0x6c, 0x1e, 0xdb, 0xb7, 0xf0, 0x14, 0x3d, 0xc7, 0x27, 0x53, 0xa2, 0x15, 0xc8, 0xc8, 0x05, 0x73, 0xe6, 0x17, 0x19, 0xf0, 0x34, 0x3b, 0x3a, 0x7a, 0x7f, 0x55, 0x65, 0xb5, 0xae, 0x8c, 0xc7, 0x8f, 0x0d, 0x8b, 0x2f, 0x62, 0xcb, 0x80, 0xa4, 0xe8, 0x49, 0x3d, 0x1e, 0xa8, 0xd2, 0x98, 0xb8, 0xab, 0x8f, 0x52, 0xcb, 0x87, 0x18, 0x93, 0x4d, 0xc3, 0x15, 0x54, 0xf2, 0x79, 0x14, 0x41, 0x5e, 0xd9, 0xd3, 0x66, 0x45, 0x55, 0x3e, 0xcb, 0x04, 0x26, 0xdf, 0x21, 0xb6, 0xfd, 0x3a, 0xf9, 0x45, 0x67, 0x81, 0x06, 0xb1, 0xef, 0x8f, 0xdb, 0x89, 0xd6, 0x9a, 0x60, 0x9e, 0xe4, 0x34, 0x50, 0xd7, 0x5a, 0xbf, 0x91, 0xf7, 0x49, 0x42, 0xdd, 0x37, 0x1f, 0x2d, 0xac, 0xae, 0x43, 0x71, 0x45, 0x63, 0x8a, 0xbb, 0x11, 0x03, 0x99, 0xce, 0x11, 0xe4, 0xa1, 0x0a, 0x44, 0x4f, 0x43, 0x95, 0x88, 0x00, 0x00, 0x1e, 0x72, 0x71, 0xa0, 0xb7, 0xc5, 0x0c, 0x35, 0xc3, 0x38, 0x30, 0x21, 0x7d, 0xbd, 0xd9, 0xc5, 0x9f, 0x0b, 0x2e, 0xa5, 0xdf, 0xd5, 0xf1, 0x3d, 0xc6, 0x5e, 0x96, 0x46, 0xdb, 0x28, 0xd4, 0x35, 0x8e, 0xde, 0x29, 0x1a, 0xe1, 0xf7, 0xab, 0x79, 0x67, 0x02, 0x7b, 0xf2, 0xa9, 0xcf, 0x02, 0x08, 0x51, 0x65, 0x00, 0x87, 0x8a, 0xa7, 0x6e, 0x12, 0x7f, 0x31, 0x2e, 0x1d, 0xc7, 0x67, 0x09, 0x36, 0x6e, 0xe9, 0x0e, 0xfb, 0x18, 0x5b, 0x09, 0x05, 0x56, 0x1c, 0x11, 0x8d, 0xa3, 0x28, 0x76, 0xc5, 0x2d, 0x86, 0x20, 0x65, 0xac, 0xa8, 0x60, 0x5a, 0x6d, 0xd6, 0x50, 0x15, 0x25, 0x31, 0x4c, 0x16, 0x37, 0x1d, 0xdf, 0x69, 0xbc, 0x2f, 0xc0, 0x97, 0xef, 0x27, 0xc8, 0x06, 0xe3, 0xcb, 0x26, 0x89, 0x4c, 0x44, 0xf7, 0xe6, 0x17, 0xed, 0xb2, 0x78, 0xbd, 0x08, 0x88, 0x32, 0x72, 0x55, 0x16, 0x48, 0xd8, 0xa1, 0xba, 0x7f, 0x26, 0x1e, 0x1a, 0x99, 0x02, 0xbf, 0xd0, 0xaf, 0x21, 0x27, 0x9f, 0xdd, 0x13, 0x49, 0x5e, 0xdb, 0x68, 0xcd, 0xa6, 0x14, 0x70, 0x26, 0xab, 0x3f, 0x9f, 0x38, 0x00, 0x16, 0x09, 0x2f, 0x23, 0x11, 0x62, 0xae, 0x70, 0x9a, 0x43, 0x6e, 0x47, 0x82, 0xb5, 0x22, 0x26, 0x33, 0x68, 0x1a, 0xc9, 0xb6 + ], + ock: [ + 0x77, 0x2b, 0x15, 0x0a, 0xa0, 0x73, 0x60, 0xfe, 0x8b, 0xcb, 0x66, 0xc6, 0x0c, 0xf5, 0x65, 0x0c, 0xec, 0xbb, 0x30, 0x43, 0xc4, 0x7d, 0xdc, 0x5e, 0xd4, 0x04, 0x02, 0x63, 0x37, 0x88, 0x47, 0x31 + ], + op: [ + 0xdf, 0x7d, 0xc9, 0xf5, 0x82, 0x3c, 0x2f, 0x25, 0x12, 0x07, 0xc3, 0xd0, 0x75, 0x47, 0x8c, 0x54, 0x59, 0x8f, 0x2b, 0x59, 0x3e, 0xa1, 0x09, 0x31, 0x2d, 0xbd, 0x6e, 0x83, 0x8b, 0x90, 0x2f, 0x2d, 0x3d, 0xc1, 0x66, 0xd5, 0x6a, 0x1d, 0x62, 0xf5, 0xa8, 0xd7, 0x55, 0x1d, 0xb5, 0xfd, 0x93, 0x13, 0xe8, 0xc7, 0x20, 0x3d, 0x99, 0x6a, 0xf7, 0xd4, 0x77, 0x08, 0x37, 0x56, 0xd5, 0x9a, 0xf8, 0x0d + ], + c_out: [ + 0xe7, 0xce, 0x81, 0x95, 0xad, 0x76, 0xca, 0xb4, 0xc7, 0xde, 0x74, 0x9e, 0x65, 0x44, 0x9e, 0x2f, 0xf2, 0x09, 0x90, 0xc3, 0x38, 0x54, 0x0a, 0xdd, 0x42, 0x55, 0x6e, 0xbd, 0x92, 0x9a, 0x5e, 0xff, 0x86, 0x1a, 0x62, 0x2e, 0xac, 0x03, 0x3b, 0x7e, 0x67, 0xea, 0x90, 0x86, 0x6e, 0xd1, 0xd1, 0x5c, 0x04, 0xc0, 0x24, 0x3f, 0x14, 0x80, 0x8c, 0x60, 0x56, 0x8a, 0x9e, 0xe2, 0x2d, 0x66, 0x62, 0x02, 0x51, 0x52, 0xbe, 0x07, 0xdd, 0xc8, 0x8b, 0x2b, 0x89, 0x23, 0x5b, 0xf5, 0xaa, 0x9e, 0xdf, 0xa3 + ], + }, + TestVector { + ovk: [ + 0x61, 0x1f, 0x89, 0x77, 0x5b, 0x10, 0x86, 0xbc, 0x30, 0xc1, 0x97, 0xa4, 0x3b, 0xbb, 0x3a, 0x55, 0xd3, 0xfd, 0x4a, 0xac, 0x41, 0x82, 0x68, 0xbd, 0x8e, 0x6c, 0x9d, 0xe8, 0xe9, 0x32, 0xe2, 0x3c + ], + ivk: [ + 0xd0, 0xc2, 0xfb, 0xf8, 0x7a, 0x28, 0xcd, 0xce, 0x8e, 0x22, 0x98, 0x96, 0xa9, 0x44, 0xd0, 0x74, 0x2f, 0xe1, 0x5c, 0x61, 0x88, 0x8f, 0x10, 0x39, 0x18, 0x9a, 0x8e, 0x80, 0x5c, 0x6c, 0xdf, 0x06 + ], + default_d: [ + 0x91, 0xd8, 0x0c, 0x32, 0x53, 0x00, 0xd9, 0x7e, 0x0c, 0x3b, 0x05 + ], + default_pk_d: [ + 0xe7, 0x25, 0xc0, 0x2a, 0xc9, 0x18, 0x84, 0xe1, 0x45, 0x2e, 0x5b, 0xbe, 0x8d, 0xbf, 0xb1, 0xe0, 0xcd, 0xee, 0x00, 0x56, 0xdc, 0x2f, 0x5f, 0xc1, 0x92, 0x39, 0xb2, 0x0b, 0x7b, 0xe7, 0x62, 0xe0 + ], + v: 600000000, + rcm: [ + 0x68, 0xf0, 0x61, 0x04, 0x60, 0x6b, 0x0c, 0x54, 0x49, 0x84, 0x5f, 0xf4, 0xc6, 0x5f, 0x73, 0xe9, 0x0f, 0x45, 0xef, 0x5a, 0x43, 0xc9, 0xd7, 0x4c, 0xb2, 0xc8, 0x5c, 0xf5, 0x6c, 0x94, 0xc0, 0x02 + ], + memo: [ + 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + ], + cv: [ + 0x61, 0x01, 0x36, 0xc4, 0x2a, 0x72, 0x80, 0xc0, 0x3c, 0xa8, 0xa3, 0x19, 0x4d, 0x39, 0x6f, 0x28, 0x1d, 0x2c, 0xa3, 0x75, 0xfa, 0xa2, 0x31, 0xfe, 0x43, 0x39, 0x88, 0x96, 0x0b, 0x28, 0xdf, 0xad + ], + cmu: [ + 0x7c, 0x4c, 0x61, 0x63, 0xa4, 0x0a, 0xc2, 0x3a, 0x3c, 0x70, 0xab, 0x61, 0xfb, 0x79, 0xbf, 0x75, 0xdc, 0x4c, 0xc9, 0x86, 0xf7, 0x7b, 0xcb, 0x6b, 0x28, 0x3a, 0xb7, 0xc0, 0x98, 0x8c, 0x97, 0x23 + ], + esk: [ + 0x4e, 0x41, 0x8c, 0x3c, 0x54, 0x3d, 0x6b, 0xf0, 0x15, 0x31, 0x74, 0xa0, 0x4e, 0x85, 0x44, 0xae, 0x7c, 0x58, 0x09, 0x2a, 0x2e, 0x4e, 0x5d, 0x7d, 0x9c, 0x67, 0x2a, 0x3a, 0x79, 0x11, 0x09, 0x03 + ], + epk: [ + 0x7a, 0x9e, 0xd4, 0xc1, 0xfb, 0xe3, 0x39, 0xd8, 0xbf, 0xab, 0x2d, 0xca, 0x72, 0xde, 0x75, 0xe5, 0x7b, 0x9a, 0x8f, 0x79, 0x52, 0xb3, 0x8f, 0xb1, 0xc6, 0x02, 0x76, 0xa7, 0xf8, 0xd9, 0x61, 0x6b + ], + shared_secret: [ + 0x5c, 0x9b, 0x08, 0xfd, 0xd7, 0x81, 0x70, 0x9f, 0xeb, 0x1a, 0x0b, 0xa1, 0xc0, 0x6a, 0x1d, 0x1a, 0x9c, 0xb3, 0xc6, 0x95, 0x9e, 0xbf, 0x88, 0x4f, 0x32, 0xb3, 0x5e, 0x06, 0x8d, 0x98, 0xc0, 0xaf + ], + k_enc: [ + 0x68, 0x55, 0x03, 0x94, 0x2d, 0x0d, 0xf2, 0xc7, 0xf5, 0x8f, 0x89, 0x68, 0xac, 0x6b, 0x56, 0xa3, 0x90, 0x3d, 0x7c, 0x9e, 0x7d, 0xea, 0xb7, 0x4f, 0x1d, 0xe9, 0xf6, 0x69, 0x40, 0x1e, 0x6a, 0xcb + ], + p_enc: [ + 0x02, 0x91, 0xd8, 0x0c, 0x32, 0x53, 0x00, 0xd9, 0x7e, 0x0c, 0x3b, 0x05, 0x00, 0x46, 0xc3, 0x23, 0x00, 0x00, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x68, 0xf0, 0x61, 0x04, 0x60, 0x6b, 0x0c, 0x54, 0x49, 0x84, 0x5f, 0xf4, 0xc6, 0x5f, 0x73, 0xe9, 0x0f, 0x45, 0xef, 0x5a, 0x43, 0xc9, 0xd7, 0x4c, 0xb2, 0xc8, 0x5c, 0xf5, 0x6c, 0x94, 0xc0, 0x02, 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + ], + c_enc: [ + 0x93, 0x12, 0xc1, 0x3c, 0x23, 0x2d, 0x33, 0x9f, 0x6f, 0x63, 0x38, 0x13, 0xa1, 0xc3, 0xf0, 0x44, 0xcb, 0xf3, 0x94, 0x24, 0x67, 0x01, 0x0f, 0x5a, 0x28, 0xc2, 0xa0, 0x51, 0x74, 0xf5, 0x4a, 0xc1, 0xbf, 0x9e, 0x56, 0x71, 0x2d, 0x7e, 0xe8, 0xf6, 0x92, 0xa4, 0x0e, 0x8f, 0x26, 0x8b, 0xbb, 0x55, 0x74, 0xdf, 0x0f, 0x08, 0x04, 0x62, 0x98, 0xcd, 0x41, 0xb8, 0xea, 0xb6, 0xac, 0xa9, 0x15, 0x60, 0x32, 0xf4, 0xfa, 0x68, 0xbc, 0x6c, 0xc0, 0xa1, 0x02, 0xb5, 0xb8, 0x48, 0x8d, 0xea, 0xa7, 0x2d, 0x65, 0xac, 0x47, 0xe9, 0x7d, 0xf6, 0x26, 0xce, 0x7a, 0x97, 0x4e, 0xd0, 0x04, 0x0d, 0x69, 0x4e, 0x85, 0x9d, 0x4b, 0xd5, 0x35, 0x16, 0xb0, 0xc2, 0xa9, 0x84, 0x9d, 0x4d, 0xc7, 0xc8, 0x1c, 0x49, 0x56, 0xf6, 0xb2, 0xdd, 0xf3, 0xf3, 0xb7, 0x40, 0x5b, 0x71, 0x61, 0xaf, 0xbc, 0x7d, 0x28, 0x12, 0x0e, 0xd7, 0xb0, 0x2d, 0xa1, 0x2d, 0x13, 0xc8, 0x0d, 0x29, 0xb3, 0xda, 0x16, 0x3b, 0x09, 0x4c, 0xcb, 0xb8, 0xd9, 0x13, 0xa3, 0xa3, 0xa1, 0x13, 0xd6, 0x72, 0x24, 0x5f, 0xdf, 0xbb, 0x72, 0xc6, 0xea, 0xaa, 0x15, 0xe1, 0x6b, 0xfd, 0xdc, 0xcb, 0x61, 0x1b, 0xf2, 0xaf, 0x92, 0x59, 0xbb, 0x0e, 0x12, 0x89, 0x31, 0xa6, 0xa3, 0x45, 0x03, 0xf6, 0xfd, 0x77, 0xbf, 0xb0, 0x9c, 0x22, 0xd5, 0x86, 0x6f, 0xc4, 0x80, 0x26, 0xf8, 0x14, 0xc2, 0x61, 0xfd, 0x74, 0x41, 0xe8, 0xe2, 0x43, 0x81, 0x7c, 0xc9, 0xd4, 0x52, 0x69, 0x05, 0x54, 0xbd, 0x94, 0x82, 0x57, 0x76, 0x26, 0xfe, 0xf7, 0x60, 0x6c, 0x39, 0x09, 0xb3, 0x14, 0x86, 0x89, 0x22, 0x2a, 0x36, 0x56, 0x59, 0x2c, 0xfe, 0x37, 0xbb, 0xbc, 0xf0, 0x74, 0x91, 0xbb, 0xa1, 0x19, 0xe0, 0x92, 0x93, 0x81, 0x98, 0xcc, 0xa0, 0xab, 0x9b, 0xa3, 0xae, 0x4f, 0x27, 0x06, 0xa3, 0xaa, 0x90, 0x1b, 0xa0, 0x3d, 0xda, 0x1e, 0x09, 0xe1, 0xfb, 0x26, 0xa5, 0xd7, 0x0b, 0x30, 0x30, 0x90, 0x9d, 0x9d, 0x32, 0x03, 0xc1, 0x4b, 0x68, 0xfb, 0xec, 0x9d, 0xf7, 0xb9, 0x78, 0xa3, 0x2f, 0x4e, 0xaf, 0x4a, 0x80, 0x85, 0xe7, 0x20, 0xd9, 0x37, 0xac, 0xf7, 0x3a, 0xca, 0x66, 0x3d, 0x21, 0xde, 0x88, 0xc7, 0xd6, 0xc3, 0xb3, 0xac, 0xab, 0x9d, 0xb9, 0x31, 0xc9, 0xc9, 0x6e, 0x56, 0xda, 0x1b, 0x8e, 0x06, 0x6d, 0xd8, 0x6b, 0x58, 0x78, 0x19, 0xf9, 0xef, 0xa2, 0x4d, 0x34, 0x29, 0xe5, 0x6d, 0x98, 0x1c, 0x68, 0xfe, 0x49, 0xaa, 0x1f, 0xfa, 0x1e, 0xc1, 0xd3, 0x18, 0xc8, 0x6c, 0x60, 0x28, 0xbf, 0xa2, 0x92, 0xfa, 0x6a, 0x6d, 0xf1, 0x1f, 0x8e, 0x9a, 0x75, 0x31, 0x1e, 0x0e, 0x73, 0xfd, 0xc8, 0x1b, 0xd5, 0xf9, 0x85, 0x25, 0x97, 0x01, 0x9f, 0x1b, 0x55, 0xf0, 0x97, 0x33, 0xee, 0x51, 0x29, 0xb5, 0x0c, 0xb2, 0x05, 0xb6, 0xe0, 0x06, 0xda, 0x67, 0x73, 0x3d, 0x52, 0xb8, 0x4d, 0xae, 0xe2, 0x6e, 0xf6, 0x0e, 0xf0, 0x99, 0x20, 0xe3, 0x5e, 0xa1, 0x7a, 0xa1, 0x6a, 0x95, 0xf5, 0x41, 0x9c, 0x3e, 0x23, 0x7a, 0xd2, 0xb5, 0x9d, 0xc8, 0xb6, 0xd7, 0xc3, 0x2a, 0xbb, 0xfa, 0x8e, 0xaf, 0xb7, 0x43, 0xda, 0x87, 0x6b, 0x18, 0x59, 0x78, 0xe4, 0xf4, 0x43, 0x2a, 0x44, 0xb4, 0x63, 0x21, 0x53, 0x09, 0xb9, 0x6a, 0xa5, 0x74, 0x6a, 0x4a, 0xa6, 0xe9, 0x34, 0xd2, 0xf3, 0x4d, 0xed, 0x72, 0xbf, 0xa4, 0x00, 0x08, 0x8b, 0xd4, 0x97, 0x0e, 0xa7, 0x74, 0x01, 0x99, 0x8e, 0xd3, 0xf8, 0xa4, 0xd0, 0x74, 0x5b, 0x4b, 0xe2, 0xb0, 0xef, 0x7f, 0x91, 0x23, 0xde, 0x06, 0x4c, 0xa8, 0x98, 0x6e, 0xde, 0xf6, 0x49, 0xbb, 0x01, 0x13, 0x71, 0x71, 0x96, 0x0b, 0x97, 0x00, 0x4d, 0x8c, 0xa5, 0x95, 0xea, 0x60, 0xfa, 0x97, 0xd9, 0x2a, 0x03, 0x54, 0xe5, 0x25, 0xdf, 0x7e, 0xc1, 0x5f, 0xa5, 0x61, 0x46, 0x18, 0x77, 0xdb, 0xb1, 0xab, 0x36, 0x2e, 0x1f, 0x4a, 0x22, 0x95, 0x2c, 0x97, 0x5c, 0x5a, 0xfa, 0x4b, 0x26, 0xe1, 0xf0, 0x53, 0x8a, 0x53, 0x47, 0x25, 0x59, 0x73, 0x0a, 0x1a, 0x85, 0xac, 0xeb, 0x7e, 0x08, 0x1b, 0x91, 0xc4, 0xb1, 0xc2, 0x0b, 0x4e, 0x89, 0x00, 0x80, 0xdc, 0xc3, 0xd9, 0xde, 0x84, 0xfc, 0xb5, 0x1e, 0x76, 0xe1, 0x52, 0x7f, 0xef, 0x0f, 0x3e, 0x7c, 0x53, 0x18, 0xd3, 0xb5, 0x1e, 0x08, 0x7c, 0x6b, 0x24, 0x14, 0xc7, 0x41, 0xa4, 0x8c, 0x06, 0x41, 0x5b + ], + ock: [ + 0xef, 0x11, 0x62, 0x27, 0x4d, 0xde, 0x33, 0xcd, 0x97, 0x69, 0xbc, 0x8e, 0xcf, 0x52, 0x52, 0x10, 0x83, 0xe2, 0x46, 0xfd, 0xeb, 0x50, 0xce, 0x36, 0xb3, 0x34, 0xc4, 0x7f, 0x98, 0x52, 0x19, 0x0e + ], + op: [ + 0xe7, 0x25, 0xc0, 0x2a, 0xc9, 0x18, 0x84, 0xe1, 0x45, 0x2e, 0x5b, 0xbe, 0x8d, 0xbf, 0xb1, 0xe0, 0xcd, 0xee, 0x00, 0x56, 0xdc, 0x2f, 0x5f, 0xc1, 0x92, 0x39, 0xb2, 0x0b, 0x7b, 0xe7, 0x62, 0xe0, 0x4e, 0x41, 0x8c, 0x3c, 0x54, 0x3d, 0x6b, 0xf0, 0x15, 0x31, 0x74, 0xa0, 0x4e, 0x85, 0x44, 0xae, 0x7c, 0x58, 0x09, 0x2a, 0x2e, 0x4e, 0x5d, 0x7d, 0x9c, 0x67, 0x2a, 0x3a, 0x79, 0x11, 0x09, 0x03 + ], + c_out: [ + 0x26, 0x2e, 0x7e, 0x3a, 0x8e, 0x00, 0x17, 0x2c, 0xad, 0xf8, 0x7a, 0x68, 0x17, 0x26, 0x29, 0xce, 0x5c, 0x46, 0xb8, 0x6a, 0xe6, 0x77, 0x98, 0xb8, 0xe1, 0xf7, 0xbe, 0xf8, 0x0f, 0x1b, 0x55, 0x5b, 0xee, 0x61, 0xb1, 0x74, 0x0c, 0xa6, 0xfd, 0x65, 0x20, 0x5a, 0x66, 0xa5, 0x81, 0x36, 0x76, 0xdc, 0x4b, 0xf3, 0xb7, 0x86, 0x86, 0x43, 0x87, 0xb9, 0xbe, 0x99, 0xae, 0x6c, 0x15, 0xb2, 0xfc, 0xf1, 0x86, 0x05, 0xfd, 0x1d, 0xfd, 0x12, 0xa5, 0xd1, 0x0a, 0x43, 0xec, 0xa6, 0xb0, 0xed, 0xfc, 0xb5 + ], + }, + TestVector { + ovk: [ + 0xda, 0x02, 0x6d, 0x6e, 0x32, 0xc4, 0xc9, 0xf4, 0x5a, 0x79, 0x3d, 0xb3, 0x23, 0xf8, 0x2b, 0xe1, 0xec, 0xcd, 0x30, 0x49, 0x3d, 0xc0, 0x70, 0x89, 0x35, 0xe0, 0xb4, 0x2e, 0x12, 0x5b, 0xfe, 0xd5 + ], + ivk: [ + 0xff, 0x6a, 0xc3, 0x6c, 0x82, 0xc1, 0x94, 0x57, 0x1e, 0x3f, 0xba, 0x8b, 0xdb, 0x26, 0x7a, 0xc9, 0x0e, 0xdb, 0x6c, 0xa7, 0xb4, 0x21, 0x60, 0x60, 0xe8, 0x26, 0xa4, 0x3a, 0x93, 0xde, 0x3c, 0x07 + ], + default_d: [ + 0xce, 0x57, 0xe4, 0x2f, 0x7d, 0x60, 0x05, 0xd0, 0xc3, 0x28, 0xb2 + ], + default_pk_d: [ + 0x75, 0x8a, 0x9a, 0xd6, 0x6f, 0xe3, 0x1a, 0x08, 0x30, 0xcb, 0x5d, 0x39, 0x89, 0x4d, 0x62, 0x23, 0xad, 0xaa, 0x11, 0x08, 0xc0, 0xae, 0xcd, 0x54, 0xcc, 0xd9, 0xfd, 0x1c, 0x1e, 0xd5, 0xe7, 0x62 + ], + v: 700000000, + rcm: [ + 0x49, 0xf9, 0x0b, 0x47, 0xfd, 0x52, 0xfe, 0xe7, 0xc1, 0xc8, 0x1f, 0x0d, 0xcb, 0x5b, 0x74, 0xc3, 0xfb, 0x9b, 0x3e, 0x03, 0x97, 0x6f, 0x8b, 0x75, 0x24, 0xea, 0xba, 0xd0, 0x08, 0x89, 0x21, 0x07 + ], + memo: [ + 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + ], + cv: [ + 0x51, 0x98, 0xc0, 0xe1, 0x10, 0x6d, 0x59, 0xab, 0x12, 0x9e, 0xb1, 0x86, 0xab, 0x4a, 0xa4, 0x05, 0x74, 0x87, 0x6e, 0xa0, 0x8f, 0x3f, 0xb5, 0x7f, 0xf6, 0x68, 0x42, 0x63, 0x20, 0x0a, 0x75, 0xe0 + ], + cmu: [ + 0x0a, 0x2d, 0x48, 0xf2, 0xc9, 0x82, 0x8a, 0xb5, 0x0b, 0x36, 0xed, 0x04, 0xb9, 0x76, 0x13, 0xcd, 0x90, 0x83, 0xe1, 0x85, 0xb9, 0xce, 0xe2, 0xf1, 0xcf, 0xb1, 0xd6, 0x2d, 0x93, 0x7d, 0x99, 0x2f + ], + esk: [ + 0x6d, 0xa9, 0x45, 0xd3, 0x03, 0x81, 0xc2, 0xee, 0xd2, 0xb8, 0x1d, 0x27, 0x08, 0x6d, 0x22, 0x48, 0xe7, 0xc4, 0x49, 0xfe, 0x50, 0x9b, 0x38, 0xe2, 0x76, 0x79, 0x11, 0x89, 0xea, 0xbc, 0x46, 0x02 + ], + epk: [ + 0x31, 0x60, 0xb3, 0xab, 0x25, 0x2e, 0xbd, 0x6b, 0xfb, 0x7f, 0x14, 0x8c, 0x14, 0x0d, 0xf5, 0x90, 0xb3, 0x29, 0x9e, 0xc4, 0x1f, 0xf4, 0xe2, 0xc4, 0x48, 0x2d, 0x8b, 0x15, 0x00, 0xe2, 0x4d, 0xec + ], + shared_secret: [ + 0x0c, 0x77, 0x10, 0x2f, 0x32, 0x04, 0x37, 0x25, 0x66, 0x0d, 0xf2, 0x41, 0x48, 0xf0, 0x60, 0x6d, 0xe6, 0x37, 0xd5, 0x92, 0xb6, 0xf6, 0x4e, 0x1f, 0xe3, 0x17, 0x90, 0x83, 0x0f, 0x4c, 0xdc, 0x5a + ], + k_enc: [ + 0x25, 0x8d, 0x67, 0x9b, 0x11, 0x4a, 0x9e, 0x1d, 0x2b, 0x29, 0xfc, 0x6b, 0xbc, 0x0e, 0xde, 0x59, 0x70, 0x13, 0x9d, 0x0a, 0x21, 0x79, 0x54, 0x8d, 0xd5, 0x7a, 0xb7, 0x63, 0x46, 0x0b, 0xb5, 0x94 + ], + p_enc: [ + 0x02, 0xce, 0x57, 0xe4, 0x2f, 0x7d, 0x60, 0x05, 0xd0, 0xc3, 0x28, 0xb2, 0x00, 0x27, 0xb9, 0x29, 0x00, 0x00, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x49, 0xf9, 0x0b, 0x47, 0xfd, 0x52, 0xfe, 0xe7, 0xc1, 0xc8, 0x1f, 0x0d, 0xcb, 0x5b, 0x74, 0xc3, 0xfb, 0x9b, 0x3e, 0x03, 0x97, 0x6f, 0x8b, 0x75, 0x24, 0xea, 0xba, 0xd0, 0x08, 0x89, 0x21, 0x07, 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + ], + c_enc: [ + 0x8d, 0xf1, 0xd9, 0x6b, 0x3b, 0x0d, 0x11, 0x45, 0xa8, 0x4b, 0x9c, 0xe9, 0x0a, 0x24, 0xf9, 0xa2, 0x4c, 0x29, 0xbe, 0x05, 0x54, 0x54, 0x80, 0xf4, 0x43, 0xae, 0xb4, 0xe1, 0x62, 0x41, 0x28, 0xe0, 0x7d, 0x61, 0x56, 0x14, 0x06, 0xea, 0x85, 0x28, 0xb0, 0xc6, 0x82, 0xec, 0xdf, 0x19, 0x4b, 0x77, 0x90, 0x06, 0x32, 0x3d, 0x72, 0xce, 0x21, 0xda, 0x9c, 0xaf, 0x5f, 0x71, 0x05, 0x9f, 0x94, 0xfe, 0x08, 0xbb, 0xea, 0x1c, 0x3c, 0xb8, 0x20, 0x81, 0x9b, 0x94, 0x44, 0x15, 0xfb, 0x86, 0x91, 0x76, 0xaa, 0x54, 0x43, 0xda, 0x0e, 0xc6, 0xf1, 0xf1, 0x3b, 0x68, 0x9b, 0x4a, 0xe1, 0x9f, 0xfc, 0x6c, 0xed, 0xa3, 0xfd, 0x92, 0xd8, 0x33, 0x43, 0x4d, 0x99, 0x87, 0x2f, 0xed, 0x90, 0xb7, 0xd4, 0xc9, 0x85, 0xc1, 0x95, 0x6d, 0xe6, 0x59, 0x1f, 0x80, 0x24, 0xbb, 0x4e, 0x6f, 0x9d, 0x41, 0xf8, 0x7c, 0x6e, 0xe9, 0x69, 0xf5, 0xfd, 0x91, 0x81, 0x59, 0x57, 0x1c, 0xd8, 0x59, 0xc7, 0x90, 0x53, 0x03, 0x0c, 0x61, 0xc6, 0x32, 0x67, 0x34, 0xce, 0xba, 0x98, 0x04, 0xcc, 0x2b, 0x3d, 0x09, 0x98, 0xad, 0x99, 0xda, 0x2e, 0xb1, 0xa9, 0x8b, 0xea, 0x62, 0x65, 0xce, 0x10, 0xcb, 0xfa, 0xdd, 0xb5, 0x8e, 0x17, 0x33, 0x73, 0x4c, 0xcb, 0xd4, 0xd7, 0x27, 0x18, 0x06, 0xdf, 0xfa, 0xd1, 0x6e, 0xb7, 0xca, 0x90, 0x69, 0xea, 0x8e, 0xbd, 0xb7, 0x16, 0xed, 0xf7, 0x6f, 0x40, 0x87, 0xee, 0xc6, 0xf8, 0xbe, 0x47, 0xa6, 0x6c, 0xad, 0x2f, 0x4e, 0xc3, 0x8f, 0xc9, 0x0f, 0x0d, 0xa2, 0x06, 0xd5, 0x8b, 0xf1, 0x72, 0x7f, 0x0e, 0x31, 0xe2, 0xe2, 0xb6, 0xb0, 0xe5, 0xd0, 0x23, 0x1e, 0x4f, 0xab, 0x59, 0x5e, 0xbd, 0x27, 0x1a, 0x37, 0x93, 0x74, 0xbb, 0x9b, 0xee, 0xab, 0x9e, 0xa7, 0xf8, 0xe7, 0xe3, 0x89, 0x28, 0x36, 0x83, 0x13, 0x4d, 0x88, 0x68, 0xa5, 0xb6, 0x61, 0x24, 0xe5, 0x55, 0xdc, 0x26, 0x9d, 0x5e, 0x11, 0x44, 0xb3, 0xb8, 0x58, 0xf8, 0x36, 0x6a, 0xa3, 0xcb, 0x70, 0x1f, 0x2a, 0xb3, 0x9f, 0x79, 0xfb, 0x45, 0x0f, 0x9d, 0x29, 0x21, 0xfd, 0x51, 0xbf, 0x2f, 0xf2, 0xb1, 0x17, 0xb4, 0x13, 0x1b, 0xd4, 0xe7, 0x01, 0x38, 0x13, 0x8f, 0xa5, 0x3b, 0x0f, 0xba, 0x1e, 0xa0, 0x9e, 0xf7, 0x7e, 0x1e, 0x28, 0x74, 0x2b, 0xa8, 0x8d, 0x62, 0x0d, 0x04, 0x78, 0xe0, 0x1e, 0x8d, 0x96, 0xb5, 0x20, 0x6f, 0x35, 0xf6, 0x01, 0x92, 0xc6, 0x80, 0xb4, 0x6c, 0x69, 0xa8, 0xe0, 0x87, 0x9d, 0xe3, 0x09, 0xd5, 0xf6, 0x1b, 0xee, 0xa5, 0xfb, 0x1c, 0xcc, 0x0d, 0xa1, 0x6c, 0x13, 0xd8, 0xaf, 0xba, 0xb6, 0xb7, 0x0f, 0xa6, 0x9b, 0x1c, 0x15, 0x3d, 0x74, 0x30, 0x7e, 0x58, 0xd8, 0xac, 0xa7, 0x3e, 0x81, 0x94, 0x4d, 0xda, 0xdd, 0xae, 0x48, 0xc7, 0x15, 0x40, 0xf4, 0xc8, 0x2e, 0xb2, 0xb3, 0xae, 0x26, 0x87, 0x91, 0x3d, 0x93, 0xb3, 0xb1, 0x6d, 0x32, 0x91, 0x88, 0x0e, 0x7a, 0x31, 0x99, 0x0e, 0xc4, 0x0f, 0x97, 0x3d, 0x34, 0xf2, 0x25, 0x40, 0x0c, 0xdd, 0x86, 0x72, 0x66, 0x55, 0x8f, 0xba, 0xff, 0x91, 0x5c, 0xd5, 0xe5, 0x2e, 0x0e, 0x9a, 0xcf, 0x56, 0xb7, 0x83, 0x33, 0x23, 0x18, 0x3d, 0xc7, 0x76, 0x57, 0x40, 0x2d, 0x53, 0x44, 0x5d, 0xe2, 0x73, 0xce, 0x89, 0x66, 0x09, 0x86, 0xbb, 0x7c, 0x91, 0x5f, 0x1f, 0x79, 0xf8, 0x9e, 0x99, 0x9b, 0x01, 0x23, 0xfd, 0xea, 0x8f, 0xb0, 0x5e, 0x8e, 0x81, 0xd5, 0xfc, 0xe9, 0xb0, 0x7b, 0xbe, 0xe0, 0x69, 0x5c, 0xbb, 0x29, 0x99, 0x34, 0xdd, 0x6f, 0x0d, 0x0d, 0xe5, 0xc6, 0x9e, 0xd6, 0x18, 0x45, 0xcb, 0x1f, 0xf7, 0x21, 0xd3, 0xf2, 0xa9, 0x9b, 0x7a, 0xed, 0xe8, 0x63, 0x61, 0x55, 0xf7, 0x17, 0x89, 0x9a, 0x66, 0x8e, 0xb9, 0xf3, 0x38, 0x17, 0xca, 0x02, 0x3b, 0x8c, 0x23, 0x71, 0x36, 0x25, 0x8e, 0x71, 0xff, 0xd7, 0x79, 0x4d, 0xa9, 0x34, 0xa1, 0x8c, 0xec, 0xbc, 0xbd, 0x0f, 0x55, 0x71, 0xd6, 0x97, 0x73, 0xd0, 0xb8, 0x1a, 0x8c, 0x1b, 0xa6, 0x61, 0xe0, 0xc4, 0xcf, 0xdb, 0xa5, 0x23, 0xa3, 0xab, 0x5a, 0x40, 0x0c, 0x0c, 0x61, 0xe2, 0x16, 0xb0, 0x3d, 0x8e, 0xb5, 0x3b, 0xe6, 0x58, 0x13, 0xa5, 0x51, 0x9d, 0x7c, 0xd3, 0xe4, 0xb3, 0xbd, 0x0f, 0x33, 0x9b, 0xad, 0x7a, 0x75, 0xc6, 0x47, 0x8f, 0x77, 0x14, 0x22, 0xb4, 0x7c, 0x5f, 0xbf, 0xa7, 0x80, 0x4f + ], + ock: [ + 0xd5, 0x81, 0x15, 0xd7, 0x77, 0x91, 0xe6, 0x4c, 0x68, 0x2a, 0x55, 0x95, 0x73, 0x54, 0x96, 0x56, 0x95, 0x9d, 0xe4, 0xee, 0xa0, 0xc0, 0x71, 0xc7, 0x80, 0x55, 0x0d, 0x60, 0xc5, 0xf1, 0x61, 0xfb + ], + op: [ + 0x75, 0x8a, 0x9a, 0xd6, 0x6f, 0xe3, 0x1a, 0x08, 0x30, 0xcb, 0x5d, 0x39, 0x89, 0x4d, 0x62, 0x23, 0xad, 0xaa, 0x11, 0x08, 0xc0, 0xae, 0xcd, 0x54, 0xcc, 0xd9, 0xfd, 0x1c, 0x1e, 0xd5, 0xe7, 0x62, 0x6d, 0xa9, 0x45, 0xd3, 0x03, 0x81, 0xc2, 0xee, 0xd2, 0xb8, 0x1d, 0x27, 0x08, 0x6d, 0x22, 0x48, 0xe7, 0xc4, 0x49, 0xfe, 0x50, 0x9b, 0x38, 0xe2, 0x76, 0x79, 0x11, 0x89, 0xea, 0xbc, 0x46, 0x02 + ], + c_out: [ + 0x6c, 0x37, 0xf0, 0xd7, 0xe0, 0xab, 0x23, 0xd5, 0x17, 0x11, 0x33, 0xa0, 0x02, 0x30, 0xf4, 0x05, 0xdc, 0x36, 0x94, 0x6e, 0xa6, 0x74, 0xc8, 0xb5, 0xf9, 0x9c, 0x07, 0x85, 0x86, 0xf8, 0xf4, 0x0c, 0xf9, 0xc3, 0x7f, 0xf7, 0x99, 0x86, 0xc6, 0x1b, 0xbd, 0xec, 0x30, 0x92, 0x86, 0x96, 0xb9, 0x3f, 0xa2, 0x53, 0xf6, 0x80, 0xdc, 0x61, 0x66, 0x58, 0xe0, 0xa8, 0x26, 0x45, 0x0d, 0x0e, 0xd9, 0x76, 0xfe, 0x52, 0xf5, 0x97, 0x94, 0x63, 0x9c, 0xf5, 0x85, 0x40, 0xa1, 0x1c, 0x07, 0x8f, 0x4d, 0xb9 + ], + }, + TestVector { + ovk: [ + 0xbd, 0x39, 0x7c, 0x76, 0x26, 0xdf, 0x00, 0xc4, 0x06, 0x78, 0xa4, 0xca, 0x22, 0x64, 0x6a, 0xd2, 0x13, 0x6b, 0xd4, 0xb0, 0xac, 0x55, 0x11, 0x53, 0x76, 0x03, 0x75, 0x75, 0x24, 0xee, 0x11, 0x4e + ], + ivk: [ + 0xd0, 0xb0, 0x3d, 0x9d, 0x8a, 0xd5, 0x3a, 0xe7, 0x70, 0xc0, 0xc8, 0x70, 0x8e, 0xc9, 0x20, 0xff, 0xd6, 0x01, 0x6c, 0x89, 0x97, 0xeb, 0x3b, 0x24, 0x13, 0xad, 0x17, 0xb8, 0xcd, 0xfd, 0xec, 0x05 + ], + default_d: [ + 0xef, 0x83, 0x2c, 0xf6, 0x6f, 0x46, 0xd8, 0x3e, 0x97, 0xbd, 0x79 + ], + default_pk_d: [ + 0x34, 0xfe, 0x17, 0xbd, 0x7f, 0xbd, 0x16, 0xa1, 0x69, 0x06, 0xd7, 0xfe, 0x2e, 0x62, 0x60, 0xe8, 0xb7, 0x25, 0x9b, 0x7c, 0x6e, 0xa3, 0x45, 0x64, 0x6f, 0x7b, 0x28, 0xf0, 0xb7, 0xe3, 0xc6, 0xce + ], + v: 800000000, + rcm: [ + 0x51, 0x65, 0xaf, 0xf2, 0x2d, 0xd4, 0xed, 0x56, 0xb4, 0xd8, 0x1d, 0x1f, 0x17, 0x1c, 0xc3, 0xd6, 0x43, 0x2f, 0xed, 0x1b, 0xeb, 0xf2, 0x0a, 0x7b, 0xea, 0xb1, 0x2d, 0xb1, 0x42, 0xf9, 0x4a, 0x0c + ], + memo: [ + 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + ], + cv: [ + 0xe1, 0x58, 0xf7, 0x46, 0xdb, 0x3d, 0xcc, 0xfa, 0x41, 0x7a, 0x50, 0xd0, 0xc3, 0x46, 0xcc, 0x6e, 0x6a, 0xad, 0x39, 0x5c, 0x34, 0xeb, 0x49, 0x76, 0x2a, 0xe6, 0x45, 0x3c, 0xbd, 0xe6, 0x97, 0xa8 + ], + cmu: [ + 0xd5, 0x5d, 0x9d, 0xcd, 0xc7, 0x8f, 0x1e, 0x20, 0x78, 0x03, 0xee, 0xb4, 0x17, 0x81, 0xb5, 0x6e, 0xd3, 0xa3, 0x7f, 0x76, 0xe0, 0xa7, 0x1d, 0xc0, 0x9c, 0xda, 0x97, 0x0d, 0x2c, 0x4d, 0x75, 0x46 + ], + esk: [ + 0xab, 0x2a, 0xff, 0x03, 0x32, 0xd5, 0x43, 0xfd, 0x1d, 0x80, 0x23, 0x18, 0x5b, 0x8e, 0xcb, 0x5f, 0x22, 0xa2, 0x9c, 0x32, 0xef, 0x74, 0x16, 0x33, 0x31, 0x6e, 0xee, 0x51, 0x4f, 0xc2, 0x23, 0x09 + ], + epk: [ + 0xa6, 0xdb, 0x6b, 0xc5, 0x3e, 0x74, 0x7f, 0x5d, 0x20, 0xb6, 0xdd, 0x39, 0xae, 0x37, 0x29, 0x67, 0x9a, 0xf3, 0x0a, 0xc8, 0xde, 0x4d, 0x27, 0x09, 0xd3, 0x3e, 0x3b, 0xb9, 0x5a, 0xef, 0x28, 0x9a + ], + shared_secret: [ + 0x91, 0x06, 0x03, 0x61, 0xc3, 0x12, 0x7e, 0x46, 0xf4, 0xfa, 0xe5, 0x05, 0x78, 0xd1, 0x63, 0x39, 0x12, 0x9f, 0x14, 0x64, 0xe5, 0x21, 0xb5, 0x64, 0x15, 0xfd, 0xe7, 0x52, 0x04, 0xc8, 0xd1, 0x93 + ], + k_enc: [ + 0x16, 0xcd, 0xc1, 0xbb, 0x53, 0x0c, 0x0a, 0x0c, 0xc2, 0x03, 0x41, 0x1b, 0x83, 0xa9, 0x17, 0x1c, 0xb7, 0x44, 0xd2, 0x53, 0xdb, 0x99, 0xd2, 0xbb, 0xd6, 0xbc, 0x0b, 0xb2, 0xae, 0x55, 0x9f, 0x32 + ], + p_enc: [ + 0x02, 0xef, 0x83, 0x2c, 0xf6, 0x6f, 0x46, 0xd8, 0x3e, 0x97, 0xbd, 0x79, 0x00, 0x08, 0xaf, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x51, 0x65, 0xaf, 0xf2, 0x2d, 0xd4, 0xed, 0x56, 0xb4, 0xd8, 0x1d, 0x1f, 0x17, 0x1c, 0xc3, 0xd6, 0x43, 0x2f, 0xed, 0x1b, 0xeb, 0xf2, 0x0a, 0x7b, 0xea, 0xb1, 0x2d, 0xb1, 0x42, 0xf9, 0x4a, 0x0c, 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + ], + c_enc: [ + 0xfb, 0x73, 0x30, 0x70, 0xc8, 0xfe, 0xe7, 0x86, 0x53, 0x05, 0xc0, 0xa9, 0xed, 0x7a, 0x96, 0x78, 0xf1, 0x0c, 0xae, 0x4b, 0x61, 0xe3, 0x28, 0x52, 0x53, 0x46, 0x54, 0xe5, 0x2e, 0xb0, 0x53, 0xe9, 0x71, 0x60, 0xd9, 0x6f, 0xad, 0x66, 0x7e, 0xe3, 0x89, 0xe0, 0xf2, 0x70, 0x24, 0x89, 0xd9, 0x0d, 0x92, 0x55, 0x58, 0xfa, 0x1a, 0xd7, 0x30, 0xda, 0x94, 0xd0, 0xab, 0x21, 0xaa, 0xd8, 0x2c, 0x06, 0xde, 0xca, 0xed, 0xe0, 0xd7, 0xcf, 0x44, 0xce, 0x85, 0x3c, 0x21, 0x28, 0x73, 0x43, 0x4b, 0x7d, 0x6e, 0x88, 0xd4, 0x1d, 0xee, 0x30, 0xe9, 0x73, 0x51, 0x44, 0xf6, 0xba, 0x5a, 0x29, 0xf3, 0x6d, 0xd8, 0x47, 0xeb, 0x46, 0x9f, 0xf3, 0x13, 0xd3, 0xce, 0x7e, 0x97, 0x57, 0x7e, 0x00, 0x40, 0xc7, 0x5f, 0x9d, 0xf2, 0x85, 0x3f, 0x62, 0xc4, 0xd9, 0x77, 0x78, 0xef, 0xd9, 0xd3, 0x39, 0xcb, 0x8e, 0x19, 0x8b, 0x3f, 0x44, 0xc7, 0x60, 0x8f, 0x4d, 0x42, 0x35, 0xdc, 0x24, 0x5e, 0xb2, 0x62, 0xe9, 0x49, 0xe0, 0x56, 0xe4, 0x33, 0x2c, 0x58, 0x9d, 0x8a, 0xc9, 0xe4, 0xb4, 0x40, 0xa0, 0x20, 0xfe, 0x64, 0x01, 0x7c, 0x68, 0xb9, 0x03, 0x0a, 0x52, 0x9a, 0xa4, 0x84, 0xab, 0x62, 0xf4, 0xee, 0x32, 0x41, 0xc5, 0x35, 0xc6, 0xb2, 0x97, 0x5b, 0xfe, 0xa8, 0x9f, 0x48, 0x41, 0x81, 0x0b, 0x7a, 0x46, 0xb3, 0xfe, 0xf4, 0x1d, 0x59, 0x2d, 0xb0, 0x12, 0xfd, 0x0d, 0x9c, 0x16, 0xb9, 0x58, 0xac, 0x2b, 0xb4, 0xf0, 0x44, 0x6e, 0x19, 0x8f, 0xd7, 0x58, 0x42, 0x66, 0xb5, 0xcb, 0xe3, 0x22, 0x78, 0x6c, 0xc1, 0x46, 0x27, 0x77, 0xf6, 0x9f, 0xe9, 0x85, 0xe5, 0xab, 0x95, 0x65, 0xca, 0x01, 0xff, 0xf8, 0x98, 0x24, 0xb2, 0x0d, 0x87, 0xb8, 0xaf, 0x70, 0x63, 0xf5, 0xbe, 0xbb, 0x13, 0x55, 0x7d, 0x7d, 0x3f, 0x08, 0xdb, 0x57, 0xab, 0xd6, 0x59, 0xd9, 0xf0, 0x40, 0x4d, 0xbf, 0xa4, 0x54, 0xba, 0x74, 0x39, 0xef, 0x8b, 0x93, 0xfb, 0x0d, 0x3a, 0xac, 0x2f, 0x88, 0x51, 0xab, 0x62, 0x8a, 0x3a, 0xf5, 0xa5, 0x04, 0xc6, 0x60, 0x5b, 0xfb, 0xcb, 0x8a, 0x8c, 0xce, 0xa8, 0x73, 0xa7, 0x5d, 0x76, 0x93, 0x8a, 0x13, 0x43, 0x97, 0x73, 0x69, 0xed, 0x39, 0xf3, 0x2a, 0x35, 0xb6, 0xf6, 0x5b, 0xda, 0xce, 0xf4, 0xba, 0xfb, 0x4a, 0x2b, 0x46, 0xbb, 0xde, 0xa7, 0xc8, 0xe5, 0xeb, 0x69, 0x0b, 0x07, 0x60, 0x9e, 0xdb, 0xe2, 0xfe, 0x28, 0xa6, 0x73, 0x4d, 0x3a, 0xf6, 0x74, 0x73, 0x74, 0xcf, 0x84, 0x21, 0x1c, 0x26, 0xdc, 0xb6, 0x37, 0x86, 0xc0, 0x59, 0xee, 0xa6, 0xf4, 0xe9, 0x1f, 0x5e, 0x67, 0x10, 0x3d, 0xd5, 0x36, 0xe0, 0x20, 0xa4, 0x75, 0x98, 0x0d, 0xd4, 0xf5, 0x07, 0xd4, 0x66, 0xb5, 0xc2, 0x87, 0xbe, 0x3b, 0x9a, 0x56, 0x17, 0x78, 0x8f, 0x20, 0xe8, 0x74, 0xa9, 0x9b, 0x2c, 0xe3, 0x7c, 0x02, 0x1d, 0x0b, 0xc8, 0xf5, 0xbf, 0x5b, 0x02, 0x32, 0xec, 0xd2, 0xc9, 0x9f, 0x27, 0x69, 0x82, 0xa6, 0xe5, 0x02, 0x5c, 0x65, 0x44, 0xe1, 0xe6, 0x62, 0x16, 0xd0, 0x47, 0x17, 0xb5, 0x96, 0x7a, 0xec, 0x14, 0xc1, 0x81, 0xe0, 0xe9, 0x12, 0xa9, 0xc9, 0x87, 0xf3, 0x16, 0x67, 0xed, 0x58, 0x07, 0x92, 0xc4, 0xc4, 0x6d, 0x94, 0x7a, 0x28, 0x75, 0x72, 0x07, 0xa7, 0x5f, 0x65, 0xe6, 0x6c, 0x16, 0xa5, 0x5f, 0x44, 0x41, 0x5c, 0xa2, 0x83, 0x25, 0x4c, 0x8f, 0xbf, 0xc4, 0x8c, 0x3d, 0x03, 0xfd, 0x47, 0xee, 0xd0, 0x5c, 0x36, 0x38, 0x29, 0xce, 0x89, 0x1f, 0x6c, 0xfa, 0xb3, 0x15, 0xc8, 0xbd, 0xa0, 0xa8, 0xa4, 0x62, 0xb6, 0xf9, 0x8d, 0x3d, 0x74, 0x81, 0x90, 0x0b, 0x04, 0x2c, 0x98, 0x80, 0x44, 0x88, 0x12, 0x0e, 0xb4, 0x46, 0x70, 0xbf, 0x37, 0xa1, 0xab, 0x37, 0x69, 0xb9, 0xe6, 0x43, 0xb8, 0xd6, 0xb3, 0x76, 0xf9, 0x68, 0xde, 0xda, 0x10, 0x08, 0x26, 0xff, 0x5c, 0xbe, 0x4d, 0x07, 0x4c, 0xe9, 0x04, 0x1c, 0x46, 0x08, 0x79, 0x2a, 0x27, 0x9c, 0xed, 0x9f, 0x65, 0xd2, 0xa0, 0x88, 0x1f, 0x1c, 0xa1, 0xa5, 0x90, 0x74, 0x47, 0x3b, 0xc4, 0xb7, 0x0a, 0xed, 0x9e, 0x0e, 0xda, 0xcf, 0xc6, 0xc3, 0xda, 0xd3, 0x13, 0x81, 0x3f, 0x38, 0x5b, 0x45, 0xb6, 0x9b, 0xba, 0x95, 0x54, 0x3f, 0x97, 0xab, 0x01, 0x54, 0xbb, 0x09, 0xd6, 0x65, 0xf0, 0xa7, 0x76, 0xd9, 0x4e, 0x17, 0xdc, 0x5e, 0x5e, 0xd1, 0x2e, 0x21 + ], + ock: [ + 0x72, 0x7b, 0xf1, 0x55, 0xdc, 0xdf, 0x0d, 0x05, 0x13, 0x2b, 0x77, 0x89, 0xaf, 0xd7, 0xfc, 0x42, 0x23, 0xf4, 0x55, 0x84, 0x3b, 0xec, 0xc1, 0xdf, 0x5f, 0xd7, 0x6f, 0xc9, 0x0f, 0x59, 0x41, 0xf0 + ], + op: [ + 0x34, 0xfe, 0x17, 0xbd, 0x7f, 0xbd, 0x16, 0xa1, 0x69, 0x06, 0xd7, 0xfe, 0x2e, 0x62, 0x60, 0xe8, 0xb7, 0x25, 0x9b, 0x7c, 0x6e, 0xa3, 0x45, 0x64, 0x6f, 0x7b, 0x28, 0xf0, 0xb7, 0xe3, 0xc6, 0xce, 0xab, 0x2a, 0xff, 0x03, 0x32, 0xd5, 0x43, 0xfd, 0x1d, 0x80, 0x23, 0x18, 0x5b, 0x8e, 0xcb, 0x5f, 0x22, 0xa2, 0x9c, 0x32, 0xef, 0x74, 0x16, 0x33, 0x31, 0x6e, 0xee, 0x51, 0x4f, 0xc2, 0x23, 0x09 + ], + c_out: [ + 0xf5, 0xda, 0x0d, 0x00, 0x1e, 0x23, 0x66, 0x3b, 0x55, 0xc8, 0x1e, 0x46, 0x81, 0x98, 0x87, 0x6f, 0x1b, 0xb0, 0x76, 0x99, 0x95, 0xe3, 0xcf, 0xa4, 0x00, 0x60, 0xe8, 0xd9, 0xf5, 0x00, 0x5c, 0xf9, 0xa4, 0xde, 0x9c, 0x0c, 0xb3, 0xb5, 0x4e, 0x8c, 0xd2, 0x10, 0x38, 0x8f, 0xac, 0x28, 0x5c, 0xd7, 0x26, 0xac, 0x13, 0x0c, 0x5f, 0xac, 0x03, 0xb4, 0xca, 0x16, 0xdf, 0xe7, 0xc3, 0x22, 0x32, 0x5d, 0x21, 0x46, 0x7a, 0xb4, 0xdf, 0xd2, 0x0f, 0xee, 0xb4, 0x15, 0xed, 0x1b, 0x2c, 0x45, 0xb7, 0x1e + ], + }, + TestVector { + ovk: [ + 0x21, 0x15, 0x33, 0xa6, 0x4b, 0xc1, 0x87, 0xb9, 0x93, 0x35, 0x99, 0xb4, 0x10, 0x12, 0x37, 0xe5, 0x05, 0x8d, 0x67, 0x7e, 0xb0, 0xa8, 0xb8, 0xdb, 0x91, 0x88, 0x67, 0x55, 0x71, 0x2f, 0xfb, 0x54 + ], + ivk: [ + 0xc4, 0x86, 0x04, 0x3b, 0x54, 0xf2, 0x1f, 0x93, 0xbf, 0x29, 0xe7, 0x0d, 0x38, 0xae, 0x9a, 0x2d, 0xa7, 0xfc, 0x48, 0x23, 0x35, 0xc9, 0x39, 0xc3, 0xbd, 0x86, 0xdb, 0xe3, 0xa6, 0x6e, 0x6e, 0x03 + ], + default_d: [ + 0x55, 0x63, 0x11, 0xd5, 0x93, 0xaf, 0x50, 0xe3, 0x1c, 0x4d, 0x1e + ], + default_pk_d: [ + 0x44, 0x7e, 0xfa, 0x0f, 0x22, 0x05, 0x00, 0x44, 0x26, 0x3d, 0x7d, 0x98, 0xd4, 0x75, 0xc2, 0x60, 0x14, 0x26, 0xf1, 0xae, 0xa1, 0x9e, 0xd5, 0xaf, 0xe3, 0xb5, 0xfc, 0x75, 0xd0, 0x81, 0x24, 0xa4 + ], + v: 900000000, + rcm: [ + 0x8c, 0x3e, 0x56, 0x44, 0x9d, 0xc8, 0x63, 0x54, 0xd3, 0x3b, 0x02, 0x5e, 0xf2, 0x79, 0x34, 0x60, 0xbc, 0xb1, 0x69, 0xf3, 0x32, 0x4e, 0x4a, 0x6b, 0x64, 0xba, 0xa6, 0x08, 0x32, 0x31, 0x57, 0x04 + ], + memo: [ + 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + ], + cv: [ + 0xd7, 0x89, 0x19, 0x2e, 0x90, 0xde, 0x28, 0xc3, 0x7f, 0x58, 0xfe, 0x46, 0xdb, 0x58, 0xdc, 0x20, 0xc5, 0x74, 0xd5, 0x97, 0x62, 0x85, 0x25, 0x30, 0xd3, 0xe9, 0x3f, 0x07, 0xfa, 0x15, 0x99, 0x1c + ], + cmu: [ + 0xa3, 0x64, 0xc4, 0x9c, 0x48, 0x4b, 0xc6, 0xc8, 0x2a, 0x7b, 0x84, 0xf4, 0x31, 0xfe, 0x56, 0xe1, 0x9b, 0x40, 0x2f, 0x74, 0x14, 0x1d, 0x47, 0x1a, 0xe0, 0x30, 0x16, 0x5f, 0xde, 0xb5, 0x82, 0x16 + ], + esk: [ + 0xa5, 0x3d, 0x19, 0xf5, 0x69, 0x45, 0x95, 0xd5, 0xae, 0x63, 0x02, 0x27, 0x67, 0x3c, 0x80, 0x24, 0x9c, 0xe1, 0x24, 0x41, 0x9f, 0x46, 0xdf, 0x4e, 0x7b, 0x3f, 0xc1, 0x04, 0x61, 0x28, 0xcd, 0x0b + ], + epk: [ + 0x28, 0xb2, 0xea, 0x96, 0x7e, 0xf2, 0xc2, 0x0e, 0xe4, 0xad, 0x38, 0x6e, 0x7d, 0xcc, 0xe1, 0x2f, 0x25, 0x20, 0x23, 0xb7, 0xa6, 0x8c, 0xc5, 0x2a, 0x8f, 0xd3, 0x87, 0x38, 0xcc, 0x36, 0x87, 0xcb + ], + shared_secret: [ + 0xec, 0xf0, 0x6e, 0x8e, 0xc5, 0xe5, 0x27, 0xe5, 0xdf, 0x71, 0x82, 0xc5, 0xdb, 0x78, 0x41, 0xf9, 0x18, 0xe2, 0xe2, 0xb3, 0xad, 0x9f, 0x4a, 0xfd, 0xce, 0xf0, 0xed, 0x48, 0xca, 0xc3, 0x67, 0x3b + ], + k_enc: [ + 0x01, 0x61, 0x89, 0xc3, 0x24, 0x24, 0xbd, 0x33, 0xb7, 0x77, 0x1f, 0x51, 0x00, 0x98, 0x66, 0xf8, 0x8b, 0xa8, 0xd5, 0x19, 0x5e, 0x4f, 0x05, 0x93, 0xec, 0x53, 0xdb, 0xfa, 0x90, 0x9a, 0x86, 0x73 + ], + p_enc: [ + 0x02, 0x55, 0x63, 0x11, 0xd5, 0x93, 0xaf, 0x50, 0xe3, 0x1c, 0x4d, 0x1e, 0x00, 0xe9, 0xa4, 0x35, 0x00, 0x00, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x8c, 0x3e, 0x56, 0x44, 0x9d, 0xc8, 0x63, 0x54, 0xd3, 0x3b, 0x02, 0x5e, 0xf2, 0x79, 0x34, 0x60, 0xbc, 0xb1, 0x69, 0xf3, 0x32, 0x4e, 0x4a, 0x6b, 0x64, 0xba, 0xa6, 0x08, 0x32, 0x31, 0x57, 0x04, 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + ], + c_enc: [ + 0x8a, 0xb5, 0x33, 0xe1, 0x20, 0x13, 0x2b, 0x5c, 0x16, 0x40, 0x3b, 0x83, 0x5f, 0x79, 0x14, 0x59, 0xd3, 0xa9, 0xaf, 0x90, 0x0d, 0xda, 0x75, 0xaa, 0xa5, 0x3c, 0x8d, 0x21, 0xa9, 0x51, 0x92, 0xa4, 0x2d, 0x97, 0x27, 0xf5, 0x48, 0x5f, 0xbf, 0x41, 0x67, 0x41, 0x1c, 0xf2, 0xcd, 0x6c, 0x65, 0xfb, 0xbb, 0x2d, 0x9b, 0x5a, 0xa0, 0xaa, 0x29, 0x4a, 0x41, 0x69, 0xe7, 0x65, 0x83, 0x19, 0x67, 0xb4, 0xa9, 0x0a, 0xca, 0xd5, 0xcf, 0x4e, 0xa9, 0xa1, 0x0b, 0xc4, 0xdd, 0x6e, 0x9e, 0x91, 0x4f, 0xa6, 0x9d, 0x63, 0x94, 0x9d, 0xdc, 0xc2, 0x79, 0xa2, 0xfa, 0xb6, 0x6f, 0x77, 0x1b, 0x8b, 0xe5, 0xf0, 0xe7, 0xf6, 0x09, 0x93, 0xc3, 0xcf, 0xe5, 0x2d, 0x90, 0x08, 0x23, 0x60, 0xf0, 0x7a, 0x05, 0x82, 0x44, 0x9e, 0x15, 0x1a, 0xe3, 0x9d, 0x51, 0x5e, 0xff, 0xea, 0x06, 0xa6, 0x5d, 0x1a, 0x14, 0xee, 0xff, 0xca, 0x8b, 0xa2, 0xec, 0x48, 0xf6, 0xbf, 0xfd, 0x17, 0x1f, 0x5e, 0x6a, 0xd1, 0x63, 0xc5, 0xe4, 0x9d, 0x89, 0x28, 0xb0, 0x94, 0x83, 0x92, 0x01, 0xbd, 0x1f, 0x3d, 0xe7, 0xb8, 0x2e, 0x9c, 0xa5, 0x6f, 0xaa, 0xa4, 0x9c, 0x78, 0x56, 0x88, 0xfb, 0x1e, 0xe7, 0xd9, 0xc1, 0xca, 0xe9, 0x63, 0x2a, 0x11, 0x88, 0x2f, 0xa3, 0x7d, 0x3c, 0xc2, 0xa9, 0xe2, 0x7c, 0x60, 0x38, 0x84, 0x4b, 0xb1, 0xc6, 0x9c, 0x3b, 0xc2, 0xc9, 0xcb, 0x5a, 0xa3, 0x90, 0xd6, 0x99, 0x97, 0x9c, 0xd9, 0x41, 0xc7, 0xca, 0x0b, 0x3c, 0xcd, 0x89, 0xc9, 0xcc, 0x42, 0x99, 0x35, 0x26, 0xd7, 0x37, 0xf7, 0x7e, 0x34, 0xd7, 0x78, 0x80, 0xd5, 0xdb, 0x4d, 0x39, 0x29, 0x0a, 0xd1, 0xbf, 0xcc, 0x55, 0xa3, 0x32, 0x35, 0xb3, 0x0c, 0xbb, 0x88, 0x3e, 0xec, 0xb2, 0x55, 0x17, 0x3a, 0x09, 0xb8, 0x2d, 0x5a, 0x7f, 0x64, 0x26, 0xb7, 0xd7, 0xaf, 0xcc, 0xf6, 0x95, 0xf1, 0xf1, 0x4a, 0xfa, 0xf4, 0x73, 0xfa, 0x65, 0xf7, 0x69, 0xe7, 0x1d, 0x84, 0xcd, 0x5e, 0x71, 0x66, 0x24, 0xa3, 0xff, 0x0c, 0xda, 0x04, 0x7f, 0x88, 0x87, 0xeb, 0x89, 0xfc, 0x7f, 0xae, 0xc5, 0x90, 0x70, 0x43, 0x30, 0x78, 0x8e, 0xe1, 0x77, 0x6c, 0xa0, 0x73, 0x47, 0x21, 0x6a, 0xe6, 0x34, 0xc7, 0x63, 0xfa, 0xf4, 0x10, 0xe4, 0xc6, 0xee, 0xcb, 0x99, 0x66, 0xf6, 0x32, 0x4d, 0x80, 0x90, 0x5a, 0x9d, 0x34, 0x5c, 0x46, 0x2c, 0xa0, 0x58, 0x18, 0xcf, 0x34, 0x23, 0x78, 0x1b, 0x89, 0x04, 0xe2, 0x42, 0xec, 0xfa, 0x10, 0x66, 0x88, 0xd4, 0xb4, 0x4b, 0xb1, 0x3a, 0xd3, 0xd9, 0x91, 0x99, 0x80, 0xf6, 0x6f, 0x81, 0xd9, 0xc4, 0x94, 0xc1, 0xd2, 0x42, 0x8f, 0x08, 0xac, 0xa0, 0x2e, 0x7a, 0x1a, 0xd0, 0xc7, 0xc8, 0x11, 0x6b, 0x9a, 0x25, 0x07, 0x04, 0xc0, 0x86, 0xdf, 0x3f, 0xbc, 0x0d, 0x80, 0x27, 0x10, 0x38, 0xf2, 0xce, 0xb8, 0xfd, 0x52, 0x07, 0xb0, 0x65, 0x98, 0xfb, 0x84, 0xd6, 0x6f, 0x3d, 0x9a, 0xdc, 0x4f, 0xeb, 0x21, 0xd7, 0x71, 0x31, 0x21, 0x57, 0x5d, 0x79, 0xc2, 0xad, 0xf5, 0x9a, 0xf7, 0xb9, 0x1d, 0x70, 0x4a, 0xcb, 0xb5, 0xb6, 0x49, 0x05, 0xac, 0x7b, 0x8d, 0x8c, 0xd6, 0xd0, 0x8c, 0xeb, 0x3c, 0x1f, 0x70, 0x68, 0x50, 0xf3, 0x21, 0x6c, 0xed, 0xc4, 0x93, 0xb4, 0xf8, 0x20, 0x9a, 0x8a, 0x58, 0x82, 0x3b, 0x45, 0x19, 0x06, 0x60, 0x94, 0x6c, 0x1c, 0xa8, 0xbf, 0x27, 0xda, 0xaf, 0xf2, 0xfc, 0x3a, 0x1f, 0xe4, 0x1c, 0xd2, 0xb3, 0x3c, 0x55, 0xbe, 0x24, 0xee, 0xa4, 0xb6, 0xf0, 0xbd, 0xb5, 0xe5, 0xa1, 0x7c, 0x42, 0xbc, 0x18, 0x3e, 0x88, 0xd6, 0x8d, 0x92, 0xc6, 0x61, 0xc2, 0xe7, 0x2a, 0x24, 0xff, 0xf1, 0x28, 0xcb, 0xcb, 0x4c, 0xad, 0xeb, 0x44, 0x3e, 0x8f, 0xcf, 0x1d, 0x8e, 0x2c, 0x4b, 0x64, 0xef, 0x57, 0x58, 0xc2, 0xde, 0x99, 0xb9, 0x0c, 0x5e, 0xfd, 0xe6, 0xa9, 0x1e, 0x7c, 0x4d, 0x2d, 0x3e, 0xed, 0x9e, 0xe8, 0x1d, 0x9f, 0x0c, 0xcf, 0x7d, 0xb7, 0x45, 0x9d, 0xf6, 0x9c, 0x93, 0x24, 0x9a, 0xa9, 0xcc, 0xef, 0xb2, 0x2d, 0x7e, 0xa0, 0x83, 0x37, 0xec, 0xfa, 0x2f, 0xd6, 0xca, 0xf5, 0xa7, 0x42, 0xf5, 0x70, 0x25, 0x2b, 0x76, 0x06, 0x7c, 0xed, 0x01, 0x56, 0x68, 0x5d, 0xd6, 0x41, 0xe6, 0xc2, 0x8a, 0xb0, 0xc0, 0x74, 0x9a, 0xdd, 0xc4, 0x7c, 0x82, 0x15, 0x61, 0x66, 0x53, 0xb5, 0xde, 0x8b, 0xea, 0x3a, 0x41, 0xbf + ], + ock: [ + 0x27, 0x6a, 0x0b, 0x0f, 0xd0, 0x38, 0xbb, 0x1f, 0xca, 0x8a, 0xf0, 0x2d, 0x37, 0x8e, 0x1e, 0x79, 0xfd, 0x45, 0xa4, 0x53, 0xa8, 0xd9, 0x8f, 0xbe, 0x13, 0xc1, 0xc3, 0x53, 0x83, 0xc8, 0x96, 0xb6 + ], + op: [ + 0x44, 0x7e, 0xfa, 0x0f, 0x22, 0x05, 0x00, 0x44, 0x26, 0x3d, 0x7d, 0x98, 0xd4, 0x75, 0xc2, 0x60, 0x14, 0x26, 0xf1, 0xae, 0xa1, 0x9e, 0xd5, 0xaf, 0xe3, 0xb5, 0xfc, 0x75, 0xd0, 0x81, 0x24, 0xa4, 0xa5, 0x3d, 0x19, 0xf5, 0x69, 0x45, 0x95, 0xd5, 0xae, 0x63, 0x02, 0x27, 0x67, 0x3c, 0x80, 0x24, 0x9c, 0xe1, 0x24, 0x41, 0x9f, 0x46, 0xdf, 0x4e, 0x7b, 0x3f, 0xc1, 0x04, 0x61, 0x28, 0xcd, 0x0b + ], + c_out: [ + 0x64, 0xd3, 0x78, 0x4a, 0x8c, 0x25, 0xf8, 0x11, 0x64, 0x81, 0xe6, 0xc6, 0xd7, 0x07, 0x22, 0xb3, 0xec, 0xe7, 0x37, 0x3b, 0x27, 0x09, 0x27, 0xea, 0x69, 0xe2, 0x17, 0x18, 0xd4, 0x30, 0xad, 0xc7, 0xea, 0x1f, 0xb1, 0x9e, 0x3a, 0x80, 0xe5, 0x55, 0xc7, 0x22, 0x6c, 0x47, 0xab, 0x06, 0x9f, 0x51, 0x32, 0x0d, 0x94, 0x4b, 0x66, 0x7b, 0x85, 0x9f, 0xb6, 0x7e, 0xd0, 0x1c, 0xbf, 0xa6, 0xf3, 0x5a, 0xac, 0x38, 0x72, 0x6c, 0x40, 0x40, 0x4c, 0xf6, 0x0b, 0x02, 0x3b, 0xa3, 0x26, 0x31, 0x50, 0x8f + ], + }, + TestVector { + ovk: [ + 0xa5, 0xaf, 0x3b, 0xdc, 0x0c, 0x32, 0xb6, 0x51, 0x85, 0x90, 0xce, 0x04, 0x9a, 0x3d, 0x7b, 0xb2, 0x35, 0x7b, 0x0f, 0x24, 0x58, 0x4e, 0xd7, 0x8d, 0x36, 0xde, 0x49, 0xbe, 0x7c, 0xed, 0xb2, 0x84 + ], + ivk: [ + 0x43, 0x98, 0x29, 0xc6, 0x7e, 0x0b, 0x12, 0xd6, 0xb5, 0x8d, 0x03, 0x17, 0x7e, 0x7b, 0x98, 0xf2, 0x01, 0x78, 0x9c, 0x43, 0xfc, 0x76, 0x6e, 0x41, 0xd8, 0x8a, 0x49, 0x40, 0x4d, 0x6b, 0x88, 0x07 + ], + default_d: [ + 0x0d, 0xaa, 0x3c, 0x1b, 0xcf, 0xda, 0xda, 0x95, 0x7e, 0x46, 0xb6 + ], + default_pk_d: [ + 0x2a, 0x9f, 0xbb, 0x3b, 0xac, 0xd1, 0x7c, 0x47, 0xa8, 0xe1, 0x57, 0x2f, 0xc5, 0x1b, 0xa4, 0x9e, 0xb4, 0x65, 0x1c, 0x6d, 0x90, 0xb0, 0x4a, 0x27, 0x4c, 0xe1, 0xb4, 0xaf, 0xc8, 0x93, 0x29, 0xcb + ], + v: 1000000000, + rcm: [ + 0x6e, 0xbb, 0xed, 0x74, 0x36, 0x19, 0xa2, 0x56, 0xf9, 0xad, 0x2e, 0x85, 0x88, 0x0c, 0xfa, 0xa9, 0x09, 0x8a, 0x5f, 0xdb, 0x16, 0x29, 0x99, 0x0d, 0x9a, 0x7d, 0x3b, 0xb9, 0x3f, 0xc9, 0x00, 0x03 + ], + memo: [ + 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + ], + cv: [ + 0x6b, 0x29, 0x10, 0x37, 0xf1, 0x4d, 0x5f, 0xc1, 0x2e, 0xe9, 0x91, 0xe4, 0xe2, 0x67, 0x36, 0xe3, 0xb6, 0x57, 0x4d, 0x1b, 0x49, 0xf7, 0x07, 0x8b, 0x85, 0x34, 0x0a, 0x82, 0xff, 0xb6, 0xb5, 0x4f + ], + cmu: [ + 0x7a, 0x9b, 0xdf, 0xcc, 0xbb, 0xdc, 0x2a, 0x26, 0xa3, 0x58, 0x37, 0x4e, 0x09, 0xcd, 0x6e, 0xb6, 0x16, 0x63, 0xa6, 0x2b, 0xae, 0x15, 0x7f, 0x42, 0xa2, 0x42, 0x79, 0xba, 0xb8, 0xbc, 0x5c, 0x34 + ], + esk: [ + 0x29, 0x95, 0x89, 0x80, 0x69, 0x4f, 0x7f, 0x67, 0x08, 0x09, 0x97, 0xc2, 0x66, 0x47, 0x02, 0x89, 0x0c, 0xd1, 0xb5, 0x03, 0xdd, 0xa4, 0x2d, 0x33, 0xa8, 0x99, 0xce, 0x99, 0x1f, 0xe0, 0xf8, 0x00 + ], + epk: [ + 0x70, 0x99, 0xe7, 0x89, 0x48, 0x89, 0x08, 0x2f, 0xb8, 0x52, 0x8b, 0xd9, 0xa9, 0x77, 0x8a, 0x4a, 0x68, 0x1a, 0x07, 0x65, 0xcf, 0x2e, 0xe3, 0x02, 0xf8, 0x9f, 0xbd, 0x14, 0xa1, 0x3f, 0x4c, 0x56 + ], + shared_secret: [ + 0x0b, 0x1a, 0x29, 0x4c, 0x04, 0x58, 0x61, 0xdf, 0x76, 0x54, 0x28, 0x19, 0xea, 0x3c, 0x96, 0xc8, 0x30, 0xcf, 0x26, 0x97, 0x16, 0x82, 0xc9, 0xa9, 0x0b, 0x36, 0xb8, 0xdc, 0x6d, 0x1b, 0x0f, 0x0f + ], + k_enc: [ + 0x19, 0x0b, 0xaf, 0x71, 0x67, 0x47, 0x82, 0xb8, 0x11, 0x14, 0x5c, 0x7a, 0xb1, 0xf4, 0x5b, 0xe9, 0x95, 0xad, 0xc8, 0xc8, 0xed, 0xff, 0xc7, 0xd9, 0x9f, 0x17, 0x8e, 0x60, 0xb8, 0xe7, 0xe2, 0x9a + ], + p_enc: [ + 0x02, 0x0d, 0xaa, 0x3c, 0x1b, 0xcf, 0xda, 0xda, 0x95, 0x7e, 0x46, 0xb6, 0x00, 0xca, 0x9a, 0x3b, 0x00, 0x00, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 0x6e, 0xbb, 0xed, 0x74, 0x36, 0x19, 0xa2, 0x56, 0xf9, 0xad, 0x2e, 0x85, 0x88, 0x0c, 0xfa, 0xa9, 0x09, 0x8a, 0x5f, 0xdb, 0x16, 0x29, 0x99, 0x0d, 0x9a, 0x7d, 0x3b, 0xb9, 0x3f, 0xc9, 0x00, 0x03, 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + ], + c_enc: [ + 0x3f, 0x09, 0xa0, 0x14, 0x0a, 0x8e, 0x3c, 0x91, 0xdc, 0x8e, 0x9d, 0x47, 0xa8, 0x52, 0x00, 0x9d, 0xc7, 0x5a, 0x88, 0x32, 0x72, 0x0c, 0xf5, 0xa8, 0xd7, 0x38, 0x20, 0xe1, 0x13, 0xdd, 0xb4, 0x5f, 0x9e, 0x96, 0x49, 0x8b, 0xc3, 0xe9, 0xe5, 0xd4, 0xed, 0x07, 0x1d, 0x08, 0x42, 0xb9, 0x86, 0x5c, 0x84, 0x53, 0xd5, 0xdc, 0x39, 0x5a, 0x50, 0xd7, 0xbf, 0x03, 0x0e, 0xc9, 0x86, 0x5c, 0xc6, 0xa2, 0x5e, 0x78, 0x65, 0xab, 0x43, 0x47, 0xa1, 0xf5, 0x24, 0xdd, 0x9e, 0xcb, 0x8f, 0x92, 0xcd, 0xb4, 0xc4, 0x21, 0x46, 0x1c, 0xee, 0x5c, 0x19, 0xab, 0x03, 0x1d, 0x68, 0x9a, 0x2b, 0xd5, 0x96, 0xcf, 0x52, 0xd4, 0x03, 0xeb, 0x5f, 0x6d, 0x31, 0x38, 0xb3, 0xd7, 0x6c, 0x5e, 0xb8, 0x66, 0x40, 0x8c, 0x7f, 0xcc, 0x82, 0xee, 0xa2, 0x31, 0x57, 0x7d, 0x99, 0x09, 0xc7, 0xe8, 0xed, 0x59, 0x8c, 0x7a, 0xad, 0x85, 0xbc, 0x07, 0xd1, 0xce, 0x06, 0xa9, 0xaf, 0x30, 0x49, 0xd9, 0x3d, 0x09, 0xef, 0x2e, 0xdb, 0xf3, 0xb5, 0x66, 0xf0, 0x17, 0xaf, 0xb5, 0x09, 0xc4, 0x1f, 0x39, 0xf6, 0x30, 0x8c, 0xf7, 0xc8, 0x8c, 0x67, 0x14, 0x1a, 0x7e, 0xf6, 0xc4, 0xa5, 0xfa, 0x68, 0x2b, 0x97, 0xae, 0x72, 0x66, 0xe4, 0x7a, 0x0e, 0x0b, 0x94, 0x86, 0xad, 0x62, 0x96, 0x0f, 0x06, 0x48, 0x7c, 0x37, 0x24, 0x0d, 0x68, 0xd5, 0x81, 0x5f, 0x4b, 0x83, 0xaf, 0xfd, 0xac, 0x53, 0x4a, 0x5b, 0xc4, 0xaf, 0xf5, 0x6b, 0x2b, 0xd9, 0xed, 0xa9, 0x10, 0xac, 0x32, 0x73, 0x6f, 0x79, 0x1d, 0x79, 0xdf, 0xdd, 0x67, 0x3f, 0xa9, 0x85, 0x0a, 0x5e, 0x1e, 0x16, 0x6a, 0x53, 0x80, 0x81, 0x7f, 0x42, 0x73, 0x26, 0x81, 0x66, 0x7f, 0xe9, 0xfa, 0x53, 0xeb, 0xff, 0x8a, 0x67, 0x92, 0x24, 0xc5, 0x65, 0x2a, 0x1f, 0x2a, 0xe6, 0x55, 0xf2, 0x22, 0xc2, 0x25, 0xdc, 0x8b, 0x6e, 0xeb, 0xb3, 0x8e, 0xf0, 0x49, 0x29, 0xd6, 0xb3, 0x9c, 0x1b, 0x0e, 0xa7, 0xea, 0x5f, 0x9d, 0x06, 0xb8, 0x59, 0x76, 0x27, 0xac, 0x62, 0x9b, 0x62, 0xaf, 0x81, 0x78, 0x24, 0x05, 0x96, 0x42, 0xa9, 0xb1, 0xe0, 0x4a, 0x27, 0x20, 0x98, 0x74, 0xc9, 0x56, 0x2a, 0xf5, 0x5d, 0x49, 0x37, 0x53, 0xa8, 0x37, 0xc6, 0x4b, 0x1e, 0xae, 0xc6, 0x7b, 0xcd, 0x58, 0x07, 0xf7, 0x1f, 0x4f, 0xb1, 0x66, 0xd9, 0x6b, 0xbf, 0x24, 0xdc, 0xc5, 0x69, 0x88, 0xbf, 0x31, 0x01, 0x36, 0xaf, 0x79, 0xd1, 0xe3, 0x2c, 0x8b, 0x13, 0x7d, 0x49, 0xe0, 0x98, 0x60, 0x5b, 0xe2, 0x31, 0x6b, 0x38, 0xbb, 0xba, 0xc6, 0x5f, 0x36, 0x68, 0x25, 0x52, 0x1d, 0x44, 0x46, 0x2f, 0x05, 0x2a, 0x86, 0xab, 0xea, 0xab, 0xf7, 0x9c, 0x4f, 0xc9, 0xff, 0xd6, 0x94, 0x38, 0x28, 0x69, 0x29, 0xf7, 0x99, 0x90, 0xcc, 0xfe, 0xa8, 0x38, 0x61, 0xd8, 0x46, 0xcf, 0xd4, 0x9e, 0xa9, 0x95, 0x34, 0x16, 0x5e, 0x1f, 0xfa, 0x7c, 0xc9, 0xea, 0xa9, 0xff, 0xa9, 0xad, 0x13, 0x1b, 0xe3, 0xd1, 0xf4, 0xd1, 0xa6, 0xcc, 0x45, 0xad, 0x00, 0x11, 0xcc, 0xfd, 0xbe, 0x50, 0x11, 0x7f, 0x80, 0x14, 0xe7, 0xf3, 0x7f, 0x3b, 0x99, 0x14, 0x5b, 0x2f, 0xb8, 0xb0, 0x5e, 0x66, 0xe3, 0x1b, 0x06, 0x1e, 0x3f, 0xf3, 0xc7, 0x43, 0xd3, 0x9b, 0x93, 0x8e, 0x14, 0x68, 0xf0, 0x83, 0xe1, 0xde, 0x0e, 0xae, 0xb3, 0x2b, 0x05, 0x9f, 0x78, 0x9c, 0x09, 0x7f, 0x82, 0xd4, 0xc3, 0x77, 0xff, 0xb6, 0x59, 0x23, 0xf3, 0x96, 0xa0, 0xd2, 0x00, 0xc4, 0xcb, 0xe4, 0x8f, 0x84, 0x0b, 0x36, 0x70, 0xe4, 0x76, 0xe9, 0xa0, 0x09, 0x3f, 0x06, 0xfe, 0x4f, 0x7c, 0x7a, 0x58, 0x40, 0xc2, 0xc4, 0xc0, 0x87, 0xb5, 0xef, 0x05, 0xdd, 0x92, 0x7f, 0x2c, 0x0b, 0x57, 0x8b, 0x13, 0xb2, 0xf8, 0xa5, 0x92, 0x97, 0xc0, 0x5b, 0x8f, 0xe0, 0x4f, 0x27, 0xee, 0xd3, 0x2a, 0xe4, 0x6b, 0xab, 0xff, 0x3b, 0xe5, 0x62, 0x01, 0xe7, 0x15, 0x7a, 0x54, 0x4f, 0x56, 0xcd, 0x7b, 0x5e, 0xbf, 0xe7, 0xe1, 0xe1, 0x51, 0xb2, 0xf3, 0x79, 0x9e, 0x16, 0x05, 0xdc, 0x39, 0x19, 0x37, 0x19, 0xa8, 0x81, 0xc6, 0x0e, 0xa4, 0xc8, 0xd0, 0x88, 0xcc, 0x30, 0xfd, 0x30, 0x03, 0x92, 0xa3, 0x33, 0xfd, 0x9c, 0x64, 0xb8, 0x32, 0x53, 0x41, 0x3d, 0x75, 0x99, 0x4e, 0x19, 0x08, 0xb8, 0xe0, 0x19, 0xf9, 0x70, 0x15, 0x38, 0x64, 0x05, 0x74, 0x7e, 0x92, 0xfa, 0x25, 0x25 + ], + ock: [ + 0x35, 0xc4, 0x5c, 0x7a, 0xad, 0x47, 0xc6, 0x08, 0x16, 0xf6, 0x51, 0xb2, 0x0e, 0xaf, 0xdd, 0xe1, 0xe7, 0xe4, 0xd5, 0xbc, 0x61, 0xfb, 0x60, 0x12, 0x17, 0x03, 0x02, 0x68, 0x13, 0xd8, 0xb4, 0x0b + ], + op: [ + 0x2a, 0x9f, 0xbb, 0x3b, 0xac, 0xd1, 0x7c, 0x47, 0xa8, 0xe1, 0x57, 0x2f, 0xc5, 0x1b, 0xa4, 0x9e, 0xb4, 0x65, 0x1c, 0x6d, 0x90, 0xb0, 0x4a, 0x27, 0x4c, 0xe1, 0xb4, 0xaf, 0xc8, 0x93, 0x29, 0xcb, 0x29, 0x95, 0x89, 0x80, 0x69, 0x4f, 0x7f, 0x67, 0x08, 0x09, 0x97, 0xc2, 0x66, 0x47, 0x02, 0x89, 0x0c, 0xd1, 0xb5, 0x03, 0xdd, 0xa4, 0x2d, 0x33, 0xa8, 0x99, 0xce, 0x99, 0x1f, 0xe0, 0xf8, 0x00 + ], + c_out: [ + 0x01, 0x01, 0x97, 0xe7, 0x6d, 0x28, 0x38, 0xcb, 0xb7, 0x4b, 0x7c, 0xcd, 0xf5, 0x8e, 0x20, 0x3c, 0x73, 0x5a, 0xbe, 0x4d, 0xb7, 0xb5, 0xe8, 0x74, 0x1b, 0x39, 0xc5, 0xfd, 0x2c, 0xab, 0xd3, 0x7e, 0xc0, 0xd2, 0x77, 0x6a, 0xd0, 0x6a, 0x6a, 0xfc, 0x1e, 0x73, 0x4a, 0x38, 0x36, 0x15, 0xb9, 0xbd, 0x9f, 0xf4, 0x22, 0x26, 0xe6, 0xd9, 0xb4, 0x6c, 0xeb, 0xba, 0x59, 0xb9, 0xfb, 0xb5, 0xc9, 0x8c, 0x86, 0xa1, 0xac, 0x22, 0x60, 0x42, 0xd8, 0x86, 0x03, 0xa6, 0x52, 0x8c, 0xec, 0x74, 0x7e, 0x25 + ], + }, + ]; diff --git a/masp_primitives/src/test_vectors/pedersen_hash_vectors.rs b/masp_primitives/src/test_vectors/pedersen_hash_vectors.rs index d0ab3f25..c25a8229 100644 --- a/masp_primitives/src/test_vectors/pedersen_hash_vectors.rs +++ b/masp_primitives/src/test_vectors/pedersen_hash_vectors.rs @@ -1,9 +1,9 @@ //! Test vectors from https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/sapling_pedersen.py -use crate::pedersen_hash::{test::TestVector, Personalization}; +use crate::sapling::pedersen_hash::{test::TestVector, Personalization}; pub fn get_vectors<'a>() -> Vec> { - return vec![ + vec![ TestVector { personalization: Personalization::NoteCommitment, input_bits: vec![1, 1, 1, 1, 1, 1], @@ -711,5 +711,5 @@ pub fn get_vectors<'a>() -> Vec> { hash_u: "0x6de8499ad50b555936477cd7b009d60b9dc855dec64e9a77fc0a9e10d1da6acd", hash_v: "0x4b736cb086e4132765757fa7239899c5655ee17cc653e006ee17c55e560d6a35", }, - ]; + ] } diff --git a/masp_primitives/src/transaction.rs b/masp_primitives/src/transaction.rs new file mode 100644 index 00000000..4ba6c5a3 --- /dev/null +++ b/masp_primitives/src/transaction.rs @@ -0,0 +1,646 @@ +use borsh::{BorshDeserialize, BorshSerialize}; + +pub mod builder; +pub mod components; +pub mod fees; +pub mod sighash; +pub mod sighash_v5; +pub mod txid; +use blake2b_simd::Hash as Blake2bHash; +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use ff::PrimeField; +use memuse::DynamicUsage; +pub use secp256k1::PublicKey as TransparentAddress; +use std::{ + fmt::{self, Debug}, + hash::Hash, + io::{self, Read, Write}, + ops::Deref, +}; +use zcash_encoding::{Array, CompactSize, Vector}; + +use crate::{ + consensus::{BlockHeight, BranchId}, + sapling::redjubjub, +}; + +use self::{ + components::{ + amount::Amount, + sapling::{ + self, ConvertDescriptionV5, OutputDescriptionV5, SpendDescription, SpendDescriptionV5, + }, + transparent::{self, TxIn, TxOut}, + }, + txid::{to_txid, BlockTxCommitmentDigester, TxIdDigester}, +}; + +pub const GROTH_PROOF_SIZE: usize = 48 + 96 + 48; +pub type GrothProofBytes = [u8; GROTH_PROOF_SIZE]; + +const MASPV5_TX_VERSION: u32 = 2; +const MASPV5_VERSION_GROUP_ID: u32 = 0x26A7270A; + +#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)] +pub struct TxId([u8; 32]); + +memuse::impl_no_dynamic_usage!(TxId); + +impl fmt::Debug for TxId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // The (byte-flipped) hex string is more useful than the raw bytes, because we can + // look that up in RPC methods and block explorers. + let txid_str = self.to_string(); + f.debug_tuple("TxId").field(&txid_str).finish() + } +} + +impl fmt::Display for TxId { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut data = self.0; + data.reverse(); + formatter.write_str(&hex::encode(data)) + } +} + +impl AsRef<[u8; 32]> for TxId { + fn as_ref(&self) -> &[u8; 32] { + &self.0 + } +} + +impl TxId { + pub fn from_bytes(bytes: [u8; 32]) -> Self { + TxId(bytes) + } + + pub fn read(mut reader: R) -> io::Result { + let mut hash = [0u8; 32]; + reader.read_exact(&mut hash)?; + Ok(TxId::from_bytes(hash)) + } + + pub fn write(&self, mut writer: W) -> io::Result<()> { + writer.write_all(&self.0)?; + Ok(()) + } +} + +/// The set of defined transaction format versions. +/// +/// This is serialized in the first four or eight bytes of the transaction format, and +/// represents valid combinations of the `(overwintered, version, version_group_id)` +/// transaction fields. Note that this is not dependent on epoch, only on transaction encoding. +/// For example, if a particular epoch defines a new transaction version but also allows the +/// previous version, then only the new version would be added to this enum. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum TxVersion { + MASPv5, +} + +impl TxVersion { + pub fn read(mut reader: R) -> io::Result { + let header = reader.read_u32::()?; + let version = header & 0x7FFFFFFF; + + match (version, reader.read_u32::()?) { + (MASPV5_TX_VERSION, MASPV5_VERSION_GROUP_ID) => Ok(TxVersion::MASPv5), + _ => Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Unknown transaction format", + )), + } + } + + pub fn header(&self) -> u32 { + match self { + TxVersion::MASPv5 => MASPV5_TX_VERSION, + } + } + + pub fn version_group_id(&self) -> u32 { + match self { + TxVersion::MASPv5 => MASPV5_VERSION_GROUP_ID, + } + } + + pub fn write(&self, mut writer: W) -> io::Result<()> { + writer.write_u32::(self.header())?; + // For consistency with librustzcash & future use + #[allow(clippy::match_single_binding)] + match self { + _ => writer.write_u32::(self.version_group_id()), + } + } + + pub fn suggested_for_branch(consensus_branch_id: BranchId) -> Self { + match consensus_branch_id { + BranchId::MASP => TxVersion::MASPv5, + } + } +} + +/// Authorization state for a bundle of transaction data. +pub trait Authorization { + type TransparentAuth: transparent::Authorization + PartialEq + BorshDeserialize + BorshSerialize; + type SaplingAuth: sapling::Authorization + PartialEq + BorshDeserialize + BorshSerialize; +} +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct Unproven; +#[derive(Debug, PartialEq, Eq)] +pub struct Authorized; + +impl Authorization for Authorized { + type TransparentAuth = transparent::Authorized; + type SaplingAuth = sapling::Authorized; +} + +pub struct Unauthorized; + +impl Authorization for Unauthorized { + type TransparentAuth = transparent::builder::Unauthorized; + type SaplingAuth = sapling::builder::Unauthorized; +} + +/// A MASP transaction. +#[derive(Debug)] +pub struct Transaction { + txid: TxId, + data: TransactionData, +} + +impl Deref for Transaction { + type Target = TransactionData; + + fn deref(&self) -> &TransactionData { + &self.data + } +} + +impl PartialEq for Transaction { + fn eq(&self, other: &Transaction) -> bool { + self.txid == other.txid + } +} + +#[derive(Debug, PartialEq)] +pub struct TransactionData { + version: TxVersion, + consensus_branch_id: BranchId, + lock_time: u32, + expiry_height: BlockHeight, + transparent_bundle: Option>, + sapling_bundle: Option>, +} + +impl TransactionData { + pub fn from_parts( + version: TxVersion, + consensus_branch_id: BranchId, + lock_time: u32, + expiry_height: BlockHeight, + transparent_bundle: Option>, + sapling_bundle: Option>, + ) -> Self { + TransactionData { + version, + consensus_branch_id, + lock_time, + expiry_height, + transparent_bundle, + sapling_bundle, + } + } + + pub fn version(&self) -> TxVersion { + self.version + } + + pub fn consensus_branch_id(&self) -> BranchId { + self.consensus_branch_id + } + + pub fn lock_time(&self) -> u32 { + self.lock_time + } + + pub fn expiry_height(&self) -> BlockHeight { + self.expiry_height + } + + pub fn transparent_bundle(&self) -> Option<&transparent::Bundle> { + self.transparent_bundle.as_ref() + } + + pub fn sapling_bundle(&self) -> Option<&sapling::Bundle> { + self.sapling_bundle.as_ref() + } + pub fn digest>(&self, digester: D) -> D::Digest { + digester.combine( + digester.digest_header( + self.version, + self.consensus_branch_id, + self.lock_time, + self.expiry_height, + ), + digester.digest_transparent(self.transparent_bundle.as_ref()), + digester.digest_sapling(self.sapling_bundle.as_ref()), + ) + } + + pub fn map_authorization( + self, + f_transparent: impl transparent::MapAuth, + f_sapling: impl sapling::MapAuth, + ) -> TransactionData { + TransactionData { + version: self.version, + consensus_branch_id: self.consensus_branch_id, + lock_time: self.lock_time, + expiry_height: self.expiry_height, + transparent_bundle: self + .transparent_bundle + .map(|b| b.map_authorization(f_transparent)), + sapling_bundle: self.sapling_bundle.map(|b| b.map_authorization(f_sapling)), + } + } +} + +impl TransactionData { + pub fn sapling_value_balance(&self) -> Amount { + self.sapling_bundle + .as_ref() + .map_or(Amount::zero(), |b| b.value_balance.clone()) + } +} + +impl TransactionData { + pub fn freeze(self) -> io::Result { + Transaction::from_data(self) + } +} + +impl Transaction { + fn from_data(data: TransactionData) -> io::Result { + match data.version { + TxVersion::MASPv5 => Ok(Self::from_data_v5(data)), + } + } + fn from_data_v5(data: TransactionData) -> Self { + let txid = to_txid( + data.version, + data.consensus_branch_id, + &data.digest(TxIdDigester), + ); + + Transaction { txid, data } + } + + pub fn into_data(self) -> TransactionData { + self.data + } + + pub fn txid(&self) -> TxId { + self.txid + } + + pub fn read(mut reader: R, _consensus_branch_id: BranchId) -> io::Result { + let version = TxVersion::read(&mut reader)?; + match version { + TxVersion::MASPv5 => Self::read_v5(reader, version), + } + } + + fn read_transparent( + mut reader: R, + ) -> io::Result>> { + let vin = Vector::read(&mut reader, TxIn::read)?; + let vout = Vector::read(&mut reader, TxOut::read)?; + Ok(if vin.is_empty() && vout.is_empty() { + None + } else { + Some(transparent::Bundle { + vin, + vout, + authorization: transparent::Authorized, + }) + }) + } + + fn read_amount(mut reader: R) -> io::Result { + Amount::read(&mut reader).map_err(|_| { + io::Error::new( + io::ErrorKind::InvalidData, + "Amount valueBalance out of range", + ) + }) + } + + fn read_v5(mut reader: R, version: TxVersion) -> io::Result { + let (consensus_branch_id, lock_time, expiry_height) = + Self::read_v5_header_fragment(&mut reader)?; + let transparent_bundle = Self::read_transparent(&mut reader)?; + let sapling_bundle = Self::read_v5_sapling(&mut reader)?; + + let data = TransactionData { + version, + consensus_branch_id, + lock_time, + expiry_height, + transparent_bundle, + sapling_bundle, + }; + + Ok(Self::from_data_v5(data)) + } + + fn read_v5_header_fragment(mut reader: R) -> io::Result<(BranchId, u32, BlockHeight)> { + let consensus_branch_id = reader.read_u32::().and_then(|value| { + BranchId::try_from(value).map_err(|e| { + io::Error::new( + io::ErrorKind::InvalidInput, + "invalid consensus branch id: ".to_owned() + e, + ) + }) + })?; + let lock_time = reader.read_u32::()?; + let expiry_height: BlockHeight = reader.read_u32::()?.into(); + Ok((consensus_branch_id, lock_time, expiry_height)) + } + + #[allow(clippy::redundant_closure)] + fn read_v5_sapling( + mut reader: R, + ) -> io::Result>> { + let sd_v5s = Vector::read(&mut reader, SpendDescriptionV5::read)?; + let cd_v5s = Vector::read(&mut reader, ConvertDescriptionV5::read)?; + let od_v5s = Vector::read(&mut reader, OutputDescriptionV5::read)?; + let n_spends = sd_v5s.len(); + let n_converts = cd_v5s.len(); + let n_outputs = od_v5s.len(); + let value_balance = if n_spends > 0 || n_outputs > 0 { + Self::read_amount(&mut reader)? + } else { + Amount::zero() + }; + + let spend_anchor = if n_spends > 0 { + Some(sapling::read_base(&mut reader, "spend anchor")?) + } else { + None + }; + + let convert_anchor = if n_converts > 0 { + Some(sapling::read_base(&mut reader, "convert anchor")?) + } else { + None + }; + + let v_spend_proofs = Array::read(&mut reader, n_spends, |r| sapling::read_zkproof(r))?; + let v_spend_auth_sigs = Array::read(&mut reader, n_spends, |r| { + SpendDescription::read_spend_auth_sig(r) + })?; + let v_convert_proofs = Array::read(&mut reader, n_converts, |r| sapling::read_zkproof(r))?; + let v_output_proofs = Array::read(&mut reader, n_outputs, |r| sapling::read_zkproof(r))?; + + let binding_sig = if n_spends > 0 || n_outputs > 0 { + Some(redjubjub::Signature::read(&mut reader)?) + } else { + None + }; + + let shielded_spends = sd_v5s + .into_iter() + .zip( + v_spend_proofs + .into_iter() + .zip(v_spend_auth_sigs.into_iter()), + ) + .map(|(sd_5, (zkproof, spend_auth_sig))| { + // the following `unwrap` is safe because we know n_spends > 0. + sd_5.into_spend_description(spend_anchor.unwrap(), zkproof, spend_auth_sig) + }) + .collect(); + + let shielded_converts = cd_v5s + .into_iter() + .zip(v_convert_proofs.into_iter()) + .map(|(cd_5, zkproof)| cd_5.into_convert_description(convert_anchor.unwrap(), zkproof)) + .collect(); + + let shielded_outputs = od_v5s + .into_iter() + .zip(v_output_proofs.into_iter()) + .map(|(od_5, zkproof)| od_5.into_output_description(zkproof)) + .collect(); + + Ok(binding_sig.map(|binding_sig| sapling::Bundle { + value_balance, + shielded_spends, + shielded_converts, + shielded_outputs, + authorization: sapling::Authorized { binding_sig }, + })) + } + pub fn write(&self, writer: W) -> io::Result<()> { + match self.version { + TxVersion::MASPv5 => self.write_v5(writer), + } + } + pub fn write_transparent(&self, mut writer: W) -> io::Result<()> { + if let Some(bundle) = &self.transparent_bundle { + Vector::write(&mut writer, &bundle.vin, |w, e| e.write(w))?; + Vector::write(&mut writer, &bundle.vout, |w, e| e.write(w))?; + } else { + CompactSize::write(&mut writer, 0)?; + CompactSize::write(&mut writer, 0)?; + } + + Ok(()) + } + + pub fn write_v5(&self, mut writer: W) -> io::Result<()> { + self.write_v5_header(&mut writer)?; + self.write_transparent(&mut writer)?; + self.write_v5_sapling(&mut writer)?; + Ok(()) + } + + pub fn write_v5_header(&self, mut writer: W) -> io::Result<()> { + self.version.write(&mut writer)?; + writer.write_u32::(u32::from(self.consensus_branch_id))?; + writer.write_u32::(self.lock_time)?; + writer.write_u32::(u32::from(self.expiry_height))?; + Ok(()) + } + + pub fn write_v5_sapling(&self, mut writer: W) -> io::Result<()> { + if let Some(bundle) = &self.sapling_bundle { + Vector::write(&mut writer, &bundle.shielded_spends, |w, e| { + e.write_v5_without_witness_data(w) + })?; + + Vector::write(&mut writer, &bundle.shielded_converts, |w, e| { + e.write_v5_without_witness_data(w) + })?; + + Vector::write(&mut writer, &bundle.shielded_outputs, |w, e| { + e.write_v5_without_proof(w) + })?; + + if !(bundle.shielded_spends.is_empty() + && bundle.shielded_converts.is_empty() + && bundle.shielded_outputs.is_empty()) + { + bundle.value_balance.write(&mut writer)?; + } + if !bundle.shielded_spends.is_empty() { + writer.write_all(bundle.shielded_spends[0].anchor.to_repr().as_ref())?; + } + if !bundle.shielded_converts.is_empty() { + writer.write_all(bundle.shielded_converts[0].anchor.to_repr().as_ref())?; + } + + Array::write( + &mut writer, + bundle.shielded_spends.iter().map(|s| s.zkproof), + |w, e| w.write_all(e), + )?; + Array::write( + &mut writer, + bundle.shielded_spends.iter().map(|s| s.spend_auth_sig), + |w, e| e.write(w), + )?; + + Array::write( + &mut writer, + bundle.shielded_converts.iter().map(|s| s.zkproof), + |w, e| w.write_all(e), + )?; + + Array::write( + &mut writer, + bundle.shielded_outputs.iter().map(|s| s.zkproof), + |w, e| w.write_all(e), + )?; + + if !(bundle.shielded_spends.is_empty() + && bundle.shielded_converts.is_empty() + && bundle.shielded_outputs.is_empty()) + { + bundle.authorization.binding_sig.write(&mut writer)?; + } + } else { + CompactSize::write(&mut writer, 0)?; + CompactSize::write(&mut writer, 0)?; + } + + Ok(()) + } + + // TODO: should this be moved to `from_data` and stored? + pub fn auth_commitment(&self) -> Blake2bHash { + self.data.digest(BlockTxCommitmentDigester) + } +} + +#[derive(Clone, Debug)] +pub struct TransparentDigests { + pub inputs_digest: A, + pub outputs_digest: A, +} + +#[derive(Clone, Debug)] +pub struct TxDigests { + pub header_digest: A, + pub transparent_digests: Option>, + pub sapling_digest: Option, +} + +pub trait TransactionDigest { + type HeaderDigest; + type TransparentDigest; + type SaplingDigest; + type Digest; + + fn digest_header( + &self, + version: TxVersion, + consensus_branch_id: BranchId, + lock_time: u32, + expiry_height: BlockHeight, + ) -> Self::HeaderDigest; + + fn digest_transparent( + &self, + transparent_bundle: Option<&transparent::Bundle>, + ) -> Self::TransparentDigest; + + fn digest_sapling( + &self, + sapling_bundle: Option<&sapling::Bundle>, + ) -> Self::SaplingDigest; + + fn combine( + &self, + header_digest: Self::HeaderDigest, + transparent_digest: Self::TransparentDigest, + sapling_digest: Self::SaplingDigest, + ) -> Self::Digest; +} + +pub enum DigestError { + NotSigned, +} + +#[cfg(any(test, feature = "test-dependencies"))] +pub mod testing { + use proptest::prelude::*; + + use crate::consensus::BranchId; + + use super::{ + components::{ + sapling::testing::{self as sapling}, + transparent::testing::{self as transparent}, + }, + Authorized, Transaction, TransactionData, TxId, TxVersion, + }; + + pub fn arb_txid() -> impl Strategy { + prop::array::uniform32(any::()).prop_map(TxId::from_bytes) + } + + pub fn arb_tx_version(branch_id: BranchId) -> impl Strategy { + match branch_id { + BranchId::MASP => Just(TxVersion::MASPv5).boxed(), + } + } + + prop_compose! { + pub fn arb_txdata(consensus_branch_id: BranchId)( + version in arb_tx_version(consensus_branch_id), + )( + lock_time in any::(), + expiry_height in any::(), + transparent_bundle in transparent::arb_bundle(), + sapling_bundle in sapling::arb_bundle_for_version(version), + version in Just(version) + ) -> TransactionData { + TransactionData { + version, + consensus_branch_id, + lock_time, + expiry_height: expiry_height.into(), + transparent_bundle, + sapling_bundle, + } + } + } + + prop_compose! { + pub fn arb_tx(branch_id: BranchId)(tx_data in arb_txdata(branch_id)) -> Transaction { + Transaction::from_data(tx_data).unwrap() + } + } +} diff --git a/masp_primitives/src/transaction/builder.rs b/masp_primitives/src/transaction/builder.rs new file mode 100644 index 00000000..050e2e59 --- /dev/null +++ b/masp_primitives/src/transaction/builder.rs @@ -0,0 +1,660 @@ +//! Structs for building transactions. + +use std::convert::TryInto; +use std::error; +use std::fmt; +use std::sync::mpsc::Sender; + +use secp256k1::PublicKey as TransparentAddress; + +use rand::{rngs::OsRng, CryptoRng, RngCore}; + +use crate::{ + asset_type::AssetType, + consensus::{self, BlockHeight, BranchId}, + keys::OutgoingViewingKey, + memo::MemoBytes, + merkle_tree::MerklePath, + sapling::{prover::TxProver, Diversifier, Node, Note, PaymentAddress}, + transaction::{ + components::{ + amount::{Amount, BalanceError, MAX_MONEY}, + sapling::{ + self, + builder::{SaplingBuilder, SaplingMetadata}, + }, + transparent::{self, builder::TransparentBuilder}, + }, + fees::FeeRule, + sighash::{signature_hash, SignableInput}, + txid::TxIdDigester, + Transaction, TransactionData, TxVersion, Unauthorized, + }, + zip32::ExtendedSpendingKey, +}; + +#[cfg(feature = "transparent-inputs")] +use crate::transaction::components::transparent::TxOut; + +const DEFAULT_TX_EXPIRY_DELTA: u32 = 20; +/// Errors that can occur during transaction construction. +#[derive(Debug, PartialEq, Eq)] +pub enum Error { + /// Insufficient funds were provided to the transaction builder; the given + /// additional amount is required in order to construct the transaction. + InsufficientFunds(Amount), + /// The transaction has inputs in excess of outputs and fees; the user must + /// add a change output. + ChangeRequired(Amount), + /// An error occurred in computing the fees for a transaction. + Fee(FeeError), + /// An overflow or underflow occurred when computing value balances + Balance(BalanceError), + /// An error occurred in constructing the transparent parts of a transaction. + TransparentBuild(transparent::builder::Error), + /// An error occurred in constructing the Sapling parts of a transaction. + SaplingBuild(sapling::builder::Error), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::InsufficientFunds(amount) => write!( + f, + "Insufficient funds for transaction construction; need an additional {:?} zatoshis", + amount + ), + Error::ChangeRequired(amount) => write!( + f, + "The transaction requires an additional change output of {:?} zatoshis", + amount + ), + Error::Balance(e) => write!(f, "Invalid amount {:?}", e), + Error::Fee(e) => write!(f, "An error occurred in fee calculation: {}", e), + Error::TransparentBuild(err) => err.fmt(f), + Error::SaplingBuild(err) => err.fmt(f), + } + } +} + +impl error::Error for Error {} + +impl From for Error { + fn from(e: BalanceError) -> Self { + Error::Balance(e) + } +} + +/// Reports on the progress made by the builder towards building a transaction. +pub struct Progress { + /// The number of steps completed. + cur: u32, + /// The expected total number of steps (as of this progress update), if known. + end: Option, +} + +impl Progress { + pub fn new(cur: u32, end: Option) -> Self { + Self { cur, end } + } + + /// Returns the number of steps completed so far while building the transaction. + /// + /// Note that each step may not be of the same complexity/duration. + pub fn cur(&self) -> u32 { + self.cur + } + + /// Returns the total expected number of steps before this transaction will be ready, + /// or `None` if the end is unknown as of this progress update. + /// + /// Note that each step may not be of the same complexity/duration. + pub fn end(&self) -> Option { + self.end + } +} + +/// Generates a [`Transaction`] from its inputs and outputs. +pub struct Builder { + params: P, + rng: R, + target_height: BlockHeight, + expiry_height: BlockHeight, + transparent_builder: TransparentBuilder, + sapling_builder: SaplingBuilder

, + progress_notifier: Option>, +} + +impl Builder { + /// Returns the network parameters that the builder has been configured for. + pub fn params(&self) -> &P { + &self.params + } + + /// Returns the target height of the transaction under construction. + pub fn target_height(&self) -> BlockHeight { + self.target_height + } + + /// Returns the set of transparent inputs currently committed to be consumed + /// by the transaction. + pub fn transparent_inputs(&self) -> &[impl transparent::fees::InputView] { + self.transparent_builder.inputs() + } + + /// Returns the set of transparent outputs currently set to be produced by + /// the transaction. + pub fn transparent_outputs(&self) -> &[impl transparent::fees::OutputView] { + self.transparent_builder.outputs() + } + + /// Returns the set of Sapling inputs currently committed to be consumed + /// by the transaction. + pub fn sapling_inputs(&self) -> &[impl sapling::fees::InputView<()>] { + self.sapling_builder.inputs() + } + + /// Returns the set of Sapling outputs currently set to be produced by + /// the transaction. + pub fn sapling_outputs(&self) -> &[impl sapling::fees::OutputView] { + self.sapling_builder.outputs() + } +} + +impl Builder { + /// Creates a new `Builder` targeted for inclusion in the block with the given height, + /// using default values for general transaction fields and the default OS random. + /// + /// # Default values + /// + /// The expiry height will be set to the given height plus the default transaction + /// expiry delta (20 blocks). + pub fn new(params: P, target_height: BlockHeight) -> Self { + Builder::new_with_rng(params, target_height, OsRng) + } +} + +impl Builder { + /// Creates a new `Builder` targeted for inclusion in the block with the given height + /// and randomness source, using default values for general transaction fields. + /// + /// # Default values + /// + /// The expiry height will be set to the given height plus the default transaction + /// expiry delta (20 blocks). + pub fn new_with_rng(params: P, target_height: BlockHeight, rng: R) -> Builder { + Self::new_internal(params, rng, target_height) + } +} + +impl Builder { + /// Common utility function for builder construction. + /// + /// WARNING: THIS MUST REMAIN PRIVATE AS IT ALLOWS CONSTRUCTION + /// OF BUILDERS WITH NON-CryptoRng RNGs + fn new_internal(params: P, rng: R, target_height: BlockHeight) -> Builder { + Builder { + params: params.clone(), + rng, + target_height, + expiry_height: target_height + DEFAULT_TX_EXPIRY_DELTA, + transparent_builder: TransparentBuilder::empty(), + sapling_builder: SaplingBuilder::new(params, target_height), + progress_notifier: None, + } + } + + /// Adds a Sapling note to be spent in this transaction. + /// + /// Returns an error if the given Merkle path does not have the same anchor as the + /// paths for previous Sapling notes. + pub fn add_sapling_spend( + &mut self, + extsk: ExtendedSpendingKey, + diversifier: Diversifier, + note: Note, + merkle_path: MerklePath, + ) -> Result<(), sapling::builder::Error> { + self.sapling_builder + .add_spend(&mut self.rng, extsk, diversifier, note, merkle_path) + } + + /// Adds a Sapling address to send funds to. + pub fn add_sapling_output( + &mut self, + ovk: Option, + to: PaymentAddress, + asset_type: AssetType, + value: u64, + memo: MemoBytes, + ) -> Result<(), sapling::builder::Error> { + if value > MAX_MONEY.try_into().unwrap() { + return Err(sapling::builder::Error::InvalidAmount); + } + self.sapling_builder + .add_output(&mut self.rng, ovk, to, asset_type, value, memo) + } + + /// Adds a transparent coin to be spent in this transaction. + #[cfg(feature = "transparent-inputs")] + #[cfg_attr(docsrs, doc(cfg(feature = "transparent-inputs")))] + pub fn add_transparent_input( + &mut self, + coin: TxOut, + ) -> Result<(), transparent::builder::Error> { + self.transparent_builder.add_input(coin) + } + + /// Adds a transparent address to send funds to. + pub fn add_transparent_output( + &mut self, + to: &TransparentAddress, + asset_type: AssetType, + value: i64, + ) -> Result<(), transparent::builder::Error> { + if value < 0 || value > MAX_MONEY { + return Err(transparent::builder::Error::InvalidAmount); + } + + self.transparent_builder.add_output(to, asset_type, value) + } + + /// Sets the notifier channel, where progress of building the transaction is sent. + /// + /// An update is sent after every Spend or Output is computed, and the `u32` sent + /// represents the total steps completed so far. It will eventually send number of + /// spends + outputs. If there's an error building the transaction, the channel is + /// closed. + pub fn with_progress_notifier(&mut self, progress_notifier: Sender) { + self.progress_notifier = Some(progress_notifier); + } + + /// Returns the sum of the transparent, Sapling, and TZE value balances. + fn value_balance(&self) -> Result { + let value_balances = [ + self.transparent_builder.value_balance()?, + self.sapling_builder.value_balance(), + ]; + + Ok(value_balances.into_iter().sum::()) + } + + /// Builds a transaction from the configured spends and outputs. + /// + /// Upon success, returns a tuple containing the final transaction, and the + /// [`SaplingMetadata`] generated during the build process. + pub fn build( + self, + prover: &impl TxProver, + fee_rule: &FR, + ) -> Result<(Transaction, SaplingMetadata), Error> { + let fee = fee_rule + .fee_required( + &self.params, + self.target_height, + self.transparent_builder.outputs(), + self.sapling_builder.inputs().len(), + self.sapling_builder.outputs().len(), + ) + .map_err(Error::Fee)?; + self.build_internal(prover, fee) + } + + fn build_internal( + self, + prover: &impl TxProver, + fee: Amount, + ) -> Result<(Transaction, SaplingMetadata), Error> { + let consensus_branch_id = BranchId::for_height(&self.params, self.target_height); + + // determine transaction version + let version = TxVersion::suggested_for_branch(consensus_branch_id); + + // + // Consistency checks + // + + // After fees are accounted for, the value balance of the transaction must be zero. + let balance_after_fees = self.value_balance()? - fee; + + if balance_after_fees != Amount::zero() { + return Err(Error::InsufficientFunds(-balance_after_fees)); + }; + + let transparent_bundle = self.transparent_builder.build(); + + let mut rng = self.rng; + let mut ctx = prover.new_sapling_proving_context(); + let sapling_bundle = self + .sapling_builder + .build( + prover, + &mut ctx, + &mut rng, + self.target_height, + self.progress_notifier.as_ref(), + ) + .map_err(Error::SaplingBuild)?; + + let unauthed_tx: TransactionData = TransactionData { + version, + consensus_branch_id: BranchId::for_height(&self.params, self.target_height), + lock_time: 0, + expiry_height: self.expiry_height, + transparent_bundle, + sapling_bundle, + }; + + // + // Signatures -- everything but the signatures must already have been added. + // + let txid_parts = unauthed_tx.digest(TxIdDigester); + + let transparent_bundle = unauthed_tx + .transparent_bundle + .clone() + .map(|b| b.apply_signatures()); + + // the commitment being signed is shared across all Sapling inputs; once + // V4 transactions are deprecated this should just be the txid, but + // for now we need to continue to compute it here. + let shielded_sig_commitment = + signature_hash(&unauthed_tx, &SignableInput::Shielded, &txid_parts); + + let (sapling_bundle, tx_metadata) = match unauthed_tx + .sapling_bundle + .map(|b| { + b.apply_signatures(prover, &mut ctx, &mut rng, shielded_sig_commitment.as_ref()) + }) + .transpose() + .map_err(Error::SaplingBuild)? + { + Some((bundle, meta)) => (Some(bundle), meta), + None => (None, SaplingMetadata::empty()), + }; + + let authorized_tx = TransactionData { + version: unauthed_tx.version, + consensus_branch_id: unauthed_tx.consensus_branch_id, + lock_time: unauthed_tx.lock_time, + expiry_height: unauthed_tx.expiry_height, + transparent_bundle, + sapling_bundle, + }; + + // The unwrap() here is safe because the txid hashing + // of freeze() should be infalliable. + Ok((authorized_tx.freeze().unwrap(), tx_metadata)) + } +} + +#[cfg(any(test, feature = "test-dependencies"))] +mod testing { + use rand::RngCore; + use std::convert::Infallible; + + use super::{Builder, Error, SaplingMetadata}; + use crate::{ + consensus::{self, BlockHeight}, + sapling::prover::mock::MockTxProver, + transaction::{fees::fixed, Transaction}, + }; + + impl Builder { + /// Creates a new `Builder` targeted for inclusion in the block with the given height + /// and randomness source, using default values for general transaction fields. + /// + /// # Default values + /// + /// The expiry height will be set to the given height plus the default transaction + /// expiry delta (20 blocks). + /// + /// WARNING: DO NOT USE IN PRODUCTION + pub fn test_only_new_with_rng(params: P, height: BlockHeight, rng: R) -> Builder { + Self::new_internal(params, rng, height) + } + + pub fn mock_build(self) -> Result<(Transaction, SaplingMetadata), Error> { + self.build(&MockTxProver, &fixed::FeeRule::standard()) + } + } +} + +#[cfg(test)] +mod tests { + use ff::Field; + use rand_core::OsRng; + use secp256k1::Secp256k1; + + use crate::{ + asset_type::AssetType, + consensus::{NetworkUpgrade, Parameters, TEST_NETWORK}, + memo::MemoBytes, + merkle_tree::{CommitmentTree, IncrementalWitness}, + sapling::Rseed, + transaction::{ + components::amount::{Amount, DEFAULT_FEE, MAX_MONEY}, + sapling::builder::{self as build_s}, + transparent::builder::{self as build_t}, + }, + zip32::ExtendedSpendingKey, + }; + + use super::{Builder, Error}; + + #[test] + fn fails_on_overflow_output() { + let extsk = ExtendedSpendingKey::master(&[]); + let dfvk = extsk.to_diversifiable_full_viewing_key(); + let ovk = dfvk.fvk().ovk; + let to = dfvk.default_address().1; + + let masp_activation_height = TEST_NETWORK + .activation_height(NetworkUpgrade::MASP) + .unwrap(); + + let mut builder = Builder::new(TEST_NETWORK, masp_activation_height); + assert_eq!( + builder.add_sapling_output( + Some(ovk), + to, + zec(), + MAX_MONEY as u64 + 1, + MemoBytes::empty() + ), + Err(build_s::Error::InvalidAmount) + ); + } + + /// Generate ZEC asset type + fn zec() -> AssetType { + AssetType::new(b"ZEC").unwrap() + } + + #[test] + fn binding_sig_present_if_shielded_spend() { + let (_, transparent_address) = Secp256k1::new().generate_keypair(&mut OsRng); + + let extsk = ExtendedSpendingKey::master(&[]); + let dfvk = extsk.to_diversifiable_full_viewing_key(); + let to = dfvk.default_address().1; + + let mut rng = OsRng; + + let note1 = to + .create_note( + zec(), + 50000, + Rseed::BeforeZip212(jubjub::Fr::random(&mut rng)), + ) + .unwrap(); + let cmu1 = note1.commitment(); + let mut tree = CommitmentTree::empty(); + tree.append(cmu1).unwrap(); + let witness1 = IncrementalWitness::from_tree(&tree); + + let tx_height = TEST_NETWORK + .activation_height(NetworkUpgrade::MASP) + .unwrap(); + let mut builder = Builder::new(TEST_NETWORK, tx_height); + + // Create a tx with a sapling spend. binding_sig should be present + builder + .add_sapling_spend(extsk, *to.diversifier(), note1, witness1.path().unwrap()) + .unwrap(); + + builder + .add_transparent_output(&transparent_address, zec(), 49000) + .unwrap(); + + // Expect a binding signature error, because our inputs aren't valid, but this shows + // that a binding signature was attempted + assert_eq!( + builder.mock_build(), + Err(Error::SaplingBuild(build_s::Error::BindingSig)) + ); + } + + #[test] + fn fails_on_negative_transparent_output() { + let secret_key = + secp256k1::SecretKey::from_slice(&[0xcd; 32]).expect("32 bytes, within curve order"); + let transparent_address = + secp256k1::PublicKey::from_secret_key(&secp256k1::Secp256k1::new(), &secret_key); + let tx_height = TEST_NETWORK + .activation_height(NetworkUpgrade::MASP) + .unwrap(); + let mut builder = Builder::new(TEST_NETWORK, tx_height); + assert_eq!( + builder.add_transparent_output(&transparent_address, zec(), -1,), + Err(build_t::Error::InvalidAmount) + ); + } + + #[test] + fn fails_on_negative_change() { + let mut rng = OsRng; + + let (_, transparent_address) = Secp256k1::new().generate_keypair(&mut OsRng); + // Just use the master key as the ExtendedSpendingKey for this test + let extsk = ExtendedSpendingKey::master(&[]); + let tx_height = TEST_NETWORK + .activation_height(NetworkUpgrade::MASP) + .unwrap(); + + // Fails with no inputs or outputs + // 0.0001 t-ZEC fee + { + let builder = Builder::new(TEST_NETWORK, tx_height); + assert_eq!( + builder.mock_build(), + Err(Error::InsufficientFunds(DEFAULT_FEE.clone())) + ); + } + + let dfvk = extsk.to_diversifiable_full_viewing_key(); + let ovk = Some(dfvk.fvk().ovk); + let to = dfvk.default_address().1; + + // Fail if there is only a Sapling output + // 0.0005 z-ZEC out, 0.00001 t-ZEC fee + { + let mut builder = Builder::new(TEST_NETWORK, tx_height); + builder + .add_sapling_output(ovk, to, zec(), 50000, MemoBytes::empty()) + .unwrap(); + assert_eq!( + builder.mock_build(), + Err(Error::InsufficientFunds( + Amount::from_pair(zec(), 50000).unwrap() + &*DEFAULT_FEE + )) + ); + } + + // Fail if there is only a transparent output + // 0.0005 t-ZEC out, 0.00001 t-ZEC fee + { + let mut builder = Builder::new(TEST_NETWORK, tx_height); + builder + .add_transparent_output(&transparent_address, zec(), 50000) + .unwrap(); + assert_eq!( + builder.mock_build(), + Err(Error::InsufficientFunds( + Amount::from_pair(zec(), 50000).unwrap() + &*DEFAULT_FEE + )) + ); + } + + let note1 = to + .create_note( + zec(), + 50999, + Rseed::BeforeZip212(jubjub::Fr::random(&mut rng)), + ) + .unwrap(); + let cmu1 = note1.commitment(); + let mut tree = CommitmentTree::empty(); + tree.append(cmu1).unwrap(); + let mut witness1 = IncrementalWitness::from_tree(&tree); + + // Fail if there is insufficient input + // 0.0003 z-ZEC out, 0.0002 t-ZEC out, 0.00001 t-ZEC fee, 0.00050999 z-ZEC in + { + let mut builder = Builder::new(TEST_NETWORK, tx_height); + builder + .add_sapling_spend( + extsk, + *to.diversifier(), + note1.clone(), + witness1.path().unwrap(), + ) + .unwrap(); + builder + .add_sapling_output(ovk, to, zec(), 30000, MemoBytes::empty()) + .unwrap(); + builder + .add_transparent_output(&transparent_address, zec(), 20000) + .unwrap(); + assert_eq!( + builder.mock_build(), + Err(Error::InsufficientFunds( + Amount::from_pair(zec(), 1).unwrap() + )) + ); + } + + let note2 = to + .create_note(zec(), 1, Rseed::BeforeZip212(jubjub::Fr::random(&mut rng))) + .unwrap(); + let cmu2 = note2.commitment(); + tree.append(cmu2).unwrap(); + witness1.append(cmu2).unwrap(); + let witness2 = IncrementalWitness::from_tree(&tree); + + // Succeeds if there is sufficient input + // 0.0003 z-ZEC out, 0.0002 t-ZEC out, 0.0001 t-ZEC fee, 0.0006 z-ZEC in + // + // (Still fails because we are using a MockTxProver which doesn't correctly + // compute bindingSig.) + { + let mut builder = Builder::new(TEST_NETWORK, tx_height); + builder + .add_sapling_spend(extsk, *to.diversifier(), note1, witness1.path().unwrap()) + .unwrap(); + builder + .add_sapling_spend(extsk, *to.diversifier(), note2, witness2.path().unwrap()) + .unwrap(); + builder + .add_sapling_output(ovk, to, zec(), 30000, MemoBytes::empty()) + .unwrap(); + builder + .add_transparent_output(&transparent_address, zec(), 20000) + .unwrap(); + assert_eq!( + builder.mock_build(), + Err(Error::SaplingBuild(build_s::Error::BindingSig)) + ) + } + } +} diff --git a/masp_primitives/src/transaction/components.rs b/masp_primitives/src/transaction/components.rs new file mode 100644 index 00000000..44fa4d89 --- /dev/null +++ b/masp_primitives/src/transaction/components.rs @@ -0,0 +1,13 @@ +//! Structs representing the components within Zcash transactions. + +pub mod amount; +pub mod sapling; +pub mod transparent; +pub use self::{ + amount::Amount, + sapling::{OutputDescription, SpendDescription}, + transparent::{TxIn, TxOut}, +}; + +// π_A + π_B + π_C +pub const GROTH_PROOF_SIZE: usize = 48 + 96 + 48; diff --git a/masp_primitives/src/transaction/components/amount.rs b/masp_primitives/src/transaction/components/amount.rs new file mode 100644 index 00000000..49da8501 --- /dev/null +++ b/masp_primitives/src/transaction/components/amount.rs @@ -0,0 +1,447 @@ +use crate::asset_type::AssetType; +use borsh::{BorshDeserialize, BorshSerialize}; +use std::cmp::Ordering; +use std::collections::btree_map::Keys; +use std::collections::btree_map::{IntoIter, Iter}; +use std::collections::BTreeMap; +use std::convert::TryInto; +use std::hash::Hash; +use std::io::{Read, Write}; +use std::iter::Sum; +use std::ops::{Add, AddAssign, Index, Mul, MulAssign, Neg, Sub, SubAssign}; +use zcash_encoding::Vector; + +pub const MAX_MONEY: i64 = i64::MAX; +lazy_static::lazy_static! { +pub static ref DEFAULT_FEE: Amount = Amount::from_pair(zec(), 1000).unwrap(); +} +/// A type-safe representation of some quantity of Zcash. +/// +/// An Amount can only be constructed from an integer that is within the valid monetary +/// range of `{-MAX_MONEY..MAX_MONEY}` (where `MAX_MONEY` = i64::MAX). +/// However, this range is not preserved as an invariant internally; it is possible to +/// add two valid Amounts together to obtain an invalid Amount. It is the user's +/// responsibility to handle the result of serializing potentially-invalid Amounts. In +/// particular, a `Transaction` containing serialized invalid Amounts will be rejected +/// by the network consensus rules. +/// +#[derive(Clone, Default, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Hash)] +pub struct Amount( + pub BTreeMap, +); + +impl memuse::DynamicUsage for Amount { + #[inline(always)] + fn dynamic_usage(&self) -> usize { + unimplemented!() + //self.0.dynamic_usage() + } + + #[inline(always)] + fn dynamic_usage_bounds(&self) -> (usize, Option) { + unimplemented!() + //self.0.dynamic_usage_bounds() + } +} + +impl Amount { + /// Returns a zero-valued Amount. + pub fn zero() -> Self { + Amount(BTreeMap::new()) + } + + /// Creates a non-negative Amount from an i64. + /// + /// Returns an error if the amount is outside the range `{0..MAX_MONEY}`. + pub fn from_nonnegative>(atype: Unit, amount: Amt) -> Result { + let amount = amount.try_into().map_err(|_| ())?; + if amount == 0 { + Ok(Self::zero()) + } else if 0 <= amount && amount <= MAX_MONEY { + let mut ret = BTreeMap::new(); + ret.insert(atype, amount); + Ok(Amount(ret)) + } else { + Err(()) + } + } + /// Creates an Amount from a type convertible to i64. + /// + /// Returns an error if the amount is outside the range `{-MAX_MONEY..MAX_MONEY}`. + pub fn from_pair>(atype: Unit, amount: Amt) -> Result { + let amount = amount.try_into().map_err(|_| ())?; + if amount == 0 { + Ok(Self::zero()) + } else if -MAX_MONEY <= amount && amount <= MAX_MONEY { + let mut ret = BTreeMap::new(); + ret.insert(atype, amount); + Ok(Amount(ret)) + } else { + Err(()) + } + } + + /// Returns an iterator over the amount's non-zero asset-types + pub fn asset_types(&self) -> Keys<'_, Unit, i64> { + self.0.keys() + } + + /// Returns an iterator over the amount's non-zero components + pub fn components(&self) -> Iter<'_, Unit, i64> { + self.0.iter() + } + + /// Returns an iterator over the amount's non-zero components + pub fn into_components(self) -> IntoIter { + self.0.into_iter() + } + + /// Filters out everything but the given AssetType from this Amount + pub fn project(&self, index: Unit) -> Self { + let val = self.0.get(&index).copied().unwrap_or(0); + Self::from_pair(index, val).unwrap() + } + + /// Filters out the given AssetType from this Amount + pub fn reject(&self, index: Unit) -> Self { + self.clone() - self.project(index) + } +} + +impl Amount { + /// Deserialize an Amount object from a list of amounts denominated by + /// different assets + pub fn read(reader: &mut R) -> std::io::Result { + let vec = Vector::read(reader, |reader| { + let mut atype = [0; 32]; + let mut value = [0; 8]; + reader.read_exact(&mut atype)?; + reader.read_exact(&mut value)?; + let atype = AssetType::from_identifier(&atype).ok_or_else(|| { + std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid asset type") + })?; + Ok((atype, i64::from_le_bytes(value))) + })?; + let mut ret = Self::zero(); + for (atype, amt) in vec { + ret += Self::from_pair(atype, amt).map_err(|_| { + std::io::Error::new(std::io::ErrorKind::InvalidData, "amount out of range") + })?; + } + Ok(ret) + } + + /// Serialize an Amount object into a list of amounts denominated by + /// distinct asset types + pub fn write(&self, writer: &mut W) -> std::io::Result<()> { + let vec: Vec<_> = self.components().collect(); + Vector::write(writer, vec.as_ref(), |writer, elt| { + writer.write_all(elt.0.get_identifier())?; + writer.write_all(elt.1.to_le_bytes().as_ref())?; + Ok(()) + }) + } +} + +impl From for Amount { + fn from(atype: Unit) -> Self { + let mut ret = BTreeMap::new(); + ret.insert(atype, 1); + Amount(ret) + } +} + +impl PartialOrd for Amount { + /// One Amount is more than or equal to another if each corresponding + /// coordinate is more than the other's. + fn partial_cmp(&self, other: &Self) -> Option { + let mut diff = other.clone(); + for (atype, amount) in self.components() { + let ent = diff[atype] - amount; + if ent == 0 { + diff.0.remove(atype); + } else { + diff.0.insert(atype.clone(), ent); + } + } + if diff.0.values().all(|x| *x == 0) { + Some(Ordering::Equal) + } else if diff.0.values().all(|x| *x >= 0) { + Some(Ordering::Less) + } else if diff.0.values().all(|x| *x <= 0) { + Some(Ordering::Greater) + } else { + None + } + } +} + +impl Index<&Unit> for Amount { + type Output = i64; + /// Query how much of the given asset this amount contains + fn index(&self, index: &Unit) -> &Self::Output { + self.0.get(index).unwrap_or(&0) + } +} + +impl MulAssign for Amount { + fn mul_assign(&mut self, rhs: i64) { + for (_atype, amount) in self.0.iter_mut() { + let ent = *amount * rhs; + if -MAX_MONEY <= ent && ent <= MAX_MONEY { + *amount = ent; + } else { + panic!("multiplication should remain in range"); + } + } + } +} + +impl Mul for Amount { + type Output = Self; + + fn mul(mut self, rhs: i64) -> Self { + self *= rhs; + self + } +} + +impl AddAssign<&Amount> + for Amount +{ + fn add_assign(&mut self, rhs: &Self) { + for (atype, amount) in rhs.components() { + let ent = self[atype] + amount; + if ent == 0 { + self.0.remove(atype); + } else if -MAX_MONEY <= ent && ent <= MAX_MONEY { + self.0.insert(atype.clone(), ent); + } else { + panic!("addition should remain in range"); + } + } + } +} + +impl AddAssign> + for Amount +{ + fn add_assign(&mut self, rhs: Self) { + *self += &rhs + } +} + +impl Add<&Amount> + for Amount +{ + type Output = Self; + + fn add(mut self, rhs: &Self) -> Self { + self += rhs; + self + } +} + +impl Add> + for Amount +{ + type Output = Self; + + fn add(mut self, rhs: Self) -> Self { + self += &rhs; + self + } +} + +impl SubAssign<&Amount> + for Amount +{ + fn sub_assign(&mut self, rhs: &Self) { + for (atype, amount) in rhs.components() { + let ent = self[atype] - amount; + if ent == 0 { + self.0.remove(atype); + } else if -MAX_MONEY <= ent && ent <= MAX_MONEY { + self.0.insert(atype.clone(), ent); + } else { + panic!("subtraction should remain in range"); + } + } + } +} + +impl SubAssign> + for Amount +{ + fn sub_assign(&mut self, rhs: Self) { + *self -= &rhs + } +} + +impl Neg for Amount { + type Output = Self; + + fn neg(mut self) -> Self { + for (_, amount) in self.0.iter_mut() { + *amount = -*amount; + } + self + } +} + +impl Sub<&Amount> + for Amount +{ + type Output = Self; + + fn sub(mut self, rhs: &Self) -> Self { + self -= rhs; + self + } +} + +impl Sub> + for Amount +{ + type Output = Self; + + fn sub(mut self, rhs: Self) -> Self { + self -= &rhs; + self + } +} + +impl Sum for Amount { + fn sum>(iter: I) -> Self { + iter.fold(Self::zero(), Add::add) + } +} + +/// A type for balance violations in amount addition and subtraction +/// (overflow and underflow of allowed ranges) +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum BalanceError { + Overflow, + Underflow, +} + +impl std::fmt::Display for BalanceError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match &self { + BalanceError::Overflow => { + write!( + f, + "Amount addition resulted in a value outside the valid range." + ) + } + BalanceError::Underflow => write!( + f, + "Amount subtraction resulted in a value outside the valid range." + ), + } + } +} + +pub fn zec() -> AssetType { + AssetType::new(b"ZEC").unwrap() +} + +pub fn default_fee() -> Amount { + Amount::from_pair(zec(), 10000).unwrap() +} + +#[cfg(any(test, feature = "test-dependencies"))] +pub mod testing { + use proptest::prelude::prop_compose; + + use super::{Amount, MAX_MONEY}; + use crate::asset_type::testing::arb_asset_type; + + prop_compose! { + pub fn arb_amount()(asset_type in arb_asset_type(), amt in -MAX_MONEY..MAX_MONEY) -> Amount { + Amount::from_pair(asset_type, amt).unwrap() + } + } + + prop_compose! { + pub fn arb_nonnegative_amount()(asset_type in arb_asset_type(), amt in 0i64..MAX_MONEY) -> Amount { + Amount::from_pair(asset_type, amt).unwrap() + } + } + + prop_compose! { + pub fn arb_positive_amount()(asset_type in arb_asset_type(), amt in 1i64..MAX_MONEY) -> Amount { + Amount::from_pair(asset_type, amt).unwrap() + } + } +} + +#[cfg(test)] +mod tests { + use super::{zec, Amount, MAX_MONEY}; + + #[test] + fn amount_in_range() { + let zero = b"\x01\x94\xf3O\xfdd\xef\n\xc3i\x08\xfd\xdf\xec\x05hX\x06)\xc4Vq\x0f\xa1\x86\x83\x12\xa8\x7f\xbf\n\xa5\t\x00\x00\x00\x00\x00\x00\x00\x00"; + assert_eq!(Amount::read(&mut zero.as_ref()).unwrap(), Amount::zero()); + + let neg_one = b"\x01\x94\xf3O\xfdd\xef\n\xc3i\x08\xfd\xdf\xec\x05hX\x06)\xc4Vq\x0f\xa1\x86\x83\x12\xa8\x7f\xbf\n\xa5\t\xff\xff\xff\xff\xff\xff\xff\xff"; + assert_eq!( + Amount::read(&mut neg_one.as_ref()).unwrap(), + Amount::from_pair(zec(), -1).unwrap() + ); + + let max_money = b"\x01\x94\xf3O\xfdd\xef\n\xc3i\x08\xfd\xdf\xec\x05hX\x06)\xc4Vq\x0f\xa1\x86\x83\x12\xa8\x7f\xbf\n\xa5\t\xff\xff\xff\xff\xff\xff\xff\x7f"; + + assert_eq!( + Amount::read(&mut max_money.as_ref()).unwrap(), + Amount::from_pair(zec(), MAX_MONEY).unwrap() + ); + + //let max_money_p1 = b"\x01\x94\xf3O\xfdd\xef\n\xc3i\x08\xfd\xdf\xec\x05hX\x06)\xc4Vq\x0f\xa1\x86\x83\x12\xa8\x7f\xbf\n\xa5\t\x01\x40\x07\x5a\xf0\x75\x07\x00"; + //assert!(Amount::read(&mut max_money_p1.as_ref()).is_err()); + + //let mut neg_max_money = [0u8; 41]; + //let mut amount = Amount::from_pair(zec(), -MAX_MONEY).unwrap(); + //*amount.0.get_mut(&zec()).unwrap() = i64::MIN; + //amount.write(&mut neg_max_money.as_mut()); + //dbg!(std::str::from_utf8(&neg_max_money.as_ref().iter().map(|b| std::ascii::escape_default(*b)).flatten().collect::>()).unwrap()); + + let neg_max_money = b"\x01\x94\xf3O\xfdd\xef\n\xc3i\x08\xfd\xdf\xec\x05hX\x06)\xc4Vq\x0f\xa1\x86\x83\x12\xa8\x7f\xbf\n\xa5\t\x01\x00\x00\x00\x00\x00\x00\x80"; + assert_eq!( + Amount::read(&mut neg_max_money.as_ref()).unwrap(), + Amount::from_pair(zec(), -MAX_MONEY).unwrap() + ); + + let neg_max_money_m1 = b"\x01\x94\xf3O\xfdd\xef\n\xc3i\x08\xfd\xdf\xec\x05hX\x06)\xc4Vq\x0f\xa1\x86\x83\x12\xa8\x7f\xbf\n\xa5\t\x00\x00\x00\x00\x00\x00\x00\x80"; + assert!(Amount::read(&mut neg_max_money_m1.as_ref()).is_err()); + } + + #[test] + #[should_panic] + fn add_panics_on_overflow() { + let v = Amount::from_pair(zec(), MAX_MONEY).unwrap(); + let _sum = v + Amount::from_pair(zec(), 1).unwrap(); + } + + #[test] + #[should_panic] + fn add_assign_panics_on_overflow() { + let mut a = Amount::from_pair(zec(), MAX_MONEY).unwrap(); + a += Amount::from_pair(zec(), 1).unwrap(); + } + + #[test] + #[should_panic] + fn sub_panics_on_underflow() { + let v = Amount::from_pair(zec(), -MAX_MONEY).unwrap(); + let _diff = v - Amount::from_pair(zec(), 1).unwrap(); + } + + #[test] + #[should_panic] + fn sub_assign_panics_on_underflow() { + let mut a = Amount::from_pair(zec(), -MAX_MONEY).unwrap(); + a -= Amount::from_pair(zec(), 1).unwrap(); + } +} diff --git a/masp_primitives/src/transaction/components/sapling.rs b/masp_primitives/src/transaction/components/sapling.rs new file mode 100644 index 00000000..2048abd9 --- /dev/null +++ b/masp_primitives/src/transaction/components/sapling.rs @@ -0,0 +1,664 @@ +use core::fmt::Debug; + +use ff::PrimeField; +use group::GroupEncoding; +use memuse::DynamicUsage; + +use std::convert::TryInto; +use std::hash::{Hash, Hasher}; +use std::io::{self, Read, Write}; + +use masp_note_encryption::{ + EphemeralKeyBytes, ShieldedOutput, COMPACT_NOTE_SIZE, ENC_CIPHERTEXT_SIZE, +}; + +use borsh::{BorshDeserialize, BorshSerialize}; + +use crate::{ + consensus, + sapling::{ + note_encryption::SaplingDomain, + redjubjub::{self, PublicKey, Signature}, + Nullifier, + }, +}; + +use super::{amount::Amount, GROTH_PROOF_SIZE}; + +pub type GrothProofBytes = [u8; GROTH_PROOF_SIZE]; + +pub mod builder; +pub mod fees; + +pub trait Authorization: Debug { + type Proof: Clone + Debug + PartialEq + Hash; + type AuthSig: Clone + Debug + PartialEq; +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct Unproven; + +impl Authorization for Unproven { + type Proof = (); + type AuthSig = (); +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +pub struct Authorized { + pub binding_sig: redjubjub::Signature, +} + +impl Authorization for Authorized { + type Proof = GrothProofBytes; + type AuthSig = redjubjub::Signature; +} + +pub trait MapAuth { + fn map_proof(&self, p: A::Proof) -> B::Proof; + fn map_auth_sig(&self, s: A::AuthSig) -> B::AuthSig; + fn map_authorization(&self, a: A) -> B; +} + +/// The identity map. +/// +/// This can be used with [`TransactionData::map_authorization`] when you want to map the +/// authorization of a subset of the transaction's bundles. +/// +/// [`TransactionData::map_authorization`]: crate::transaction::TransactionData::map_authorization +impl MapAuth for () { + fn map_proof( + &self, + p: ::Proof, + ) -> ::Proof { + p + } + + fn map_auth_sig( + &self, + s: ::AuthSig, + ) -> ::AuthSig { + s + } + + fn map_authorization(&self, a: Authorized) -> Authorized { + a + } +} + +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq)] +pub struct Bundle { + pub shielded_spends: Vec>, + pub shielded_converts: Vec>, + pub shielded_outputs: Vec>, + pub value_balance: Amount, + pub authorization: A, +} + +impl Bundle { + pub fn map_authorization< + B: Authorization + PartialEq + BorshSerialize + BorshDeserialize, + F: MapAuth, + >( + self, + f: F, + ) -> Bundle { + Bundle { + shielded_spends: self + .shielded_spends + .into_iter() + .map(|d| SpendDescription { + cv: d.cv, + anchor: d.anchor, + nullifier: d.nullifier, + rk: d.rk, + zkproof: f.map_proof(d.zkproof), + spend_auth_sig: f.map_auth_sig(d.spend_auth_sig), + }) + .collect(), + shielded_converts: self + .shielded_converts + .into_iter() + .map(|c| ConvertDescription { + cv: c.cv, + anchor: c.anchor, + zkproof: f.map_proof(c.zkproof), + }) + .collect(), + shielded_outputs: self + .shielded_outputs + .into_iter() + .map(|o| OutputDescription { + cv: o.cv, + cmu: o.cmu, + ephemeral_key: o.ephemeral_key, + enc_ciphertext: o.enc_ciphertext, + out_ciphertext: o.out_ciphertext, + zkproof: f.map_proof(o.zkproof), + }) + .collect(), + value_balance: self.value_balance, + authorization: f.map_authorization(self.authorization), + } + } +} + +#[derive(Clone, PartialEq, Eq)] +pub struct SpendDescription { + pub cv: jubjub::ExtendedPoint, + pub anchor: bls12_381::Scalar, + pub nullifier: Nullifier, + pub rk: PublicKey, + pub zkproof: A::Proof, + pub spend_auth_sig: A::AuthSig, +} + +impl std::fmt::Debug for SpendDescription { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!( + f, + "SpendDescription(cv = {:?}, anchor = {:?}, nullifier = {:?}, rk = {:?}, spend_auth_sig = {:?})", + self.cv, self.anchor, self.nullifier, self.rk, self.spend_auth_sig + ) + } +} + +/// Consensus rules (§4.4) & (§4.5): +/// - Canonical encoding is enforced here. +/// - "Not small order" is enforced in SaplingVerificationContext::(check_spend()/check_output()) +/// (located in zcash_proofs::sapling::verifier). +pub fn read_point(mut reader: R, field: &str) -> io::Result { + let mut bytes = [0u8; 32]; + reader.read_exact(&mut bytes)?; + let point = jubjub::ExtendedPoint::from_bytes(&bytes); + + if point.is_none().into() { + Err(io::Error::new( + io::ErrorKind::InvalidInput, + format!("invalid {}", field), + )) + } else { + Ok(point.unwrap()) + } +} + +/// Consensus rules (§7.3) & (§7.4): +/// - Canonical encoding is enforced here +pub fn read_base(mut reader: R, field: &str) -> io::Result { + let mut f = [0u8; 32]; + reader.read_exact(&mut f)?; + Option::from(bls12_381::Scalar::from_repr(f)).ok_or_else(|| { + io::Error::new( + io::ErrorKind::InvalidInput, + format!("{} not in field", field), + ) + }) +} + +/// Consensus rules (§4.4) & (§4.5): +/// - Canonical encoding is enforced by the API of SaplingVerificationContext::check_spend() +/// and SaplingVerificationContext::check_output() due to the need to parse this into a +/// bellman::groth16::Proof. +/// - Proof validity is enforced in SaplingVerificationContext::check_spend() +/// and SaplingVerificationContext::check_output() +pub fn read_zkproof(mut reader: R) -> io::Result { + let mut zkproof = [0u8; GROTH_PROOF_SIZE]; + reader.read_exact(&mut zkproof)?; + Ok(zkproof) +} + +impl SpendDescription { + pub fn read_nullifier(mut reader: R) -> io::Result { + let mut nullifier = Nullifier([0u8; 32]); + reader.read_exact(&mut nullifier.0)?; + Ok(nullifier) + } + + /// Consensus rules (§4.4): + /// - Canonical encoding is enforced here. + /// - "Not small order" is enforced in SaplingVerificationContext::check_spend() + pub fn read_rk(mut reader: R) -> io::Result { + PublicKey::read(&mut reader) + } + + /// Consensus rules (§4.4): + /// - Canonical encoding is enforced here. + /// - Signature validity is enforced in SaplingVerificationContext::check_spend() + pub fn read_spend_auth_sig(mut reader: R) -> io::Result { + Signature::read(&mut reader) + } + + pub fn write_v5_without_witness_data(&self, mut writer: W) -> io::Result<()> { + writer.write_all(&self.cv.to_bytes())?; + writer.write_all(&self.nullifier.0)?; + self.rk.write(&mut writer) + } +} + +#[derive(Clone)] +pub struct SpendDescriptionV5 { + pub cv: jubjub::ExtendedPoint, + pub nullifier: Nullifier, + pub rk: PublicKey, +} + +impl SpendDescriptionV5 { + pub fn read(mut reader: &mut R) -> io::Result { + let cv = read_point(&mut reader, "cv")?; + let nullifier = SpendDescription::read_nullifier(&mut reader)?; + let rk = SpendDescription::read_rk(&mut reader)?; + + Ok(SpendDescriptionV5 { cv, nullifier, rk }) + } + + pub fn into_spend_description( + self, + anchor: bls12_381::Scalar, + zkproof: GrothProofBytes, + spend_auth_sig: Signature, + ) -> SpendDescription { + SpendDescription { + cv: self.cv, + anchor, + nullifier: self.nullifier, + rk: self.rk, + zkproof, + spend_auth_sig, + } + } +} + +#[derive(Clone, PartialEq, Eq)] +pub struct OutputDescription { + pub cv: jubjub::ExtendedPoint, + pub cmu: bls12_381::Scalar, + pub ephemeral_key: EphemeralKeyBytes, + pub enc_ciphertext: [u8; 580 + 32], + pub out_ciphertext: [u8; 80], + pub zkproof: Proof, +} + +impl DynamicUsage for OutputDescription { + fn dynamic_usage(&self) -> usize { + self.zkproof.dynamic_usage() + } + + fn dynamic_usage_bounds(&self) -> (usize, Option) { + self.zkproof.dynamic_usage_bounds() + } +} + +impl ShieldedOutput, ENC_CIPHERTEXT_SIZE> + for OutputDescription +{ + fn ephemeral_key(&self) -> EphemeralKeyBytes { + self.ephemeral_key.clone() + } + + fn cmstar_bytes(&self) -> [u8; 32] { + self.cmu.to_repr() + } + + fn enc_ciphertext(&self) -> &[u8; ENC_CIPHERTEXT_SIZE] { + &self.enc_ciphertext + } +} + +impl std::fmt::Debug for OutputDescription +where + Proof: Clone + PartialEq, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!( + f, + "OutputDescription(cv = {:?}, cmu = {:?}, ephemeral_key = {:?})", + self.cv, self.cmu, self.ephemeral_key + ) + } +} + +impl OutputDescription { + pub fn write_v5_without_proof(&self, mut writer: W) -> io::Result<()> { + writer.write_all(&self.cv.to_bytes())?; + writer.write_all(self.cmu.to_repr().as_ref())?; + writer.write_all(self.ephemeral_key.as_ref())?; + writer.write_all(&self.enc_ciphertext)?; + writer.write_all(&self.out_ciphertext) + } +} + +#[derive(Clone)] +pub struct OutputDescriptionV5 { + pub cv: jubjub::ExtendedPoint, + pub cmu: bls12_381::Scalar, + pub ephemeral_key: EphemeralKeyBytes, + pub enc_ciphertext: [u8; 580 + 32], + pub out_ciphertext: [u8; 80], +} + +memuse::impl_no_dynamic_usage!(OutputDescriptionV5); + +impl OutputDescriptionV5 { + pub fn read(mut reader: &mut R) -> io::Result { + let cv = read_point(&mut reader, "cv")?; + let cmu = read_base(&mut reader, "cmu")?; + + // Consensus rules (§4.5): + // - Canonical encoding is enforced in librustzcash_sapling_check_output by zcashd + // - "Not small order" is enforced in SaplingVerificationContext::check_output() + let mut ephemeral_key = EphemeralKeyBytes([0u8; 32]); + reader.read_exact(&mut ephemeral_key.0)?; + + let mut enc_ciphertext = [0u8; 580 + 32]; + let mut out_ciphertext = [0u8; 80]; + reader.read_exact(&mut enc_ciphertext)?; + reader.read_exact(&mut out_ciphertext)?; + + Ok(OutputDescriptionV5 { + cv, + cmu, + ephemeral_key, + enc_ciphertext, + out_ciphertext, + }) + } + + pub fn into_output_description( + self, + zkproof: GrothProofBytes, + ) -> OutputDescription { + OutputDescription { + cv: self.cv, + cmu: self.cmu, + ephemeral_key: self.ephemeral_key, + enc_ciphertext: self.enc_ciphertext, + out_ciphertext: self.out_ciphertext, + zkproof, + } + } +} + +#[derive(Clone)] +pub struct CompactOutputDescription { + pub ephemeral_key: EphemeralKeyBytes, + pub cmu: bls12_381::Scalar, + pub enc_ciphertext: [u8; COMPACT_NOTE_SIZE], +} + +memuse::impl_no_dynamic_usage!(CompactOutputDescription); + +impl From> for CompactOutputDescription { + fn from(out: OutputDescription) -> CompactOutputDescription { + CompactOutputDescription { + ephemeral_key: out.ephemeral_key, + cmu: out.cmu, + enc_ciphertext: out.enc_ciphertext[..COMPACT_NOTE_SIZE].try_into().unwrap(), + } + } +} + +impl ShieldedOutput, COMPACT_NOTE_SIZE> + for CompactOutputDescription +{ + fn ephemeral_key(&self) -> EphemeralKeyBytes { + self.ephemeral_key.clone() + } + + fn cmstar_bytes(&self) -> [u8; 32] { + self.cmu.to_repr() + } + + fn enc_ciphertext(&self) -> &[u8; COMPACT_NOTE_SIZE] { + &self.enc_ciphertext + } +} +impl PartialOrd for OutputDescription { + fn partial_cmp(&self, other: &Self) -> Option { + ( + self.cv.to_bytes(), + self.cmu.to_bytes(), + self.ephemeral_key.clone(), + self.enc_ciphertext, + self.out_ciphertext, + self.zkproof.clone(), + ) + .partial_cmp(&( + other.cv.to_bytes(), + other.cmu.to_bytes(), + other.ephemeral_key.clone(), + other.enc_ciphertext, + other.out_ciphertext, + other.zkproof.clone(), + )) + } +} +impl Hash for OutputDescription { + fn hash(&self, state: &mut H) + where + H: Hasher, + { + self.cv.to_bytes().hash(state); + self.cmu.to_bytes().hash(state); + self.ephemeral_key.hash(state); + self.enc_ciphertext.hash(state); + self.out_ciphertext.hash(state); + self.zkproof.hash(state); + } +} +#[derive(Clone, PartialEq, Eq)] +pub struct ConvertDescription { + pub cv: jubjub::ExtendedPoint, + pub anchor: bls12_381::Scalar, + pub zkproof: Proof, +} + +impl PartialOrd for ConvertDescription { + fn partial_cmp(&self, other: &Self) -> Option { + ( + self.cv.to_bytes(), + self.anchor.to_bytes(), + self.zkproof.clone(), + ) + .partial_cmp(&( + other.cv.to_bytes(), + other.anchor.to_bytes(), + other.zkproof.clone(), + )) + } +} + +impl Hash for ConvertDescription { + fn hash(&self, state: &mut H) + where + H: Hasher, + { + self.cv.to_bytes().hash(state); + self.anchor.to_bytes().hash(state); + self.zkproof.hash(state); + } +} +impl std::fmt::Debug for ConvertDescription { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!( + f, + "ConvertDescription(cv = {:?}, anchor = {:?})", + self.cv, self.anchor + ) + } +} + +impl ConvertDescription { + pub fn write_v5_without_witness_data(&self, mut writer: W) -> io::Result<()> { + writer.write_all(&self.cv.to_bytes()) + } +} + +#[derive(Clone, PartialEq, Eq)] +pub struct ConvertDescriptionV5 { + pub cv: jubjub::ExtendedPoint, +} + +impl ConvertDescriptionV5 { + pub fn read(mut reader: &mut R) -> io::Result { + // Consensus rules (§4.4) & (§4.5): + // - Canonical encoding is enforced here. + // - "Not small order" is enforced in SaplingVerificationContext::(check_spend()/check_output()) + // (located in zcash_proofs::sapling::verifier). + let cv = read_point(&mut reader, "cv")?; + + Ok(ConvertDescriptionV5 { cv }) + } + pub fn into_convert_description( + self, + anchor: bls12_381::Scalar, + zkproof: GrothProofBytes, + ) -> ConvertDescription { + ConvertDescription { + cv: self.cv, + anchor, + zkproof, + } + } +} + +#[cfg(any(test, feature = "test-dependencies"))] +pub mod testing { + use ff::Field; + use group::{Group, GroupEncoding}; + use proptest::collection::vec; + use proptest::prelude::*; + use rand::{rngs::StdRng, SeedableRng}; + + use crate::{ + constants::{SPENDING_KEY_GENERATOR, VALUE_COMMITMENT_RANDOMNESS_GENERATOR}, + sapling::{ + redjubjub::{PrivateKey, PublicKey}, + Nullifier, + }, + transaction::{ + components::{amount::testing::arb_amount, GROTH_PROOF_SIZE}, + TxVersion, + }, + }; + + use super::{ + Authorized, Bundle, ConvertDescription, GrothProofBytes, OutputDescription, + SpendDescription, + }; + + prop_compose! { + fn arb_extended_point()(rng_seed in prop::array::uniform32(any::())) -> jubjub::ExtendedPoint { + let mut rng = StdRng::from_seed(rng_seed); + let scalar = jubjub::Scalar::random(&mut rng); + jubjub::ExtendedPoint::generator() * scalar + } + } + + prop_compose! { + /// produce a spend description with invalid data (useful only for serialization + /// roundtrip testing). + pub fn arb_spend_description()( + cv in arb_extended_point(), + anchor in vec(any::(), 64) + .prop_map(|v| <[u8;64]>::try_from(v.as_slice()).unwrap()) + .prop_map(|v| bls12_381::Scalar::from_bytes_wide(&v)), + nullifier in prop::array::uniform32(any::()) + .prop_map(|v| Nullifier::from_slice(&v).unwrap()), + zkproof in vec(any::(), GROTH_PROOF_SIZE) + .prop_map(|v| <[u8;GROTH_PROOF_SIZE]>::try_from(v.as_slice()).unwrap()), + rng_seed in prop::array::uniform32(prop::num::u8::ANY), + fake_sighash_bytes in prop::array::uniform32(prop::num::u8::ANY), + ) -> SpendDescription { + let mut rng = StdRng::from_seed(rng_seed); + let sk1 = PrivateKey(jubjub::Fr::random(&mut rng)); + let rk = PublicKey::from_private(&sk1, SPENDING_KEY_GENERATOR); + SpendDescription { + cv, + anchor, + nullifier, + rk, + zkproof, + spend_auth_sig: sk1.sign(&fake_sighash_bytes, &mut rng, SPENDING_KEY_GENERATOR), + } + } + } + + prop_compose! { + /// produce an output description with invalid data (useful only for serialization + /// roundtrip testing). + pub fn arb_output_description()( + cv in arb_extended_point(), + cmu in vec(any::(), 64) + .prop_map(|v| <[u8;64]>::try_from(v.as_slice()).unwrap()) + .prop_map(|v| bls12_381::Scalar::from_bytes_wide(&v)), + enc_ciphertext in vec(any::(), 580+32) + .prop_map(|v| <[u8;580+32]>::try_from(v.as_slice()).unwrap()), + epk in arb_extended_point(), + out_ciphertext in vec(any::(), 80) + .prop_map(|v| <[u8;80]>::try_from(v.as_slice()).unwrap()), + zkproof in vec(any::(), GROTH_PROOF_SIZE) + .prop_map(|v| <[u8;GROTH_PROOF_SIZE]>::try_from(v.as_slice()).unwrap()), + ) -> OutputDescription { + OutputDescription { + cv, + cmu, + ephemeral_key: epk.to_bytes().into(), + enc_ciphertext, + out_ciphertext, + zkproof, + } + } + } + + prop_compose! { + pub fn arb_bundle()( + shielded_spends in vec(arb_spend_description(), 0..30), + shielded_converts in vec(arb_convert_description(), 0..30), + shielded_outputs in vec(arb_output_description(), 0..30), + value_balance in arb_amount(), + rng_seed in prop::array::uniform32(prop::num::u8::ANY), + fake_bvk_bytes in prop::array::uniform32(prop::num::u8::ANY), + ) -> Option> { + if shielded_spends.is_empty() && shielded_outputs.is_empty() { + None + } else { + let mut rng = StdRng::from_seed(rng_seed); + let bsk = PrivateKey(jubjub::Fr::random(&mut rng)); + + Some( + Bundle { + shielded_spends, + shielded_converts, + shielded_outputs, + value_balance, + authorization: Authorized { binding_sig: bsk.sign(&fake_bvk_bytes, &mut rng, VALUE_COMMITMENT_RANDOMNESS_GENERATOR) }, + } + ) + } + } + } + + pub fn arb_bundle_for_version( + _v: TxVersion, + ) -> impl Strategy>> { + Strategy::boxed(arb_bundle()) + } + + prop_compose! { + /// produce a spend description with invalid data (useful only for serialization + /// roundtrip testing). + pub fn arb_convert_description()( + cv in arb_extended_point(), + anchor in vec(any::(), 64) + .prop_map(|v| <[u8;64]>::try_from(v.as_slice()).unwrap()) + .prop_map(|v| bls12_381::Scalar::from_bytes_wide(&v)), + zkproof in vec(any::(), GROTH_PROOF_SIZE) + .prop_map(|v| <[u8;GROTH_PROOF_SIZE]>::try_from(v.as_slice()).unwrap()), + ) -> ConvertDescription { + ConvertDescription { + cv, + anchor, + zkproof, + } + } + } +} diff --git a/masp_primitives/src/transaction/components/sapling/builder.rs b/masp_primitives/src/transaction/components/sapling/builder.rs new file mode 100644 index 00000000..3e5ced86 --- /dev/null +++ b/masp_primitives/src/transaction/components/sapling/builder.rs @@ -0,0 +1,782 @@ +//! Types and functions for building MASP shielded transaction components. + +use core::fmt; +use std::sync::mpsc::Sender; + +use ff::Field; +use group::GroupEncoding; +use rand::{seq::SliceRandom, RngCore}; + +use crate::{ + asset_type::AssetType, + consensus::{self, BlockHeight}, + convert::AllowedConversion, + keys::OutgoingViewingKey, + memo::MemoBytes, + merkle_tree::MerklePath, + sapling::{ + note_encryption::sapling_note_encryption, + prover::TxProver, + redjubjub::{PrivateKey, Signature}, + spend_sig_internal, + util::generate_random_rseed_internal, + Diversifier, Node, Note, PaymentAddress, + }, + transaction::{ + builder::Progress, + components::{ + amount::{Amount, MAX_MONEY}, + sapling::{ + fees, Authorization, Authorized, Bundle, ConvertDescription, GrothProofBytes, + OutputDescription, SpendDescription, + }, + }, + }, + zip32::ExtendedSpendingKey, +}; +use borsh::{BorshDeserialize, BorshSerialize}; + +/// If there are any shielded inputs, always have at least two shielded outputs, padding +/// with dummy outputs if necessary. See . +const MIN_SHIELDED_OUTPUTS: usize = 2; + +#[derive(Debug, PartialEq, Eq)] +pub enum Error { + AnchorMismatch, + BindingSig, + InvalidAddress, + InvalidAmount, + SpendProof, + ConvertProof, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::AnchorMismatch => { + write!(f, "Anchor mismatch (anchors for all spends must be equal)") + } + Error::BindingSig => write!(f, "Failed to create bindingSig"), + Error::InvalidAddress => write!(f, "Invalid address"), + Error::InvalidAmount => write!(f, "Invalid amount"), + Error::SpendProof => write!(f, "Failed to create MASP spend proof"), + Error::ConvertProof => write!(f, "Failed to create MASP convert proof"), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct SpendDescriptionInfo { + extsk: ExtendedSpendingKey, + diversifier: Diversifier, + note: Note, + alpha: jubjub::Fr, + merkle_path: MerklePath, +} + +impl fees::InputView<()> for SpendDescriptionInfo { + fn note_id(&self) -> &() { + // The builder does not make use of note identifiers, so we can just return the unit value. + &() + } + + fn value(&self) -> Amount { + // An existing note to be spent must have a valid amount value. + Amount::from_pair(self.note.asset_type, self.note.value) + .expect("Existing note value with invalid asset type or value.") + } +} + +/// A struct containing the information required in order to construct a +/// MASP output to a transaction. +#[derive(Clone)] +pub struct SaplingOutputInfo { + /// `None` represents the `ovk = ⊥` case. + ovk: Option, + to: PaymentAddress, + note: Note, + memo: MemoBytes, +} + +impl SaplingOutputInfo { + #[allow(clippy::too_many_arguments)] + fn new_internal( + params: &P, + rng: &mut R, + target_height: BlockHeight, + ovk: Option, + to: PaymentAddress, + asset_type: AssetType, + value: u64, + memo: MemoBytes, + ) -> Result { + let g_d = to.g_d().ok_or(Error::InvalidAddress)?; + if value > MAX_MONEY.try_into().unwrap() { + return Err(Error::InvalidAmount); + } + + let rseed = generate_random_rseed_internal(params, target_height, rng); + + let note = Note { + g_d, + pk_d: *to.pk_d(), + value, + rseed, + asset_type, + }; + + Ok(SaplingOutputInfo { + ovk, + to, + note, + memo, + }) + } + + fn build( + self, + prover: &Pr, + ctx: &mut Pr::SaplingProvingContext, + rng: &mut R, + ) -> OutputDescription { + let encryptor = + sapling_note_encryption::

(self.ovk, self.note.clone(), self.to, self.memo); + + let (zkproof, cv) = prover.output_proof( + ctx, + *encryptor.esk(), + self.to, + self.note.rcm(), + self.note.asset_type, + self.note.value, + ); + + let cmu = self.note.cmu(); + + let enc_ciphertext = encryptor.encrypt_note_plaintext(); + let out_ciphertext = encryptor.encrypt_outgoing_plaintext(&cv, &cmu, rng); + + let epk = *encryptor.epk(); + + OutputDescription { + cv, + cmu, + ephemeral_key: epk.to_bytes().into(), + enc_ciphertext, + out_ciphertext, + zkproof, + } + } +} + +impl fees::OutputView for SaplingOutputInfo { + fn value(&self) -> Amount { + Amount::from_pair(self.note.asset_type, self.note.value) + .expect("Note values should be checked at construction.") + } +} + +/// Metadata about a transaction created by a [`SaplingBuilder`]. +#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +pub struct SaplingMetadata { + spend_indices: Vec, + convert_indices: Vec, + output_indices: Vec, +} + +impl SaplingMetadata { + pub fn empty() -> Self { + SaplingMetadata { + spend_indices: vec![], + convert_indices: vec![], + output_indices: vec![], + } + } + + /// Returns the index within the transaction of the [`SpendDescription`] corresponding + /// to the `n`-th call to [`SaplingBuilder::add_spend`]. + /// + /// Note positions are randomized when building transactions for indistinguishability. + /// This means that the transaction consumer cannot assume that e.g. the first spend + /// they added (via the first call to [`SaplingBuilder::add_spend`]) is the first + /// [`SpendDescription`] in the transaction. + pub fn spend_index(&self, n: usize) -> Option { + self.spend_indices.get(n).copied() + } + + /// Returns the index within the transaction of the [`OutputDescription`] corresponding + /// to the `n`-th call to [`SaplingBuilder::add_output`]. + /// + /// Note positions are randomized when building transactions for indistinguishability. + /// This means that the transaction consumer cannot assume that e.g. the first output + /// they added (via the first call to [`SaplingBuilder::add_output`]) is the first + /// [`OutputDescription`] in the transaction. + pub fn output_index(&self, n: usize) -> Option { + self.output_indices.get(n).copied() + } + /// Returns the index within the transaction of the [`ConvertDescription`] corresponding + /// to the `n`-th call to [`SaplingBuilder::add_convert`]. + /// + /// Note positions are randomized when building transactions for indistinguishability. + /// This means that the transaction consumer cannot assume that e.g. the first output + /// they added (via the first call to [`SaplingBuilder::add_output`]) is the first + /// [`ConvertDescription`] in the transaction. + pub fn convert_index(&self, n: usize) -> Option { + self.convert_indices.get(n).copied() + } +} + +pub struct SaplingBuilder

{ + params: P, + spend_anchor: Option, + target_height: BlockHeight, + value_balance: Amount, + convert_anchor: Option, + spends: Vec, + converts: Vec, + outputs: Vec, +} + +#[derive(Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +pub struct Unauthorized { + tx_metadata: SaplingMetadata, +} + +impl std::fmt::Debug for Unauthorized { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!(f, "Unauthorized") + } +} + +impl Authorization for Unauthorized { + type Proof = GrothProofBytes; + type AuthSig = SpendDescriptionInfo; +} + +impl

SaplingBuilder

{ + pub fn new(params: P, target_height: BlockHeight) -> Self { + SaplingBuilder { + params, + spend_anchor: None, + target_height, + value_balance: Amount::zero(), + convert_anchor: None, + spends: vec![], + converts: vec![], + outputs: vec![], + } + } + + /// Returns the list of Sapling inputs that will be consumed by the transaction being + /// constructed. + pub fn inputs(&self) -> &[impl fees::InputView<()>] { + &self.spends + } + + pub fn converts(&self) -> &[ConvertDescriptionInfo] { + &self.converts + } + /// Returns the Sapling outputs that will be produced by the transaction being constructed + pub fn outputs(&self) -> &[impl fees::OutputView] { + &self.outputs + } + + /// Returns the net value represented by the spends and outputs added to this builder. + pub fn value_balance(&self) -> Amount { + self.value_balance.clone() + } +} + +impl SaplingBuilder

{ + /// Adds a Sapling note to be spent in this transaction. + /// + /// Returns an error if the given Merkle path does not have the same anchor as the + /// paths for previous Sapling notes. + pub fn add_spend( + &mut self, + mut rng: R, + extsk: ExtendedSpendingKey, + diversifier: Diversifier, + note: Note, + merkle_path: MerklePath, + ) -> Result<(), Error> { + // Consistency check: all anchors must equal the first one + let node = note.commitment(); + if let Some(anchor) = self.spend_anchor { + let path_root: bls12_381::Scalar = merkle_path.root(node).into(); + if path_root != anchor { + return Err(Error::AnchorMismatch); + } + } else { + self.spend_anchor = Some(merkle_path.root(node).into()) + } + + let alpha = jubjub::Fr::random(&mut rng); + + self.value_balance += + Amount::from_pair(note.asset_type, note.value).map_err(|_| Error::InvalidAmount)?; + + self.spends.push(SpendDescriptionInfo { + extsk, + diversifier, + note, + alpha, + merkle_path, + }); + + Ok(()) + } + + /// Adds a convert note to be applied in this transaction. + /// + /// Returns an error if the given Merkle path does not have the same anchor as the + /// paths for previous convert notes. + pub fn add_convert( + &mut self, + allowed: AllowedConversion, + value: u64, + merkle_path: MerklePath, + ) -> Result<(), Error> { + // Consistency check: all anchors must equal the first one + + let node = allowed.commitment(); + if let Some(anchor) = self.convert_anchor { + let path_root: bls12_381::Scalar = merkle_path.root(node).into(); + if path_root != anchor { + return Err(Error::AnchorMismatch); + } + } else { + self.convert_anchor = Some(merkle_path.root(node).into()) + } + + let allowed_amt: Amount = allowed.clone().into(); + self.value_balance += allowed_amt * value.try_into().unwrap(); + + self.converts.push(ConvertDescriptionInfo { + allowed, + value, + merkle_path, + }); + + Ok(()) + } + + /// Adds a Sapling address to send funds to. + #[allow(clippy::too_many_arguments)] + pub fn add_output( + &mut self, + mut rng: R, + ovk: Option, + to: PaymentAddress, + asset_type: AssetType, + value: u64, + memo: MemoBytes, + ) -> Result<(), Error> { + let output = SaplingOutputInfo::new_internal( + &self.params, + &mut rng, + self.target_height, + ovk, + to, + asset_type, + value, + memo, + )?; + + self.value_balance -= + Amount::from_pair(asset_type, value).map_err(|_| Error::InvalidAmount)?; + + self.outputs.push(output); + + Ok(()) + } + + pub fn build( + self, + prover: &Pr, + ctx: &mut Pr::SaplingProvingContext, + mut rng: R, + target_height: BlockHeight, + progress_notifier: Option<&Sender>, + ) -> Result>, Error> { + // Record initial positions of spends and outputs + let params = self.params; + let mut indexed_spends: Vec<_> = self.spends.into_iter().enumerate().collect(); + let mut indexed_converts: Vec<_> = self.converts.into_iter().enumerate().collect(); + let mut indexed_outputs: Vec<_> = self + .outputs + .iter() + .enumerate() + .map(|(i, o)| Some((i, o))) + .collect(); + + // Set up the transaction metadata that will be used to record how + // inputs and outputs are shuffled. + let mut tx_metadata = SaplingMetadata::empty(); + tx_metadata.spend_indices.resize(indexed_spends.len(), 0); + tx_metadata + .convert_indices + .resize(indexed_converts.len(), 0); + tx_metadata.output_indices.resize(indexed_outputs.len(), 0); + + // Pad Sapling outputs + if !indexed_spends.is_empty() { + while indexed_outputs.len() < MIN_SHIELDED_OUTPUTS { + indexed_outputs.push(None); + } + } + + // Randomize order of inputs and outputs + indexed_spends.shuffle(&mut rng); + indexed_converts.shuffle(&mut rng); + indexed_outputs.shuffle(&mut rng); + + // Keep track of the total number of steps computed + let total_progress = indexed_spends.len() as u32 + indexed_outputs.len() as u32; + let mut progress = 0u32; + + // Create Sapling SpendDescriptions + let shielded_spends: Vec> = if !indexed_spends.is_empty() { + let anchor = self + .spend_anchor + .expect("MASP Spend anchor must be set if MASP spends are present."); + + indexed_spends + .into_iter() + .enumerate() + .map(|(i, (pos, spend))| { + let proof_generation_key = spend.extsk.expsk.proof_generation_key(); + + let nullifier = spend.note.nf( + &proof_generation_key.to_viewing_key().nk, + spend.merkle_path.position, + ); + + let (zkproof, cv, rk) = prover + .spend_proof( + ctx, + proof_generation_key, + spend.diversifier, + spend.note.rseed, + spend.alpha, + spend.note.asset_type, + spend.note.value, + anchor, + spend.merkle_path.clone(), + ) + .map_err(|_| Error::SpendProof)?; + + // Record the post-randomized spend location + tx_metadata.spend_indices[pos] = i; + + // Update progress and send a notification on the channel + progress += 1; + if let Some(sender) = progress_notifier { + // If the send fails, we should ignore the error, not crash. + sender + .send(Progress::new(progress, Some(total_progress))) + .unwrap_or(()); + } + + Ok(SpendDescription { + cv, + anchor, + nullifier, + rk, + zkproof, + spend_auth_sig: spend, + }) + }) + .collect::, Error>>()? + } else { + vec![] + }; + + // Create Sapling ConvertDescriptions + let shielded_converts: Vec> = + if !indexed_converts.is_empty() { + let anchor = self + .convert_anchor + .expect("MASP convert_anchor must be set if MASP converts are present."); + + indexed_converts + .into_iter() + .enumerate() + .map(|(i, (pos, convert))| { + let (zkproof, cv) = prover + .convert_proof( + ctx, + convert.allowed.clone(), + convert.value, + anchor, + convert.merkle_path, + ) + .map_err(|_| Error::ConvertProof)?; + + // Record the post-randomized spend location + tx_metadata.convert_indices[pos] = i; + + // Update progress and send a notification on the channel + progress += 1; + if let Some(sender) = progress_notifier { + // If the send fails, we should ignore the error, not crash. + sender + .send(Progress::new(progress, Some(total_progress))) + .unwrap_or(()); + } + + Ok(ConvertDescription { + cv, + anchor, + zkproof, + }) + }) + .collect::, Error>>()? + } else { + vec![] + }; + + // Create Sapling OutputDescriptions + let shielded_outputs: Vec> = indexed_outputs + .into_iter() + .enumerate() + .map(|(i, output)| { + let result = if let Some((pos, output)) = output { + // Record the post-randomized output location + tx_metadata.output_indices[pos] = i; + + output.clone().build::(prover, ctx, &mut rng) + } else { + // This is a dummy output + let (dummy_to, dummy_note) = { + let (diversifier, g_d) = { + let mut diversifier; + let g_d; + loop { + let mut d = [0; 11]; + rng.fill_bytes(&mut d); + diversifier = Diversifier(d); + if let Some(val) = diversifier.g_d() { + g_d = val; + break; + } + } + (diversifier, g_d) + }; + let (pk_d, payment_address) = loop { + let dummy_ivk = jubjub::Fr::random(&mut rng); + let pk_d = g_d * dummy_ivk; + if let Some(addr) = PaymentAddress::from_parts(diversifier, pk_d) { + break (pk_d, addr); + } + }; + + let rseed = + generate_random_rseed_internal(¶ms, target_height, &mut rng); + + ( + payment_address, + Note { + g_d, + pk_d, + rseed, + value: 0, + asset_type: AssetType::new(b"dummy").unwrap(), + }, + ) + }; + + let esk = dummy_note.generate_or_derive_esk_internal(&mut rng); + let epk = dummy_note.g_d * esk; + + let (zkproof, cv) = prover.output_proof( + ctx, + esk, + dummy_to, + dummy_note.rcm(), + dummy_note.asset_type, + dummy_note.value, + ); + + let cmu = dummy_note.cmu(); + + let mut enc_ciphertext = [0u8; 580 + 32]; + let mut out_ciphertext = [0u8; 80]; + rng.fill_bytes(&mut enc_ciphertext[..]); + rng.fill_bytes(&mut out_ciphertext[..]); + + OutputDescription { + cv, + cmu, + ephemeral_key: epk.to_bytes().into(), + enc_ciphertext, + out_ciphertext, + zkproof, + } + }; + + // Update progress and send a notification on the channel + progress += 1; + if let Some(sender) = progress_notifier { + // If the send fails, we should ignore the error, not crash. + sender + .send(Progress::new(progress, Some(total_progress))) + .unwrap_or(()); + } + + result + }) + .collect(); + + let bundle = if shielded_spends.is_empty() && shielded_outputs.is_empty() { + None + } else { + Some(Bundle { + shielded_spends, + shielded_converts, + shielded_outputs, + value_balance: self.value_balance, + authorization: Unauthorized { tx_metadata }, + }) + }; + + Ok(bundle) + } +} + +impl SpendDescription { + pub fn apply_signature(&self, spend_auth_sig: Signature) -> SpendDescription { + SpendDescription { + cv: self.cv, + anchor: self.anchor, + nullifier: self.nullifier, + rk: self.rk, + zkproof: self.zkproof, + spend_auth_sig, + } + } +} + +impl Bundle { + pub fn apply_signatures( + self, + prover: &Pr, + ctx: &mut Pr::SaplingProvingContext, + rng: &mut R, + sighash_bytes: &[u8; 32], + ) -> Result<(Bundle, SaplingMetadata), Error> { + let binding_sig = prover + .binding_sig(ctx, &self.value_balance, sighash_bytes) + .map_err(|_| Error::BindingSig)?; + + Ok(( + Bundle { + shielded_spends: self + .shielded_spends + .iter() + .map(|spend| { + spend.apply_signature(spend_sig_internal( + PrivateKey(spend.spend_auth_sig.extsk.expsk.ask), + spend.spend_auth_sig.alpha, + sighash_bytes, + rng, + )) + }) + .collect(), + shielded_converts: self.shielded_converts, + shielded_outputs: self.shielded_outputs, + value_balance: self.value_balance, + authorization: Authorized { binding_sig }, + }, + self.authorization.tx_metadata, + )) + } +} + +/// A struct containing the information required in order to construct a +/// MASP conversion in a transaction. +#[derive(Clone)] +pub struct ConvertDescriptionInfo { + allowed: AllowedConversion, + value: u64, + merkle_path: MerklePath, +} + +#[cfg(any(test, feature = "test-dependencies"))] +pub mod testing { + use proptest::collection::vec; + use proptest::prelude::*; + use rand::{rngs::StdRng, SeedableRng}; + + use crate::{ + consensus::{ + testing::{arb_branch_id, arb_height}, + TEST_NETWORK, + }, + merkle_tree::{testing::arb_commitment_tree, IncrementalWitness}, + sapling::{ + prover::mock::MockTxProver, + testing::{arb_node, arb_note, arb_positive_note_value}, + Diversifier, + }, + transaction::components::{ + amount::MAX_MONEY, + sapling::{Authorized, Bundle}, + }, + zip32::sapling::testing::arb_extended_spending_key, + }; + + use super::SaplingBuilder; + + prop_compose! { + fn arb_bundle()(n_notes in 1..30usize)( + extsk in arb_extended_spending_key(), + spendable_notes in vec( + arb_positive_note_value(MAX_MONEY as u64 / 10000).prop_flat_map(arb_note), + n_notes + ), + commitment_trees in vec( + arb_commitment_tree(n_notes, arb_node(), 32).prop_map( + |t| IncrementalWitness::from_tree(&t).path().unwrap() + ), + n_notes + ), + diversifiers in vec(prop::array::uniform11(any::()).prop_map(Diversifier), n_notes), + target_height in arb_branch_id().prop_flat_map(|b| arb_height(b, &TEST_NETWORK)), + rng_seed in prop::array::uniform32(any::()), + fake_sighash_bytes in prop::array::uniform32(any::()), + ) -> Bundle { + let mut builder = SaplingBuilder::new(TEST_NETWORK, target_height.unwrap()); + let mut rng = StdRng::from_seed(rng_seed); + + for ((note, path), diversifier) in spendable_notes.into_iter().zip(commitment_trees.into_iter()).zip(diversifiers.into_iter()) { + builder.add_spend( + &mut rng, + extsk, + diversifier, + note, + path + ).unwrap(); + } + + let prover = MockTxProver; + + let bundle = builder.build( + &prover, + &mut (), + &mut rng, + target_height.unwrap(), + None + ).unwrap().unwrap(); + + let (bundle, _) = bundle.apply_signatures( + &prover, + &mut (), + &mut rng, + &fake_sighash_bytes, + ).unwrap(); + + bundle + } + } +} diff --git a/masp_primitives/src/transaction/components/sapling/fees.rs b/masp_primitives/src/transaction/components/sapling/fees.rs new file mode 100644 index 00000000..10d72adb --- /dev/null +++ b/masp_primitives/src/transaction/components/sapling/fees.rs @@ -0,0 +1,20 @@ +//! Types related to computation of fees and change related to the Sapling components +//! of a transaction. + +use crate::transaction::components::amount::Amount; + +/// A trait that provides a minimized view of a Sapling input suitable for use in +/// fee and change calculation. +pub trait InputView { + /// An identifier for the input being spent. + fn note_id(&self) -> &NoteRef; + /// The value of the input being spent. + fn value(&self) -> Amount; +} + +/// A trait that provides a minimized view of a Sapling output suitable for use in +/// fee and change calculation. +pub trait OutputView { + /// The value of the output being produced. + fn value(&self) -> Amount; +} diff --git a/masp_primitives/src/transaction/components/transparent.rs b/masp_primitives/src/transaction/components/transparent.rs new file mode 100644 index 00000000..a1c81b56 --- /dev/null +++ b/masp_primitives/src/transaction/components/transparent.rs @@ -0,0 +1,221 @@ +//! Structs representing the components within Zcash transactions. + +use borsh::{BorshDeserialize, BorshSerialize}; +use secp256k1::PublicKey as TransparentAddress; +use std::fmt::{self, Debug}; +use std::io::{self, Read, Write}; + +use crate::asset_type::AssetType; + +use super::amount::{Amount, BalanceError, MAX_MONEY}; + +pub mod builder; +pub mod fees; + +pub trait Authorization: fmt::Debug { + type TransparentSig: fmt::Debug + Clone + PartialEq; +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +pub struct Authorized; + +impl Authorization for Authorized { + type TransparentSig = (); +} + +pub trait MapAuth { + fn map_authorization(&self, s: A) -> B; +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Bundle { + pub vin: Vec, + pub vout: Vec, + pub authorization: A, +} + +impl Bundle { + pub fn map_authorization>(self, f: F) -> Bundle { + Bundle { + vin: self.vin, + vout: self.vout, + authorization: f.map_authorization(self.authorization), + } + } + + /// The amount of value added to or removed from the transparent pool by the action of this + /// bundle. A positive value represents that the containing transaction has funds being + /// transferred out of the transparent pool into shielded pools or to fees; a negative value + /// means that the containing transaction has funds being transferred into the transparent pool + /// from the shielded pools. + pub fn value_balance(&self) -> Result + where + E: From, + { + let input_sum = self + .vin + .iter() + .map(|p| { + if p.value >= 0 { + Amount::from_pair(p.asset_type, p.value) + } else { + Err(()) + } + }) + .sum::>() + .map_err(|_| BalanceError::Overflow)?; + + let output_sum = self + .vout + .iter() + .map(|p| { + if p.value >= 0 { + Amount::from_pair(p.asset_type, p.value) + } else { + Err(()) + } + }) + .sum::>() + .map_err(|_| BalanceError::Overflow)?; + + // Cannot panic when subtracting two positive i64 + Ok(input_sum - output_sum) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TxIn { + pub asset_type: AssetType, + pub value: i64, +} + +impl TxIn { + pub fn read(reader: &mut R) -> io::Result { + let asset_type = { + let mut tmp = [0u8; 32]; + reader.read_exact(&mut tmp)?; + AssetType::from_identifier(&tmp) + } + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "invalid asset identifier"))?; + let value = { + let mut tmp = [0u8; 8]; + reader.read_exact(&mut tmp)?; + i64::from_le_bytes(tmp) + }; + if value < 0 || value > MAX_MONEY { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "value out of range", + )); + } + + Ok(TxIn { asset_type, value }) + } + + pub fn write(&self, mut writer: W) -> io::Result<()> { + writer.write_all(self.asset_type.get_identifier())?; + writer.write_all(&self.value.to_le_bytes()) + } +} + +#[derive(Clone, Debug, Hash, PartialOrd, PartialEq, Ord, Eq)] +pub struct TxOut { + pub asset_type: AssetType, + pub value: i64, + pub transparent_address: TransparentAddress, +} + +impl TxOut { + pub fn read(reader: &mut R) -> io::Result { + let asset_type = { + let mut tmp = [0u8; 32]; + reader.read_exact(&mut tmp)?; + AssetType::from_identifier(&tmp) + } + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "invalid asset identifier"))?; + let value = { + let mut tmp = [0u8; 8]; + reader.read_exact(&mut tmp)?; + i64::from_le_bytes(tmp) + }; + if value < 0 || value > MAX_MONEY { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "value out of range", + )); + } + + let mut tmp = [0u8; 33]; + reader.read_exact(&mut tmp)?; + let transparent_address = TransparentAddress::from_slice(&tmp) + .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "bad public key"))?; + + Ok(TxOut { + asset_type, + value, + transparent_address, + }) + } + + pub fn write(&self, mut writer: W) -> io::Result<()> { + writer.write_all(self.asset_type.get_identifier())?; + writer.write_all(&self.value.to_le_bytes())?; + writer.write_all(&self.transparent_address.serialize()) + } + /// Returns the address to which the TxOut was sent, if this is a valid P2SH or P2PKH output. + pub fn recipient_address(&self) -> TransparentAddress { + self.transparent_address + } +} + +impl BorshDeserialize for TxOut { + fn deserialize(buf: &mut &[u8]) -> borsh::maybestd::io::Result { + Self::read(buf) + } +} + +impl BorshSerialize for TxOut { + fn serialize(&self, writer: &mut W) -> borsh::maybestd::io::Result<()> { + self.write(writer) + } +} + +#[cfg(any(test, feature = "test-dependencies"))] +pub mod testing { + use proptest::collection::vec; + use proptest::prelude::*; + + use crate::transaction::components::amount::testing::arb_nonnegative_amount; + + use super::{Authorized, Bundle, TxIn, TxOut}; + + prop_compose! { + pub fn arb_txin()(amt in arb_nonnegative_amount()) -> TxIn { + let (asset_type, value) = amt.components().next().unwrap(); + TxIn { asset_type: *asset_type, value: *value } + } + } + + prop_compose! { + pub fn arb_txout()(amt in arb_nonnegative_amount()) -> TxOut { + let secp = secp256k1::Secp256k1::new(); + let (_, public_key) = secp.generate_keypair(&mut rand_core::OsRng); + let (asset_type, value) = amt.components().next().unwrap(); + + TxOut { asset_type: *asset_type, value: *value, transparent_address : public_key } + } + } + + prop_compose! { + pub fn arb_bundle()( + vin in vec(arb_txin(), 0..10), + vout in vec(arb_txout(), 0..10), + ) -> Option> { + if vout.is_empty() { + None + } else { + Some(Bundle {vin, vout, authorization: Authorized }) + } + } + } +} diff --git a/masp_primitives/src/transaction/components/transparent/builder.rs b/masp_primitives/src/transaction/components/transparent/builder.rs new file mode 100644 index 00000000..d620b020 --- /dev/null +++ b/masp_primitives/src/transaction/components/transparent/builder.rs @@ -0,0 +1,225 @@ +//! Types and functions for building transparent transaction components. + +use std::fmt; + +use crate::{ + asset_type::AssetType, + transaction::{ + components::{ + amount::{Amount, BalanceError, MAX_MONEY}, + transparent::{self, fees, Authorization, Authorized, Bundle, TxIn, TxOut}, + }, + sighash::TransparentAuthorizingContext, + TransparentAddress, + }, +}; +use borsh::{BorshDeserialize, BorshSerialize}; + +#[derive(Debug, PartialEq, Eq)] +pub enum Error { + InvalidAddress, + InvalidAmount, + InvalidAsset, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::InvalidAddress => write!(f, "Invalid address"), + Error::InvalidAmount => write!(f, "Invalid amount"), + Error::InvalidAsset => write!(f, "Invalid asset"), + } + } +} + +/// An uninhabited type that allows the type of [`TransparentBuilder::inputs`] +/// to resolve when the transparent-inputs feature is not turned on. +#[cfg(not(feature = "transparent-inputs"))] +enum InvalidTransparentInput {} + +#[cfg(not(feature = "transparent-inputs"))] +impl fees::InputView for InvalidTransparentInput { + fn coin(&self) -> &TxOut { + panic!("transparent-inputs feature flag is not enabled."); + } +} + +#[cfg(feature = "transparent-inputs")] +#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +struct TransparentInputInfo { + coin: TxOut, +} + +#[cfg(feature = "transparent-inputs")] +impl fees::InputView for TransparentInputInfo { + fn coin(&self) -> &TxOut { + &self.coin + } +} + +pub struct TransparentBuilder { + #[cfg(feature = "transparent-inputs")] + inputs: Vec, + vout: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +pub struct Unauthorized { + #[cfg(feature = "transparent-inputs")] + inputs: Vec, +} + +impl Authorization for Unauthorized { + type TransparentSig = (); +} + +impl TransparentBuilder { + /// Constructs a new TransparentBuilder + pub fn empty() -> Self { + TransparentBuilder { + #[cfg(feature = "transparent-inputs")] + inputs: vec![], + vout: vec![], + } + } + + /// Returns the list of transparent inputs that will be consumed by the transaction being + /// constructed. + pub fn inputs(&self) -> &[impl fees::InputView] { + #[cfg(feature = "transparent-inputs")] + return &self.inputs; + + #[cfg(not(feature = "transparent-inputs"))] + { + let invalid: &[InvalidTransparentInput] = &[]; + return invalid; + } + } + + /// Returns the transparent outputs that will be produced by the transaction being constructed. + pub fn outputs(&self) -> &[impl fees::OutputView] { + &self.vout + } + + /// Adds a coin (the output of a previous transaction) to be spent to the transaction. + #[cfg(feature = "transparent-inputs")] + pub fn add_input(&mut self, coin: TxOut) -> Result<(), Error> { + if coin.value.is_negative() { + return Err(Error::InvalidAmount); + } + + self.inputs.push(TransparentInputInfo { coin }); + + Ok(()) + } + + pub fn add_output( + &mut self, + to: &TransparentAddress, + asset_type: AssetType, + value: i64, + ) -> Result<(), Error> { + if value < 0 || value > MAX_MONEY { + return Err(Error::InvalidAmount); + } + + self.vout.push(TxOut { + asset_type, + value, + transparent_address: *to, + }); + + Ok(()) + } + + pub fn value_balance(&self) -> Result { + #[cfg(feature = "transparent-inputs")] + let input_sum = self + .inputs + .iter() + .map(|input| { + if input.coin.value >= 0 { + Amount::from_pair(input.coin.asset_type, input.coin.value) + } else { + Err(()) + } + }) + .sum::>() + .map_err(|_| BalanceError::Overflow)?; + + #[cfg(not(feature = "transparent-inputs"))] + let input_sum = Amount::zero(); + + let output_sum = self + .vout + .iter() + .map(|vo| { + if vo.value >= 0 { + Amount::from_pair(vo.asset_type, vo.value) + } else { + Err(()) + } + }) + .sum::>() + .map_err(|_| BalanceError::Overflow)?; + + // Cannot panic when subtracting two positive i64 + Ok(input_sum - output_sum) + } + + pub fn build(self) -> Option> { + #[cfg(feature = "transparent-inputs")] + let vin: Vec = self + .inputs + .iter() + .map(|i| TxIn { + asset_type: i.coin.asset_type, + value: i.coin.value, + }) + .collect(); + + #[cfg(not(feature = "transparent-inputs"))] + let vin: Vec = vec![]; + + if vin.is_empty() && self.vout.is_empty() { + None + } else { + Some(transparent::Bundle { + vin, + vout: self.vout, + authorization: Unauthorized { + #[cfg(feature = "transparent-inputs")] + inputs: self.inputs, + }, + }) + } + } +} + +#[cfg(not(feature = "transparent-inputs"))] +impl TransparentAuthorizingContext for Unauthorized { + fn input_amounts(&self) -> Vec { + vec![] + } +} + +#[cfg(feature = "transparent-inputs")] +impl TransparentAuthorizingContext for Unauthorized { + fn input_amounts(&self) -> Vec> { + return self + .inputs + .iter() + .map(|txin| Amount::from_pair(txin.coin.asset_type, txin.coin.value)) + .collect(); + } +} + +impl Bundle { + pub fn apply_signatures(self) -> Bundle { + transparent::Bundle { + vin: self.vin, + vout: self.vout, + authorization: Authorized, + } + } +} diff --git a/masp_primitives/src/transaction/components/transparent/fees.rs b/masp_primitives/src/transaction/components/transparent/fees.rs new file mode 100644 index 00000000..812e8f0a --- /dev/null +++ b/masp_primitives/src/transaction/components/transparent/fees.rs @@ -0,0 +1,31 @@ +//! Types related to computation of fees and change related to the transparent components +//! of a transaction. + +use super::TxOut; +use crate::transaction::{components::amount::Amount, TransparentAddress}; + +/// This trait provides a minimized view of a transparent input suitable for use in +/// fee and change computation. +pub trait InputView { + /// The previous output being spent. + fn coin(&self) -> &TxOut; +} + +/// This trait provides a minimized view of a transparent output suitable for use in +/// fee and change computation. +pub trait OutputView { + /// Returns the value of the output being created. + fn value(&self) -> Amount; + /// Returns the script corresponding to the newly created output. + fn transparent_address(&self) -> &TransparentAddress; +} + +impl OutputView for TxOut { + fn value(&self) -> Amount { + Amount::from_pair(self.asset_type, self.value).unwrap() + } + + fn transparent_address(&self) -> &TransparentAddress { + &self.transparent_address + } +} diff --git a/masp_primitives/src/transaction/fees.rs b/masp_primitives/src/transaction/fees.rs new file mode 100644 index 00000000..0108e20f --- /dev/null +++ b/masp_primitives/src/transaction/fees.rs @@ -0,0 +1,28 @@ +//! Abstractions and types related to fee calculations. + +use crate::{ + consensus::{self, BlockHeight}, + transaction::components::{amount::Amount, transparent::fees as transparent}, +}; + +pub mod fixed; + +/// A trait that represents the ability to compute the fees that must be paid +/// by a transaction having a specified set of inputs and outputs. +pub trait FeeRule { + type Error; + + /// Computes the total fee required for a transaction given the provided inputs and outputs. + /// + /// Implementations of this method should compute the fee amount given exactly the inputs and + /// outputs specified, and should NOT compute speculative fees given any additional change + /// outputs that may need to be created in order for inputs and outputs to balance. + fn fee_required( + &self, + params: &P, + target_height: BlockHeight, + transparent_outputs: &[impl transparent::OutputView], + sapling_input_count: usize, + sapling_output_count: usize, + ) -> Result; +} diff --git a/masp_primitives/src/transaction/fees/fixed.rs b/masp_primitives/src/transaction/fees/fixed.rs new file mode 100644 index 00000000..02e3bd93 --- /dev/null +++ b/masp_primitives/src/transaction/fees/fixed.rs @@ -0,0 +1,48 @@ +use crate::{ + consensus::{self, BlockHeight}, + transaction::components::{ + amount::{Amount, DEFAULT_FEE}, + transparent::fees as transparent, + }, +}; + +/// A fee rule that always returns a fixed fee, irrespective of the structure of +/// the transaction being constructed. +#[derive(Clone, Debug)] +pub struct FeeRule { + fixed_fee: Amount, +} + +impl FeeRule { + /// Creates a new nonstandard fixed fee rule with the specified fixed fee. + pub fn non_standard(fixed_fee: Amount) -> Self { + Self { fixed_fee } + } + + /// Creates a new fixed fee rule with the standard default fee. + pub fn standard() -> Self { + Self { + fixed_fee: DEFAULT_FEE.clone(), + } + } + + /// Returns the fixed fee amount which which this rule was configured. + pub fn fixed_fee(&self) -> Amount { + self.fixed_fee.clone() + } +} + +impl super::FeeRule for FeeRule { + type Error = std::convert::Infallible; + + fn fee_required( + &self, + _params: &P, + _target_height: BlockHeight, + _transparent_outputs: &[impl transparent::OutputView], + _sapling_input_count: usize, + _sapling_output_count: usize, + ) -> Result { + Ok(self.fixed_fee.clone()) + } +} diff --git a/masp_primitives/src/transaction/sighash.rs b/masp_primitives/src/transaction/sighash.rs new file mode 100644 index 00000000..20b3498c --- /dev/null +++ b/masp_primitives/src/transaction/sighash.rs @@ -0,0 +1,77 @@ +use std::convert::TryInto; + +use blake2b_simd::Hash as Blake2bHash; + +use super::{ + components::{ + sapling::{self, GrothProofBytes}, + transparent, Amount, + }, + sighash_v5::v5_signature_hash, + Authorization, TransactionData, TxDigests, TxVersion, +}; + +use crate::asset_type::AssetType; +#[cfg(feature = "zfuture")] +use crate::extensions::transparent::Precondition; + +pub const SIGHASH_ALL: u8 = 0x01; +pub const SIGHASH_NONE: u8 = 0x02; +pub const SIGHASH_SINGLE: u8 = 0x03; +pub const SIGHASH_MASK: u8 = 0x1f; +pub const SIGHASH_ANYONECANPAY: u8 = 0x80; + +pub enum SignableInput { + Shielded, + Transparent { + hash_type: u8, + index: usize, + value: u64, + asset_type: AssetType, + }, +} + +impl SignableInput { + pub fn hash_type(&self) -> u8 { + match self { + SignableInput::Shielded => SIGHASH_ALL, + SignableInput::Transparent { hash_type, .. } => *hash_type, + } + } +} + +pub struct SignatureHash(Blake2bHash); + +impl AsRef<[u8; 32]> for SignatureHash { + fn as_ref(&self) -> &[u8; 32] { + self.0.as_ref().try_into().unwrap() + } +} + +/// Additional context that is needed to compute signature hashes +/// for transactions that include transparent inputs or outputs. +pub trait TransparentAuthorizingContext: transparent::Authorization { + /// Returns the list of all transparent input amounts, provided + /// so that wallets can commit to the transparent input breakdown + /// without requiring the full data of the previous transactions + /// providing these inputs. + fn input_amounts(&self) -> Vec>; +} + +/// Computes the signature hash for an input to a transaction, given +/// the full data of the transaction, the input being signed, and the +/// set of precomputed hashes produced in the construction of the +/// transaction ID. +pub fn signature_hash< + TA: TransparentAuthorizingContext, + SA: sapling::Authorization, + A: Authorization, +>( + tx: &TransactionData, + signable_input: &SignableInput, + txid_parts: &TxDigests, +) -> SignatureHash { + SignatureHash(match tx.version { + TxVersion::MASPv5 => v5_signature_hash(tx, signable_input, txid_parts), + }) +} diff --git a/masp_primitives/src/transaction/sighash_v5.rs b/masp_primitives/src/transaction/sighash_v5.rs new file mode 100644 index 00000000..6b3ac4fb --- /dev/null +++ b/masp_primitives/src/transaction/sighash_v5.rs @@ -0,0 +1,51 @@ +use blake2b_simd::Hash as Blake2bHash; + +use crate::transaction::{ + sighash::{SignableInput, TransparentAuthorizingContext}, + transparent, + txid::{hash_transparent_txid_data, to_hash}, + Authorization, TransactionData, TransparentDigests, TxDigests, +}; + +/// Implements [ZIP 244 section S.2](https://zips.z.cash/zip-0244#s-2-transparent-sig-digest). +fn transparent_sig_digest( + tx_data: Option<(&transparent::Bundle, &TransparentDigests)>, + _input: &SignableInput, +) -> Blake2bHash { + match tx_data { + // No transparent inputs or outputs. + None => hash_transparent_txid_data(None), + // No transparent inputs, or coinbase. + Some((_bundle, txid_digests)) => hash_transparent_txid_data(Some(txid_digests)), + } +} + +/// Implements the [Signature Digest section of ZIP 244](https://zips.z.cash/zip-0244#signature-digest) +pub fn v5_signature_hash< + TA: TransparentAuthorizingContext, + A: Authorization, +>( + tx: &TransactionData, + signable_input: &SignableInput, + txid_parts: &TxDigests, +) -> Blake2bHash { + // The caller must provide the transparent digests if and only if the transaction has a + // transparent component. + assert_eq!( + tx.transparent_bundle.is_some(), + txid_parts.transparent_digests.is_some() + ); + + to_hash( + tx.version, + tx.consensus_branch_id, + txid_parts.header_digest, + transparent_sig_digest( + tx.transparent_bundle + .as_ref() + .zip(txid_parts.transparent_digests.as_ref()), + signable_input, + ), + txid_parts.sapling_digest, + ) +} diff --git a/masp_primitives/src/transaction/txid.rs b/masp_primitives/src/transaction/txid.rs new file mode 100644 index 00000000..2a02b757 --- /dev/null +++ b/masp_primitives/src/transaction/txid.rs @@ -0,0 +1,389 @@ +use std::borrow::Borrow; +use std::convert::TryFrom; +use std::io::Write; + +use blake2b_simd::{Hash as Blake2bHash, Params, State}; +use borsh::{BorshDeserialize, BorshSerialize}; +use byteorder::{LittleEndian, WriteBytesExt}; +use ff::PrimeField; +use group::GroupEncoding; + +use crate::consensus::{BlockHeight, BranchId}; + +use super::{ + sapling::{self, OutputDescription, SpendDescription}, + transparent::{self, TxIn, TxOut}, + Authorization, Authorized, TransactionDigest, TransparentDigests, TxDigests, TxId, TxVersion, +}; + +/// TxId tree root personalization +const ZCASH_TX_PERSONALIZATION_PREFIX: &[u8; 12] = b"ZcashTxHash_"; + +// TxId level 1 node personalization +const ZCASH_HEADERS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdHeadersHash"; +pub(crate) const ZCASH_TRANSPARENT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdTranspaHash"; +const ZCASH_SAPLING_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSaplingHash"; + +// TxId transparent level 2 node personalization +const ZCASH_INPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdInputs_Hash"; +const ZCASH_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdOutputsHash"; + +// TxId sapling level 2 node personalization +const ZCASH_SAPLING_SPENDS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSSpendsHash"; +const ZCASH_SAPLING_SPENDS_COMPACT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSSpendCHash"; +const ZCASH_SAPLING_SPENDS_NONCOMPACT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSSpendNHash"; + +const ZCASH_SAPLING_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSOutputHash"; +const ZCASH_SAPLING_OUTPUTS_COMPACT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSOutC__Hash"; +const ZCASH_SAPLING_OUTPUTS_MEMOS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSOutM__Hash"; +const ZCASH_SAPLING_OUTPUTS_NONCOMPACT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSOutN__Hash"; + +const ZCASH_AUTH_PERSONALIZATION_PREFIX: &[u8; 12] = b"ZTxAuthHash_"; +const ZCASH_TRANSPARENT_SCRIPTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxAuthTransHash"; +const ZCASH_SAPLING_SIGS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxAuthSapliHash"; + +fn hasher(personal: &[u8; 16]) -> State { + Params::new().hash_length(32).personal(personal).to_state() +} + +/// Sequentially append the full serialized value of each transparent output +/// to a hash personalized by ZCASH_OUTPUTS_HASH_PERSONALIZATION. +/// In the case that no outputs are provided, this produces a default +/// hash from just the personalization string. +pub(crate) fn transparent_outputs_hash>(vout: &[T]) -> Blake2bHash { + let mut h = hasher(ZCASH_OUTPUTS_HASH_PERSONALIZATION); + for t_out in vout { + t_out.borrow().write(&mut h).unwrap(); + } + h.finalize() +} + +/// Sequentially append the full serialized value of each transparent input +/// to a hash personalized by ZCASH_INPUTS_HASH_PERSONALIZATION. +/// In the case that no inputs are provided, this produces a default +/// hash from just the personalization string. +pub(crate) fn transparent_inputs_hash>(vin: &[T]) -> Blake2bHash { + let mut h = hasher(ZCASH_INPUTS_HASH_PERSONALIZATION); + for t_in in vin { + t_in.borrow().write(&mut h).unwrap(); + } + h.finalize() +} + +/// Implements [ZIP 244 section T.3a](https://zips.z.cash/zip-0244#t-3a-sapling-spends-digest) +/// +/// Write disjoint parts of each Sapling shielded spend to a pair of hashes: +/// * \[nullifier*\] - personalized with ZCASH_SAPLING_SPENDS_COMPACT_HASH_PERSONALIZATION +/// * \[(cv, anchor, rk, zkproof)*\] - personalized with ZCASH_SAPLING_SPENDS_NONCOMPACT_HASH_PERSONALIZATION +/// +/// Then, hash these together personalized by ZCASH_SAPLING_SPENDS_HASH_PERSONALIZATION +pub(crate) fn hash_sapling_spends( + shielded_spends: &[SpendDescription], +) -> Blake2bHash { + let mut h = hasher(ZCASH_SAPLING_SPENDS_HASH_PERSONALIZATION); + if !shielded_spends.is_empty() { + let mut ch = hasher(ZCASH_SAPLING_SPENDS_COMPACT_HASH_PERSONALIZATION); + let mut nh = hasher(ZCASH_SAPLING_SPENDS_NONCOMPACT_HASH_PERSONALIZATION); + for s_spend in shielded_spends { + // we build the hash of nullifiers separately for compact blocks. + ch.write_all(s_spend.nullifier.as_ref()).unwrap(); + + nh.write_all(&s_spend.cv.to_bytes()).unwrap(); + nh.write_all(&s_spend.anchor.to_repr()).unwrap(); + s_spend.rk.write(&mut nh).unwrap(); + } + + let compact_digest = ch.finalize(); + h.write_all(compact_digest.as_bytes()).unwrap(); + let noncompact_digest = nh.finalize(); + h.write_all(noncompact_digest.as_bytes()).unwrap(); + } + h.finalize() +} + +/// Implements [ZIP 244 section T.3b](https://zips.z.cash/zip-0244#t-3b-sapling-outputs-digest) +/// +/// Write disjoint parts of each Sapling shielded output as 3 separate hashes: +/// * \[(cmu, epk, enc_ciphertext\[..52\])*\] personalized with ZCASH_SAPLING_OUTPUTS_COMPACT_HASH_PERSONALIZATION +/// * \[enc_ciphertext\[52..564\]*\] (memo ciphertexts) personalized with ZCASH_SAPLING_OUTPUTS_MEMOS_HASH_PERSONALIZATION +/// * \[(cv, enc_ciphertext\[564..\], out_ciphertext, zkproof)*\] personalized with ZCASH_SAPLING_OUTPUTS_NONCOMPACT_HASH_PERSONALIZATION +/// +/// Then, hash these together personalized with ZCASH_SAPLING_OUTPUTS_HASH_PERSONALIZATION +pub(crate) fn hash_sapling_outputs( + shielded_outputs: &[OutputDescription], +) -> Blake2bHash { + let mut h = hasher(ZCASH_SAPLING_OUTPUTS_HASH_PERSONALIZATION); + if !shielded_outputs.is_empty() { + let mut ch = hasher(ZCASH_SAPLING_OUTPUTS_COMPACT_HASH_PERSONALIZATION); + let mut mh = hasher(ZCASH_SAPLING_OUTPUTS_MEMOS_HASH_PERSONALIZATION); + let mut nh = hasher(ZCASH_SAPLING_OUTPUTS_NONCOMPACT_HASH_PERSONALIZATION); + for s_out in shielded_outputs { + ch.write_all(s_out.cmu.to_repr().as_ref()).unwrap(); + ch.write_all(s_out.ephemeral_key.as_ref()).unwrap(); + ch.write_all(&s_out.enc_ciphertext[..52]).unwrap(); + + mh.write_all(&s_out.enc_ciphertext[52..564]).unwrap(); + + nh.write_all(&s_out.cv.to_bytes()).unwrap(); + nh.write_all(&s_out.enc_ciphertext[564..]).unwrap(); + nh.write_all(&s_out.out_ciphertext).unwrap(); + } + + h.write_all(ch.finalize().as_bytes()).unwrap(); + h.write_all(mh.finalize().as_bytes()).unwrap(); + h.write_all(nh.finalize().as_bytes()).unwrap(); + } + h.finalize() +} + +/// The txid commits to the hash of all transparent outputs. The +/// prevout and sequence_hash components of txid +fn transparent_digests( + bundle: &transparent::Bundle, +) -> TransparentDigests { + TransparentDigests { + inputs_digest: transparent_inputs_hash(&bundle.vin), + outputs_digest: transparent_outputs_hash(&bundle.vout), + } +} +/// Implements [ZIP 244 section T.1](https://zips.z.cash/zip-0244#t-1-header-digest) +fn hash_header_txid_data( + version: TxVersion, + // we commit to the consensus branch ID with the header + consensus_branch_id: BranchId, + lock_time: u32, + expiry_height: BlockHeight, +) -> Blake2bHash { + let mut h = hasher(ZCASH_HEADERS_HASH_PERSONALIZATION); + + h.write_u32::(version.header()).unwrap(); + h.write_u32::(version.version_group_id()) + .unwrap(); + h.write_u32::(consensus_branch_id.into()) + .unwrap(); + h.write_u32::(lock_time).unwrap(); + h.write_u32::(expiry_height.into()).unwrap(); + + h.finalize() +} + +/// Implements [ZIP 244 section T.2](https://zips.z.cash/zip-0244#t-2-transparent-digest) +pub(crate) fn hash_transparent_txid_data( + t_digests: Option<&TransparentDigests>, +) -> Blake2bHash { + let mut h = hasher(ZCASH_TRANSPARENT_HASH_PERSONALIZATION); + if let Some(d) = t_digests { + h.write_all(d.inputs_digest.as_bytes()).unwrap(); + h.write_all(d.outputs_digest.as_bytes()).unwrap(); + } + h.finalize() +} + +/// Implements [ZIP 244 section T.3](https://zips.z.cash/zip-0244#t-3-sapling-digest) +fn hash_sapling_txid_data< + A: sapling::Authorization + PartialEq + BorshSerialize + BorshDeserialize, +>( + bundle: &sapling::Bundle, +) -> Blake2bHash { + let mut h = hasher(ZCASH_SAPLING_HASH_PERSONALIZATION); + if !(bundle.shielded_spends.is_empty() && bundle.shielded_outputs.is_empty()) { + h.write_all(hash_sapling_spends(&bundle.shielded_spends).as_bytes()) + .unwrap(); + + h.write_all(hash_sapling_outputs(&bundle.shielded_outputs).as_bytes()) + .unwrap(); + + bundle.value_balance.serialize(&mut h).unwrap(); + } + h.finalize() +} + +fn hash_sapling_txid_empty() -> Blake2bHash { + hasher(ZCASH_SAPLING_HASH_PERSONALIZATION).finalize() +} + +/// A TransactionDigest implementation that commits to all of the effecting +/// data of a transaction to produce a nonmalleable transaction identifier. +/// +/// This expects and relies upon the existence of canonical encodings for +/// each effecting component of a transaction. +/// +/// This implements the [TxId Digest section of ZIP 244](https://zips.z.cash/zip-0244#txid-digest) +pub struct TxIdDigester; + +impl TransactionDigest for TxIdDigester { + type HeaderDigest = Blake2bHash; + type TransparentDigest = Option>; + type SaplingDigest = Option; + + type Digest = TxDigests; + + fn digest_header( + &self, + version: TxVersion, + consensus_branch_id: BranchId, + lock_time: u32, + expiry_height: BlockHeight, + ) -> Self::HeaderDigest { + hash_header_txid_data(version, consensus_branch_id, lock_time, expiry_height) + } + + fn digest_transparent( + &self, + transparent_bundle: Option<&transparent::Bundle>, + ) -> Self::TransparentDigest { + transparent_bundle.map(transparent_digests) + } + + fn digest_sapling( + &self, + sapling_bundle: Option<&sapling::Bundle>, + ) -> Self::SaplingDigest { + sapling_bundle.map(hash_sapling_txid_data) + } + + fn combine( + &self, + header_digest: Self::HeaderDigest, + transparent_digests: Self::TransparentDigest, + sapling_digest: Self::SaplingDigest, + ) -> Self::Digest { + TxDigests { + header_digest, + transparent_digests, + sapling_digest, + } + } +} + +pub(crate) fn to_hash( + _txversion: TxVersion, + consensus_branch_id: BranchId, + header_digest: Blake2bHash, + transparent_digest: Blake2bHash, + sapling_digest: Option, +) -> Blake2bHash { + let mut personal = [0; 16]; + personal[..12].copy_from_slice(ZCASH_TX_PERSONALIZATION_PREFIX); + (&mut personal[12..]) + .write_u32::(consensus_branch_id.into()) + .unwrap(); + + let mut h = hasher(&personal); + h.write_all(header_digest.as_bytes()).unwrap(); + h.write_all(transparent_digest.as_bytes()).unwrap(); + h.write_all( + sapling_digest + .unwrap_or_else(hash_sapling_txid_empty) + .as_bytes(), + ) + .unwrap(); + + h.finalize() +} + +pub fn to_txid( + txversion: TxVersion, + consensus_branch_id: BranchId, + digests: &TxDigests, +) -> TxId { + let txid_digest = to_hash( + txversion, + consensus_branch_id, + digests.header_digest, + hash_transparent_txid_data(digests.transparent_digests.as_ref()), + digests.sapling_digest, + ); + + TxId(<[u8; 32]>::try_from(txid_digest.as_bytes()).unwrap()) +} + +/// Digester which constructs a digest of only the witness data. +/// This does not internally commit to the txid, so if that is +/// desired it should be done using the result of this digest +/// function. +pub struct BlockTxCommitmentDigester; + +impl TransactionDigest for BlockTxCommitmentDigester { + /// We use the header digest to pass the transaction ID into + /// where it needs to be used for personalization string construction. + type HeaderDigest = BranchId; + type TransparentDigest = Blake2bHash; + type SaplingDigest = Blake2bHash; + + type Digest = Blake2bHash; + + fn digest_header( + &self, + _version: TxVersion, + consensus_branch_id: BranchId, + _lock_time: u32, + _expiry_height: BlockHeight, + ) -> Self::HeaderDigest { + consensus_branch_id + } + + fn digest_transparent( + &self, + transparent_bundle: Option<&transparent::Bundle>, + ) -> Blake2bHash { + let mut h = hasher(ZCASH_TRANSPARENT_SCRIPTS_HASH_PERSONALIZATION); + if let Some(bundle) = transparent_bundle { + for txout in &bundle.vout { + h.write_all(txout.asset_type.get_identifier()).unwrap(); + h.write_all(&txout.value.to_le_bytes()).unwrap(); + h.write_all(&txout.transparent_address.serialize()).unwrap(); + } + } + h.finalize() + } + + fn digest_sapling( + &self, + sapling_bundle: Option<&sapling::Bundle>, + ) -> Blake2bHash { + let mut h = hasher(ZCASH_SAPLING_SIGS_HASH_PERSONALIZATION); + if let Some(bundle) = sapling_bundle { + for spend in &bundle.shielded_spends { + h.write_all(&spend.zkproof).unwrap(); + } + + for spend in &bundle.shielded_spends { + spend.spend_auth_sig.write(&mut h).unwrap(); + } + + for convert in &bundle.shielded_converts { + h.write_all(&convert.zkproof).unwrap(); + } + + for output in &bundle.shielded_outputs { + h.write_all(&output.zkproof).unwrap(); + } + + bundle.authorization.binding_sig.write(&mut h).unwrap(); + } + h.finalize() + } + + fn combine( + &self, + consensus_branch_id: Self::HeaderDigest, + transparent_digest: Self::TransparentDigest, + sapling_digest: Self::SaplingDigest, + ) -> Self::Digest { + let digests = [transparent_digest, sapling_digest]; + + let mut personal = [0; 16]; + personal[..12].copy_from_slice(ZCASH_AUTH_PERSONALIZATION_PREFIX); + (&mut personal[12..]) + .write_u32::(consensus_branch_id.into()) + .unwrap(); + + let mut h = hasher(&personal); + for digest in &digests { + h.write_all(digest.as_bytes()).unwrap(); + } + + h.finalize() + } +} diff --git a/masp_primitives/src/zip32.rs b/masp_primitives/src/zip32.rs index 02fdfe0b..2e1c9725 100644 --- a/masp_primitives/src/zip32.rs +++ b/masp_primitives/src/zip32.rs @@ -2,73 +2,25 @@ //! //! [ZIP 32]: https://zips.z.cash/zip-0032 -use aes::Aes256; -use blake2b_simd::Params as Blake2bParams; -use byteorder::{ByteOrder, LittleEndian, ReadBytesExt, WriteBytesExt}; -use fpe::ff1::{BinaryNumeralString, FF1}; -use std::convert::TryInto; -use std::ops::AddAssign; +use memuse::{self, DynamicUsage}; +use std::convert::{TryFrom, TryInto}; -use crate::{ - constants::{PROOF_GENERATION_KEY_GENERATOR, SPENDING_KEY_GENERATOR}, - primitives::{Diversifier, PaymentAddress, ViewingKey}, -}; -use std::io::{self, Read, Write}; - -use crate::keys::{ - prf_expand, prf_expand_vec, ExpandedSpendingKey, FullViewingKey, OutgoingViewingKey, -}; - -pub const ZIP32_SAPLING_MASTER_PERSONALIZATION: &[u8; 16] = b"MASP_IP32Sapling"; -pub const ZIP32_SAPLING_FVFP_PERSONALIZATION: &[u8; 16] = b"MASP_SaplingFVFP"; -pub const ZIP32_SAPLING_INT_PERSONALIZATION: &[u8; 16] = b"MASP__SaplingInt"; +use crate::sapling::{Diversifier, NullifierDerivingKey, PaymentAddress, ViewingKey}; -// Common helper functions +pub mod sapling; -fn derive_child_ovk(parent: &OutgoingViewingKey, i_l: &[u8]) -> OutgoingViewingKey { - let mut ovk = [0u8; 32]; - ovk.copy_from_slice(&prf_expand_vec(i_l, &[&[0x15], &parent.0]).as_bytes()[..32]); - OutgoingViewingKey(ovk) -} +#[deprecated(note = "Please use the types exported from the `zip32::sapling` module instead.")] +pub use sapling::{ + sapling_address, sapling_default_address, sapling_derive_internal_fvk, sapling_find_address, + DiversifiableFullViewingKey, ExtendedFullViewingKey, ExtendedSpendingKey, + ZIP32_SAPLING_FVFP_PERSONALIZATION, ZIP32_SAPLING_INT_PERSONALIZATION, + ZIP32_SAPLING_MASTER_PERSONALIZATION, +}; // ZIP 32 structures -/// A Sapling full viewing key fingerprint -struct FvkFingerprint([u8; 32]); - -impl From<&FullViewingKey> for FvkFingerprint { - fn from(fvk: &FullViewingKey) -> Self { - let mut h = Blake2bParams::new() - .hash_length(32) - .personal(ZIP32_SAPLING_FVFP_PERSONALIZATION) - .to_state(); - h.update(&fvk.to_bytes()); - let mut fvfp = [0u8; 32]; - fvfp.copy_from_slice(h.finalize().as_bytes()); - FvkFingerprint(fvfp) - } -} - -/// A Sapling full viewing key tag -#[derive(Clone, Copy, Debug, PartialEq)] -struct FvkTag([u8; 4]); - -impl FvkFingerprint { - fn tag(&self) -> FvkTag { - let mut tag = [0u8; 4]; - tag.copy_from_slice(&self.0[..4]); - FvkTag(tag) - } -} - -impl FvkTag { - fn master() -> Self { - FvkTag([0u8; 4]) - } -} - /// A child index for a derived key -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum ChildIndex { NonHardened(u32), Hardened(u32), // Hardened(n) == n + (1 << 31) == n' in path notation @@ -95,10 +47,18 @@ impl ChildIndex { } /// A BIP-32 chain code -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct ChainCode([u8; 32]); -#[derive(Clone, Copy, Debug, PartialEq)] +impl ChainCode { + /// Returns byte representation of the chain code, as required for + /// [ZIP 32](https://zips.z.cash/zip-0032) encoding. + fn as_bytes(&self) -> &[u8; 32] { + &self.0 + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct DiversifierIndex(pub [u8; 11]); impl Default for DiversifierIndex { @@ -107,6 +67,30 @@ impl Default for DiversifierIndex { } } +impl From for DiversifierIndex { + fn from(i: u32) -> Self { + u64::from(i).into() + } +} + +impl From for DiversifierIndex { + fn from(i: u64) -> Self { + let mut result = DiversifierIndex([0; 11]); + result.0[..8].copy_from_slice(&i.to_le_bytes()); + result + } +} + +impl TryFrom for u32 { + type Error = std::num::TryFromIntError; + + fn try_from(di: DiversifierIndex) -> Result { + let mut u128_bytes = [0u8; 16]; + u128_bytes[0..11].copy_from_slice(&di.0[..]); + u128::from_le_bytes(u128_bytes).try_into() + } +} + impl DiversifierIndex { pub fn new() -> Self { DiversifierIndex([0; 11]) @@ -125,1155 +109,49 @@ impl DiversifierIndex { } } -/// A key used to derive diversifiers for a particular child key -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct DiversifierKey(pub [u8; 32]); - -impl DiversifierKey { - pub fn master(sk_m: &[u8]) -> Self { - let mut dk_m = [0u8; 32]; - dk_m.copy_from_slice(&prf_expand(sk_m, &[0x10]).as_bytes()[..32]); - DiversifierKey(dk_m) - } - - fn derive_child(&self, i_l: &[u8]) -> Self { - let mut dk = [0u8; 32]; - dk.copy_from_slice(&prf_expand_vec(i_l, &[&[0x16], &self.0]).as_bytes()[..32]); - DiversifierKey(dk) - } - - fn try_diversifier_internal(ff: &FF1, j: DiversifierIndex) -> Option { - // Generate d_j - let enc = ff - .encrypt(&[], &BinaryNumeralString::from_bytes_le(&j.0[..])) - .unwrap(); - let mut d_j = [0; 11]; - d_j.copy_from_slice(&enc.to_bytes_le()); - let diversifier = Diversifier(d_j); - - // validate that the generated diversifier maps to a jubjub subgroup point. - diversifier.g_d().map(|_| diversifier) - } - - /// Attempts to produce a diversifier at the given index. Returns None - /// if the index does not produce a valid diversifier. - pub fn diversifier(&self, j: DiversifierIndex) -> Option { - let ff = FF1::::new(&self.0, 2).unwrap(); - Self::try_diversifier_internal(&ff, j) - } - - /// Returns the diversifier index to which this key maps the given diversifier. - /// - /// This method cannot be used to verify whether the diversifier was originally - /// generated with this diversifier key, because all valid diversifiers can be - /// produced by all diversifier keys. - pub fn diversifier_index(&self, d: &Diversifier) -> DiversifierIndex { - let ff = FF1::::new(&self.0, 2).unwrap(); - let dec = ff - .decrypt(&[], &BinaryNumeralString::from_bytes_le(&d.0[..])) - .unwrap(); - let mut j = DiversifierIndex::new(); - j.0.copy_from_slice(&dec.to_bytes_le()); - j - } - - /// Returns the first index starting from j that generates a valid - /// diversifier, along with the corresponding diversifier. Returns - /// `None` if the diversifier space contains no valid diversifiers - /// at or above the specified diversifier index. - pub fn find_diversifier( - &self, - mut j: DiversifierIndex, - ) -> Option<(DiversifierIndex, Diversifier)> { - let ff = FF1::::new(&self.0, 2).unwrap(); - loop { - match Self::try_diversifier_internal(&ff, j) { - Some(d_j) => return Some((j, d_j)), - None => { - if j.increment().is_err() { - return None; - } - } - } - } - } -} - -/// Attempt to produce a payment address given the specified diversifier -/// index, and return None if the specified index does not produce a valid -/// diversifier. -pub fn sapling_address( - fvk: &FullViewingKey, - dk: &DiversifierKey, - j: DiversifierIndex, -) -> Option { - dk.diversifier(j) - .and_then(|d_j| fvk.vk.to_payment_address(d_j)) -} - -/// Search the diversifier space starting at diversifier index `j` for -/// one which will produce a valid diversifier, and return the payment address -/// constructed using that diversifier along with the index at which the -/// valid diversifier was found. -pub fn sapling_find_address( - fvk: &FullViewingKey, - dk: &DiversifierKey, - j: DiversifierIndex, -) -> Option<(DiversifierIndex, PaymentAddress)> { - let (j, d_j) = dk.find_diversifier(j)?; - fvk.vk.to_payment_address(d_j).map(|addr| (j, addr)) -} - -/// Returns the payment address corresponding to the smallest valid diversifier -/// index, along with that index. -pub fn sapling_default_address( - fvk: &FullViewingKey, - dk: &DiversifierKey, -) -> (DiversifierIndex, PaymentAddress) { - // This unwrap is safe, if you have to search the 2^88 space of - // diversifiers it'll never return anyway. - sapling_find_address(fvk, dk, DiversifierIndex::new()).unwrap() -} - -/// Returns the internal full viewing key and diversifier key -/// for the provided external FVK = (ak, nk, ovk) and dk encoded -/// in a [Unified FVK]. +/// The scope of a viewing key or address. /// -/// [Unified FVK]: https://zips.z.cash/zip-0316#encoding-of-unified-full-incoming-viewing-keys -pub fn sapling_derive_internal_fvk( - fvk: &FullViewingKey, - dk: &DiversifierKey, -) -> (FullViewingKey, DiversifierKey) { - let i = { - let mut h = Blake2bParams::new() - .hash_length(32) - .personal(crate::zip32::ZIP32_SAPLING_INT_PERSONALIZATION) - .to_state(); - h.update(&fvk.to_bytes()); - h.update(&dk.0); - h.finalize() - }; - let i_nsk = jubjub::Fr::from_bytes_wide(prf_expand(i.as_bytes(), &[0x17]).as_array()); - let r = prf_expand(i.as_bytes(), &[0x18]); - let r = r.as_bytes(); - // PROOF_GENERATION_KEY_GENERATOR = \mathcal{H}^Sapling - let nk_internal = PROOF_GENERATION_KEY_GENERATOR * i_nsk + fvk.vk.nk; - let dk_internal = DiversifierKey(r[..32].try_into().unwrap()); - let ovk_internal = OutgoingViewingKey(r[32..].try_into().unwrap()); - - ( - FullViewingKey { - vk: ViewingKey { - ak: fvk.vk.ak, - nk: nk_internal, - }, - ovk: ovk_internal, - }, - dk_internal, - ) -} - -/// A Sapling extended spending key -#[derive(Clone)] -pub struct ExtendedSpendingKey { - depth: u8, - parent_fvk_tag: FvkTag, - child_index: ChildIndex, - chain_code: ChainCode, - pub expsk: ExpandedSpendingKey, - dk: DiversifierKey, -} - -// A Sapling extended full viewing key -#[derive(Clone)] -pub struct ExtendedFullViewingKey { - depth: u8, - parent_fvk_tag: FvkTag, - child_index: ChildIndex, - chain_code: ChainCode, - pub fvk: FullViewingKey, - dk: DiversifierKey, -} - -impl std::cmp::PartialEq for ExtendedSpendingKey { - fn eq(&self, rhs: &ExtendedSpendingKey) -> bool { - self.depth == rhs.depth - && self.parent_fvk_tag == rhs.parent_fvk_tag - && self.child_index == rhs.child_index - && self.chain_code == rhs.chain_code - && self.expsk.ask == rhs.expsk.ask - && self.expsk.nsk == rhs.expsk.nsk - && self.expsk.ovk == rhs.expsk.ovk - && self.dk == rhs.dk - } -} - -impl std::fmt::Debug for ExtendedSpendingKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - write!( - f, - "ExtendedSpendingKey(d = {}, tag_p = {:?}, i = {:?})", - self.depth, self.parent_fvk_tag, self.child_index - ) - } -} - -impl std::cmp::PartialEq for ExtendedFullViewingKey { - fn eq(&self, rhs: &ExtendedFullViewingKey) -> bool { - self.depth == rhs.depth - && self.parent_fvk_tag == rhs.parent_fvk_tag - && self.child_index == rhs.child_index - && self.chain_code == rhs.chain_code - && self.fvk.vk.ak == rhs.fvk.vk.ak - && self.fvk.vk.nk == rhs.fvk.vk.nk - && self.fvk.ovk == rhs.fvk.ovk - && self.dk == rhs.dk - } -} - -impl std::fmt::Debug for ExtendedFullViewingKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - write!( - f, - "ExtendedFullViewingKey(d = {}, tag_p = {:?}, i = {:?})", - self.depth, self.parent_fvk_tag, self.child_index - ) - } -} - -impl ExtendedSpendingKey { - pub fn master(seed: &[u8]) -> Self { - let i = Blake2bParams::new() - .hash_length(64) - .personal(ZIP32_SAPLING_MASTER_PERSONALIZATION) - .hash(seed); - - let sk_m = &i.as_bytes()[..32]; - let mut c_m = [0u8; 32]; - c_m.copy_from_slice(&i.as_bytes()[32..]); - - ExtendedSpendingKey { - depth: 0, - parent_fvk_tag: FvkTag::master(), - child_index: ChildIndex::master(), - chain_code: ChainCode(c_m), - expsk: ExpandedSpendingKey::from_spending_key(sk_m), - dk: DiversifierKey::master(sk_m), - } - } - - pub fn read(mut reader: R) -> io::Result { - let depth = reader.read_u8()?; - let mut tag = [0; 4]; - reader.read_exact(&mut tag)?; - let i = reader.read_u32::()?; - let mut c = [0; 32]; - reader.read_exact(&mut c)?; - let expsk = ExpandedSpendingKey::read(&mut reader)?; - let mut dk = [0; 32]; - reader.read_exact(&mut dk)?; - - Ok(ExtendedSpendingKey { - depth, - parent_fvk_tag: FvkTag(tag), - child_index: ChildIndex::from_index(i), - chain_code: ChainCode(c), - expsk, - dk: DiversifierKey(dk), - }) - } - - pub fn write(&self, mut writer: W) -> io::Result<()> { - writer.write_u8(self.depth)?; - writer.write_all(&self.parent_fvk_tag.0)?; - writer.write_u32::(self.child_index.value())?; - writer.write_all(&self.chain_code.0)?; - writer.write_all(&self.expsk.to_bytes())?; - writer.write_all(&self.dk.0)?; - - Ok(()) - } - - /// Returns the child key corresponding to the path derived from the master key - pub fn from_path(master: &ExtendedSpendingKey, path: &[ChildIndex]) -> Self { - let mut xsk = master.clone(); - for &i in path.iter() { - xsk = xsk.derive_child(i); - } - xsk - } - - #[must_use] - pub fn derive_child(&self, i: ChildIndex) -> Self { - let fvk = FullViewingKey::from_expanded_spending_key(&self.expsk); - let tmp = match i { - ChildIndex::Hardened(i) => { - let mut le_i = [0; 4]; - LittleEndian::write_u32(&mut le_i, i + (1 << 31)); - prf_expand_vec( - &self.chain_code.0, - &[&[0x11], &self.expsk.to_bytes(), &self.dk.0, &le_i], - ) - } - ChildIndex::NonHardened(i) => { - let mut le_i = [0; 4]; - LittleEndian::write_u32(&mut le_i, i); - prf_expand_vec( - &self.chain_code.0, - &[&[0x12], &fvk.to_bytes(), &self.dk.0, &le_i], - ) - } - }; - let i_l = &tmp.as_bytes()[..32]; - let mut c_i = [0u8; 32]; - c_i.copy_from_slice(&tmp.as_bytes()[32..]); - - ExtendedSpendingKey { - depth: self.depth + 1, - parent_fvk_tag: FvkFingerprint::from(&fvk).tag(), - child_index: i, - chain_code: ChainCode(c_i), - expsk: { - let mut ask = jubjub::Fr::from_bytes_wide(prf_expand(i_l, &[0x13]).as_array()); - let mut nsk = jubjub::Fr::from_bytes_wide(prf_expand(i_l, &[0x14]).as_array()); - ask.add_assign(&self.expsk.ask); - nsk.add_assign(&self.expsk.nsk); - let ovk = derive_child_ovk(&self.expsk.ovk, i_l); - ExpandedSpendingKey { ask, nsk, ovk } - }, - dk: self.dk.derive_child(i_l), - } - } - - /// Returns the address with the lowest valid diversifier index, along with - /// the diversifier index that generated that address. - pub fn default_address(&self) -> (DiversifierIndex, PaymentAddress) { - ExtendedFullViewingKey::from(self).default_address() - } - - /// Derives an internal spending key given an external spending key. - /// - /// Specified in [ZIP 32](https://zips.z.cash/zip-0032#deriving-a-sapling-internal-spending-key). - #[must_use] - pub fn derive_internal(&self) -> Self { - let i = { - let fvk = FullViewingKey::from_expanded_spending_key(&self.expsk); - let mut h = Blake2bParams::new() - .hash_length(32) - .personal(crate::zip32::ZIP32_SAPLING_INT_PERSONALIZATION) - .to_state(); - h.update(&fvk.to_bytes()); - h.update(&self.dk.0); - h.finalize() - }; - let i_nsk = jubjub::Fr::from_bytes_wide(prf_expand(i.as_bytes(), &[0x17]).as_array()); - let r = prf_expand(i.as_bytes(), &[0x18]); - let r = r.as_bytes(); - let nsk_internal = i_nsk + self.expsk.nsk; - let dk_internal = DiversifierKey(r[..32].try_into().unwrap()); - let ovk_internal = OutgoingViewingKey(r[32..].try_into().unwrap()); - - ExtendedSpendingKey { - depth: self.depth, - parent_fvk_tag: self.parent_fvk_tag, - child_index: self.child_index, - chain_code: self.chain_code, - expsk: ExpandedSpendingKey { - ask: self.expsk.ask, - nsk: nsk_internal, - ovk: ovk_internal, - }, - dk: dk_internal, - } - } -} - -impl<'a> From<&'a ExtendedSpendingKey> for ExtendedFullViewingKey { - fn from(xsk: &ExtendedSpendingKey) -> Self { - ExtendedFullViewingKey { - depth: xsk.depth, - parent_fvk_tag: xsk.parent_fvk_tag, - child_index: xsk.child_index, - chain_code: xsk.chain_code, - fvk: FullViewingKey::from_expanded_spending_key(&xsk.expsk), - dk: xsk.dk, - } - } +/// A "scope" narrows the visibility or usage to a level below "full". +/// +/// Consistent usage of `Scope` enables the user to provide consistent views over a wallet +/// to other people. For example, a user can give an external [SaplingIvk] to a merchant +/// terminal, enabling it to only detect "real" transactions from customers and not +/// internal transactions from the wallet. +/// +/// [SaplingIvk]: crate::sapling::SaplingIvk +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum Scope { + /// A scope used for wallet-external operations, namely deriving addresses to give to + /// other users in order to receive funds. + External, + /// A scope used for wallet-internal operations, such as creating change notes, + /// auto-shielding, and note management. + Internal, } -impl ExtendedFullViewingKey { - pub fn read(mut reader: R) -> io::Result { - let depth = reader.read_u8()?; - let mut tag = [0; 4]; - reader.read_exact(&mut tag)?; - let i = reader.read_u32::()?; - let mut c = [0; 32]; - reader.read_exact(&mut c)?; - let fvk = FullViewingKey::read(&mut reader)?; - let mut dk = [0; 32]; - reader.read_exact(&mut dk)?; - - Ok(ExtendedFullViewingKey { - depth, - parent_fvk_tag: FvkTag(tag), - child_index: ChildIndex::from_index(i), - chain_code: ChainCode(c), - fvk, - dk: DiversifierKey(dk), - }) - } - - pub fn write(&self, mut writer: W) -> io::Result<()> { - writer.write_u8(self.depth)?; - writer.write_all(&self.parent_fvk_tag.0)?; - writer.write_u32::(self.child_index.value())?; - writer.write_all(&self.chain_code.0)?; - writer.write_all(&self.fvk.to_bytes())?; - writer.write_all(&self.dk.0)?; - - Ok(()) - } - - pub fn derive_child(&self, i: ChildIndex) -> Result { - let tmp = match i { - ChildIndex::Hardened(_) => return Err(()), - ChildIndex::NonHardened(i) => { - let mut le_i = [0; 4]; - LittleEndian::write_u32(&mut le_i, i); - prf_expand_vec( - &self.chain_code.0, - &[&[0x12], &self.fvk.to_bytes(), &self.dk.0, &le_i], - ) - } - }; - let i_l = &tmp.as_bytes()[..32]; - let mut c_i = [0u8; 32]; - c_i.copy_from_slice(&tmp.as_bytes()[32..]); - - Ok(ExtendedFullViewingKey { - depth: self.depth + 1, - parent_fvk_tag: FvkFingerprint::from(&self.fvk).tag(), - child_index: i, - chain_code: ChainCode(c_i), - fvk: { - let i_ask = jubjub::Fr::from_bytes_wide(prf_expand(i_l, &[0x13]).as_array()); - let i_nsk = jubjub::Fr::from_bytes_wide(prf_expand(i_l, &[0x14]).as_array()); - let ak = (SPENDING_KEY_GENERATOR * i_ask) + self.fvk.vk.ak; - let nk = (PROOF_GENERATION_KEY_GENERATOR * i_nsk) + self.fvk.vk.nk; - - FullViewingKey { - vk: ViewingKey { ak, nk }, - ovk: derive_child_ovk(&self.fvk.ovk, i_l), - } - }, - dk: self.dk.derive_child(i_l), - }) - } - - /// Attempt to produce a payment address given the specified diversifier - /// index, and return None if the specified index does not produce a valid - /// diversifier. - pub fn address(&self, j: DiversifierIndex) -> Option { - sapling_address(&self.fvk, &self.dk, j) - } - - /// Search the diversifier space starting at diversifier index `j` for - /// one which will produce a valid diversifier, and return the payment address - /// constructed using that diversifier along with the index at which the - /// valid diversifier was found. - pub fn find_address(&self, j: DiversifierIndex) -> Option<(DiversifierIndex, PaymentAddress)> { - sapling_find_address(&self.fvk, &self.dk, j) - } - - /// Returns the payment address corresponding to the smallest valid diversifier - /// index, along with that index. - pub fn default_address(&self) -> (DiversifierIndex, PaymentAddress) { - sapling_default_address(&self.fvk, &self.dk) - } - - /// Derives an internal full viewing key used for internal operations such - /// as change and auto-shielding. The internal FVK has the same spend authority - /// (the private key corresponding to ak) as the original, but viewing authority - /// only for internal transfers. - /// - /// Specified in [ZIP 32](https://zips.z.cash/zip-0032#deriving-a-sapling-internal-full-viewing-key). - #[must_use] - pub fn derive_internal(&self) -> Self { - let (fvk_internal, dk_internal) = sapling_derive_internal_fvk(&self.fvk, &self.dk); - - ExtendedFullViewingKey { - depth: self.depth, - parent_fvk_tag: self.parent_fvk_tag, - child_index: self.child_index, - chain_code: self.chain_code, - fvk: fvk_internal, - dk: dk_internal, - } - } -} +memuse::impl_no_dynamic_usage!(Scope); #[cfg(test)] mod tests { - use super::*; - - use ff::PrimeField; - use group::GroupEncoding; - - #[test] - fn derive_nonhardened_child() { - let seed = [0; 32]; - let xsk_m = ExtendedSpendingKey::master(&seed); - let xfvk_m = ExtendedFullViewingKey::from(&xsk_m); - - let i_5 = ChildIndex::NonHardened(5); - let xsk_5 = xsk_m.derive_child(i_5); - let xfvk_5 = xfvk_m.derive_child(i_5); - - assert!(xfvk_5.is_ok()); - assert_eq!(ExtendedFullViewingKey::from(&xsk_5), xfvk_5.unwrap()); - } - - #[test] - fn derive_hardened_child() { - let seed = [0; 32]; - let xsk_m = ExtendedSpendingKey::master(&seed); - let xfvk_m = ExtendedFullViewingKey::from(&xsk_m); - - let i_5h = ChildIndex::Hardened(5); - let xsk_5h = xsk_m.derive_child(i_5h); - let xfvk_5h = xfvk_m.derive_child(i_5h); - - // Cannot derive a hardened child from an ExtendedFullViewingKey - assert!(xfvk_5h.is_err()); - let xfvk_5h = ExtendedFullViewingKey::from(&xsk_5h); - - let i_7 = ChildIndex::NonHardened(7); - let xsk_5h_7 = xsk_5h.derive_child(i_7); - let xfvk_5h_7 = xfvk_5h.derive_child(i_7); - - // But we *can* derive a non-hardened child from a hardened parent - assert!(xfvk_5h_7.is_ok()); - assert_eq!(ExtendedFullViewingKey::from(&xsk_5h_7), xfvk_5h_7.unwrap()); - } - - #[test] - fn path() { - let seed = [0; 32]; - let xsk_m = ExtendedSpendingKey::master(&seed); - - let xsk_5h = xsk_m.derive_child(ChildIndex::Hardened(5)); - assert_eq!( - ExtendedSpendingKey::from_path(&xsk_m, &[ChildIndex::Hardened(5)]), - xsk_5h - ); - - let xsk_5h_7 = xsk_5h.derive_child(ChildIndex::NonHardened(7)); - assert_eq!( - ExtendedSpendingKey::from_path( - &xsk_m, - &[ChildIndex::Hardened(5), ChildIndex::NonHardened(7)] - ), - xsk_5h_7 - ); - } - - #[test] - fn diversifier() { - let dk = DiversifierKey([0; 32]); - let j_0 = DiversifierIndex::new(); - let j_1 = DiversifierIndex([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); - let j_2 = DiversifierIndex([2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); - let j_3 = DiversifierIndex([3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); - // Computed using this Rust implementation - let d_0 = [220, 231, 126, 188, 236, 10, 38, 175, 214, 153, 140]; - let d_3 = [60, 253, 170, 8, 171, 147, 220, 31, 3, 144, 34]; - - // j = 0 - let d_j = dk.diversifier(j_0).unwrap(); - assert_eq!(d_j.0, d_0); - assert_eq!(dk.diversifier_index(&Diversifier(d_0)), j_0); - - // j = 1 - assert_eq!(dk.diversifier(j_1), None); - - // j = 2 - assert_eq!(dk.diversifier(j_2), None); - - // j = 3 - let d_j = dk.diversifier(j_3).unwrap(); - assert_eq!(d_j.0, d_3); - assert_eq!(dk.diversifier_index(&Diversifier(d_3)), j_3); - } - - #[test] - fn find_diversifier() { - let dk = DiversifierKey([0; 32]); - let j_0 = DiversifierIndex::new(); - let j_1 = DiversifierIndex([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); - let j_2 = DiversifierIndex([2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); - let j_3 = DiversifierIndex([3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); - // Computed using this Rust implementation - let d_0 = [220, 231, 126, 188, 236, 10, 38, 175, 214, 153, 140]; - let d_3 = [60, 253, 170, 8, 171, 147, 220, 31, 3, 144, 34]; - - // j = 0 - let (j, d_j) = dk.find_diversifier(j_0).unwrap(); - assert_eq!(j, j_0); - assert_eq!(d_j.0, d_0); - - // j = 1 - let (j, d_j) = dk.find_diversifier(j_1).unwrap(); - assert_eq!(j, j_3); - assert_eq!(d_j.0, d_3); - - // j = 2 - let (j, d_j) = dk.find_diversifier(j_2).unwrap(); - assert_eq!(j, j_3); - assert_eq!(d_j.0, d_3); - - // j = 3 - let (j, d_j) = dk.find_diversifier(j_3).unwrap(); - assert_eq!(j, j_3); - assert_eq!(d_j.0, d_3); - } + use super::DiversifierIndex; + use std::convert::TryFrom; #[test] - fn address() { - let seed = [0; 32]; - let xsk_m = ExtendedSpendingKey::master(&seed); - let xfvk_m = ExtendedFullViewingKey::from(&xsk_m); - let j_0 = DiversifierIndex::new(); - let addr_m = xfvk_m.address(j_0).unwrap(); - assert_eq!( - addr_m.diversifier().0, - // Computed using this Rust implementation - [1, 176, 125, 234, 196, 5, 225, 212, 95, 175, 239] - ); - - let j_1 = DiversifierIndex([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); - assert_eq!(xfvk_m.address(j_1), None); - } - #[test] - fn default_address() { - let seed = [0; 32]; - let xsk_m = ExtendedSpendingKey::master(&seed); - let (j_m, addr_m) = xsk_m.default_address(); - assert_eq!(j_m.0, [0; 11]); - assert_eq!( - addr_m.diversifier().0, - // Computed using ExtendedSpendingKey.master(bytes([0]*32)).diversifier(0) in sapling_zip32.py using MASP personalizations - [1, 176, 125, 234, 196, 5, 225, 212, 95, 175, 239] - ); - } - - #[test] - fn read_write() { - let seed = [0; 32]; - let xsk = ExtendedSpendingKey::master(&seed); - let fvk = ExtendedFullViewingKey::from(&xsk); - - let mut ser = vec![]; - xsk.write(&mut ser).unwrap(); - let xsk2 = ExtendedSpendingKey::read(&ser[..]).unwrap(); - assert_eq!(xsk2, xsk); - - let mut ser = vec![]; - fvk.write(&mut ser).unwrap(); - let fvk2 = ExtendedFullViewingKey::read(&ser[..]).unwrap(); - assert_eq!(fvk2, fvk); - } - - #[test] - fn test_vectors() { - struct TestVector { - ask: Option<[u8; 32]>, - nsk: Option<[u8; 32]>, - ovk: [u8; 32], - dk: [u8; 32], - c: [u8; 32], - ak: [u8; 32], - nk: [u8; 32], - ivk: [u8; 32], - xsk: Option<[u8; 169]>, - xfvk: [u8; 169], - fp: [u8; 32], - d0: Option<[u8; 11]>, - d1: Option<[u8; 11]>, - d2: Option<[u8; 11]>, - dmax: Option<[u8; 11]>, - } - - // From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/sapling_zip32.py - let test_vectors = vec![ - TestVector { - ask: Some([ - 0xac, 0x4d, 0xa2, 0xa5, 0xe0, 0xa5, 0xe3, 0xec, 0x2d, 0xcb, 0xd7, 0x04, 0xf1, - 0xb0, 0x8d, 0x85, 0x0f, 0xe1, 0x40, 0xea, 0x61, 0x07, 0x2c, 0xe3, 0xf8, 0x70, - 0xe2, 0x70, 0xae, 0xcd, 0x8f, 0x05, - ]), - nsk: Some([ - 0x47, 0x29, 0x3f, 0xb1, 0xe9, 0x3a, 0x86, 0x63, 0xf9, 0xa9, 0x12, 0x56, 0x52, - 0xb6, 0xdc, 0x3d, 0x56, 0x17, 0x89, 0xc0, 0x3b, 0x67, 0x4a, 0x4c, 0xc7, 0x38, - 0xa9, 0x24, 0x9a, 0xaf, 0x08, 0x09, - ]), - ovk: [ - 0xcf, 0x6b, 0xed, 0xb6, 0xc5, 0x49, 0x4e, 0xba, 0xb7, 0x7f, 0x58, 0xa8, 0x57, - 0x35, 0x59, 0xc5, 0xd2, 0x68, 0x3a, 0x25, 0x22, 0x46, 0x49, 0xcb, 0x8d, 0x44, - 0x80, 0xe8, 0xa0, 0x54, 0x58, 0xd6, - ], - dk: [ - 0xab, 0xcb, 0x9e, 0x0a, 0x9b, 0xb0, 0x77, 0xb4, 0x34, 0x50, 0x68, 0x96, 0xde, - 0x92, 0x9a, 0x7a, 0xc3, 0x7f, 0xea, 0xa8, 0x1b, 0xec, 0x17, 0xe0, 0x3b, 0x60, - 0xd0, 0x60, 0x5e, 0xf7, 0xbc, 0x42, - ], - c: [ - 0xe4, 0xca, 0x49, 0x8a, 0x73, 0x59, 0x2a, 0x72, 0xc6, 0x0c, 0x2e, 0x61, 0x1e, - 0x79, 0x70, 0x2f, 0x9d, 0xc0, 0x17, 0x60, 0x23, 0x01, 0xdc, 0xb5, 0xcc, 0x3d, - 0xf0, 0x1d, 0x5c, 0xc0, 0xf0, 0x67, - ], - ak: [ - 0xf6, 0x5d, 0x7b, 0x4a, 0xb9, 0x71, 0x5c, 0x07, 0xc6, 0xb7, 0x8b, 0xd8, 0x22, - 0xac, 0x39, 0xa7, 0x84, 0x81, 0xeb, 0x36, 0x07, 0x9d, 0x06, 0xdc, 0x86, 0x79, - 0xda, 0xab, 0xab, 0x92, 0x00, 0x55, - ], - nk: [ - 0x2b, 0x41, 0x55, 0x3f, 0x32, 0xa2, 0xb6, 0x60, 0xe1, 0x72, 0x6c, 0x31, 0x33, - 0x19, 0xd3, 0x55, 0x33, 0x16, 0x6c, 0xcf, 0x52, 0xc1, 0x5a, 0xc2, 0x3c, 0xbd, - 0xe3, 0xd2, 0x0d, 0x55, 0xcb, 0x01, - ], - ivk: [ - 0x8c, 0x90, 0xb7, 0x87, 0x36, 0x4d, 0xd1, 0x29, 0x11, 0xb6, 0x4b, 0x1e, 0xbf, - 0x8b, 0xfc, 0x04, 0xbd, 0xc5, 0x5f, 0x97, 0xae, 0x85, 0x1e, 0xb3, 0x96, 0x27, - 0x75, 0x56, 0x42, 0x42, 0xa1, 0x02, - ], - xsk: Some([ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe4, 0xca, 0x49, 0x8a, - 0x73, 0x59, 0x2a, 0x72, 0xc6, 0x0c, 0x2e, 0x61, 0x1e, 0x79, 0x70, 0x2f, 0x9d, - 0xc0, 0x17, 0x60, 0x23, 0x01, 0xdc, 0xb5, 0xcc, 0x3d, 0xf0, 0x1d, 0x5c, 0xc0, - 0xf0, 0x67, 0xac, 0x4d, 0xa2, 0xa5, 0xe0, 0xa5, 0xe3, 0xec, 0x2d, 0xcb, 0xd7, - 0x04, 0xf1, 0xb0, 0x8d, 0x85, 0x0f, 0xe1, 0x40, 0xea, 0x61, 0x07, 0x2c, 0xe3, - 0xf8, 0x70, 0xe2, 0x70, 0xae, 0xcd, 0x8f, 0x05, 0x47, 0x29, 0x3f, 0xb1, 0xe9, - 0x3a, 0x86, 0x63, 0xf9, 0xa9, 0x12, 0x56, 0x52, 0xb6, 0xdc, 0x3d, 0x56, 0x17, - 0x89, 0xc0, 0x3b, 0x67, 0x4a, 0x4c, 0xc7, 0x38, 0xa9, 0x24, 0x9a, 0xaf, 0x08, - 0x09, 0xcf, 0x6b, 0xed, 0xb6, 0xc5, 0x49, 0x4e, 0xba, 0xb7, 0x7f, 0x58, 0xa8, - 0x57, 0x35, 0x59, 0xc5, 0xd2, 0x68, 0x3a, 0x25, 0x22, 0x46, 0x49, 0xcb, 0x8d, - 0x44, 0x80, 0xe8, 0xa0, 0x54, 0x58, 0xd6, 0xab, 0xcb, 0x9e, 0x0a, 0x9b, 0xb0, - 0x77, 0xb4, 0x34, 0x50, 0x68, 0x96, 0xde, 0x92, 0x9a, 0x7a, 0xc3, 0x7f, 0xea, - 0xa8, 0x1b, 0xec, 0x17, 0xe0, 0x3b, 0x60, 0xd0, 0x60, 0x5e, 0xf7, 0xbc, 0x42, - ]), - xfvk: [ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe4, 0xca, 0x49, 0x8a, - 0x73, 0x59, 0x2a, 0x72, 0xc6, 0x0c, 0x2e, 0x61, 0x1e, 0x79, 0x70, 0x2f, 0x9d, - 0xc0, 0x17, 0x60, 0x23, 0x01, 0xdc, 0xb5, 0xcc, 0x3d, 0xf0, 0x1d, 0x5c, 0xc0, - 0xf0, 0x67, 0xf6, 0x5d, 0x7b, 0x4a, 0xb9, 0x71, 0x5c, 0x07, 0xc6, 0xb7, 0x8b, - 0xd8, 0x22, 0xac, 0x39, 0xa7, 0x84, 0x81, 0xeb, 0x36, 0x07, 0x9d, 0x06, 0xdc, - 0x86, 0x79, 0xda, 0xab, 0xab, 0x92, 0x00, 0x55, 0x2b, 0x41, 0x55, 0x3f, 0x32, - 0xa2, 0xb6, 0x60, 0xe1, 0x72, 0x6c, 0x31, 0x33, 0x19, 0xd3, 0x55, 0x33, 0x16, - 0x6c, 0xcf, 0x52, 0xc1, 0x5a, 0xc2, 0x3c, 0xbd, 0xe3, 0xd2, 0x0d, 0x55, 0xcb, - 0x01, 0xcf, 0x6b, 0xed, 0xb6, 0xc5, 0x49, 0x4e, 0xba, 0xb7, 0x7f, 0x58, 0xa8, - 0x57, 0x35, 0x59, 0xc5, 0xd2, 0x68, 0x3a, 0x25, 0x22, 0x46, 0x49, 0xcb, 0x8d, - 0x44, 0x80, 0xe8, 0xa0, 0x54, 0x58, 0xd6, 0xab, 0xcb, 0x9e, 0x0a, 0x9b, 0xb0, - 0x77, 0xb4, 0x34, 0x50, 0x68, 0x96, 0xde, 0x92, 0x9a, 0x7a, 0xc3, 0x7f, 0xea, - 0xa8, 0x1b, 0xec, 0x17, 0xe0, 0x3b, 0x60, 0xd0, 0x60, 0x5e, 0xf7, 0xbc, 0x42, - ], - fp: [ - 0x17, 0x27, 0x55, 0xf6, 0x51, 0x82, 0xb4, 0xe4, 0x32, 0x12, 0xe2, 0xe6, 0x4f, - 0x73, 0xbe, 0xc7, 0x43, 0xd3, 0xa6, 0xbd, 0x75, 0xaf, 0x08, 0xfe, 0xaa, 0x2d, - 0x6d, 0x65, 0x02, 0x31, 0xdc, 0xb3, - ], - d0: Some([ - 0x99, 0x3f, 0x45, 0x5b, 0x74, 0x15, 0x9e, 0x49, 0xf9, 0xcf, 0x33, - ]), - d1: None, - d2: None, - dmax: Some([ - 0x50, 0xac, 0x45, 0xb9, 0x79, 0xa1, 0x7d, 0x83, 0xa7, 0x49, 0xea, - ]), - }, - TestVector { - ask: Some([ - 0x39, 0xc1, 0x95, 0x8c, 0x62, 0x11, 0x2e, 0x41, 0x35, 0xa2, 0x66, 0xe5, 0x4e, - 0x92, 0x1b, 0x13, 0xd7, 0xd9, 0x81, 0x43, 0x6e, 0x7f, 0x7a, 0x8c, 0x03, 0xf0, - 0xd5, 0xb8, 0x2e, 0x57, 0x09, 0x0a, - ]), - nsk: Some([ - 0x3b, 0x42, 0x80, 0x25, 0x1e, 0x66, 0x9e, 0xb7, 0xcd, 0x81, 0xe4, 0x52, 0xed, - 0x95, 0x5e, 0x82, 0xe7, 0xae, 0x02, 0x7c, 0x33, 0x21, 0x82, 0x7c, 0x58, 0x8c, - 0x91, 0xec, 0xad, 0x56, 0xce, 0x00, - ]), - ovk: [ - 0x0c, 0x7b, 0xf0, 0x2a, 0x34, 0xc8, 0x02, 0x81, 0x8f, 0xee, 0xf8, 0x8b, 0x17, - 0x92, 0x7d, 0xfe, 0xb1, 0x6c, 0x36, 0xea, 0x0b, 0x3b, 0x49, 0xe6, 0x49, 0xb4, - 0x05, 0x51, 0x13, 0xe7, 0xa2, 0xfb, - ], - dk: [ - 0x13, 0x8d, 0x73, 0x3b, 0xa4, 0x20, 0x50, 0x4b, 0xa3, 0x04, 0x3b, 0x26, 0x80, - 0x4d, 0x69, 0x4c, 0x5c, 0x7a, 0x07, 0xc8, 0xb2, 0x85, 0x43, 0xfd, 0x25, 0xab, - 0x69, 0xa7, 0x00, 0x7f, 0xd9, 0xe0, - ], - c: [ - 0xb9, 0x7e, 0x35, 0x12, 0x19, 0x50, 0x9a, 0xba, 0x1a, 0xb6, 0x3d, 0xfe, 0xdc, - 0x6e, 0x4b, 0x68, 0x17, 0x60, 0x6e, 0xc3, 0xe1, 0xac, 0x96, 0x51, 0x42, 0xa1, - 0x90, 0x7b, 0x50, 0xe4, 0x95, 0xfc, - ], - ak: [ - 0x82, 0xf1, 0x67, 0x79, 0xcb, 0xf9, 0xad, 0x9a, 0x3d, 0xb2, 0xff, 0x07, 0xea, - 0x4e, 0xbc, 0x15, 0x9d, 0x0a, 0x31, 0x42, 0x46, 0xbe, 0xd6, 0x39, 0x39, 0x34, - 0xe1, 0x22, 0x0a, 0xcc, 0xa9, 0x14, - ], - nk: [ - 0x97, 0x14, 0x52, 0xc9, 0x62, 0x54, 0xff, 0xa1, 0xed, 0xe7, 0xad, 0x1e, 0x5b, - 0x66, 0x3e, 0x70, 0x53, 0x1a, 0x8b, 0xfb, 0x1e, 0x91, 0x63, 0x8d, 0xdc, 0x58, - 0xab, 0xb8, 0xb9, 0x25, 0x48, 0xd2, - ], - ivk: [ - 0xcf, 0xa2, 0x2b, 0xb7, 0x3c, 0xc3, 0x66, 0x7c, 0x2f, 0x3b, 0xb4, 0xdc, 0x6f, - 0x33, 0xde, 0xe6, 0x9c, 0x4d, 0x51, 0xde, 0x5c, 0x25, 0x52, 0x68, 0x7e, 0x18, - 0xcd, 0x26, 0x78, 0xc9, 0xf7, 0x00, - ], - xsk: Some([ - 0x01, 0x17, 0x27, 0x55, 0xf6, 0x01, 0x00, 0x00, 0x00, 0xb9, 0x7e, 0x35, 0x12, - 0x19, 0x50, 0x9a, 0xba, 0x1a, 0xb6, 0x3d, 0xfe, 0xdc, 0x6e, 0x4b, 0x68, 0x17, - 0x60, 0x6e, 0xc3, 0xe1, 0xac, 0x96, 0x51, 0x42, 0xa1, 0x90, 0x7b, 0x50, 0xe4, - 0x95, 0xfc, 0x39, 0xc1, 0x95, 0x8c, 0x62, 0x11, 0x2e, 0x41, 0x35, 0xa2, 0x66, - 0xe5, 0x4e, 0x92, 0x1b, 0x13, 0xd7, 0xd9, 0x81, 0x43, 0x6e, 0x7f, 0x7a, 0x8c, - 0x03, 0xf0, 0xd5, 0xb8, 0x2e, 0x57, 0x09, 0x0a, 0x3b, 0x42, 0x80, 0x25, 0x1e, - 0x66, 0x9e, 0xb7, 0xcd, 0x81, 0xe4, 0x52, 0xed, 0x95, 0x5e, 0x82, 0xe7, 0xae, - 0x02, 0x7c, 0x33, 0x21, 0x82, 0x7c, 0x58, 0x8c, 0x91, 0xec, 0xad, 0x56, 0xce, - 0x00, 0x0c, 0x7b, 0xf0, 0x2a, 0x34, 0xc8, 0x02, 0x81, 0x8f, 0xee, 0xf8, 0x8b, - 0x17, 0x92, 0x7d, 0xfe, 0xb1, 0x6c, 0x36, 0xea, 0x0b, 0x3b, 0x49, 0xe6, 0x49, - 0xb4, 0x05, 0x51, 0x13, 0xe7, 0xa2, 0xfb, 0x13, 0x8d, 0x73, 0x3b, 0xa4, 0x20, - 0x50, 0x4b, 0xa3, 0x04, 0x3b, 0x26, 0x80, 0x4d, 0x69, 0x4c, 0x5c, 0x7a, 0x07, - 0xc8, 0xb2, 0x85, 0x43, 0xfd, 0x25, 0xab, 0x69, 0xa7, 0x00, 0x7f, 0xd9, 0xe0, - ]), - xfvk: [ - 0x01, 0x17, 0x27, 0x55, 0xf6, 0x01, 0x00, 0x00, 0x00, 0xb9, 0x7e, 0x35, 0x12, - 0x19, 0x50, 0x9a, 0xba, 0x1a, 0xb6, 0x3d, 0xfe, 0xdc, 0x6e, 0x4b, 0x68, 0x17, - 0x60, 0x6e, 0xc3, 0xe1, 0xac, 0x96, 0x51, 0x42, 0xa1, 0x90, 0x7b, 0x50, 0xe4, - 0x95, 0xfc, 0x82, 0xf1, 0x67, 0x79, 0xcb, 0xf9, 0xad, 0x9a, 0x3d, 0xb2, 0xff, - 0x07, 0xea, 0x4e, 0xbc, 0x15, 0x9d, 0x0a, 0x31, 0x42, 0x46, 0xbe, 0xd6, 0x39, - 0x39, 0x34, 0xe1, 0x22, 0x0a, 0xcc, 0xa9, 0x14, 0x97, 0x14, 0x52, 0xc9, 0x62, - 0x54, 0xff, 0xa1, 0xed, 0xe7, 0xad, 0x1e, 0x5b, 0x66, 0x3e, 0x70, 0x53, 0x1a, - 0x8b, 0xfb, 0x1e, 0x91, 0x63, 0x8d, 0xdc, 0x58, 0xab, 0xb8, 0xb9, 0x25, 0x48, - 0xd2, 0x0c, 0x7b, 0xf0, 0x2a, 0x34, 0xc8, 0x02, 0x81, 0x8f, 0xee, 0xf8, 0x8b, - 0x17, 0x92, 0x7d, 0xfe, 0xb1, 0x6c, 0x36, 0xea, 0x0b, 0x3b, 0x49, 0xe6, 0x49, - 0xb4, 0x05, 0x51, 0x13, 0xe7, 0xa2, 0xfb, 0x13, 0x8d, 0x73, 0x3b, 0xa4, 0x20, - 0x50, 0x4b, 0xa3, 0x04, 0x3b, 0x26, 0x80, 0x4d, 0x69, 0x4c, 0x5c, 0x7a, 0x07, - 0xc8, 0xb2, 0x85, 0x43, 0xfd, 0x25, 0xab, 0x69, 0xa7, 0x00, 0x7f, 0xd9, 0xe0, - ], - fp: [ - 0xe5, 0x1f, 0x7b, 0xd0, 0x24, 0x36, 0x88, 0xe3, 0xa7, 0x5f, 0x09, 0xf3, 0x5e, - 0xe8, 0xee, 0xbc, 0xad, 0x30, 0x69, 0x88, 0xed, 0xb3, 0x80, 0x9f, 0x76, 0xd6, - 0xd4, 0xbb, 0x53, 0xb6, 0x3f, 0x7c, - ], - d0: None, - d1: Some([ - 0x42, 0xce, 0x67, 0xa3, 0x2d, 0x00, 0xe3, 0xb8, 0xfb, 0x05, 0x13, - ]), - d2: None, - dmax: Some([ - 0xbc, 0x15, 0x9c, 0x91, 0xe7, 0xab, 0x50, 0xb2, 0x52, 0x91, 0x03, - ]), - }, - TestVector { - ask: Some([ - 0xe3, 0x78, 0xd4, 0x24, 0x13, 0x88, 0x99, 0x46, 0xa2, 0x3e, 0x4c, 0x1b, 0x79, - 0x0e, 0x5d, 0xde, 0xbc, 0xce, 0x31, 0x5f, 0xdc, 0x87, 0xe4, 0x69, 0xfe, 0x21, - 0xd6, 0x39, 0xf2, 0x82, 0x06, 0x0b, - ]), - nsk: Some([ - 0x29, 0x6d, 0x06, 0xb9, 0xda, 0xf7, 0x9d, 0x33, 0xbf, 0xac, 0x3d, 0xaa, 0x13, - 0x28, 0x3a, 0xd8, 0x0e, 0xf9, 0xb7, 0xc2, 0xab, 0xa2, 0x0b, 0x0b, 0x22, 0x8c, - 0xc8, 0x33, 0x0c, 0x8d, 0x70, 0x03, - ]), - ovk: [ - 0xb1, 0x62, 0x15, 0x54, 0x71, 0x8f, 0xbe, 0xc3, 0xac, 0x3d, 0xb9, 0x4d, 0x23, - 0xfe, 0x16, 0xd5, 0xbb, 0x13, 0x7f, 0xe3, 0x24, 0xb8, 0x53, 0xa5, 0xa0, 0xee, - 0xf3, 0x36, 0x23, 0x98, 0x75, 0x4e, - ], - dk: [ - 0xad, 0xf8, 0xd1, 0xba, 0x74, 0xf4, 0xdf, 0xdd, 0xe6, 0xb0, 0x44, 0x37, 0x94, - 0x74, 0xaa, 0xc3, 0xc8, 0xef, 0x00, 0x3e, 0xce, 0xe7, 0x14, 0xdd, 0xcf, 0x4c, - 0x94, 0x7c, 0xa7, 0x2a, 0xeb, 0xa2, - ], - c: [ - 0xdb, 0xaa, 0x2d, 0xde, 0xd8, 0x6b, 0xdb, 0x32, 0xfd, 0x60, 0x5b, 0x5e, 0xa0, - 0xdb, 0x6a, 0x57, 0xa5, 0xb3, 0x3b, 0x36, 0x20, 0x94, 0x8f, 0x76, 0x9c, 0x12, - 0x85, 0x88, 0x51, 0xeb, 0x83, 0xca, - ], - ak: [ - 0x42, 0xec, 0x8b, 0x50, 0x8d, 0xbb, 0x9a, 0x6d, 0x4a, 0x58, 0xf1, 0xb7, 0xcb, - 0x96, 0x06, 0xfd, 0x75, 0xdd, 0x1c, 0x0d, 0x03, 0x9c, 0x2c, 0xac, 0x19, 0xb2, - 0x66, 0x52, 0xcb, 0x3b, 0x27, 0xcd, - ], - nk: [ - 0x4f, 0x60, 0x3c, 0x21, 0x05, 0xa4, 0x0f, 0x4f, 0xc3, 0xdf, 0x19, 0x76, 0x18, - 0x25, 0x7c, 0xa0, 0xfc, 0x4e, 0x8b, 0x73, 0x39, 0xd4, 0x80, 0xcd, 0x73, 0xa1, - 0x08, 0x38, 0xe5, 0xcd, 0x9d, 0x0f, - ], - ivk: [ - 0xc5, 0xb1, 0x73, 0x5b, 0xf7, 0xd2, 0xd7, 0x1d, 0x8e, 0x1f, 0x91, 0x62, 0xaf, - 0x7c, 0x96, 0xb5, 0x3e, 0x95, 0xa2, 0xdd, 0x12, 0x55, 0x27, 0x4a, 0xf6, 0x2d, - 0x3a, 0x78, 0xf6, 0xd7, 0x4e, 0x05, - ], - xsk: Some([ - 0x02, 0xe5, 0x1f, 0x7b, 0xd0, 0x02, 0x00, 0x00, 0x80, 0xdb, 0xaa, 0x2d, 0xde, - 0xd8, 0x6b, 0xdb, 0x32, 0xfd, 0x60, 0x5b, 0x5e, 0xa0, 0xdb, 0x6a, 0x57, 0xa5, - 0xb3, 0x3b, 0x36, 0x20, 0x94, 0x8f, 0x76, 0x9c, 0x12, 0x85, 0x88, 0x51, 0xeb, - 0x83, 0xca, 0xe3, 0x78, 0xd4, 0x24, 0x13, 0x88, 0x99, 0x46, 0xa2, 0x3e, 0x4c, - 0x1b, 0x79, 0x0e, 0x5d, 0xde, 0xbc, 0xce, 0x31, 0x5f, 0xdc, 0x87, 0xe4, 0x69, - 0xfe, 0x21, 0xd6, 0x39, 0xf2, 0x82, 0x06, 0x0b, 0x29, 0x6d, 0x06, 0xb9, 0xda, - 0xf7, 0x9d, 0x33, 0xbf, 0xac, 0x3d, 0xaa, 0x13, 0x28, 0x3a, 0xd8, 0x0e, 0xf9, - 0xb7, 0xc2, 0xab, 0xa2, 0x0b, 0x0b, 0x22, 0x8c, 0xc8, 0x33, 0x0c, 0x8d, 0x70, - 0x03, 0xb1, 0x62, 0x15, 0x54, 0x71, 0x8f, 0xbe, 0xc3, 0xac, 0x3d, 0xb9, 0x4d, - 0x23, 0xfe, 0x16, 0xd5, 0xbb, 0x13, 0x7f, 0xe3, 0x24, 0xb8, 0x53, 0xa5, 0xa0, - 0xee, 0xf3, 0x36, 0x23, 0x98, 0x75, 0x4e, 0xad, 0xf8, 0xd1, 0xba, 0x74, 0xf4, - 0xdf, 0xdd, 0xe6, 0xb0, 0x44, 0x37, 0x94, 0x74, 0xaa, 0xc3, 0xc8, 0xef, 0x00, - 0x3e, 0xce, 0xe7, 0x14, 0xdd, 0xcf, 0x4c, 0x94, 0x7c, 0xa7, 0x2a, 0xeb, 0xa2, - ]), - xfvk: [ - 0x02, 0xe5, 0x1f, 0x7b, 0xd0, 0x02, 0x00, 0x00, 0x80, 0xdb, 0xaa, 0x2d, 0xde, - 0xd8, 0x6b, 0xdb, 0x32, 0xfd, 0x60, 0x5b, 0x5e, 0xa0, 0xdb, 0x6a, 0x57, 0xa5, - 0xb3, 0x3b, 0x36, 0x20, 0x94, 0x8f, 0x76, 0x9c, 0x12, 0x85, 0x88, 0x51, 0xeb, - 0x83, 0xca, 0x42, 0xec, 0x8b, 0x50, 0x8d, 0xbb, 0x9a, 0x6d, 0x4a, 0x58, 0xf1, - 0xb7, 0xcb, 0x96, 0x06, 0xfd, 0x75, 0xdd, 0x1c, 0x0d, 0x03, 0x9c, 0x2c, 0xac, - 0x19, 0xb2, 0x66, 0x52, 0xcb, 0x3b, 0x27, 0xcd, 0x4f, 0x60, 0x3c, 0x21, 0x05, - 0xa4, 0x0f, 0x4f, 0xc3, 0xdf, 0x19, 0x76, 0x18, 0x25, 0x7c, 0xa0, 0xfc, 0x4e, - 0x8b, 0x73, 0x39, 0xd4, 0x80, 0xcd, 0x73, 0xa1, 0x08, 0x38, 0xe5, 0xcd, 0x9d, - 0x0f, 0xb1, 0x62, 0x15, 0x54, 0x71, 0x8f, 0xbe, 0xc3, 0xac, 0x3d, 0xb9, 0x4d, - 0x23, 0xfe, 0x16, 0xd5, 0xbb, 0x13, 0x7f, 0xe3, 0x24, 0xb8, 0x53, 0xa5, 0xa0, - 0xee, 0xf3, 0x36, 0x23, 0x98, 0x75, 0x4e, 0xad, 0xf8, 0xd1, 0xba, 0x74, 0xf4, - 0xdf, 0xdd, 0xe6, 0xb0, 0x44, 0x37, 0x94, 0x74, 0xaa, 0xc3, 0xc8, 0xef, 0x00, - 0x3e, 0xce, 0xe7, 0x14, 0xdd, 0xcf, 0x4c, 0x94, 0x7c, 0xa7, 0x2a, 0xeb, 0xa2, - ], - fp: [ - 0xe1, 0x61, 0xbc, 0xa7, 0x4c, 0xac, 0x0b, 0xbd, 0x66, 0xb4, 0xa4, 0xad, 0x12, - 0x71, 0x32, 0x11, 0x60, 0x52, 0xef, 0xf7, 0x65, 0x96, 0x67, 0xd9, 0xf7, 0xfd, - 0xad, 0xd0, 0x1f, 0x10, 0x08, 0xa1, - ], - d0: Some([ - 0x18, 0x36, 0xc0, 0x6f, 0x69, 0x94, 0x47, 0x49, 0xaa, 0x48, 0x0b, - ]), - d1: None, - d2: None, - dmax: Some([ - 0x63, 0xea, 0x9f, 0xbb, 0x99, 0x95, 0xc9, 0x39, 0x7a, 0xc2, 0x23, - ]), - }, - TestVector { - ask: None, - nsk: None, - ovk: [ - 0xb1, 0x62, 0x15, 0x54, 0x71, 0x8f, 0xbe, 0xc3, 0xac, 0x3d, 0xb9, 0x4d, 0x23, - 0xfe, 0x16, 0xd5, 0xbb, 0x13, 0x7f, 0xe3, 0x24, 0xb8, 0x53, 0xa5, 0xa0, 0xee, - 0xf3, 0x36, 0x23, 0x98, 0x75, 0x4e, - ], - dk: [ - 0xad, 0xf8, 0xd1, 0xba, 0x74, 0xf4, 0xdf, 0xdd, 0xe6, 0xb0, 0x44, 0x37, 0x94, - 0x74, 0xaa, 0xc3, 0xc8, 0xef, 0x00, 0x3e, 0xce, 0xe7, 0x14, 0xdd, 0xcf, 0x4c, - 0x94, 0x7c, 0xa7, 0x2a, 0xeb, 0xa2, - ], - c: [ - 0xdb, 0xaa, 0x2d, 0xde, 0xd8, 0x6b, 0xdb, 0x32, 0xfd, 0x60, 0x5b, 0x5e, 0xa0, - 0xdb, 0x6a, 0x57, 0xa5, 0xb3, 0x3b, 0x36, 0x20, 0x94, 0x8f, 0x76, 0x9c, 0x12, - 0x85, 0x88, 0x51, 0xeb, 0x83, 0xca, - ], - ak: [ - 0x42, 0xec, 0x8b, 0x50, 0x8d, 0xbb, 0x9a, 0x6d, 0x4a, 0x58, 0xf1, 0xb7, 0xcb, - 0x96, 0x06, 0xfd, 0x75, 0xdd, 0x1c, 0x0d, 0x03, 0x9c, 0x2c, 0xac, 0x19, 0xb2, - 0x66, 0x52, 0xcb, 0x3b, 0x27, 0xcd, - ], - nk: [ - 0x4f, 0x60, 0x3c, 0x21, 0x05, 0xa4, 0x0f, 0x4f, 0xc3, 0xdf, 0x19, 0x76, 0x18, - 0x25, 0x7c, 0xa0, 0xfc, 0x4e, 0x8b, 0x73, 0x39, 0xd4, 0x80, 0xcd, 0x73, 0xa1, - 0x08, 0x38, 0xe5, 0xcd, 0x9d, 0x0f, - ], - ivk: [ - 0xc5, 0xb1, 0x73, 0x5b, 0xf7, 0xd2, 0xd7, 0x1d, 0x8e, 0x1f, 0x91, 0x62, 0xaf, - 0x7c, 0x96, 0xb5, 0x3e, 0x95, 0xa2, 0xdd, 0x12, 0x55, 0x27, 0x4a, 0xf6, 0x2d, - 0x3a, 0x78, 0xf6, 0xd7, 0x4e, 0x05, - ], - xsk: None, - xfvk: [ - 0x02, 0xe5, 0x1f, 0x7b, 0xd0, 0x02, 0x00, 0x00, 0x80, 0xdb, 0xaa, 0x2d, 0xde, - 0xd8, 0x6b, 0xdb, 0x32, 0xfd, 0x60, 0x5b, 0x5e, 0xa0, 0xdb, 0x6a, 0x57, 0xa5, - 0xb3, 0x3b, 0x36, 0x20, 0x94, 0x8f, 0x76, 0x9c, 0x12, 0x85, 0x88, 0x51, 0xeb, - 0x83, 0xca, 0x42, 0xec, 0x8b, 0x50, 0x8d, 0xbb, 0x9a, 0x6d, 0x4a, 0x58, 0xf1, - 0xb7, 0xcb, 0x96, 0x06, 0xfd, 0x75, 0xdd, 0x1c, 0x0d, 0x03, 0x9c, 0x2c, 0xac, - 0x19, 0xb2, 0x66, 0x52, 0xcb, 0x3b, 0x27, 0xcd, 0x4f, 0x60, 0x3c, 0x21, 0x05, - 0xa4, 0x0f, 0x4f, 0xc3, 0xdf, 0x19, 0x76, 0x18, 0x25, 0x7c, 0xa0, 0xfc, 0x4e, - 0x8b, 0x73, 0x39, 0xd4, 0x80, 0xcd, 0x73, 0xa1, 0x08, 0x38, 0xe5, 0xcd, 0x9d, - 0x0f, 0xb1, 0x62, 0x15, 0x54, 0x71, 0x8f, 0xbe, 0xc3, 0xac, 0x3d, 0xb9, 0x4d, - 0x23, 0xfe, 0x16, 0xd5, 0xbb, 0x13, 0x7f, 0xe3, 0x24, 0xb8, 0x53, 0xa5, 0xa0, - 0xee, 0xf3, 0x36, 0x23, 0x98, 0x75, 0x4e, 0xad, 0xf8, 0xd1, 0xba, 0x74, 0xf4, - 0xdf, 0xdd, 0xe6, 0xb0, 0x44, 0x37, 0x94, 0x74, 0xaa, 0xc3, 0xc8, 0xef, 0x00, - 0x3e, 0xce, 0xe7, 0x14, 0xdd, 0xcf, 0x4c, 0x94, 0x7c, 0xa7, 0x2a, 0xeb, 0xa2, - ], - fp: [ - 0xe1, 0x61, 0xbc, 0xa7, 0x4c, 0xac, 0x0b, 0xbd, 0x66, 0xb4, 0xa4, 0xad, 0x12, - 0x71, 0x32, 0x11, 0x60, 0x52, 0xef, 0xf7, 0x65, 0x96, 0x67, 0xd9, 0xf7, 0xfd, - 0xad, 0xd0, 0x1f, 0x10, 0x08, 0xa1, - ], - d0: Some([ - 0x18, 0x36, 0xc0, 0x6f, 0x69, 0x94, 0x47, 0x49, 0xaa, 0x48, 0x0b, - ]), - d1: None, - d2: None, - dmax: Some([ - 0x63, 0xea, 0x9f, 0xbb, 0x99, 0x95, 0xc9, 0x39, 0x7a, 0xc2, 0x23, - ]), - }, - TestVector { - ask: None, - nsk: None, - ovk: [ - 0x83, 0x55, 0xaa, 0x44, 0x4f, 0x48, 0xb7, 0x6c, 0xcd, 0x42, 0x83, 0x5f, 0x5f, - 0x3d, 0x18, 0x2f, 0x10, 0xf6, 0x7b, 0x3f, 0x9b, 0xd1, 0xa7, 0xab, 0xac, 0x7a, - 0x02, 0xea, 0x8b, 0xa2, 0x91, 0x4b, - ], - dk: [ - 0x64, 0xe8, 0x88, 0x71, 0x4d, 0x39, 0x55, 0x03, 0xe8, 0x34, 0xa7, 0x8e, 0xee, - 0xb9, 0xf4, 0x29, 0x4d, 0x52, 0xac, 0x55, 0xe0, 0xe9, 0x0e, 0x90, 0xc8, 0x1d, - 0x12, 0x67, 0x97, 0x86, 0x92, 0x70, - ], - c: [ - 0xb5, 0xa0, 0x09, 0xf3, 0xad, 0x52, 0xb0, 0x4f, 0xee, 0xac, 0x65, 0xe7, 0x9a, - 0x6e, 0x30, 0xd8, 0x94, 0x82, 0x51, 0xb7, 0xa8, 0x82, 0x47, 0xb2, 0xce, 0x96, - 0x78, 0x22, 0xfe, 0x49, 0xcc, 0xa1, - ], - ak: [ - 0xc4, 0x74, 0x8f, 0x3e, 0x63, 0xe9, 0x7f, 0x0a, 0xea, 0xff, 0x39, 0x20, 0x51, - 0x9b, 0x7c, 0x2c, 0x1e, 0xd8, 0x40, 0xd4, 0xdd, 0x7a, 0xc1, 0x1f, 0xb0, 0x46, - 0x0e, 0xd5, 0xff, 0x9e, 0x2f, 0xe0, - ], - nk: [ - 0x01, 0x7d, 0xee, 0xa7, 0x7c, 0x0f, 0xa6, 0x87, 0xfd, 0x0e, 0x7a, 0x11, 0xff, - 0xcd, 0x3d, 0x3d, 0x11, 0xb8, 0x5c, 0xf5, 0xc0, 0x53, 0x6f, 0xf8, 0xca, 0xea, - 0x74, 0x88, 0x37, 0xa5, 0x3a, 0xd6, - ], - ivk: [ - 0x2d, 0xf3, 0xe1, 0x49, 0xf6, 0xd3, 0x4e, 0x9f, 0xa9, 0xac, 0x66, 0xbd, 0xdc, - 0x40, 0xe2, 0xb5, 0x93, 0x66, 0x99, 0x99, 0x87, 0xd7, 0xdf, 0x82, 0x9d, 0xec, - 0x5d, 0x51, 0x74, 0xab, 0xcd, 0x05, - ], - xsk: None, - xfvk: [ - 0x03, 0xe1, 0x61, 0xbc, 0xa7, 0x03, 0x00, 0x00, 0x00, 0xb5, 0xa0, 0x09, 0xf3, - 0xad, 0x52, 0xb0, 0x4f, 0xee, 0xac, 0x65, 0xe7, 0x9a, 0x6e, 0x30, 0xd8, 0x94, - 0x82, 0x51, 0xb7, 0xa8, 0x82, 0x47, 0xb2, 0xce, 0x96, 0x78, 0x22, 0xfe, 0x49, - 0xcc, 0xa1, 0xc4, 0x74, 0x8f, 0x3e, 0x63, 0xe9, 0x7f, 0x0a, 0xea, 0xff, 0x39, - 0x20, 0x51, 0x9b, 0x7c, 0x2c, 0x1e, 0xd8, 0x40, 0xd4, 0xdd, 0x7a, 0xc1, 0x1f, - 0xb0, 0x46, 0x0e, 0xd5, 0xff, 0x9e, 0x2f, 0xe0, 0x01, 0x7d, 0xee, 0xa7, 0x7c, - 0x0f, 0xa6, 0x87, 0xfd, 0x0e, 0x7a, 0x11, 0xff, 0xcd, 0x3d, 0x3d, 0x11, 0xb8, - 0x5c, 0xf5, 0xc0, 0x53, 0x6f, 0xf8, 0xca, 0xea, 0x74, 0x88, 0x37, 0xa5, 0x3a, - 0xd6, 0x83, 0x55, 0xaa, 0x44, 0x4f, 0x48, 0xb7, 0x6c, 0xcd, 0x42, 0x83, 0x5f, - 0x5f, 0x3d, 0x18, 0x2f, 0x10, 0xf6, 0x7b, 0x3f, 0x9b, 0xd1, 0xa7, 0xab, 0xac, - 0x7a, 0x02, 0xea, 0x8b, 0xa2, 0x91, 0x4b, 0x64, 0xe8, 0x88, 0x71, 0x4d, 0x39, - 0x55, 0x03, 0xe8, 0x34, 0xa7, 0x8e, 0xee, 0xb9, 0xf4, 0x29, 0x4d, 0x52, 0xac, - 0x55, 0xe0, 0xe9, 0x0e, 0x90, 0xc8, 0x1d, 0x12, 0x67, 0x97, 0x86, 0x92, 0x70, - ], - fp: [ - 0x16, 0x74, 0xa8, 0x94, 0xa4, 0xf3, 0x4c, 0xcb, 0x76, 0x92, 0x03, 0xa0, 0x1a, - 0x4f, 0xb7, 0x76, 0xc5, 0xe0, 0x68, 0xde, 0xe2, 0x4b, 0x1a, 0xce, 0x7a, 0x42, - 0x48, 0x6f, 0x35, 0x8e, 0x94, 0x36, - ], - d0: Some([ - 0x1b, 0x9b, 0x96, 0x29, 0xb3, 0x83, 0x1c, 0x12, 0xad, 0x1d, 0x06, - ]), - d1: Some([ - 0x7a, 0xa8, 0x22, 0x53, 0x7d, 0x01, 0x5c, 0x19, 0xd8, 0x37, 0x46, - ]), - d2: None, - dmax: None, - }, - ]; - - let seed = [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, - 24, 25, 26, 27, 28, 29, 30, 31, - ]; - - let i1 = ChildIndex::NonHardened(1); - let i2h = ChildIndex::Hardened(2); - let i3 = ChildIndex::NonHardened(3); - - let m = ExtendedSpendingKey::master(&seed); - let m_1 = m.derive_child(i1); - let m_1_2h = ExtendedSpendingKey::from_path(&m, &[i1, i2h]); - let m_1_2hv = ExtendedFullViewingKey::from(&m_1_2h); - let m_1_2hv_3 = m_1_2hv.derive_child(i3).unwrap(); - - let xfvks = [ - ExtendedFullViewingKey::from(&m), - ExtendedFullViewingKey::from(&m_1), - ExtendedFullViewingKey::from(&m_1_2h), - m_1_2hv, // Appears twice so we can de-duplicate test code below - m_1_2hv_3, - ]; - assert_eq!(test_vectors.len(), xfvks.len()); - - let xsks = [m, m_1, m_1_2h]; - - for j in 0..xsks.len() { - let xsk = &xsks[j]; - let tv = &test_vectors[j]; - - assert_eq!(xsk.expsk.ask.to_repr().as_ref(), tv.ask.unwrap()); - assert_eq!(xsk.expsk.nsk.to_repr().as_ref(), tv.nsk.unwrap()); - - assert_eq!(xsk.expsk.ovk.0, tv.ovk); - assert_eq!(xsk.dk.0, tv.dk); - assert_eq!(xsk.chain_code.0, tv.c); - - let mut ser = vec![]; - xsk.write(&mut ser).unwrap(); - assert_eq!(&ser[..], &tv.xsk.unwrap()[..]); - } - - for (xfvk, tv) in xfvks.iter().zip(test_vectors.iter()) { - assert_eq!(xfvk.fvk.vk.ak.to_bytes(), tv.ak); - assert_eq!(xfvk.fvk.vk.nk.to_bytes(), tv.nk); - - assert_eq!(xfvk.fvk.ovk.0, tv.ovk); - assert_eq!(xfvk.dk.0, tv.dk); - assert_eq!(xfvk.chain_code.0, tv.c); - - assert_eq!(xfvk.fvk.vk.ivk().to_repr().as_ref(), tv.ivk); - - let mut ser = vec![]; - xfvk.write(&mut ser).unwrap(); - assert_eq!(&ser[..], &tv.xfvk[..]); - assert_eq!(FvkFingerprint::from(&xfvk.fvk).0, tv.fp); - - // d0 - let mut di = DiversifierIndex::new(); - match xfvk.dk.find_diversifier(di).unwrap() { - (l, d) if l == di => assert_eq!(d.0, tv.d0.unwrap()), - (_, _) => assert!(tv.d0.is_none()), - } - - // d1 - di.increment().unwrap(); - match xfvk.dk.find_diversifier(di).unwrap() { - (l, d) if l == di => assert_eq!(d.0, tv.d1.unwrap()), - (_, _) => assert!(tv.d1.is_none()), - } - - // d2 - di.increment().unwrap(); - match xfvk.dk.find_diversifier(di).unwrap() { - (l, d) if l == di => assert_eq!(d.0, tv.d2.unwrap()), - (_, _) => assert!(tv.d2.is_none()), - } - - // dmax - let dmax = DiversifierIndex([0xff; 11]); - match xfvk.dk.find_diversifier(dmax) { - Some((l, d)) if l == dmax => assert_eq!(d.0, tv.dmax.unwrap()), - Some((_, _)) => panic!(), - None => assert!(tv.dmax.is_none()), - } - } + fn diversifier_index_to_u32() { + let two = DiversifierIndex([ + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]); + assert_eq!(u32::try_from(two), Ok(2)); + + let max_u32 = DiversifierIndex([ + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]); + assert_eq!(u32::try_from(max_u32), Ok(u32::MAX)); + + let too_big = DiversifierIndex([ + 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]); + assert!(matches!(u32::try_from(too_big), Err(_))); } } diff --git a/masp_primitives/src/zip32/sapling.rs b/masp_primitives/src/zip32/sapling.rs new file mode 100644 index 00000000..cef06ca8 --- /dev/null +++ b/masp_primitives/src/zip32/sapling.rs @@ -0,0 +1,1922 @@ +//! Sapling key derivation according to ZIP 32 and ZIP 316 +//! +//! Implements [section 4.2.2] of the Zcash Protocol Specification. +//! +//! [section 4.2.2]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents + +use super::{ + ChainCode, ChildIndex, Diversifier, DiversifierIndex, NullifierDerivingKey, PaymentAddress, + Scope, ViewingKey, +}; +use crate::{ + constants::{PROOF_GENERATION_KEY_GENERATOR, SPENDING_KEY_GENERATOR}, + keys::{prf_expand, prf_expand_vec}, + sapling::keys::{DecodingError, ExpandedSpendingKey, FullViewingKey, OutgoingViewingKey}, + sapling::SaplingIvk, +}; +use aes::Aes256; +use blake2b_simd::Params as Blake2bParams; +use borsh::{BorshDeserialize, BorshSerialize}; +use byteorder::{ByteOrder, LittleEndian, ReadBytesExt, WriteBytesExt}; +use fpe::ff1::{BinaryNumeralString, FF1}; +use std::{ + cmp::Ordering, + convert::TryInto, + io::{self, Error, ErrorKind, Read, Write}, + ops::AddAssign, + str::FromStr, +}; + +pub const ZIP32_SAPLING_MASTER_PERSONALIZATION: &[u8; 16] = b"MASP_IP32Sapling"; +pub const ZIP32_SAPLING_FVFP_PERSONALIZATION: &[u8; 16] = b"MASP_SaplingFVFP"; +pub const ZIP32_SAPLING_INT_PERSONALIZATION: &[u8; 16] = b"MASP__SaplingInt"; + +/// Attempt to produce a payment address given the specified diversifier +/// index, and return None if the specified index does not produce a valid +/// diversifier. +pub fn sapling_address( + fvk: &FullViewingKey, + dk: &DiversifierKey, + j: DiversifierIndex, +) -> Option { + dk.diversifier(j) + .and_then(|d_j| fvk.vk.to_payment_address(d_j)) +} + +/// Search the diversifier space starting at diversifier index `j` for +/// one which will produce a valid diversifier, and return the payment address +/// constructed using that diversifier along with the index at which the +/// valid diversifier was found. +pub fn sapling_find_address( + fvk: &FullViewingKey, + dk: &DiversifierKey, + j: DiversifierIndex, +) -> Option<(DiversifierIndex, PaymentAddress)> { + let (j, d_j) = dk.find_diversifier(j)?; + fvk.vk.to_payment_address(d_j).map(|addr| (j, addr)) +} + +/// Returns the payment address corresponding to the smallest valid diversifier +/// index, along with that index. +pub fn sapling_default_address( + fvk: &FullViewingKey, + dk: &DiversifierKey, +) -> (DiversifierIndex, PaymentAddress) { + // This unwrap is safe, if you have to search the 2^88 space of + // diversifiers it'll never return anyway. + sapling_find_address(fvk, dk, DiversifierIndex::new()).unwrap() +} + +/// Convenience function for child OVK derivation +fn derive_child_ovk(parent: &OutgoingViewingKey, i_l: &[u8]) -> OutgoingViewingKey { + let mut ovk = [0u8; 32]; + ovk.copy_from_slice(&prf_expand_vec(i_l, &[&[0x15], &parent.0]).as_bytes()[..32]); + OutgoingViewingKey(ovk) +} + +/// Returns the internal full viewing key and diversifier key +/// for the provided external FVK = (ak, nk, ovk) and dk encoded +/// in a [Unified FVK]. +/// +/// [Unified FVK]: https://zips.z.cash/zip-0316#encoding-of-unified-full-incoming-viewing-keys +pub fn sapling_derive_internal_fvk( + fvk: &FullViewingKey, + dk: &DiversifierKey, +) -> (FullViewingKey, DiversifierKey) { + let i = { + let mut h = Blake2bParams::new() + .hash_length(32) + .personal(ZIP32_SAPLING_INT_PERSONALIZATION) + .to_state(); + h.update(&fvk.to_bytes()); + h.update(&dk.0); + h.finalize() + }; + let i_nsk = jubjub::Fr::from_bytes_wide(prf_expand(i.as_bytes(), &[0x17]).as_array()); + let r = prf_expand(i.as_bytes(), &[0x18]); + let r = r.as_bytes(); + // PROOF_GENERATION_KEY_GENERATOR = \mathcal{H}^Sapling + let nk_internal = NullifierDerivingKey(PROOF_GENERATION_KEY_GENERATOR * i_nsk + fvk.vk.nk.0); + let dk_internal = DiversifierKey(r[..32].try_into().unwrap()); + let ovk_internal = OutgoingViewingKey(r[32..].try_into().unwrap()); + + ( + FullViewingKey { + vk: ViewingKey { + ak: fvk.vk.ak, + nk: nk_internal, + }, + ovk: ovk_internal, + }, + dk_internal, + ) +} + +/// A Sapling full viewing key fingerprint +struct FvkFingerprint([u8; 32]); + +impl From<&FullViewingKey> for FvkFingerprint { + fn from(fvk: &FullViewingKey) -> Self { + let mut h = Blake2bParams::new() + .hash_length(32) + .personal(ZIP32_SAPLING_FVFP_PERSONALIZATION) + .to_state(); + h.update(&fvk.to_bytes()); + let mut fvfp = [0u8; 32]; + fvfp.copy_from_slice(h.finalize().as_bytes()); + FvkFingerprint(fvfp) + } +} + +impl FvkFingerprint { + fn tag(&self) -> FvkTag { + let mut tag = [0u8; 4]; + tag.copy_from_slice(&self.0[..4]); + FvkTag(tag) + } +} + +/// A Sapling full viewing key tag +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +struct FvkTag([u8; 4]); + +impl FvkTag { + fn master() -> Self { + FvkTag([0u8; 4]) + } + + fn as_bytes(&self) -> &[u8; 4] { + &self.0 + } +} + +/// A key used to derive diversifiers for a particular child key +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct DiversifierKey(pub [u8; 32]); + +impl DiversifierKey { + pub fn master(sk_m: &[u8]) -> Self { + let mut dk_m = [0u8; 32]; + dk_m.copy_from_slice(&prf_expand(sk_m, &[0x10]).as_bytes()[..32]); + DiversifierKey(dk_m) + } + + /// Constructs the diversifier key from its constituent bytes. + pub fn from_bytes(key: [u8; 32]) -> Self { + DiversifierKey(key) + } + + /// Returns the byte representation of the diversifier key. + pub fn as_bytes(&self) -> &[u8; 32] { + &self.0 + } + + fn derive_child(&self, i_l: &[u8]) -> Self { + let mut dk = [0u8; 32]; + dk.copy_from_slice(&prf_expand_vec(i_l, &[&[0x16], &self.0]).as_bytes()[..32]); + DiversifierKey(dk) + } + + fn try_diversifier_internal(ff: &FF1, j: DiversifierIndex) -> Option { + // Generate d_j + let enc = ff + .encrypt(&[], &BinaryNumeralString::from_bytes_le(&j.0[..])) + .unwrap(); + let mut d_j = [0; 11]; + d_j.copy_from_slice(&enc.to_bytes_le()); + let diversifier = Diversifier(d_j); + + // validate that the generated diversifier maps to a jubjub subgroup point. + diversifier.g_d().map(|_| diversifier) + } + + /// Attempts to produce a diversifier at the given index. Returns None + /// if the index does not produce a valid diversifier. + pub fn diversifier(&self, j: DiversifierIndex) -> Option { + let ff = FF1::::new(&self.0, 2).unwrap(); + Self::try_diversifier_internal(&ff, j) + } + + /// Returns the diversifier index to which this key maps the given diversifier. + /// + /// This method cannot be used to verify whether the diversifier was originally + /// generated with this diversifier key, because all valid diversifiers can be + /// produced by all diversifier keys. + pub fn diversifier_index(&self, d: &Diversifier) -> DiversifierIndex { + let ff = FF1::::new(&self.0, 2).unwrap(); + let dec = ff + .decrypt(&[], &BinaryNumeralString::from_bytes_le(&d.0[..])) + .unwrap(); + let mut j = DiversifierIndex::new(); + j.0.copy_from_slice(&dec.to_bytes_le()); + j + } + + /// Returns the first index starting from j that generates a valid + /// diversifier, along with the corresponding diversifier. Returns + /// `None` if the diversifier space contains no valid diversifiers + /// at or above the specified diversifier index. + pub fn find_diversifier( + &self, + mut j: DiversifierIndex, + ) -> Option<(DiversifierIndex, Diversifier)> { + let ff = FF1::::new(&self.0, 2).unwrap(); + loop { + match Self::try_diversifier_internal(&ff, j) { + Some(d_j) => return Some((j, d_j)), + None => { + if j.increment().is_err() { + return None; + } + } + } + } + } +} + +/// A Sapling extended spending key +#[derive(Clone, Eq, Hash, Copy)] +pub struct ExtendedSpendingKey { + depth: u8, + parent_fvk_tag: FvkTag, + child_index: ChildIndex, + chain_code: ChainCode, + pub expsk: ExpandedSpendingKey, + dk: DiversifierKey, +} + +impl std::cmp::PartialEq for ExtendedSpendingKey { + fn eq(&self, rhs: &ExtendedSpendingKey) -> bool { + self.depth == rhs.depth + && self.parent_fvk_tag == rhs.parent_fvk_tag + && self.child_index == rhs.child_index + && self.chain_code == rhs.chain_code + && self.expsk.ask == rhs.expsk.ask + && self.expsk.nsk == rhs.expsk.nsk + && self.expsk.ovk == rhs.expsk.ovk + && self.dk == rhs.dk + } +} + +impl std::fmt::Debug for ExtendedSpendingKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!( + f, + "ExtendedSpendingKey(d = {}, tag_p = {:?}, i = {:?})", + self.depth, self.parent_fvk_tag, self.child_index + ) + } +} + +impl ExtendedSpendingKey { + pub fn master(seed: &[u8]) -> Self { + let i = Blake2bParams::new() + .hash_length(64) + .personal(ZIP32_SAPLING_MASTER_PERSONALIZATION) + .hash(seed); + + let sk_m = &i.as_bytes()[..32]; + let mut c_m = [0u8; 32]; + c_m.copy_from_slice(&i.as_bytes()[32..]); + + ExtendedSpendingKey { + depth: 0, + parent_fvk_tag: FvkTag::master(), + child_index: ChildIndex::master(), + chain_code: ChainCode(c_m), + expsk: ExpandedSpendingKey::from_spending_key(sk_m), + dk: DiversifierKey::master(sk_m), + } + } + + /// Decodes the extended spending key from its serialized representation as defined in + /// [ZIP 32](https://zips.z.cash/zip-0032) + pub fn from_bytes(b: &[u8]) -> Result { + if b.len() != 169 { + return Err(DecodingError::LengthInvalid { + expected: 169, + actual: b.len(), + }); + } + + let depth = b[0]; + + let mut parent_fvk_tag = FvkTag([0; 4]); + parent_fvk_tag.0[..].copy_from_slice(&b[1..5]); + + let mut ci_bytes = [0u8; 4]; + ci_bytes[..].copy_from_slice(&b[5..9]); + let child_index = ChildIndex::from_index(u32::from_le_bytes(ci_bytes)); + + let mut chain_code = ChainCode([0u8; 32]); + chain_code.0[..].copy_from_slice(&b[9..41]); + + let expsk = ExpandedSpendingKey::from_bytes(&b[41..137])?; + + let mut dk = DiversifierKey([0u8; 32]); + dk.0[..].copy_from_slice(&b[137..169]); + + Ok(ExtendedSpendingKey { + depth, + parent_fvk_tag, + child_index, + chain_code, + expsk, + dk, + }) + } + + /// Reads and decodes the encoded form of the extended spending key as define in + /// [ZIP 32](https://zips.z.cash/zip-0032) from the provided reader. + pub fn read(mut reader: R) -> io::Result { + let depth = reader.read_u8()?; + let mut tag = [0; 4]; + reader.read_exact(&mut tag)?; + let i = reader.read_u32::()?; + let mut c = [0; 32]; + reader.read_exact(&mut c)?; + let expsk = ExpandedSpendingKey::read(&mut reader)?; + let mut dk = [0; 32]; + reader.read_exact(&mut dk)?; + + Ok(ExtendedSpendingKey { + depth, + parent_fvk_tag: FvkTag(tag), + child_index: ChildIndex::from_index(i), + chain_code: ChainCode(c), + expsk, + dk: DiversifierKey(dk), + }) + } + + /// Encodes the extended spending key to the its seralized representation as defined in + /// [ZIP 32](https://zips.z.cash/zip-0032) + pub fn to_bytes(&self) -> [u8; 169] { + let mut result = [0u8; 169]; + result[0] = self.depth; + result[1..5].copy_from_slice(&self.parent_fvk_tag.as_bytes()[..]); + result[5..9].copy_from_slice(&self.child_index.value().to_le_bytes()[..]); + result[9..41].copy_from_slice(&self.chain_code.as_bytes()[..]); + result[41..137].copy_from_slice(&self.expsk.to_bytes()[..]); + result[137..169].copy_from_slice(&self.dk.as_bytes()[..]); + result + } + + /// Writes the encoded form of the extended spending key as defined in + /// [ZIP 32](https://zips.z.cash/zip-0032) to the provided writer. + pub fn write(&self, mut writer: W) -> io::Result<()> { + writer.write_all(&self.to_bytes()) + } + + /// Returns the child key corresponding to the path derived from the master key + pub fn from_path(master: &ExtendedSpendingKey, path: &[ChildIndex]) -> Self { + let mut xsk = *master; + for &i in path.iter() { + xsk = xsk.derive_child(i); + } + xsk + } + + #[must_use] + pub fn derive_child(&self, i: ChildIndex) -> Self { + let fvk = FullViewingKey::from_expanded_spending_key(&self.expsk); + let tmp = match i { + ChildIndex::Hardened(i) => { + let mut le_i = [0; 4]; + LittleEndian::write_u32(&mut le_i, i + (1 << 31)); + prf_expand_vec( + &self.chain_code.0, + &[&[0x11], &self.expsk.to_bytes(), &self.dk.0, &le_i], + ) + } + ChildIndex::NonHardened(i) => { + let mut le_i = [0; 4]; + LittleEndian::write_u32(&mut le_i, i); + prf_expand_vec( + &self.chain_code.0, + &[&[0x12], &fvk.to_bytes(), &self.dk.0, &le_i], + ) + } + }; + let i_l = &tmp.as_bytes()[..32]; + let mut c_i = [0u8; 32]; + c_i.copy_from_slice(&tmp.as_bytes()[32..]); + + ExtendedSpendingKey { + depth: self.depth + 1, + parent_fvk_tag: FvkFingerprint::from(&fvk).tag(), + child_index: i, + chain_code: ChainCode(c_i), + expsk: { + let mut ask = jubjub::Fr::from_bytes_wide(prf_expand(i_l, &[0x13]).as_array()); + let mut nsk = jubjub::Fr::from_bytes_wide(prf_expand(i_l, &[0x14]).as_array()); + ask.add_assign(&self.expsk.ask); + nsk.add_assign(&self.expsk.nsk); + let ovk = derive_child_ovk(&self.expsk.ovk, i_l); + ExpandedSpendingKey { ask, nsk, ovk } + }, + dk: self.dk.derive_child(i_l), + } + } + + /// Returns the address with the lowest valid diversifier index, along with + /// the diversifier index that generated that address. + pub fn default_address(&self) -> (DiversifierIndex, PaymentAddress) { + self.to_diversifiable_full_viewing_key().default_address() + } + + /// Derives an internal spending key given an external spending key. + /// + /// Specified in [ZIP 32](https://zips.z.cash/zip-0032#deriving-a-sapling-internal-spending-key). + #[must_use] + pub fn derive_internal(&self) -> Self { + let i = { + let fvk = FullViewingKey::from_expanded_spending_key(&self.expsk); + let mut h = Blake2bParams::new() + .hash_length(32) + .personal(ZIP32_SAPLING_INT_PERSONALIZATION) + .to_state(); + h.update(&fvk.to_bytes()); + h.update(&self.dk.0); + h.finalize() + }; + let i_nsk = jubjub::Fr::from_bytes_wide(prf_expand(i.as_bytes(), &[0x17]).as_array()); + let r = prf_expand(i.as_bytes(), &[0x18]); + let r = r.as_bytes(); + let nsk_internal = i_nsk + self.expsk.nsk; + let dk_internal = DiversifierKey(r[..32].try_into().unwrap()); + let ovk_internal = OutgoingViewingKey(r[32..].try_into().unwrap()); + + ExtendedSpendingKey { + depth: self.depth, + parent_fvk_tag: self.parent_fvk_tag, + child_index: self.child_index, + chain_code: self.chain_code, + expsk: ExpandedSpendingKey { + ask: self.expsk.ask, + nsk: nsk_internal, + ovk: ovk_internal, + }, + dk: dk_internal, + } + } + + #[deprecated(note = "Use `to_diversifiable_full_viewing_key` instead.")] + pub fn to_extended_full_viewing_key(&self) -> ExtendedFullViewingKey { + ExtendedFullViewingKey { + depth: self.depth, + parent_fvk_tag: self.parent_fvk_tag, + child_index: self.child_index, + chain_code: self.chain_code, + fvk: FullViewingKey::from_expanded_spending_key(&self.expsk), + dk: self.dk, + } + } + + pub fn to_diversifiable_full_viewing_key(&self) -> DiversifiableFullViewingKey { + DiversifiableFullViewingKey { + fvk: FullViewingKey::from_expanded_spending_key(&self.expsk), + dk: self.dk, + } + } +} + +// A Sapling extended full viewing key +#[derive(Clone, Eq, Hash, Copy)] +pub struct ExtendedFullViewingKey { + depth: u8, + parent_fvk_tag: FvkTag, + child_index: ChildIndex, + chain_code: ChainCode, + pub fvk: FullViewingKey, + pub(crate) dk: DiversifierKey, +} + +impl std::cmp::PartialEq for ExtendedFullViewingKey { + fn eq(&self, rhs: &ExtendedFullViewingKey) -> bool { + self.depth == rhs.depth + && self.parent_fvk_tag == rhs.parent_fvk_tag + && self.child_index == rhs.child_index + && self.chain_code == rhs.chain_code + && self.fvk.vk.ak == rhs.fvk.vk.ak + && self.fvk.vk.nk == rhs.fvk.vk.nk + && self.fvk.ovk == rhs.fvk.ovk + && self.dk == rhs.dk + } +} + +impl std::fmt::Debug for ExtendedFullViewingKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!( + f, + "ExtendedFullViewingKey(d = {}, tag_p = {:?}, i = {:?})", + self.depth, self.parent_fvk_tag, self.child_index + ) + } +} + +impl BorshDeserialize for ExtendedSpendingKey { + fn deserialize(buf: &mut &[u8]) -> borsh::maybestd::io::Result { + Self::read(buf) + } +} + +impl BorshSerialize for ExtendedSpendingKey { + fn serialize(&self, writer: &mut W) -> borsh::maybestd::io::Result<()> { + self.write(writer) + } +} + +impl<'a> From<&'a ExtendedSpendingKey> for ExtendedFullViewingKey { + fn from(xsk: &ExtendedSpendingKey) -> Self { + ExtendedFullViewingKey { + depth: xsk.depth, + parent_fvk_tag: xsk.parent_fvk_tag, + child_index: xsk.child_index, + chain_code: xsk.chain_code, + fvk: FullViewingKey::from_expanded_spending_key(&xsk.expsk), + dk: xsk.dk, + } + } +} + +impl BorshDeserialize for ExtendedFullViewingKey { + fn deserialize(buf: &mut &[u8]) -> borsh::maybestd::io::Result { + Self::read(buf) + } +} + +impl BorshSerialize for ExtendedFullViewingKey { + fn serialize(&self, writer: &mut W) -> borsh::maybestd::io::Result<()> { + self.write(writer) + } +} + +impl PartialOrd for ExtendedFullViewingKey { + fn partial_cmp(&self, other: &Self) -> Option { + let a = self + .try_to_vec() + .expect("unable to canonicalize ExtendedFullViewingKey"); + let b = other + .try_to_vec() + .expect("unable to canonicalize ExtendedFullViewingKey"); + a.partial_cmp(&b) + } +} + +impl Ord for ExtendedFullViewingKey { + fn cmp(&self, other: &Self) -> Ordering { + let a = self + .try_to_vec() + .expect("unable to canonicalize ExtendedFullViewingKey"); + let b = other + .try_to_vec() + .expect("unable to canonicalize ExtendedFullViewingKey"); + a.cmp(&b) + } +} + +impl ExtendedFullViewingKey { + pub fn read(mut reader: R) -> io::Result { + let depth = reader.read_u8()?; + let mut tag = [0; 4]; + reader.read_exact(&mut tag)?; + let i = reader.read_u32::()?; + let mut c = [0; 32]; + reader.read_exact(&mut c)?; + let fvk = FullViewingKey::read(&mut reader)?; + let mut dk = [0; 32]; + reader.read_exact(&mut dk)?; + + Ok(ExtendedFullViewingKey { + depth, + parent_fvk_tag: FvkTag(tag), + child_index: ChildIndex::from_index(i), + chain_code: ChainCode(c), + fvk, + dk: DiversifierKey(dk), + }) + } + + pub fn write(&self, mut writer: W) -> io::Result<()> { + writer.write_u8(self.depth)?; + writer.write_all(&self.parent_fvk_tag.0)?; + writer.write_u32::(self.child_index.value())?; + writer.write_all(&self.chain_code.0)?; + writer.write_all(&self.fvk.to_bytes())?; + writer.write_all(&self.dk.0)?; + + Ok(()) + } + + pub fn derive_child(&self, i: ChildIndex) -> Result { + let tmp = match i { + ChildIndex::Hardened(_) => return Err(()), + ChildIndex::NonHardened(i) => { + let mut le_i = [0; 4]; + LittleEndian::write_u32(&mut le_i, i); + prf_expand_vec( + &self.chain_code.0, + &[&[0x12], &self.fvk.to_bytes(), &self.dk.0, &le_i], + ) + } + }; + let i_l = &tmp.as_bytes()[..32]; + let mut c_i = [0u8; 32]; + c_i.copy_from_slice(&tmp.as_bytes()[32..]); + + Ok(ExtendedFullViewingKey { + depth: self.depth + 1, + parent_fvk_tag: FvkFingerprint::from(&self.fvk).tag(), + child_index: i, + chain_code: ChainCode(c_i), + fvk: { + let i_ask = jubjub::Fr::from_bytes_wide(prf_expand(i_l, &[0x13]).as_array()); + let i_nsk = jubjub::Fr::from_bytes_wide(prf_expand(i_l, &[0x14]).as_array()); + let ak = (SPENDING_KEY_GENERATOR * i_ask) + self.fvk.vk.ak; + let nk = NullifierDerivingKey( + (PROOF_GENERATION_KEY_GENERATOR * i_nsk) + self.fvk.vk.nk.0, + ); + + FullViewingKey { + vk: ViewingKey { ak, nk }, + ovk: derive_child_ovk(&self.fvk.ovk, i_l), + } + }, + dk: self.dk.derive_child(i_l), + }) + } + + /// Attempt to produce a payment address given the specified diversifier + /// index, and return None if the specified index does not produce a valid + /// diversifier. + pub fn address(&self, j: DiversifierIndex) -> Option { + sapling_address(&self.fvk, &self.dk, j) + } + + /// Search the diversifier space starting at diversifier index `j` for + /// one which will produce a valid diversifier, and return the payment address + /// constructed using that diversifier along with the index at which the + /// valid diversifier was found. + pub fn find_address(&self, j: DiversifierIndex) -> Option<(DiversifierIndex, PaymentAddress)> { + sapling_find_address(&self.fvk, &self.dk, j) + } + + /// Returns the payment address corresponding to the smallest valid diversifier + /// index, along with that index. + pub fn default_address(&self) -> (DiversifierIndex, PaymentAddress) { + sapling_default_address(&self.fvk, &self.dk) + } + + /// Derives an internal full viewing key used for internal operations such + /// as change and auto-shielding. The internal FVK has the same spend authority + /// (the private key corresponding to ak) as the original, but viewing authority + /// only for internal transfers. + /// + /// Specified in [ZIP 32](https://zips.z.cash/zip-0032#deriving-a-sapling-internal-full-viewing-key). + #[must_use] + pub fn derive_internal(&self) -> Self { + let (fvk_internal, dk_internal) = sapling_derive_internal_fvk(&self.fvk, &self.dk); + + ExtendedFullViewingKey { + depth: self.depth, + parent_fvk_tag: self.parent_fvk_tag, + child_index: self.child_index, + chain_code: self.chain_code, + fvk: fvk_internal, + dk: dk_internal, + } + } + + pub fn to_diversifiable_full_viewing_key(&self) -> DiversifiableFullViewingKey { + DiversifiableFullViewingKey { + fvk: self.fvk, + dk: self.dk, + } + } +} + +/// A Sapling key that provides the capability to view incoming and outgoing transactions. +/// +/// This key is useful anywhere you need to maintain accurate balance, but do not want the +/// ability to spend funds (such as a view-only wallet). +/// +/// It comprises the subset of the ZIP 32 extended full viewing key that is used for the +/// Sapling item in a [ZIP 316 Unified Full Viewing Key][zip-0316-ufvk]. +/// +/// [zip-0316-ufvk]: https://zips.z.cash/zip-0316#encoding-of-unified-full-incoming-viewing-keys +#[derive(Clone, Debug)] +pub struct DiversifiableFullViewingKey { + fvk: FullViewingKey, + dk: DiversifierKey, +} + +impl From for DiversifiableFullViewingKey { + fn from(extfvk: ExtendedFullViewingKey) -> Self { + DiversifiableFullViewingKey { + fvk: extfvk.fvk, + dk: extfvk.dk, + } + } +} + +impl From<&ExtendedFullViewingKey> for DiversifiableFullViewingKey { + fn from(extfvk: &ExtendedFullViewingKey) -> Self { + extfvk.to_diversifiable_full_viewing_key() + } +} + +impl DiversifiableFullViewingKey { + /// Parses a `DiversifiableFullViewingKey` from its raw byte encoding. + /// + /// Returns `None` if the bytes do not contain a valid encoding of a diversifiable + /// Sapling full viewing key. + pub fn from_bytes(bytes: &[u8; 128]) -> Option { + FullViewingKey::read(&bytes[..96]).ok().map(|fvk| Self { + fvk, + dk: DiversifierKey::from_bytes(bytes[96..].try_into().unwrap()), + }) + } + + /// Returns the raw encoding of this `DiversifiableFullViewingKey`. + pub fn to_bytes(&self) -> [u8; 128] { + let mut bytes = [0; 128]; + self.fvk + .write(&mut bytes[..96]) + .expect("slice should be the correct length"); + bytes[96..].copy_from_slice(&self.dk.as_bytes()[..]); + bytes + } + + /// Derives the internal `DiversifiableFullViewingKey` corresponding to `self` (which + /// is assumed here to be an external DFVK). + fn derive_internal(&self) -> Self { + let (fvk, dk) = sapling_derive_internal_fvk(&self.fvk, &self.dk); + Self { fvk, dk } + } + + /// Exposes the external [`FullViewingKey`] component of this diversifiable full viewing key. + pub fn fvk(&self) -> &FullViewingKey { + &self.fvk + } + + /// Derives a nullifier-deriving key for the provided scope. + /// + /// This API is provided so that nullifiers for change notes can be correctly computed. + pub fn to_nk(&self, scope: Scope) -> NullifierDerivingKey { + match scope { + Scope::External => self.fvk.vk.nk, + Scope::Internal => self.derive_internal().fvk.vk.nk, + } + } + + /// Derives an incoming viewing key corresponding to this full viewing key. + pub fn to_ivk(&self, scope: Scope) -> SaplingIvk { + match scope { + Scope::External => self.fvk.vk.ivk(), + Scope::Internal => self.derive_internal().fvk.vk.ivk(), + } + } + + /// Derives an outgoing viewing key corresponding to this full viewing key. + pub fn to_ovk(&self, scope: Scope) -> OutgoingViewingKey { + match scope { + Scope::External => self.fvk.ovk, + Scope::Internal => self.derive_internal().fvk.ovk, + } + } + + /// Attempts to produce a valid payment address for the given diversifier index. + /// + /// Returns `None` if the diversifier index does not produce a valid diversifier for + /// this `DiversifiableFullViewingKey`. + pub fn address(&self, j: DiversifierIndex) -> Option { + sapling_address(&self.fvk, &self.dk, j) + } + + /// Finds the next valid payment address starting from the given diversifier index. + /// + /// This searches the diversifier space starting at `j` and incrementing, to find an + /// index which will produce a valid diversifier (a 50% probability for each index). + /// + /// Returns the index at which the valid diversifier was found along with the payment + /// address constructed using that diversifier, or `None` if the maximum index was + /// reached and no valid diversifier was found. + pub fn find_address(&self, j: DiversifierIndex) -> Option<(DiversifierIndex, PaymentAddress)> { + sapling_find_address(&self.fvk, &self.dk, j) + } + + /// Returns the payment address corresponding to the smallest valid diversifier index, + /// along with that index. + pub fn default_address(&self) -> (DiversifierIndex, PaymentAddress) { + sapling_default_address(&self.fvk, &self.dk) + } + + /// Returns the payment address corresponding to the specified diversifier, if any. + /// + /// In general, it is preferable to use `find_address` instead, but this method is + /// useful in some cases for matching keys to existing payment addresses. + pub fn diversified_address(&self, diversifier: Diversifier) -> Option { + self.fvk.vk.to_payment_address(diversifier) + } + + /// Returns the internal address corresponding to the smallest valid diversifier index, + /// along with that index. + /// + /// This address **MUST NOT** be encoded and exposed to end users. User interfaces + /// should instead mark these notes as "change notes" or "internal wallet operations". + pub fn change_address(&self) -> (DiversifierIndex, PaymentAddress) { + let internal_dfvk = self.derive_internal(); + sapling_default_address(&internal_dfvk.fvk, &internal_dfvk.dk) + } + + /// Returns the change address corresponding to the specified diversifier, if any. + /// + /// In general, it is preferable to use `change_address` instead, but this method is + /// useful in some cases for matching keys to existing payment addresses. + pub fn diversified_change_address(&self, diversifier: Diversifier) -> Option { + self.derive_internal() + .fvk + .vk + .to_payment_address(diversifier) + } + + /// Attempts to decrypt the given address's diversifier with this full viewing key. + /// + /// This method extracts the diversifier from the given address and decrypts it as a + /// diversifier index, then verifies that this diversifier index produces the same + /// address. Decryption is attempted using both the internal and external parts of the + /// full viewing key. + /// + /// Returns the decrypted diversifier index and its scope, or `None` if the address + /// was not generated from this key. + pub fn decrypt_diversifier(&self, addr: &PaymentAddress) -> Option<(DiversifierIndex, Scope)> { + let j_external = self.dk.diversifier_index(addr.diversifier()); + if self.address(j_external).as_ref() == Some(addr) { + return Some((j_external, Scope::External)); + } + + let j_internal = self + .derive_internal() + .dk + .diversifier_index(addr.diversifier()); + if self.address(j_internal).as_ref() == Some(addr) { + return Some((j_internal, Scope::Internal)); + } + + None + } +} + +impl FromStr for ExtendedSpendingKey { + type Err = std::io::Error; + fn from_str(s: &str) -> Result { + let vec = hex::decode(s).map_err(|x| Error::new(ErrorKind::InvalidData, x))?; + Ok(ExtendedSpendingKey::master(vec.as_ref())) + } +} + +impl PartialOrd for ExtendedSpendingKey { + fn partial_cmp(&self, other: &Self) -> Option { + let a = self + .try_to_vec() + .expect("unable to canonicalize ExtendedSpendingKey"); + let b = other + .try_to_vec() + .expect("unable to canonicalize ExtendedSpendingKey"); + a.partial_cmp(&b) + } +} + +impl Ord for ExtendedSpendingKey { + fn cmp(&self, other: &Self) -> Ordering { + let a = self + .try_to_vec() + .expect("unable to canonicalize ExtendedSpendingKey"); + let b = other + .try_to_vec() + .expect("unable to canonicalize ExtendedSpendingKey"); + a.cmp(&b) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use super::{DiversifiableFullViewingKey, ExtendedSpendingKey}; + use ff::PrimeField; + use group::GroupEncoding; + + #[test] + #[allow(deprecated)] + fn derive_nonhardened_child() { + let seed = [0; 32]; + let xsk_m = ExtendedSpendingKey::master(&seed); + let xfvk_m = xsk_m.to_extended_full_viewing_key(); + + let i_5 = ChildIndex::NonHardened(5); + let xsk_5 = xsk_m.derive_child(i_5); + let xfvk_5 = xfvk_m.derive_child(i_5); + + assert!(xfvk_5.is_ok()); + assert_eq!(xsk_5.to_extended_full_viewing_key(), xfvk_5.unwrap()); + } + + #[test] + #[allow(deprecated)] + fn derive_hardened_child() { + let seed = [0; 32]; + let xsk_m = ExtendedSpendingKey::master(&seed); + let xfvk_m = xsk_m.to_extended_full_viewing_key(); + + let i_5h = ChildIndex::Hardened(5); + let xsk_5h = xsk_m.derive_child(i_5h); + let xfvk_5h = xfvk_m.derive_child(i_5h); + + // Cannot derive a hardened child from an ExtendedFullViewingKey + assert!(xfvk_5h.is_err()); + let xfvk_5h = xsk_5h.to_extended_full_viewing_key(); + + let i_7 = ChildIndex::NonHardened(7); + let xsk_5h_7 = xsk_5h.derive_child(i_7); + let xfvk_5h_7 = xfvk_5h.derive_child(i_7); + + // But we *can* derive a non-hardened child from a hardened parent + assert!(xfvk_5h_7.is_ok()); + assert_eq!(xsk_5h_7.to_extended_full_viewing_key(), xfvk_5h_7.unwrap()); + } + + #[test] + fn path() { + let seed = [0; 32]; + let xsk_m = ExtendedSpendingKey::master(&seed); + + let xsk_5h = xsk_m.derive_child(ChildIndex::Hardened(5)); + assert_eq!( + ExtendedSpendingKey::from_path(&xsk_m, &[ChildIndex::Hardened(5)]), + xsk_5h + ); + + let xsk_5h_7 = xsk_5h.derive_child(ChildIndex::NonHardened(7)); + assert_eq!( + ExtendedSpendingKey::from_path( + &xsk_m, + &[ChildIndex::Hardened(5), ChildIndex::NonHardened(7)] + ), + xsk_5h_7 + ); + } + + #[test] + fn diversifier() { + let dk = DiversifierKey([0; 32]); + let j_0 = DiversifierIndex::new(); + let j_1 = DiversifierIndex([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + let j_2 = DiversifierIndex([2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + let j_3 = DiversifierIndex([3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + // Computed using this Rust implementation + let d_0 = [220, 231, 126, 188, 236, 10, 38, 175, 214, 153, 140]; + let d_3 = [60, 253, 170, 8, 171, 147, 220, 31, 3, 144, 34]; + + // j = 0 + let d_j = dk.diversifier(j_0).unwrap(); + assert_eq!(d_j.0, d_0); + assert_eq!(dk.diversifier_index(&Diversifier(d_0)), j_0); + + // j = 1 + assert_eq!(dk.diversifier(j_1), None); + + // j = 2 + assert_eq!(dk.diversifier(j_2), None); + + // j = 3 + let d_j = dk.diversifier(j_3).unwrap(); + assert_eq!(d_j.0, d_3); + assert_eq!(dk.diversifier_index(&Diversifier(d_3)), j_3); + } + + #[test] + fn diversifier_index_from() { + let di32: u32 = 0xa0b0c0d0; + assert_eq!( + DiversifierIndex::from(di32), + DiversifierIndex([0xd0, 0xc0, 0xb0, 0xa0, 0, 0, 0, 0, 0, 0, 0]) + ); + let di64: u64 = 0x0102030405060708; + assert_eq!( + DiversifierIndex::from(di64), + DiversifierIndex([8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0]) + ); + } + + #[test] + fn find_diversifier() { + let dk = DiversifierKey([0; 32]); + let j_0 = DiversifierIndex::new(); + let j_1 = DiversifierIndex([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + let j_2 = DiversifierIndex([2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + let j_3 = DiversifierIndex([3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + // Computed using this Rust implementation + let d_0 = [220, 231, 126, 188, 236, 10, 38, 175, 214, 153, 140]; + let d_3 = [60, 253, 170, 8, 171, 147, 220, 31, 3, 144, 34]; + + // j = 0 + let (j, d_j) = dk.find_diversifier(j_0).unwrap(); + assert_eq!(j, j_0); + assert_eq!(d_j.0, d_0); + + // j = 1 + let (j, d_j) = dk.find_diversifier(j_1).unwrap(); + assert_eq!(j, j_3); + assert_eq!(d_j.0, d_3); + + // j = 2 + let (j, d_j) = dk.find_diversifier(j_2).unwrap(); + assert_eq!(j, j_3); + assert_eq!(d_j.0, d_3); + + // j = 3 + let (j, d_j) = dk.find_diversifier(j_3).unwrap(); + assert_eq!(j, j_3); + assert_eq!(d_j.0, d_3); + } + + #[test] + fn dfvk_round_trip() { + let dfvk = { + let extsk = ExtendedSpendingKey::master(&[]); + #[allow(deprecated)] + let extfvk = extsk.to_extended_full_viewing_key(); + DiversifiableFullViewingKey::from(extfvk) + }; + + // Check value -> bytes -> parsed round trip. + let dfvk_bytes = dfvk.to_bytes(); + let dfvk_parsed = DiversifiableFullViewingKey::from_bytes(&dfvk_bytes).unwrap(); + assert_eq!(dfvk_parsed.fvk.vk.ak, dfvk.fvk.vk.ak); + assert_eq!(dfvk_parsed.fvk.vk.nk, dfvk.fvk.vk.nk); + assert_eq!(dfvk_parsed.fvk.ovk, dfvk.fvk.ovk); + assert_eq!(dfvk_parsed.dk, dfvk.dk); + + // Check bytes -> parsed -> bytes round trip. + assert_eq!(dfvk_parsed.to_bytes(), dfvk_bytes); + } + + #[test] + fn address() { + let seed = [0u8; 32]; + let xsk_m = ExtendedSpendingKey::master(&seed); + let xfvk_m = xsk_m.to_diversifiable_full_viewing_key(); + let j_0 = DiversifierIndex::new(); + let addr_m = xfvk_m.address(j_0).unwrap(); + assert_eq!( + addr_m.diversifier().0, + // Computed using this Rust implementation + [1, 176, 125, 234, 196, 5, 225, 212, 95, 175, 239] + ); + + let j_1 = DiversifierIndex([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + assert_eq!(xfvk_m.address(j_1), None); + } + + #[test] + fn default_address() { + let seed = [0; 32]; + let xsk_m = ExtendedSpendingKey::master(&seed); + let (j_m, addr_m) = xsk_m.default_address(); + assert_eq!(j_m.0, [0; 11]); + assert_eq!( + addr_m.diversifier().0, + // Computed using ExtendedSpendingKey.master(bytes([0]*32)).diversifier(0) in sapling_zip32.py using MASP personalizations + [1, 176, 125, 234, 196, 5, 225, 212, 95, 175, 239] + ); + } + + #[test] + #[allow(deprecated)] + fn read_write() { + let seed = [0; 32]; + let xsk = ExtendedSpendingKey::master(&seed); + let fvk = xsk.to_extended_full_viewing_key(); + + let mut ser = vec![]; + xsk.write(&mut ser).unwrap(); + let xsk2 = ExtendedSpendingKey::read(&ser[..]).unwrap(); + assert_eq!(xsk2, xsk); + + let mut ser = vec![]; + fvk.write(&mut ser).unwrap(); + let fvk2 = ExtendedFullViewingKey::read(&ser[..]).unwrap(); + assert_eq!(fvk2, fvk); + } + + #[test] + #[allow(deprecated)] + fn test_vectors() { + struct TestVector { + ask: Option<[u8; 32]>, + nsk: Option<[u8; 32]>, + ovk: [u8; 32], + dk: [u8; 32], + c: [u8; 32], + ak: [u8; 32], + nk: [u8; 32], + ivk: [u8; 32], + xsk: Option<[u8; 169]>, + xfvk: [u8; 169], + fp: [u8; 32], + d0: Option<[u8; 11]>, + d1: Option<[u8; 11]>, + d2: Option<[u8; 11]>, + dmax: Option<[u8; 11]>, + internal_nsk: Option<[u8; 32]>, + internal_ovk: [u8; 32], + internal_dk: [u8; 32], + internal_nk: [u8; 32], + internal_ivk: [u8; 32], + internal_xsk: Option<[u8; 169]>, + internal_xfvk: [u8; 169], + internal_fp: [u8; 32], + } + + // From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/sapling_zip32.py + let test_vectors = vec![ + TestVector { + ask: Some([ + 0xac, 0x4d, 0xa2, 0xa5, 0xe0, 0xa5, 0xe3, 0xec, 0x2d, 0xcb, 0xd7, 0x04, 0xf1, + 0xb0, 0x8d, 0x85, 0x0f, 0xe1, 0x40, 0xea, 0x61, 0x07, 0x2c, 0xe3, 0xf8, 0x70, + 0xe2, 0x70, 0xae, 0xcd, 0x8f, 0x05, + ]), + nsk: Some([ + 0x47, 0x29, 0x3f, 0xb1, 0xe9, 0x3a, 0x86, 0x63, 0xf9, 0xa9, 0x12, 0x56, 0x52, + 0xb6, 0xdc, 0x3d, 0x56, 0x17, 0x89, 0xc0, 0x3b, 0x67, 0x4a, 0x4c, 0xc7, 0x38, + 0xa9, 0x24, 0x9a, 0xaf, 0x08, 0x09, + ]), + ovk: [ + 0xcf, 0x6b, 0xed, 0xb6, 0xc5, 0x49, 0x4e, 0xba, 0xb7, 0x7f, 0x58, 0xa8, 0x57, + 0x35, 0x59, 0xc5, 0xd2, 0x68, 0x3a, 0x25, 0x22, 0x46, 0x49, 0xcb, 0x8d, 0x44, + 0x80, 0xe8, 0xa0, 0x54, 0x58, 0xd6, + ], + dk: [ + 0xab, 0xcb, 0x9e, 0x0a, 0x9b, 0xb0, 0x77, 0xb4, 0x34, 0x50, 0x68, 0x96, 0xde, + 0x92, 0x9a, 0x7a, 0xc3, 0x7f, 0xea, 0xa8, 0x1b, 0xec, 0x17, 0xe0, 0x3b, 0x60, + 0xd0, 0x60, 0x5e, 0xf7, 0xbc, 0x42, + ], + c: [ + 0xe4, 0xca, 0x49, 0x8a, 0x73, 0x59, 0x2a, 0x72, 0xc6, 0x0c, 0x2e, 0x61, 0x1e, + 0x79, 0x70, 0x2f, 0x9d, 0xc0, 0x17, 0x60, 0x23, 0x01, 0xdc, 0xb5, 0xcc, 0x3d, + 0xf0, 0x1d, 0x5c, 0xc0, 0xf0, 0x67, + ], + ak: [ + 0xf6, 0x5d, 0x7b, 0x4a, 0xb9, 0x71, 0x5c, 0x07, 0xc6, 0xb7, 0x8b, 0xd8, 0x22, + 0xac, 0x39, 0xa7, 0x84, 0x81, 0xeb, 0x36, 0x07, 0x9d, 0x06, 0xdc, 0x86, 0x79, + 0xda, 0xab, 0xab, 0x92, 0x00, 0x55, + ], + nk: [ + 0x2b, 0x41, 0x55, 0x3f, 0x32, 0xa2, 0xb6, 0x60, 0xe1, 0x72, 0x6c, 0x31, 0x33, + 0x19, 0xd3, 0x55, 0x33, 0x16, 0x6c, 0xcf, 0x52, 0xc1, 0x5a, 0xc2, 0x3c, 0xbd, + 0xe3, 0xd2, 0x0d, 0x55, 0xcb, 0x01, + ], + ivk: [ + 0x8c, 0x90, 0xb7, 0x87, 0x36, 0x4d, 0xd1, 0x29, 0x11, 0xb6, 0x4b, 0x1e, 0xbf, + 0x8b, 0xfc, 0x04, 0xbd, 0xc5, 0x5f, 0x97, 0xae, 0x85, 0x1e, 0xb3, 0x96, 0x27, + 0x75, 0x56, 0x42, 0x42, 0xa1, 0x02, + ], + xsk: Some([ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe4, 0xca, 0x49, 0x8a, + 0x73, 0x59, 0x2a, 0x72, 0xc6, 0x0c, 0x2e, 0x61, 0x1e, 0x79, 0x70, 0x2f, 0x9d, + 0xc0, 0x17, 0x60, 0x23, 0x01, 0xdc, 0xb5, 0xcc, 0x3d, 0xf0, 0x1d, 0x5c, 0xc0, + 0xf0, 0x67, 0xac, 0x4d, 0xa2, 0xa5, 0xe0, 0xa5, 0xe3, 0xec, 0x2d, 0xcb, 0xd7, + 0x04, 0xf1, 0xb0, 0x8d, 0x85, 0x0f, 0xe1, 0x40, 0xea, 0x61, 0x07, 0x2c, 0xe3, + 0xf8, 0x70, 0xe2, 0x70, 0xae, 0xcd, 0x8f, 0x05, 0x47, 0x29, 0x3f, 0xb1, 0xe9, + 0x3a, 0x86, 0x63, 0xf9, 0xa9, 0x12, 0x56, 0x52, 0xb6, 0xdc, 0x3d, 0x56, 0x17, + 0x89, 0xc0, 0x3b, 0x67, 0x4a, 0x4c, 0xc7, 0x38, 0xa9, 0x24, 0x9a, 0xaf, 0x08, + 0x09, 0xcf, 0x6b, 0xed, 0xb6, 0xc5, 0x49, 0x4e, 0xba, 0xb7, 0x7f, 0x58, 0xa8, + 0x57, 0x35, 0x59, 0xc5, 0xd2, 0x68, 0x3a, 0x25, 0x22, 0x46, 0x49, 0xcb, 0x8d, + 0x44, 0x80, 0xe8, 0xa0, 0x54, 0x58, 0xd6, 0xab, 0xcb, 0x9e, 0x0a, 0x9b, 0xb0, + 0x77, 0xb4, 0x34, 0x50, 0x68, 0x96, 0xde, 0x92, 0x9a, 0x7a, 0xc3, 0x7f, 0xea, + 0xa8, 0x1b, 0xec, 0x17, 0xe0, 0x3b, 0x60, 0xd0, 0x60, 0x5e, 0xf7, 0xbc, 0x42, + ]), + xfvk: [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe4, 0xca, 0x49, 0x8a, + 0x73, 0x59, 0x2a, 0x72, 0xc6, 0x0c, 0x2e, 0x61, 0x1e, 0x79, 0x70, 0x2f, 0x9d, + 0xc0, 0x17, 0x60, 0x23, 0x01, 0xdc, 0xb5, 0xcc, 0x3d, 0xf0, 0x1d, 0x5c, 0xc0, + 0xf0, 0x67, 0xf6, 0x5d, 0x7b, 0x4a, 0xb9, 0x71, 0x5c, 0x07, 0xc6, 0xb7, 0x8b, + 0xd8, 0x22, 0xac, 0x39, 0xa7, 0x84, 0x81, 0xeb, 0x36, 0x07, 0x9d, 0x06, 0xdc, + 0x86, 0x79, 0xda, 0xab, 0xab, 0x92, 0x00, 0x55, 0x2b, 0x41, 0x55, 0x3f, 0x32, + 0xa2, 0xb6, 0x60, 0xe1, 0x72, 0x6c, 0x31, 0x33, 0x19, 0xd3, 0x55, 0x33, 0x16, + 0x6c, 0xcf, 0x52, 0xc1, 0x5a, 0xc2, 0x3c, 0xbd, 0xe3, 0xd2, 0x0d, 0x55, 0xcb, + 0x01, 0xcf, 0x6b, 0xed, 0xb6, 0xc5, 0x49, 0x4e, 0xba, 0xb7, 0x7f, 0x58, 0xa8, + 0x57, 0x35, 0x59, 0xc5, 0xd2, 0x68, 0x3a, 0x25, 0x22, 0x46, 0x49, 0xcb, 0x8d, + 0x44, 0x80, 0xe8, 0xa0, 0x54, 0x58, 0xd6, 0xab, 0xcb, 0x9e, 0x0a, 0x9b, 0xb0, + 0x77, 0xb4, 0x34, 0x50, 0x68, 0x96, 0xde, 0x92, 0x9a, 0x7a, 0xc3, 0x7f, 0xea, + 0xa8, 0x1b, 0xec, 0x17, 0xe0, 0x3b, 0x60, 0xd0, 0x60, 0x5e, 0xf7, 0xbc, 0x42, + ], + fp: [ + 0x17, 0x27, 0x55, 0xf6, 0x51, 0x82, 0xb4, 0xe4, 0x32, 0x12, 0xe2, 0xe6, 0x4f, + 0x73, 0xbe, 0xc7, 0x43, 0xd3, 0xa6, 0xbd, 0x75, 0xaf, 0x08, 0xfe, 0xaa, 0x2d, + 0x6d, 0x65, 0x02, 0x31, 0xdc, 0xb3, + ], + d0: Some([ + 0x99, 0x3f, 0x45, 0x5b, 0x74, 0x15, 0x9e, 0x49, 0xf9, 0xcf, 0x33, + ]), + d1: None, + d2: None, + dmax: Some([ + 0x50, 0xac, 0x45, 0xb9, 0x79, 0xa1, 0x7d, 0x83, 0xa7, 0x49, 0xea, + ]), + internal_nsk: Some([ + 0x01, 0x0c, 0xcb, 0x77, 0xed, 0x04, 0x77, 0xdc, 0xf8, 0x0a, 0xef, 0x52, 0x1b, + 0xd5, 0x80, 0x06, 0xb2, 0x30, 0xd2, 0x5d, 0x7d, 0x77, 0x92, 0xbb, 0xf2, 0x56, + 0x67, 0x2d, 0xd4, 0x9a, 0x03, 0x07, + ]), + internal_ovk: [ + 0x81, 0x95, 0x02, 0xd7, 0x97, 0x3e, 0x1c, 0x0d, 0x15, 0xbe, 0xbc, 0xea, 0x59, + 0x30, 0xf7, 0x3b, 0x82, 0x7b, 0x09, 0x85, 0xac, 0x68, 0xb4, 0x52, 0xd4, 0x98, + 0xa4, 0xbd, 0xf6, 0xf7, 0x15, 0x43, + ], + internal_dk: [ + 0x63, 0xb7, 0xaa, 0xd9, 0xf9, 0xc4, 0x2c, 0x8a, 0xa7, 0x33, 0x27, 0x13, 0x91, + 0xe8, 0xa0, 0x74, 0xd6, 0x23, 0xc3, 0x18, 0xcf, 0x75, 0x3c, 0x99, 0x3a, 0xd6, + 0x22, 0x9e, 0x80, 0xa5, 0xa7, 0xb7, + ], + internal_nk: [ + 0x88, 0x4d, 0xf0, 0x77, 0x9f, 0xb1, 0x29, 0x2c, 0x03, 0x49, 0x43, 0xa6, 0x00, + 0xf8, 0x47, 0xb2, 0x74, 0x3b, 0x53, 0x8d, 0x50, 0x54, 0x5f, 0x23, 0x13, 0x00, + 0xd2, 0x6c, 0xeb, 0x65, 0xc4, 0x46, + ], + internal_ivk: [ + 0x49, 0x11, 0xf7, 0x6b, 0x87, 0x8d, 0xee, 0x5a, 0x2d, 0xec, 0xe8, 0x13, 0xa8, + 0xe3, 0x76, 0x36, 0x0c, 0xa6, 0xa5, 0xa6, 0x2c, 0x10, 0xf6, 0xca, 0x84, 0x87, + 0x38, 0x13, 0x20, 0xc7, 0xa1, 0x07, + ], + internal_xsk: Some([ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe4, 0xca, 0x49, 0x8a, + 0x73, 0x59, 0x2a, 0x72, 0xc6, 0x0c, 0x2e, 0x61, 0x1e, 0x79, 0x70, 0x2f, 0x9d, + 0xc0, 0x17, 0x60, 0x23, 0x01, 0xdc, 0xb5, 0xcc, 0x3d, 0xf0, 0x1d, 0x5c, 0xc0, + 0xf0, 0x67, 0xac, 0x4d, 0xa2, 0xa5, 0xe0, 0xa5, 0xe3, 0xec, 0x2d, 0xcb, 0xd7, + 0x04, 0xf1, 0xb0, 0x8d, 0x85, 0x0f, 0xe1, 0x40, 0xea, 0x61, 0x07, 0x2c, 0xe3, + 0xf8, 0x70, 0xe2, 0x70, 0xae, 0xcd, 0x8f, 0x05, 0x01, 0x0c, 0xcb, 0x77, 0xed, + 0x04, 0x77, 0xdc, 0xf8, 0x0a, 0xef, 0x52, 0x1b, 0xd5, 0x80, 0x06, 0xb2, 0x30, + 0xd2, 0x5d, 0x7d, 0x77, 0x92, 0xbb, 0xf2, 0x56, 0x67, 0x2d, 0xd4, 0x9a, 0x03, + 0x07, 0x81, 0x95, 0x02, 0xd7, 0x97, 0x3e, 0x1c, 0x0d, 0x15, 0xbe, 0xbc, 0xea, + 0x59, 0x30, 0xf7, 0x3b, 0x82, 0x7b, 0x09, 0x85, 0xac, 0x68, 0xb4, 0x52, 0xd4, + 0x98, 0xa4, 0xbd, 0xf6, 0xf7, 0x15, 0x43, 0x63, 0xb7, 0xaa, 0xd9, 0xf9, 0xc4, + 0x2c, 0x8a, 0xa7, 0x33, 0x27, 0x13, 0x91, 0xe8, 0xa0, 0x74, 0xd6, 0x23, 0xc3, + 0x18, 0xcf, 0x75, 0x3c, 0x99, 0x3a, 0xd6, 0x22, 0x9e, 0x80, 0xa5, 0xa7, 0xb7, + ]), + internal_xfvk: [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe4, 0xca, 0x49, 0x8a, + 0x73, 0x59, 0x2a, 0x72, 0xc6, 0x0c, 0x2e, 0x61, 0x1e, 0x79, 0x70, 0x2f, 0x9d, + 0xc0, 0x17, 0x60, 0x23, 0x01, 0xdc, 0xb5, 0xcc, 0x3d, 0xf0, 0x1d, 0x5c, 0xc0, + 0xf0, 0x67, 0xf6, 0x5d, 0x7b, 0x4a, 0xb9, 0x71, 0x5c, 0x07, 0xc6, 0xb7, 0x8b, + 0xd8, 0x22, 0xac, 0x39, 0xa7, 0x84, 0x81, 0xeb, 0x36, 0x07, 0x9d, 0x06, 0xdc, + 0x86, 0x79, 0xda, 0xab, 0xab, 0x92, 0x00, 0x55, 0x88, 0x4d, 0xf0, 0x77, 0x9f, + 0xb1, 0x29, 0x2c, 0x03, 0x49, 0x43, 0xa6, 0x00, 0xf8, 0x47, 0xb2, 0x74, 0x3b, + 0x53, 0x8d, 0x50, 0x54, 0x5f, 0x23, 0x13, 0x00, 0xd2, 0x6c, 0xeb, 0x65, 0xc4, + 0x46, 0x81, 0x95, 0x02, 0xd7, 0x97, 0x3e, 0x1c, 0x0d, 0x15, 0xbe, 0xbc, 0xea, + 0x59, 0x30, 0xf7, 0x3b, 0x82, 0x7b, 0x09, 0x85, 0xac, 0x68, 0xb4, 0x52, 0xd4, + 0x98, 0xa4, 0xbd, 0xf6, 0xf7, 0x15, 0x43, 0x63, 0xb7, 0xaa, 0xd9, 0xf9, 0xc4, + 0x2c, 0x8a, 0xa7, 0x33, 0x27, 0x13, 0x91, 0xe8, 0xa0, 0x74, 0xd6, 0x23, 0xc3, + 0x18, 0xcf, 0x75, 0x3c, 0x99, 0x3a, 0xd6, 0x22, 0x9e, 0x80, 0xa5, 0xa7, 0xb7, + ], + internal_fp: [ + 0x15, 0xb2, 0x65, 0xae, 0xfe, 0xf9, 0x3c, 0x64, 0xf0, 0x44, 0xef, 0xa6, 0x4d, + 0x2b, 0x2b, 0x53, 0x63, 0x28, 0x31, 0x60, 0xf9, 0x51, 0x57, 0xeb, 0xde, 0xfc, + 0xb6, 0x2f, 0x18, 0xaf, 0xe8, 0x34, + ], + }, + TestVector { + ask: Some([ + 0x39, 0xc1, 0x95, 0x8c, 0x62, 0x11, 0x2e, 0x41, 0x35, 0xa2, 0x66, 0xe5, 0x4e, + 0x92, 0x1b, 0x13, 0xd7, 0xd9, 0x81, 0x43, 0x6e, 0x7f, 0x7a, 0x8c, 0x03, 0xf0, + 0xd5, 0xb8, 0x2e, 0x57, 0x09, 0x0a, + ]), + nsk: Some([ + 0x3b, 0x42, 0x80, 0x25, 0x1e, 0x66, 0x9e, 0xb7, 0xcd, 0x81, 0xe4, 0x52, 0xed, + 0x95, 0x5e, 0x82, 0xe7, 0xae, 0x02, 0x7c, 0x33, 0x21, 0x82, 0x7c, 0x58, 0x8c, + 0x91, 0xec, 0xad, 0x56, 0xce, 0x00, + ]), + ovk: [ + 0x0c, 0x7b, 0xf0, 0x2a, 0x34, 0xc8, 0x02, 0x81, 0x8f, 0xee, 0xf8, 0x8b, 0x17, + 0x92, 0x7d, 0xfe, 0xb1, 0x6c, 0x36, 0xea, 0x0b, 0x3b, 0x49, 0xe6, 0x49, 0xb4, + 0x05, 0x51, 0x13, 0xe7, 0xa2, 0xfb, + ], + dk: [ + 0x13, 0x8d, 0x73, 0x3b, 0xa4, 0x20, 0x50, 0x4b, 0xa3, 0x04, 0x3b, 0x26, 0x80, + 0x4d, 0x69, 0x4c, 0x5c, 0x7a, 0x07, 0xc8, 0xb2, 0x85, 0x43, 0xfd, 0x25, 0xab, + 0x69, 0xa7, 0x00, 0x7f, 0xd9, 0xe0, + ], + c: [ + 0xb9, 0x7e, 0x35, 0x12, 0x19, 0x50, 0x9a, 0xba, 0x1a, 0xb6, 0x3d, 0xfe, 0xdc, + 0x6e, 0x4b, 0x68, 0x17, 0x60, 0x6e, 0xc3, 0xe1, 0xac, 0x96, 0x51, 0x42, 0xa1, + 0x90, 0x7b, 0x50, 0xe4, 0x95, 0xfc, + ], + ak: [ + 0x82, 0xf1, 0x67, 0x79, 0xcb, 0xf9, 0xad, 0x9a, 0x3d, 0xb2, 0xff, 0x07, 0xea, + 0x4e, 0xbc, 0x15, 0x9d, 0x0a, 0x31, 0x42, 0x46, 0xbe, 0xd6, 0x39, 0x39, 0x34, + 0xe1, 0x22, 0x0a, 0xcc, 0xa9, 0x14, + ], + nk: [ + 0x97, 0x14, 0x52, 0xc9, 0x62, 0x54, 0xff, 0xa1, 0xed, 0xe7, 0xad, 0x1e, 0x5b, + 0x66, 0x3e, 0x70, 0x53, 0x1a, 0x8b, 0xfb, 0x1e, 0x91, 0x63, 0x8d, 0xdc, 0x58, + 0xab, 0xb8, 0xb9, 0x25, 0x48, 0xd2, + ], + ivk: [ + 0xcf, 0xa2, 0x2b, 0xb7, 0x3c, 0xc3, 0x66, 0x7c, 0x2f, 0x3b, 0xb4, 0xdc, 0x6f, + 0x33, 0xde, 0xe6, 0x9c, 0x4d, 0x51, 0xde, 0x5c, 0x25, 0x52, 0x68, 0x7e, 0x18, + 0xcd, 0x26, 0x78, 0xc9, 0xf7, 0x00, + ], + xsk: Some([ + 0x01, 0x17, 0x27, 0x55, 0xf6, 0x01, 0x00, 0x00, 0x00, 0xb9, 0x7e, 0x35, 0x12, + 0x19, 0x50, 0x9a, 0xba, 0x1a, 0xb6, 0x3d, 0xfe, 0xdc, 0x6e, 0x4b, 0x68, 0x17, + 0x60, 0x6e, 0xc3, 0xe1, 0xac, 0x96, 0x51, 0x42, 0xa1, 0x90, 0x7b, 0x50, 0xe4, + 0x95, 0xfc, 0x39, 0xc1, 0x95, 0x8c, 0x62, 0x11, 0x2e, 0x41, 0x35, 0xa2, 0x66, + 0xe5, 0x4e, 0x92, 0x1b, 0x13, 0xd7, 0xd9, 0x81, 0x43, 0x6e, 0x7f, 0x7a, 0x8c, + 0x03, 0xf0, 0xd5, 0xb8, 0x2e, 0x57, 0x09, 0x0a, 0x3b, 0x42, 0x80, 0x25, 0x1e, + 0x66, 0x9e, 0xb7, 0xcd, 0x81, 0xe4, 0x52, 0xed, 0x95, 0x5e, 0x82, 0xe7, 0xae, + 0x02, 0x7c, 0x33, 0x21, 0x82, 0x7c, 0x58, 0x8c, 0x91, 0xec, 0xad, 0x56, 0xce, + 0x00, 0x0c, 0x7b, 0xf0, 0x2a, 0x34, 0xc8, 0x02, 0x81, 0x8f, 0xee, 0xf8, 0x8b, + 0x17, 0x92, 0x7d, 0xfe, 0xb1, 0x6c, 0x36, 0xea, 0x0b, 0x3b, 0x49, 0xe6, 0x49, + 0xb4, 0x05, 0x51, 0x13, 0xe7, 0xa2, 0xfb, 0x13, 0x8d, 0x73, 0x3b, 0xa4, 0x20, + 0x50, 0x4b, 0xa3, 0x04, 0x3b, 0x26, 0x80, 0x4d, 0x69, 0x4c, 0x5c, 0x7a, 0x07, + 0xc8, 0xb2, 0x85, 0x43, 0xfd, 0x25, 0xab, 0x69, 0xa7, 0x00, 0x7f, 0xd9, 0xe0, + ]), + xfvk: [ + 0x01, 0x17, 0x27, 0x55, 0xf6, 0x01, 0x00, 0x00, 0x00, 0xb9, 0x7e, 0x35, 0x12, + 0x19, 0x50, 0x9a, 0xba, 0x1a, 0xb6, 0x3d, 0xfe, 0xdc, 0x6e, 0x4b, 0x68, 0x17, + 0x60, 0x6e, 0xc3, 0xe1, 0xac, 0x96, 0x51, 0x42, 0xa1, 0x90, 0x7b, 0x50, 0xe4, + 0x95, 0xfc, 0x82, 0xf1, 0x67, 0x79, 0xcb, 0xf9, 0xad, 0x9a, 0x3d, 0xb2, 0xff, + 0x07, 0xea, 0x4e, 0xbc, 0x15, 0x9d, 0x0a, 0x31, 0x42, 0x46, 0xbe, 0xd6, 0x39, + 0x39, 0x34, 0xe1, 0x22, 0x0a, 0xcc, 0xa9, 0x14, 0x97, 0x14, 0x52, 0xc9, 0x62, + 0x54, 0xff, 0xa1, 0xed, 0xe7, 0xad, 0x1e, 0x5b, 0x66, 0x3e, 0x70, 0x53, 0x1a, + 0x8b, 0xfb, 0x1e, 0x91, 0x63, 0x8d, 0xdc, 0x58, 0xab, 0xb8, 0xb9, 0x25, 0x48, + 0xd2, 0x0c, 0x7b, 0xf0, 0x2a, 0x34, 0xc8, 0x02, 0x81, 0x8f, 0xee, 0xf8, 0x8b, + 0x17, 0x92, 0x7d, 0xfe, 0xb1, 0x6c, 0x36, 0xea, 0x0b, 0x3b, 0x49, 0xe6, 0x49, + 0xb4, 0x05, 0x51, 0x13, 0xe7, 0xa2, 0xfb, 0x13, 0x8d, 0x73, 0x3b, 0xa4, 0x20, + 0x50, 0x4b, 0xa3, 0x04, 0x3b, 0x26, 0x80, 0x4d, 0x69, 0x4c, 0x5c, 0x7a, 0x07, + 0xc8, 0xb2, 0x85, 0x43, 0xfd, 0x25, 0xab, 0x69, 0xa7, 0x00, 0x7f, 0xd9, 0xe0, + ], + fp: [ + 0xe5, 0x1f, 0x7b, 0xd0, 0x24, 0x36, 0x88, 0xe3, 0xa7, 0x5f, 0x09, 0xf3, 0x5e, + 0xe8, 0xee, 0xbc, 0xad, 0x30, 0x69, 0x88, 0xed, 0xb3, 0x80, 0x9f, 0x76, 0xd6, + 0xd4, 0xbb, 0x53, 0xb6, 0x3f, 0x7c, + ], + d0: None, + d1: Some([ + 0x42, 0xce, 0x67, 0xa3, 0x2d, 0x00, 0xe3, 0xb8, 0xfb, 0x05, 0x13, + ]), + d2: None, + dmax: Some([ + 0xbc, 0x15, 0x9c, 0x91, 0xe7, 0xab, 0x50, 0xb2, 0x52, 0x91, 0x03, + ]), + internal_nsk: Some([ + 0xb3, 0xb6, 0xcc, 0xd1, 0xf0, 0xb7, 0x7e, 0x0a, 0xe3, 0xdf, 0x52, 0xc6, 0xe0, + 0x5f, 0x84, 0x6f, 0x4a, 0x06, 0xd2, 0xc8, 0xb3, 0xea, 0x5b, 0x58, 0x9a, 0x33, + 0x30, 0x03, 0x3b, 0xb0, 0x58, 0x05, + ]), + internal_ovk: [ + 0x27, 0xdb, 0x99, 0xd0, 0x91, 0xe1, 0xb9, 0x1a, 0x0d, 0x66, 0x40, 0x0d, 0x6b, + 0xc5, 0xb6, 0x59, 0xf8, 0x0e, 0x6b, 0x76, 0x37, 0x21, 0xd9, 0xca, 0xfd, 0xca, + 0x6e, 0x3d, 0x4e, 0xe3, 0x79, 0xa6, + ], + internal_dk: [ + 0xa7, 0xdd, 0x8c, 0x85, 0x3e, 0x49, 0x53, 0x55, 0xbc, 0x09, 0xb1, 0x4a, 0x14, + 0xbd, 0xe6, 0xc1, 0xba, 0x4c, 0xf8, 0x26, 0xe7, 0x4a, 0x95, 0x9b, 0x10, 0x8b, + 0x84, 0x4a, 0xb1, 0x46, 0x5e, 0x0f, + ], + internal_nk: [ + 0x67, 0xf8, 0xa3, 0x3a, 0xd3, 0x52, 0xdd, 0x1a, 0x33, 0x1b, 0x21, 0x6f, 0x97, + 0x5c, 0x6a, 0x15, 0x31, 0x79, 0x04, 0x2d, 0x5a, 0xbe, 0xe1, 0x84, 0x54, 0xe1, + 0x05, 0xb6, 0x98, 0xa5, 0x8d, 0xef, + ], + internal_ivk: [ + 0x66, 0x91, 0x72, 0xb5, 0xfc, 0x7f, 0xd4, 0x3a, 0x58, 0xaa, 0x5c, 0xa9, 0x89, + 0xdb, 0x10, 0xcd, 0xe9, 0x42, 0xd0, 0x93, 0x2b, 0x9e, 0xef, 0x24, 0x2d, 0xc0, + 0x3d, 0x74, 0x86, 0x9b, 0xb5, 0x06, + ], + internal_xsk: Some([ + 0x01, 0x17, 0x27, 0x55, 0xf6, 0x01, 0x00, 0x00, 0x00, 0xb9, 0x7e, 0x35, 0x12, + 0x19, 0x50, 0x9a, 0xba, 0x1a, 0xb6, 0x3d, 0xfe, 0xdc, 0x6e, 0x4b, 0x68, 0x17, + 0x60, 0x6e, 0xc3, 0xe1, 0xac, 0x96, 0x51, 0x42, 0xa1, 0x90, 0x7b, 0x50, 0xe4, + 0x95, 0xfc, 0x39, 0xc1, 0x95, 0x8c, 0x62, 0x11, 0x2e, 0x41, 0x35, 0xa2, 0x66, + 0xe5, 0x4e, 0x92, 0x1b, 0x13, 0xd7, 0xd9, 0x81, 0x43, 0x6e, 0x7f, 0x7a, 0x8c, + 0x03, 0xf0, 0xd5, 0xb8, 0x2e, 0x57, 0x09, 0x0a, 0xb3, 0xb6, 0xcc, 0xd1, 0xf0, + 0xb7, 0x7e, 0x0a, 0xe3, 0xdf, 0x52, 0xc6, 0xe0, 0x5f, 0x84, 0x6f, 0x4a, 0x06, + 0xd2, 0xc8, 0xb3, 0xea, 0x5b, 0x58, 0x9a, 0x33, 0x30, 0x03, 0x3b, 0xb0, 0x58, + 0x05, 0x27, 0xdb, 0x99, 0xd0, 0x91, 0xe1, 0xb9, 0x1a, 0x0d, 0x66, 0x40, 0x0d, + 0x6b, 0xc5, 0xb6, 0x59, 0xf8, 0x0e, 0x6b, 0x76, 0x37, 0x21, 0xd9, 0xca, 0xfd, + 0xca, 0x6e, 0x3d, 0x4e, 0xe3, 0x79, 0xa6, 0xa7, 0xdd, 0x8c, 0x85, 0x3e, 0x49, + 0x53, 0x55, 0xbc, 0x09, 0xb1, 0x4a, 0x14, 0xbd, 0xe6, 0xc1, 0xba, 0x4c, 0xf8, + 0x26, 0xe7, 0x4a, 0x95, 0x9b, 0x10, 0x8b, 0x84, 0x4a, 0xb1, 0x46, 0x5e, 0x0f, + ]), + internal_xfvk: [ + 0x01, 0x17, 0x27, 0x55, 0xf6, 0x01, 0x00, 0x00, 0x00, 0xb9, 0x7e, 0x35, 0x12, + 0x19, 0x50, 0x9a, 0xba, 0x1a, 0xb6, 0x3d, 0xfe, 0xdc, 0x6e, 0x4b, 0x68, 0x17, + 0x60, 0x6e, 0xc3, 0xe1, 0xac, 0x96, 0x51, 0x42, 0xa1, 0x90, 0x7b, 0x50, 0xe4, + 0x95, 0xfc, 0x82, 0xf1, 0x67, 0x79, 0xcb, 0xf9, 0xad, 0x9a, 0x3d, 0xb2, 0xff, + 0x07, 0xea, 0x4e, 0xbc, 0x15, 0x9d, 0x0a, 0x31, 0x42, 0x46, 0xbe, 0xd6, 0x39, + 0x39, 0x34, 0xe1, 0x22, 0x0a, 0xcc, 0xa9, 0x14, 0x67, 0xf8, 0xa3, 0x3a, 0xd3, + 0x52, 0xdd, 0x1a, 0x33, 0x1b, 0x21, 0x6f, 0x97, 0x5c, 0x6a, 0x15, 0x31, 0x79, + 0x04, 0x2d, 0x5a, 0xbe, 0xe1, 0x84, 0x54, 0xe1, 0x05, 0xb6, 0x98, 0xa5, 0x8d, + 0xef, 0x27, 0xdb, 0x99, 0xd0, 0x91, 0xe1, 0xb9, 0x1a, 0x0d, 0x66, 0x40, 0x0d, + 0x6b, 0xc5, 0xb6, 0x59, 0xf8, 0x0e, 0x6b, 0x76, 0x37, 0x21, 0xd9, 0xca, 0xfd, + 0xca, 0x6e, 0x3d, 0x4e, 0xe3, 0x79, 0xa6, 0xa7, 0xdd, 0x8c, 0x85, 0x3e, 0x49, + 0x53, 0x55, 0xbc, 0x09, 0xb1, 0x4a, 0x14, 0xbd, 0xe6, 0xc1, 0xba, 0x4c, 0xf8, + 0x26, 0xe7, 0x4a, 0x95, 0x9b, 0x10, 0x8b, 0x84, 0x4a, 0xb1, 0x46, 0x5e, 0x0f, + ], + internal_fp: [ + 0x9f, 0x83, 0x35, 0xf5, 0xd4, 0x70, 0x9e, 0x25, 0x3e, 0x14, 0x3a, 0x44, 0xa5, + 0x50, 0x42, 0x4f, 0x19, 0xdf, 0xbc, 0x77, 0x57, 0xbb, 0x32, 0x5c, 0xec, 0x4c, + 0x02, 0x5f, 0x8e, 0x30, 0x16, 0x09, + ], + }, + TestVector { + ask: Some([ + 0xe3, 0x78, 0xd4, 0x24, 0x13, 0x88, 0x99, 0x46, 0xa2, 0x3e, 0x4c, 0x1b, 0x79, + 0x0e, 0x5d, 0xde, 0xbc, 0xce, 0x31, 0x5f, 0xdc, 0x87, 0xe4, 0x69, 0xfe, 0x21, + 0xd6, 0x39, 0xf2, 0x82, 0x06, 0x0b, + ]), + nsk: Some([ + 0x29, 0x6d, 0x06, 0xb9, 0xda, 0xf7, 0x9d, 0x33, 0xbf, 0xac, 0x3d, 0xaa, 0x13, + 0x28, 0x3a, 0xd8, 0x0e, 0xf9, 0xb7, 0xc2, 0xab, 0xa2, 0x0b, 0x0b, 0x22, 0x8c, + 0xc8, 0x33, 0x0c, 0x8d, 0x70, 0x03, + ]), + ovk: [ + 0xb1, 0x62, 0x15, 0x54, 0x71, 0x8f, 0xbe, 0xc3, 0xac, 0x3d, 0xb9, 0x4d, 0x23, + 0xfe, 0x16, 0xd5, 0xbb, 0x13, 0x7f, 0xe3, 0x24, 0xb8, 0x53, 0xa5, 0xa0, 0xee, + 0xf3, 0x36, 0x23, 0x98, 0x75, 0x4e, + ], + dk: [ + 0xad, 0xf8, 0xd1, 0xba, 0x74, 0xf4, 0xdf, 0xdd, 0xe6, 0xb0, 0x44, 0x37, 0x94, + 0x74, 0xaa, 0xc3, 0xc8, 0xef, 0x00, 0x3e, 0xce, 0xe7, 0x14, 0xdd, 0xcf, 0x4c, + 0x94, 0x7c, 0xa7, 0x2a, 0xeb, 0xa2, + ], + c: [ + 0xdb, 0xaa, 0x2d, 0xde, 0xd8, 0x6b, 0xdb, 0x32, 0xfd, 0x60, 0x5b, 0x5e, 0xa0, + 0xdb, 0x6a, 0x57, 0xa5, 0xb3, 0x3b, 0x36, 0x20, 0x94, 0x8f, 0x76, 0x9c, 0x12, + 0x85, 0x88, 0x51, 0xeb, 0x83, 0xca, + ], + ak: [ + 0x42, 0xec, 0x8b, 0x50, 0x8d, 0xbb, 0x9a, 0x6d, 0x4a, 0x58, 0xf1, 0xb7, 0xcb, + 0x96, 0x06, 0xfd, 0x75, 0xdd, 0x1c, 0x0d, 0x03, 0x9c, 0x2c, 0xac, 0x19, 0xb2, + 0x66, 0x52, 0xcb, 0x3b, 0x27, 0xcd, + ], + nk: [ + 0x4f, 0x60, 0x3c, 0x21, 0x05, 0xa4, 0x0f, 0x4f, 0xc3, 0xdf, 0x19, 0x76, 0x18, + 0x25, 0x7c, 0xa0, 0xfc, 0x4e, 0x8b, 0x73, 0x39, 0xd4, 0x80, 0xcd, 0x73, 0xa1, + 0x08, 0x38, 0xe5, 0xcd, 0x9d, 0x0f, + ], + ivk: [ + 0xc5, 0xb1, 0x73, 0x5b, 0xf7, 0xd2, 0xd7, 0x1d, 0x8e, 0x1f, 0x91, 0x62, 0xaf, + 0x7c, 0x96, 0xb5, 0x3e, 0x95, 0xa2, 0xdd, 0x12, 0x55, 0x27, 0x4a, 0xf6, 0x2d, + 0x3a, 0x78, 0xf6, 0xd7, 0x4e, 0x05, + ], + xsk: Some([ + 0x02, 0xe5, 0x1f, 0x7b, 0xd0, 0x02, 0x00, 0x00, 0x80, 0xdb, 0xaa, 0x2d, 0xde, + 0xd8, 0x6b, 0xdb, 0x32, 0xfd, 0x60, 0x5b, 0x5e, 0xa0, 0xdb, 0x6a, 0x57, 0xa5, + 0xb3, 0x3b, 0x36, 0x20, 0x94, 0x8f, 0x76, 0x9c, 0x12, 0x85, 0x88, 0x51, 0xeb, + 0x83, 0xca, 0xe3, 0x78, 0xd4, 0x24, 0x13, 0x88, 0x99, 0x46, 0xa2, 0x3e, 0x4c, + 0x1b, 0x79, 0x0e, 0x5d, 0xde, 0xbc, 0xce, 0x31, 0x5f, 0xdc, 0x87, 0xe4, 0x69, + 0xfe, 0x21, 0xd6, 0x39, 0xf2, 0x82, 0x06, 0x0b, 0x29, 0x6d, 0x06, 0xb9, 0xda, + 0xf7, 0x9d, 0x33, 0xbf, 0xac, 0x3d, 0xaa, 0x13, 0x28, 0x3a, 0xd8, 0x0e, 0xf9, + 0xb7, 0xc2, 0xab, 0xa2, 0x0b, 0x0b, 0x22, 0x8c, 0xc8, 0x33, 0x0c, 0x8d, 0x70, + 0x03, 0xb1, 0x62, 0x15, 0x54, 0x71, 0x8f, 0xbe, 0xc3, 0xac, 0x3d, 0xb9, 0x4d, + 0x23, 0xfe, 0x16, 0xd5, 0xbb, 0x13, 0x7f, 0xe3, 0x24, 0xb8, 0x53, 0xa5, 0xa0, + 0xee, 0xf3, 0x36, 0x23, 0x98, 0x75, 0x4e, 0xad, 0xf8, 0xd1, 0xba, 0x74, 0xf4, + 0xdf, 0xdd, 0xe6, 0xb0, 0x44, 0x37, 0x94, 0x74, 0xaa, 0xc3, 0xc8, 0xef, 0x00, + 0x3e, 0xce, 0xe7, 0x14, 0xdd, 0xcf, 0x4c, 0x94, 0x7c, 0xa7, 0x2a, 0xeb, 0xa2, + ]), + xfvk: [ + 0x02, 0xe5, 0x1f, 0x7b, 0xd0, 0x02, 0x00, 0x00, 0x80, 0xdb, 0xaa, 0x2d, 0xde, + 0xd8, 0x6b, 0xdb, 0x32, 0xfd, 0x60, 0x5b, 0x5e, 0xa0, 0xdb, 0x6a, 0x57, 0xa5, + 0xb3, 0x3b, 0x36, 0x20, 0x94, 0x8f, 0x76, 0x9c, 0x12, 0x85, 0x88, 0x51, 0xeb, + 0x83, 0xca, 0x42, 0xec, 0x8b, 0x50, 0x8d, 0xbb, 0x9a, 0x6d, 0x4a, 0x58, 0xf1, + 0xb7, 0xcb, 0x96, 0x06, 0xfd, 0x75, 0xdd, 0x1c, 0x0d, 0x03, 0x9c, 0x2c, 0xac, + 0x19, 0xb2, 0x66, 0x52, 0xcb, 0x3b, 0x27, 0xcd, 0x4f, 0x60, 0x3c, 0x21, 0x05, + 0xa4, 0x0f, 0x4f, 0xc3, 0xdf, 0x19, 0x76, 0x18, 0x25, 0x7c, 0xa0, 0xfc, 0x4e, + 0x8b, 0x73, 0x39, 0xd4, 0x80, 0xcd, 0x73, 0xa1, 0x08, 0x38, 0xe5, 0xcd, 0x9d, + 0x0f, 0xb1, 0x62, 0x15, 0x54, 0x71, 0x8f, 0xbe, 0xc3, 0xac, 0x3d, 0xb9, 0x4d, + 0x23, 0xfe, 0x16, 0xd5, 0xbb, 0x13, 0x7f, 0xe3, 0x24, 0xb8, 0x53, 0xa5, 0xa0, + 0xee, 0xf3, 0x36, 0x23, 0x98, 0x75, 0x4e, 0xad, 0xf8, 0xd1, 0xba, 0x74, 0xf4, + 0xdf, 0xdd, 0xe6, 0xb0, 0x44, 0x37, 0x94, 0x74, 0xaa, 0xc3, 0xc8, 0xef, 0x00, + 0x3e, 0xce, 0xe7, 0x14, 0xdd, 0xcf, 0x4c, 0x94, 0x7c, 0xa7, 0x2a, 0xeb, 0xa2, + ], + fp: [ + 0xe1, 0x61, 0xbc, 0xa7, 0x4c, 0xac, 0x0b, 0xbd, 0x66, 0xb4, 0xa4, 0xad, 0x12, + 0x71, 0x32, 0x11, 0x60, 0x52, 0xef, 0xf7, 0x65, 0x96, 0x67, 0xd9, 0xf7, 0xfd, + 0xad, 0xd0, 0x1f, 0x10, 0x08, 0xa1, + ], + d0: Some([ + 0x18, 0x36, 0xc0, 0x6f, 0x69, 0x94, 0x47, 0x49, 0xaa, 0x48, 0x0b, + ]), + d1: None, + d2: None, + dmax: Some([ + 0x63, 0xea, 0x9f, 0xbb, 0x99, 0x95, 0xc9, 0x39, 0x7a, 0xc2, 0x23, + ]), + internal_nsk: Some([ + 0xe1, 0x11, 0xf7, 0xd1, 0xfc, 0xe1, 0x69, 0x6e, 0xe1, 0x6a, 0xf7, 0xa4, 0x49, + 0x30, 0xa1, 0xd8, 0x70, 0xe2, 0x48, 0xb4, 0x9d, 0x55, 0xb2, 0xf0, 0x0b, 0xe5, + 0x8f, 0x58, 0x51, 0xab, 0x49, 0x08, + ]), + internal_ovk: [ + 0x21, 0x51, 0x3d, 0x60, 0x47, 0x7e, 0xd9, 0xcd, 0xf5, 0xdb, 0xcf, 0x6d, 0xba, + 0x3e, 0x7d, 0xec, 0xf0, 0xb9, 0xf6, 0x25, 0x99, 0xa5, 0x5d, 0x19, 0x1f, 0x04, + 0xe0, 0xe0, 0xe6, 0x3e, 0xa4, 0x62, + ], + internal_dk: [ + 0x72, 0x4f, 0xaa, 0x86, 0x25, 0x1b, 0x1f, 0x19, 0xc1, 0xde, 0xfb, 0x42, 0xbc, + 0x0b, 0x92, 0x51, 0xf0, 0xc5, 0x3e, 0x08, 0x2b, 0x01, 0xea, 0xf1, 0x9c, 0x47, + 0xaa, 0x9e, 0x2d, 0xfb, 0x4f, 0x5e, + ], + internal_nk: [ + 0x1b, 0x69, 0x5f, 0x0f, 0x38, 0x64, 0x5d, 0xfb, 0xa5, 0x60, 0x15, 0x52, 0x39, + 0x53, 0x3f, 0x2d, 0xf2, 0x9d, 0x79, 0x98, 0x49, 0x9a, 0x4d, 0x62, 0x5c, 0x65, + 0x8e, 0xdb, 0xd9, 0x54, 0x84, 0xe7, + ], + internal_ivk: [ + 0xcb, 0x2d, 0x46, 0xbb, 0x53, 0x20, 0xfd, 0x19, 0xd4, 0xc2, 0xdc, 0x76, 0xc2, + 0xd7, 0xc8, 0xd7, 0x01, 0xd4, 0xb0, 0xe0, 0x1a, 0xe9, 0xf7, 0x49, 0xb8, 0xa4, + 0x84, 0x81, 0x00, 0xd1, 0x0e, 0x04, + ], + internal_xsk: Some([ + 0x02, 0xe5, 0x1f, 0x7b, 0xd0, 0x02, 0x00, 0x00, 0x80, 0xdb, 0xaa, 0x2d, 0xde, + 0xd8, 0x6b, 0xdb, 0x32, 0xfd, 0x60, 0x5b, 0x5e, 0xa0, 0xdb, 0x6a, 0x57, 0xa5, + 0xb3, 0x3b, 0x36, 0x20, 0x94, 0x8f, 0x76, 0x9c, 0x12, 0x85, 0x88, 0x51, 0xeb, + 0x83, 0xca, 0xe3, 0x78, 0xd4, 0x24, 0x13, 0x88, 0x99, 0x46, 0xa2, 0x3e, 0x4c, + 0x1b, 0x79, 0x0e, 0x5d, 0xde, 0xbc, 0xce, 0x31, 0x5f, 0xdc, 0x87, 0xe4, 0x69, + 0xfe, 0x21, 0xd6, 0x39, 0xf2, 0x82, 0x06, 0x0b, 0xe1, 0x11, 0xf7, 0xd1, 0xfc, + 0xe1, 0x69, 0x6e, 0xe1, 0x6a, 0xf7, 0xa4, 0x49, 0x30, 0xa1, 0xd8, 0x70, 0xe2, + 0x48, 0xb4, 0x9d, 0x55, 0xb2, 0xf0, 0x0b, 0xe5, 0x8f, 0x58, 0x51, 0xab, 0x49, + 0x08, 0x21, 0x51, 0x3d, 0x60, 0x47, 0x7e, 0xd9, 0xcd, 0xf5, 0xdb, 0xcf, 0x6d, + 0xba, 0x3e, 0x7d, 0xec, 0xf0, 0xb9, 0xf6, 0x25, 0x99, 0xa5, 0x5d, 0x19, 0x1f, + 0x04, 0xe0, 0xe0, 0xe6, 0x3e, 0xa4, 0x62, 0x72, 0x4f, 0xaa, 0x86, 0x25, 0x1b, + 0x1f, 0x19, 0xc1, 0xde, 0xfb, 0x42, 0xbc, 0x0b, 0x92, 0x51, 0xf0, 0xc5, 0x3e, + 0x08, 0x2b, 0x01, 0xea, 0xf1, 0x9c, 0x47, 0xaa, 0x9e, 0x2d, 0xfb, 0x4f, 0x5e, + ]), + internal_xfvk: [ + 0x02, 0xe5, 0x1f, 0x7b, 0xd0, 0x02, 0x00, 0x00, 0x80, 0xdb, 0xaa, 0x2d, 0xde, + 0xd8, 0x6b, 0xdb, 0x32, 0xfd, 0x60, 0x5b, 0x5e, 0xa0, 0xdb, 0x6a, 0x57, 0xa5, + 0xb3, 0x3b, 0x36, 0x20, 0x94, 0x8f, 0x76, 0x9c, 0x12, 0x85, 0x88, 0x51, 0xeb, + 0x83, 0xca, 0x42, 0xec, 0x8b, 0x50, 0x8d, 0xbb, 0x9a, 0x6d, 0x4a, 0x58, 0xf1, + 0xb7, 0xcb, 0x96, 0x06, 0xfd, 0x75, 0xdd, 0x1c, 0x0d, 0x03, 0x9c, 0x2c, 0xac, + 0x19, 0xb2, 0x66, 0x52, 0xcb, 0x3b, 0x27, 0xcd, 0x1b, 0x69, 0x5f, 0x0f, 0x38, + 0x64, 0x5d, 0xfb, 0xa5, 0x60, 0x15, 0x52, 0x39, 0x53, 0x3f, 0x2d, 0xf2, 0x9d, + 0x79, 0x98, 0x49, 0x9a, 0x4d, 0x62, 0x5c, 0x65, 0x8e, 0xdb, 0xd9, 0x54, 0x84, + 0xe7, 0x21, 0x51, 0x3d, 0x60, 0x47, 0x7e, 0xd9, 0xcd, 0xf5, 0xdb, 0xcf, 0x6d, + 0xba, 0x3e, 0x7d, 0xec, 0xf0, 0xb9, 0xf6, 0x25, 0x99, 0xa5, 0x5d, 0x19, 0x1f, + 0x04, 0xe0, 0xe0, 0xe6, 0x3e, 0xa4, 0x62, 0x72, 0x4f, 0xaa, 0x86, 0x25, 0x1b, + 0x1f, 0x19, 0xc1, 0xde, 0xfb, 0x42, 0xbc, 0x0b, 0x92, 0x51, 0xf0, 0xc5, 0x3e, + 0x08, 0x2b, 0x01, 0xea, 0xf1, 0x9c, 0x47, 0xaa, 0x9e, 0x2d, 0xfb, 0x4f, 0x5e, + ], + internal_fp: [ + 0x64, 0x5b, 0xd5, 0x1d, 0x95, 0xbc, 0xdd, 0x36, 0xc7, 0x55, 0x71, 0x3d, 0xf6, + 0x13, 0x09, 0xf1, 0xbf, 0x4e, 0x29, 0x8a, 0x71, 0xb5, 0xec, 0x55, 0xed, 0xdd, + 0xb2, 0x25, 0xab, 0xbd, 0xfd, 0x36, + ], + }, + TestVector { + ask: None, + nsk: None, + ovk: [ + 0xb1, 0x62, 0x15, 0x54, 0x71, 0x8f, 0xbe, 0xc3, 0xac, 0x3d, 0xb9, 0x4d, 0x23, + 0xfe, 0x16, 0xd5, 0xbb, 0x13, 0x7f, 0xe3, 0x24, 0xb8, 0x53, 0xa5, 0xa0, 0xee, + 0xf3, 0x36, 0x23, 0x98, 0x75, 0x4e, + ], + dk: [ + 0xad, 0xf8, 0xd1, 0xba, 0x74, 0xf4, 0xdf, 0xdd, 0xe6, 0xb0, 0x44, 0x37, 0x94, + 0x74, 0xaa, 0xc3, 0xc8, 0xef, 0x00, 0x3e, 0xce, 0xe7, 0x14, 0xdd, 0xcf, 0x4c, + 0x94, 0x7c, 0xa7, 0x2a, 0xeb, 0xa2, + ], + c: [ + 0xdb, 0xaa, 0x2d, 0xde, 0xd8, 0x6b, 0xdb, 0x32, 0xfd, 0x60, 0x5b, 0x5e, 0xa0, + 0xdb, 0x6a, 0x57, 0xa5, 0xb3, 0x3b, 0x36, 0x20, 0x94, 0x8f, 0x76, 0x9c, 0x12, + 0x85, 0x88, 0x51, 0xeb, 0x83, 0xca, + ], + ak: [ + 0x42, 0xec, 0x8b, 0x50, 0x8d, 0xbb, 0x9a, 0x6d, 0x4a, 0x58, 0xf1, 0xb7, 0xcb, + 0x96, 0x06, 0xfd, 0x75, 0xdd, 0x1c, 0x0d, 0x03, 0x9c, 0x2c, 0xac, 0x19, 0xb2, + 0x66, 0x52, 0xcb, 0x3b, 0x27, 0xcd, + ], + nk: [ + 0x4f, 0x60, 0x3c, 0x21, 0x05, 0xa4, 0x0f, 0x4f, 0xc3, 0xdf, 0x19, 0x76, 0x18, + 0x25, 0x7c, 0xa0, 0xfc, 0x4e, 0x8b, 0x73, 0x39, 0xd4, 0x80, 0xcd, 0x73, 0xa1, + 0x08, 0x38, 0xe5, 0xcd, 0x9d, 0x0f, + ], + ivk: [ + 0xc5, 0xb1, 0x73, 0x5b, 0xf7, 0xd2, 0xd7, 0x1d, 0x8e, 0x1f, 0x91, 0x62, 0xaf, + 0x7c, 0x96, 0xb5, 0x3e, 0x95, 0xa2, 0xdd, 0x12, 0x55, 0x27, 0x4a, 0xf6, 0x2d, + 0x3a, 0x78, 0xf6, 0xd7, 0x4e, 0x05, + ], + xsk: None, + xfvk: [ + 0x02, 0xe5, 0x1f, 0x7b, 0xd0, 0x02, 0x00, 0x00, 0x80, 0xdb, 0xaa, 0x2d, 0xde, + 0xd8, 0x6b, 0xdb, 0x32, 0xfd, 0x60, 0x5b, 0x5e, 0xa0, 0xdb, 0x6a, 0x57, 0xa5, + 0xb3, 0x3b, 0x36, 0x20, 0x94, 0x8f, 0x76, 0x9c, 0x12, 0x85, 0x88, 0x51, 0xeb, + 0x83, 0xca, 0x42, 0xec, 0x8b, 0x50, 0x8d, 0xbb, 0x9a, 0x6d, 0x4a, 0x58, 0xf1, + 0xb7, 0xcb, 0x96, 0x06, 0xfd, 0x75, 0xdd, 0x1c, 0x0d, 0x03, 0x9c, 0x2c, 0xac, + 0x19, 0xb2, 0x66, 0x52, 0xcb, 0x3b, 0x27, 0xcd, 0x4f, 0x60, 0x3c, 0x21, 0x05, + 0xa4, 0x0f, 0x4f, 0xc3, 0xdf, 0x19, 0x76, 0x18, 0x25, 0x7c, 0xa0, 0xfc, 0x4e, + 0x8b, 0x73, 0x39, 0xd4, 0x80, 0xcd, 0x73, 0xa1, 0x08, 0x38, 0xe5, 0xcd, 0x9d, + 0x0f, 0xb1, 0x62, 0x15, 0x54, 0x71, 0x8f, 0xbe, 0xc3, 0xac, 0x3d, 0xb9, 0x4d, + 0x23, 0xfe, 0x16, 0xd5, 0xbb, 0x13, 0x7f, 0xe3, 0x24, 0xb8, 0x53, 0xa5, 0xa0, + 0xee, 0xf3, 0x36, 0x23, 0x98, 0x75, 0x4e, 0xad, 0xf8, 0xd1, 0xba, 0x74, 0xf4, + 0xdf, 0xdd, 0xe6, 0xb0, 0x44, 0x37, 0x94, 0x74, 0xaa, 0xc3, 0xc8, 0xef, 0x00, + 0x3e, 0xce, 0xe7, 0x14, 0xdd, 0xcf, 0x4c, 0x94, 0x7c, 0xa7, 0x2a, 0xeb, 0xa2, + ], + fp: [ + 0xe1, 0x61, 0xbc, 0xa7, 0x4c, 0xac, 0x0b, 0xbd, 0x66, 0xb4, 0xa4, 0xad, 0x12, + 0x71, 0x32, 0x11, 0x60, 0x52, 0xef, 0xf7, 0x65, 0x96, 0x67, 0xd9, 0xf7, 0xfd, + 0xad, 0xd0, 0x1f, 0x10, 0x08, 0xa1, + ], + d0: Some([ + 0x18, 0x36, 0xc0, 0x6f, 0x69, 0x94, 0x47, 0x49, 0xaa, 0x48, 0x0b, + ]), + d1: None, + d2: None, + dmax: Some([ + 0x63, 0xea, 0x9f, 0xbb, 0x99, 0x95, 0xc9, 0x39, 0x7a, 0xc2, 0x23, + ]), + internal_nsk: None, + internal_ovk: [ + 0x21, 0x51, 0x3d, 0x60, 0x47, 0x7e, 0xd9, 0xcd, 0xf5, 0xdb, 0xcf, 0x6d, 0xba, + 0x3e, 0x7d, 0xec, 0xf0, 0xb9, 0xf6, 0x25, 0x99, 0xa5, 0x5d, 0x19, 0x1f, 0x04, + 0xe0, 0xe0, 0xe6, 0x3e, 0xa4, 0x62, + ], + internal_dk: [ + 0x72, 0x4f, 0xaa, 0x86, 0x25, 0x1b, 0x1f, 0x19, 0xc1, 0xde, 0xfb, 0x42, 0xbc, + 0x0b, 0x92, 0x51, 0xf0, 0xc5, 0x3e, 0x08, 0x2b, 0x01, 0xea, 0xf1, 0x9c, 0x47, + 0xaa, 0x9e, 0x2d, 0xfb, 0x4f, 0x5e, + ], + internal_nk: [ + 0x1b, 0x69, 0x5f, 0x0f, 0x38, 0x64, 0x5d, 0xfb, 0xa5, 0x60, 0x15, 0x52, 0x39, + 0x53, 0x3f, 0x2d, 0xf2, 0x9d, 0x79, 0x98, 0x49, 0x9a, 0x4d, 0x62, 0x5c, 0x65, + 0x8e, 0xdb, 0xd9, 0x54, 0x84, 0xe7, + ], + internal_ivk: [ + 0xcb, 0x2d, 0x46, 0xbb, 0x53, 0x20, 0xfd, 0x19, 0xd4, 0xc2, 0xdc, 0x76, 0xc2, + 0xd7, 0xc8, 0xd7, 0x01, 0xd4, 0xb0, 0xe0, 0x1a, 0xe9, 0xf7, 0x49, 0xb8, 0xa4, + 0x84, 0x81, 0x00, 0xd1, 0x0e, 0x04, + ], + internal_xsk: None, + internal_xfvk: [ + 0x02, 0xe5, 0x1f, 0x7b, 0xd0, 0x02, 0x00, 0x00, 0x80, 0xdb, 0xaa, 0x2d, 0xde, + 0xd8, 0x6b, 0xdb, 0x32, 0xfd, 0x60, 0x5b, 0x5e, 0xa0, 0xdb, 0x6a, 0x57, 0xa5, + 0xb3, 0x3b, 0x36, 0x20, 0x94, 0x8f, 0x76, 0x9c, 0x12, 0x85, 0x88, 0x51, 0xeb, + 0x83, 0xca, 0x42, 0xec, 0x8b, 0x50, 0x8d, 0xbb, 0x9a, 0x6d, 0x4a, 0x58, 0xf1, + 0xb7, 0xcb, 0x96, 0x06, 0xfd, 0x75, 0xdd, 0x1c, 0x0d, 0x03, 0x9c, 0x2c, 0xac, + 0x19, 0xb2, 0x66, 0x52, 0xcb, 0x3b, 0x27, 0xcd, 0x1b, 0x69, 0x5f, 0x0f, 0x38, + 0x64, 0x5d, 0xfb, 0xa5, 0x60, 0x15, 0x52, 0x39, 0x53, 0x3f, 0x2d, 0xf2, 0x9d, + 0x79, 0x98, 0x49, 0x9a, 0x4d, 0x62, 0x5c, 0x65, 0x8e, 0xdb, 0xd9, 0x54, 0x84, + 0xe7, 0x21, 0x51, 0x3d, 0x60, 0x47, 0x7e, 0xd9, 0xcd, 0xf5, 0xdb, 0xcf, 0x6d, + 0xba, 0x3e, 0x7d, 0xec, 0xf0, 0xb9, 0xf6, 0x25, 0x99, 0xa5, 0x5d, 0x19, 0x1f, + 0x04, 0xe0, 0xe0, 0xe6, 0x3e, 0xa4, 0x62, 0x72, 0x4f, 0xaa, 0x86, 0x25, 0x1b, + 0x1f, 0x19, 0xc1, 0xde, 0xfb, 0x42, 0xbc, 0x0b, 0x92, 0x51, 0xf0, 0xc5, 0x3e, + 0x08, 0x2b, 0x01, 0xea, 0xf1, 0x9c, 0x47, 0xaa, 0x9e, 0x2d, 0xfb, 0x4f, 0x5e, + ], + internal_fp: [ + 0x64, 0x5b, 0xd5, 0x1d, 0x95, 0xbc, 0xdd, 0x36, 0xc7, 0x55, 0x71, 0x3d, 0xf6, + 0x13, 0x09, 0xf1, 0xbf, 0x4e, 0x29, 0x8a, 0x71, 0xb5, 0xec, 0x55, 0xed, 0xdd, + 0xb2, 0x25, 0xab, 0xbd, 0xfd, 0x36, + ], + }, + TestVector { + ask: None, + nsk: None, + ovk: [ + 0x83, 0x55, 0xaa, 0x44, 0x4f, 0x48, 0xb7, 0x6c, 0xcd, 0x42, 0x83, 0x5f, 0x5f, + 0x3d, 0x18, 0x2f, 0x10, 0xf6, 0x7b, 0x3f, 0x9b, 0xd1, 0xa7, 0xab, 0xac, 0x7a, + 0x02, 0xea, 0x8b, 0xa2, 0x91, 0x4b, + ], + dk: [ + 0x64, 0xe8, 0x88, 0x71, 0x4d, 0x39, 0x55, 0x03, 0xe8, 0x34, 0xa7, 0x8e, 0xee, + 0xb9, 0xf4, 0x29, 0x4d, 0x52, 0xac, 0x55, 0xe0, 0xe9, 0x0e, 0x90, 0xc8, 0x1d, + 0x12, 0x67, 0x97, 0x86, 0x92, 0x70, + ], + c: [ + 0xb5, 0xa0, 0x09, 0xf3, 0xad, 0x52, 0xb0, 0x4f, 0xee, 0xac, 0x65, 0xe7, 0x9a, + 0x6e, 0x30, 0xd8, 0x94, 0x82, 0x51, 0xb7, 0xa8, 0x82, 0x47, 0xb2, 0xce, 0x96, + 0x78, 0x22, 0xfe, 0x49, 0xcc, 0xa1, + ], + ak: [ + 0xc4, 0x74, 0x8f, 0x3e, 0x63, 0xe9, 0x7f, 0x0a, 0xea, 0xff, 0x39, 0x20, 0x51, + 0x9b, 0x7c, 0x2c, 0x1e, 0xd8, 0x40, 0xd4, 0xdd, 0x7a, 0xc1, 0x1f, 0xb0, 0x46, + 0x0e, 0xd5, 0xff, 0x9e, 0x2f, 0xe0, + ], + nk: [ + 0x01, 0x7d, 0xee, 0xa7, 0x7c, 0x0f, 0xa6, 0x87, 0xfd, 0x0e, 0x7a, 0x11, 0xff, + 0xcd, 0x3d, 0x3d, 0x11, 0xb8, 0x5c, 0xf5, 0xc0, 0x53, 0x6f, 0xf8, 0xca, 0xea, + 0x74, 0x88, 0x37, 0xa5, 0x3a, 0xd6, + ], + ivk: [ + 0x2d, 0xf3, 0xe1, 0x49, 0xf6, 0xd3, 0x4e, 0x9f, 0xa9, 0xac, 0x66, 0xbd, 0xdc, + 0x40, 0xe2, 0xb5, 0x93, 0x66, 0x99, 0x99, 0x87, 0xd7, 0xdf, 0x82, 0x9d, 0xec, + 0x5d, 0x51, 0x74, 0xab, 0xcd, 0x05, + ], + xsk: None, + xfvk: [ + 0x03, 0xe1, 0x61, 0xbc, 0xa7, 0x03, 0x00, 0x00, 0x00, 0xb5, 0xa0, 0x09, 0xf3, + 0xad, 0x52, 0xb0, 0x4f, 0xee, 0xac, 0x65, 0xe7, 0x9a, 0x6e, 0x30, 0xd8, 0x94, + 0x82, 0x51, 0xb7, 0xa8, 0x82, 0x47, 0xb2, 0xce, 0x96, 0x78, 0x22, 0xfe, 0x49, + 0xcc, 0xa1, 0xc4, 0x74, 0x8f, 0x3e, 0x63, 0xe9, 0x7f, 0x0a, 0xea, 0xff, 0x39, + 0x20, 0x51, 0x9b, 0x7c, 0x2c, 0x1e, 0xd8, 0x40, 0xd4, 0xdd, 0x7a, 0xc1, 0x1f, + 0xb0, 0x46, 0x0e, 0xd5, 0xff, 0x9e, 0x2f, 0xe0, 0x01, 0x7d, 0xee, 0xa7, 0x7c, + 0x0f, 0xa6, 0x87, 0xfd, 0x0e, 0x7a, 0x11, 0xff, 0xcd, 0x3d, 0x3d, 0x11, 0xb8, + 0x5c, 0xf5, 0xc0, 0x53, 0x6f, 0xf8, 0xca, 0xea, 0x74, 0x88, 0x37, 0xa5, 0x3a, + 0xd6, 0x83, 0x55, 0xaa, 0x44, 0x4f, 0x48, 0xb7, 0x6c, 0xcd, 0x42, 0x83, 0x5f, + 0x5f, 0x3d, 0x18, 0x2f, 0x10, 0xf6, 0x7b, 0x3f, 0x9b, 0xd1, 0xa7, 0xab, 0xac, + 0x7a, 0x02, 0xea, 0x8b, 0xa2, 0x91, 0x4b, 0x64, 0xe8, 0x88, 0x71, 0x4d, 0x39, + 0x55, 0x03, 0xe8, 0x34, 0xa7, 0x8e, 0xee, 0xb9, 0xf4, 0x29, 0x4d, 0x52, 0xac, + 0x55, 0xe0, 0xe9, 0x0e, 0x90, 0xc8, 0x1d, 0x12, 0x67, 0x97, 0x86, 0x92, 0x70, + ], + fp: [ + 0x16, 0x74, 0xa8, 0x94, 0xa4, 0xf3, 0x4c, 0xcb, 0x76, 0x92, 0x03, 0xa0, 0x1a, + 0x4f, 0xb7, 0x76, 0xc5, 0xe0, 0x68, 0xde, 0xe2, 0x4b, 0x1a, 0xce, 0x7a, 0x42, + 0x48, 0x6f, 0x35, 0x8e, 0x94, 0x36, + ], + d0: Some([ + 0x1b, 0x9b, 0x96, 0x29, 0xb3, 0x83, 0x1c, 0x12, 0xad, 0x1d, 0x06, + ]), + d1: Some([ + 0x7a, 0xa8, 0x22, 0x53, 0x7d, 0x01, 0x5c, 0x19, 0xd8, 0x37, 0x46, + ]), + d2: None, + dmax: None, + internal_nsk: None, + internal_ovk: [ + 0x41, 0x77, 0x92, 0x32, 0x32, 0x46, 0xd3, 0x0c, 0xff, 0x01, 0x92, 0xb3, 0x8c, + 0x2b, 0x70, 0x99, 0x16, 0x19, 0x58, 0x09, 0x04, 0xa2, 0x4e, 0x6c, 0x29, 0xe8, + 0xef, 0x4c, 0xe5, 0x6d, 0x4d, 0xa1, + ], + internal_dk: [ + 0xe5, 0x9d, 0x76, 0xc5, 0x48, 0xe5, 0x9b, 0x83, 0x25, 0xa5, 0x9c, 0x42, 0x8b, + 0xcc, 0xe4, 0xb1, 0xf3, 0xd6, 0x4b, 0xb6, 0x96, 0x4d, 0x6f, 0xcd, 0x23, 0x8b, + 0xe7, 0xfe, 0xb1, 0x20, 0x5f, 0xb6, + ], + internal_nk: [ + 0xf3, 0x21, 0xb1, 0x18, 0xcf, 0x1b, 0x6f, 0xfa, 0x70, 0xd3, 0x54, 0x69, 0xb8, + 0xa2, 0xcc, 0xc8, 0x25, 0x5e, 0x50, 0xfd, 0x4a, 0x7c, 0xe8, 0x9b, 0x90, 0xa7, + 0x9e, 0xfe, 0x63, 0xb8, 0xa9, 0x68, + ], + internal_ivk: [ + 0x23, 0xf8, 0xcd, 0x69, 0x15, 0x58, 0x74, 0x8a, 0x94, 0xe7, 0xb8, 0xd9, 0x56, + 0x7c, 0xb2, 0xb3, 0xc5, 0x80, 0x6c, 0x66, 0xba, 0x27, 0xee, 0x4c, 0x01, 0x8d, + 0x55, 0x11, 0x0e, 0x6c, 0xff, 0x05, + ], + internal_xsk: None, + internal_xfvk: [ + 0x03, 0xe1, 0x61, 0xbc, 0xa7, 0x03, 0x00, 0x00, 0x00, 0xb5, 0xa0, 0x09, 0xf3, + 0xad, 0x52, 0xb0, 0x4f, 0xee, 0xac, 0x65, 0xe7, 0x9a, 0x6e, 0x30, 0xd8, 0x94, + 0x82, 0x51, 0xb7, 0xa8, 0x82, 0x47, 0xb2, 0xce, 0x96, 0x78, 0x22, 0xfe, 0x49, + 0xcc, 0xa1, 0xc4, 0x74, 0x8f, 0x3e, 0x63, 0xe9, 0x7f, 0x0a, 0xea, 0xff, 0x39, + 0x20, 0x51, 0x9b, 0x7c, 0x2c, 0x1e, 0xd8, 0x40, 0xd4, 0xdd, 0x7a, 0xc1, 0x1f, + 0xb0, 0x46, 0x0e, 0xd5, 0xff, 0x9e, 0x2f, 0xe0, 0xf3, 0x21, 0xb1, 0x18, 0xcf, + 0x1b, 0x6f, 0xfa, 0x70, 0xd3, 0x54, 0x69, 0xb8, 0xa2, 0xcc, 0xc8, 0x25, 0x5e, + 0x50, 0xfd, 0x4a, 0x7c, 0xe8, 0x9b, 0x90, 0xa7, 0x9e, 0xfe, 0x63, 0xb8, 0xa9, + 0x68, 0x41, 0x77, 0x92, 0x32, 0x32, 0x46, 0xd3, 0x0c, 0xff, 0x01, 0x92, 0xb3, + 0x8c, 0x2b, 0x70, 0x99, 0x16, 0x19, 0x58, 0x09, 0x04, 0xa2, 0x4e, 0x6c, 0x29, + 0xe8, 0xef, 0x4c, 0xe5, 0x6d, 0x4d, 0xa1, 0xe5, 0x9d, 0x76, 0xc5, 0x48, 0xe5, + 0x9b, 0x83, 0x25, 0xa5, 0x9c, 0x42, 0x8b, 0xcc, 0xe4, 0xb1, 0xf3, 0xd6, 0x4b, + 0xb6, 0x96, 0x4d, 0x6f, 0xcd, 0x23, 0x8b, 0xe7, 0xfe, 0xb1, 0x20, 0x5f, 0xb6, + ], + internal_fp: [ + 0x6c, 0xa1, 0xa5, 0xc2, 0xd2, 0x1b, 0x87, 0x95, 0xa9, 0xed, 0xb6, 0xb7, 0x5e, + 0x79, 0x72, 0x49, 0x22, 0x95, 0x3e, 0x2f, 0x08, 0x57, 0x69, 0x4b, 0xcc, 0xf4, + 0xc1, 0xdd, 0xf5, 0x07, 0x55, 0xf4, + ], + }, + ]; + + let seed = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + ]; + + let i1 = ChildIndex::NonHardened(1); + let i2h = ChildIndex::Hardened(2); + let i3 = ChildIndex::NonHardened(3); + + let m = ExtendedSpendingKey::master(&seed); + let m_1 = m.derive_child(i1); + let m_1_2h = ExtendedSpendingKey::from_path(&m, &[i1, i2h]); + let m_1_2hv = ExtendedFullViewingKey::from(&m_1_2h); + let m_1_2hv_3 = m_1_2hv.derive_child(i3).unwrap(); + + let xfvks = [ + ExtendedFullViewingKey::from(&m), + ExtendedFullViewingKey::from(&m_1), + ExtendedFullViewingKey::from(&m_1_2h), + m_1_2hv, // Appears twice so we can de-duplicate test code below + m_1_2hv_3, + ]; + assert_eq!(test_vectors.len(), xfvks.len()); + + let xsks = [m, m_1, m_1_2h]; + + for (xsk, tv) in xsks.iter().zip(test_vectors.iter()) { + assert_eq!(xsk.expsk.ask.to_repr().as_ref(), tv.ask.unwrap()); + assert_eq!(xsk.expsk.nsk.to_repr().as_ref(), tv.nsk.unwrap()); + + assert_eq!(xsk.expsk.ovk.0, tv.ovk); + assert_eq!(xsk.dk.0, tv.dk); + assert_eq!(xsk.chain_code.0, tv.c); + + let mut ser = vec![]; + xsk.write(&mut ser).unwrap(); + assert_eq!(&ser[..], &tv.xsk.unwrap()[..]); + let internal_xsk = xsk.derive_internal(); + assert_eq!(internal_xsk.expsk.ask.to_repr().as_ref(), tv.ask.unwrap()); + assert_eq!( + internal_xsk.expsk.nsk.to_repr().as_ref(), + tv.internal_nsk.unwrap() + ); + + assert_eq!(internal_xsk.expsk.ovk.0, tv.internal_ovk); + assert_eq!(internal_xsk.dk.0, tv.internal_dk); + assert_eq!(internal_xsk.chain_code.0, tv.c); + + let mut ser = vec![]; + internal_xsk.write(&mut ser).unwrap(); + assert_eq!(&ser[..], &tv.internal_xsk.unwrap()[..]); + } + + for (xfvk, tv) in xfvks.iter().zip(test_vectors.iter()) { + assert_eq!(xfvk.fvk.vk.ak.to_bytes(), tv.ak); + assert_eq!(xfvk.fvk.vk.nk.0.to_bytes(), tv.nk); + + assert_eq!(xfvk.fvk.ovk.0, tv.ovk); + assert_eq!(xfvk.dk.0, tv.dk); + assert_eq!(xfvk.chain_code.0, tv.c); + + assert_eq!(xfvk.fvk.vk.ivk().to_repr().as_ref(), tv.ivk); + + let mut ser = vec![]; + xfvk.write(&mut ser).unwrap(); + assert_eq!(&ser[..], &tv.xfvk[..]); + assert_eq!(FvkFingerprint::from(&xfvk.fvk).0, tv.fp); + + // d0 + let mut di = DiversifierIndex::new(); + match xfvk.dk.find_diversifier(di).unwrap() { + (l, d) if l == di => assert_eq!(d.0, tv.d0.unwrap()), + (_, _) => assert!(tv.d0.is_none()), + } + + // d1 + di.increment().unwrap(); + match xfvk.dk.find_diversifier(di).unwrap() { + (l, d) if l == di => assert_eq!(d.0, tv.d1.unwrap()), + (_, _) => assert!(tv.d1.is_none()), + } + + // d2 + di.increment().unwrap(); + match xfvk.dk.find_diversifier(di).unwrap() { + (l, d) if l == di => assert_eq!(d.0, tv.d2.unwrap()), + (_, _) => assert!(tv.d2.is_none()), + } + + // dmax + let dmax = DiversifierIndex([0xff; 11]); + match xfvk.dk.find_diversifier(dmax) { + Some((l, d)) if l == dmax => assert_eq!(d.0, tv.dmax.unwrap()), + Some((_, _)) => panic!(), + None => assert!(tv.dmax.is_none()), + } + + let internal_xfvk = xfvk.derive_internal(); + assert_eq!(internal_xfvk.fvk.vk.ak.to_bytes(), tv.ak); + assert_eq!(internal_xfvk.fvk.vk.nk.0.to_bytes(), tv.internal_nk); + + assert_eq!(internal_xfvk.fvk.ovk.0, tv.internal_ovk); + assert_eq!(internal_xfvk.dk.0, tv.internal_dk); + assert_eq!(internal_xfvk.chain_code.0, tv.c); + + assert_eq!( + internal_xfvk.fvk.vk.ivk().to_repr().as_ref(), + tv.internal_ivk + ); + + let mut ser = vec![]; + internal_xfvk.write(&mut ser).unwrap(); + assert_eq!(&ser[..], &tv.internal_xfvk[..]); + assert_eq!(FvkFingerprint::from(&internal_xfvk.fvk).0, tv.internal_fp); + } + } +} + +#[cfg(any(test, feature = "test-dependencies"))] +pub mod testing { + use proptest::collection::vec; + use proptest::prelude::{any, prop_compose}; + + use super::ExtendedSpendingKey; + + prop_compose! { + pub fn arb_extended_spending_key()(v in vec(any::(), 32..252)) -> ExtendedSpendingKey { + ExtendedSpendingKey::master(&v) + } + } +} diff --git a/masp_proofs/Cargo.toml b/masp_proofs/Cargo.toml index 35212123..b7b90d08 100644 --- a/masp_proofs/Cargo.toml +++ b/masp_proofs/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "masp_proofs" description = "Experimental MASP zk-SNARK circuits and proving APIs, based on zcash_proofs" -version = "0.5.0" +version = "0.9.0" authors = [ "Jack Grigg ", "joe ", @@ -10,44 +10,55 @@ homepage = "https://github.com/anoma/masp" repository = "https://github.com/anoma/masp" readme = "README.md" license = "MIT OR Apache-2.0" -edition = "2018" +edition = "2021" +rust-version = "1.65" +categories = ["cryptography::cryptocurrencies"] [package.metadata.docs.rs] all-features = true [dependencies] -bellman = { version = "0.11.1", default-features = false, features = ["groth16"] } -blake2b_simd = "1.0.0" -bls12_381 = "0.6" -byteorder = "1" -directories = { version = "4.0.1", optional = true } -ff = "0.11" -group = "0.11" -itertools = "0.10.1" -jubjub = "0.8" +masp_primitives = { version = "0.9", path = "../masp_primitives" } + +# Dependencies exposed in a public API: +# (Breaking upgrades to these require a breaking upgrade to this crate.) +bellman = { version = "0.13.1", default-features = false, features = ["groth16"] } +bls12_381 = "0.7" +group = "0.12" +jubjub = "0.9" lazy_static = "1" minreq = { version = "2", features = ["https"], optional = true } rand_core = "0.6" +tracing = "0.1" + +# Dependencies used internally: +# (Breaking upgrades to these are usually backwards-compatible, but check MSRVs.) +blake2b_simd = "1" +directories = { version = "4", optional = true } +redjubjub = "0.5" wagyu-zcash-parameters = { version = "0.2", optional = true } -masp_primitives = { version = "0.5", path = "../masp_primitives" } -zcash_primitives = { git = "https://github.com/zcash/librustzcash", rev = "43c18d0" } -zcash_proofs = { git = "https://github.com/zcash/librustzcash", features = ["multicore"], rev = "43c18d0" } getrandom = { version = "0.2", features = ["js"] } +itertools = "0.10.1" [dev-dependencies] -criterion = "0.3" +byteorder = "1" +criterion = "0.4" rand_xorshift = "0.3" +[target.'cfg(unix)'.dev-dependencies] +pprof = { version = "0.11", features = ["criterion", "flamegraph"] } # MSRV 1.56 + [features] default = ["local-prover", "multicore"] bundled-prover = ["wagyu-zcash-parameters"] -download-params = ["minreq"] +download-params = ["minreq", "directories"] local-prover = ["directories"] multicore = ["bellman/multicore"] embed-verifying-key = [] [lib] bench = false + [[bench]] name = "sapling" harness = false diff --git a/masp_proofs/benches/convert.rs b/masp_proofs/benches/convert.rs index 94984741..da496ad1 100644 --- a/masp_proofs/benches/convert.rs +++ b/masp_proofs/benches/convert.rs @@ -4,8 +4,10 @@ extern crate criterion; use bellman::groth16::*; use bls12_381::Bls12; use criterion::Criterion; -use ff::Field; -use masp_primitives::{asset_type::AssetType, convert::AllowedConversion}; +use group::ff::Field; +use masp_primitives::{ + asset_type::AssetType, convert::AllowedConversion, transaction::components::Amount, +}; use masp_proofs::circuit::convert::{Convert, TREE_DEPTH}; use rand_core::{RngCore, SeedableRng}; use rand_xorshift::XorShiftRng; @@ -36,13 +38,11 @@ fn criterion_benchmark(c: &mut Criterion) { let output_value = i as i64 + 1; let mint_value = i as i64 + 1; - let allowed_conversion = AllowedConversion { - assets: vec![ - (spend_asset, spend_value), - (output_asset, output_value), - (mint_asset, mint_value), - ], - }; + let allowed_conversion: AllowedConversion = (Amount::from_pair(spend_asset, spend_value) + .unwrap() + + Amount::from_pair(output_asset, output_value).unwrap() + + Amount::from_pair(mint_asset, mint_value).unwrap()) + .into(); let value = rng.next_u64(); diff --git a/masp_proofs/benches/sapling.rs b/masp_proofs/benches/sapling.rs index d4b6fe86..74dd7296 100644 --- a/masp_proofs/benches/sapling.rs +++ b/masp_proofs/benches/sapling.rs @@ -4,11 +4,10 @@ extern crate criterion; use bellman::groth16::*; use bls12_381::Bls12; use criterion::Criterion; -use ff::Field; -use group::Group; +use group::{ff::Field, Group}; use masp_primitives::{ asset_type::AssetType, - primitives::{Diversifier, ProofGenerationKey}, + sapling::{Diversifier, ProofGenerationKey}, }; use masp_proofs::circuit::sapling::Spend; use rand_core::{RngCore, SeedableRng}; @@ -74,7 +73,7 @@ fn criterion_benchmark(c: &mut Criterion) { Spend { value_commitment: Some(value_commitment.clone()), proof_generation_key: Some(proof_generation_key.clone()), - payment_address: Some(payment_address.clone()), + payment_address: Some(payment_address), commitment_randomness: Some(commitment_randomness), ar: Some(ar), auth_path: auth_path.clone(), diff --git a/masp_proofs/examples/download-params.rs b/masp_proofs/examples/download-params.rs index f6d0e86b..71308792 100644 --- a/masp_proofs/examples/download-params.rs +++ b/masp_proofs/examples/download-params.rs @@ -1,3 +1,3 @@ fn main() -> Result<(), minreq::Error> { - masp_proofs::download_parameters() + masp_proofs::download_masp_parameters(None).map(|_masp_paths| ()) } diff --git a/masp_proofs/examples/serialize-params.rs b/masp_proofs/examples/serialize-params.rs index 0b00da5c..d6cdffdd 100644 --- a/masp_proofs/examples/serialize-params.rs +++ b/masp_proofs/examples/serialize-params.rs @@ -2,7 +2,8 @@ use std::io::Write; fn main() { // Download params first - masp_proofs::download_parameters().unwrap(); + #[cfg(feature = "download-params")] + masp_proofs::download_masp_parameters(None).unwrap(); if let Some(path) = masp_proofs::default_params_folder() { let params = masp_proofs::load_parameters( @@ -12,9 +13,9 @@ fn main() { ); for (filename, vk) in [ - ("spend_TESTING_vk.params", params.spend_params.vk), - ("output_TESTING_vk.params", params.output_params.vk), - ("convert_TESTING_vk.params", params.convert_params.vk), + ("masp-spend.vk", params.spend_params.vk), + ("masp-output.vk", params.output_params.vk), + ("masp-convert.vk", params.convert_params.vk), ] .iter() { diff --git a/masp_proofs/params/.gitattributes b/masp_proofs/params/.gitattributes new file mode 100644 index 00000000..9af9cb72 --- /dev/null +++ b/masp_proofs/params/.gitattributes @@ -0,0 +1,3 @@ +masp-convert.vk filter=lfs diff=lfs merge=lfs -text +masp-output.vk filter=lfs diff=lfs merge=lfs -text +masp-spend.vk filter=lfs diff=lfs merge=lfs -text diff --git a/masp_proofs/params/masp-convert.vk b/masp_proofs/params/masp-convert.vk new file mode 100644 index 00000000..8bc9ff67 --- /dev/null +++ b/masp_proofs/params/masp-convert.vk @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:771d423cafd7238a21bdbe8663e86bf138b53727a41bad88a66c01707ecc1058 +size 1252 diff --git a/masp_proofs/params/masp-output.vk b/masp_proofs/params/masp-output.vk new file mode 100644 index 00000000..c4fcdd19 --- /dev/null +++ b/masp_proofs/params/masp-output.vk @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9ed0ae9cb0616b0e06502f4ff2f51f69e6ebb617f6e4fba3b9f83df383d26f1 +size 1444 diff --git a/masp_proofs/params/masp-spend.vk b/masp_proofs/params/masp-spend.vk new file mode 100644 index 00000000..10c29040 --- /dev/null +++ b/masp_proofs/params/masp-spend.vk @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2dba395c5951ba56176ed2937c3c6b5163b9a9bd4658070bf938ac6f99176a56 +size 1636 diff --git a/masp_proofs/src/circuit.rs b/masp_proofs/src/circuit.rs index f529ec79..e6dbf9d0 100644 --- a/masp_proofs/src/circuit.rs +++ b/masp_proofs/src/circuit.rs @@ -1,5 +1,6 @@ //! Implementations of the MASP circuits and personalized Pedersen hash pub mod convert; +pub mod ecc; pub mod pedersen_hash; pub mod sapling; diff --git a/masp_proofs/src/circuit/convert.rs b/masp_proofs/src/circuit/convert.rs index 848e60d7..d555ed67 100644 --- a/masp_proofs/src/circuit/convert.rs +++ b/masp_proofs/src/circuit/convert.rs @@ -1,8 +1,8 @@ -//! The Sapling circuits. +//! The Convert circuit. use bellman::{Circuit, ConstraintSystem, SynthesisError}; -use masp_primitives::primitives::ValueCommitment; +use masp_primitives::sapling::ValueCommitment; use super::pedersen_hash; use crate::circuit::sapling::expose_value_commitment; @@ -11,7 +11,7 @@ use bellman::gadgets::boolean; use bellman::gadgets::num; use bellman::gadgets::Assignment; -pub const TREE_DEPTH: usize = zcash_primitives::sapling::SAPLING_COMMITMENT_TREE_DEPTH; +pub const TREE_DEPTH: usize = masp_primitives::sapling::SAPLING_COMMITMENT_TREE_DEPTH; /// This is an instance of the `Convert` circuit. pub struct Convert { @@ -129,9 +129,11 @@ impl Circuit for Convert { #[test] fn test_convert_circuit_with_bls12_381() { use bellman::gadgets::test::*; - use ff::{Field, PrimeField, PrimeFieldBits}; - use group::Curve; - use masp_primitives::{asset_type::AssetType, convert::AllowedConversion, pedersen_hash}; + use group::{ff::Field, ff::PrimeField, ff::PrimeFieldBits, Curve}; + use masp_primitives::{ + asset_type::AssetType, convert::AllowedConversion, sapling::pedersen_hash, + transaction::components::Amount, + }; use rand_core::{RngCore, SeedableRng}; use rand_xorshift::XorShiftRng; @@ -152,13 +154,11 @@ fn test_convert_circuit_with_bls12_381() { let output_value = i as i64 + 1; let mint_value = i as i64 + 1; - let allowed_conversion = AllowedConversion { - assets: vec![ - (spend_asset, spend_value), - (output_asset, output_value), - (mint_asset, mint_value), - ], - }; + let allowed_conversion: AllowedConversion = (Amount::from_pair(spend_asset, spend_value) + .unwrap() + + Amount::from_pair(output_asset, output_value).unwrap() + + Amount::from_pair(mint_asset, mint_value).unwrap()) + .into(); let value = rng.next_u64(); @@ -191,11 +191,11 @@ fn test_convert_circuit_with_bls12_381() { cur = jubjub::ExtendedPoint::from(pedersen_hash::pedersen_hash( pedersen_hash::Personalization::MerkleTree(i), lhs.iter() - .by_val() + .by_vals() .take(bls12_381::Scalar::NUM_BITS as usize) .chain( rhs.iter() - .by_val() + .by_vals() .take(bls12_381::Scalar::NUM_BITS as usize), ), )) diff --git a/masp_proofs/src/circuit/ecc.rs b/masp_proofs/src/circuit/ecc.rs new file mode 100644 index 00000000..48d093f7 --- /dev/null +++ b/masp_proofs/src/circuit/ecc.rs @@ -0,0 +1,1114 @@ +//! Gadgets implementing Jubjub elliptic curve operations. + +use std::ops::{AddAssign, MulAssign, Neg, SubAssign}; + +use bellman::{ConstraintSystem, SynthesisError}; + +use bellman::gadgets::Assignment; + +use bellman::gadgets::num::{AllocatedNum, Num}; + +use bellman::gadgets::lookup::lookup3_xy; + +use bellman::gadgets::boolean::Boolean; + +use group::Curve; + +use crate::constants::{FixedGenerator, EDWARDS_D, MONTGOMERY_A, MONTGOMERY_SCALE}; + +#[derive(Clone)] +pub struct EdwardsPoint { + u: AllocatedNum, + v: AllocatedNum, +} + +/// Perform a fixed-base scalar multiplication with +/// `by` being in little-endian bit order. +pub fn fixed_base_multiplication( + mut cs: CS, + base: FixedGenerator, + by: &[Boolean], +) -> Result +where + CS: ConstraintSystem, +{ + // Represents the result of the multiplication + let mut result = None; + + for (i, (chunk, window)) in by.chunks(3).zip(base.iter()).enumerate() { + let chunk_a = chunk + .get(0) + .cloned() + .unwrap_or_else(|| Boolean::constant(false)); + let chunk_b = chunk + .get(1) + .cloned() + .unwrap_or_else(|| Boolean::constant(false)); + let chunk_c = chunk + .get(2) + .cloned() + .unwrap_or_else(|| Boolean::constant(false)); + + // TODO: rename to lookup3_uv + let (u, v) = lookup3_xy( + cs.namespace(|| format!("window table lookup {}", i)), + &[chunk_a, chunk_b, chunk_c], + window, + )?; + + let p = EdwardsPoint { u, v }; + + if result.is_none() { + result = Some(p); + } else { + result = Some( + result + .unwrap() + .add(cs.namespace(|| format!("addition {}", i)), &p)?, + ); + } + } + + Ok(result.get()?.clone()) +} + +impl EdwardsPoint { + pub fn get_u(&self) -> &AllocatedNum { + &self.u + } + + pub fn get_v(&self) -> &AllocatedNum { + &self.v + } + + pub fn assert_not_small_order(&self, mut cs: CS) -> Result<(), SynthesisError> + where + CS: ConstraintSystem, + { + let tmp = self.double(cs.namespace(|| "first doubling"))?; + let tmp = tmp.double(cs.namespace(|| "second doubling"))?; + let tmp = tmp.double(cs.namespace(|| "third doubling"))?; + + // (0, -1) is a small order point, but won't ever appear here + // because cofactor is 2^3, and we performed three doublings. + // (0, 1) is the neutral element, so checking if u is nonzero + // is sufficient to prevent small order points here. + tmp.u.assert_nonzero(cs.namespace(|| "check u != 0"))?; + + Ok(()) + } + + pub fn inputize(&self, mut cs: CS) -> Result<(), SynthesisError> + where + CS: ConstraintSystem, + { + self.u.inputize(cs.namespace(|| "u"))?; + self.v.inputize(cs.namespace(|| "v"))?; + + Ok(()) + } + + /// This converts the point into a representation. + pub fn repr(&self, mut cs: CS) -> Result, SynthesisError> + where + CS: ConstraintSystem, + { + let mut tmp = vec![]; + + let u = self.u.to_bits_le_strict(cs.namespace(|| "unpack u"))?; + + let v = self.v.to_bits_le_strict(cs.namespace(|| "unpack v"))?; + + tmp.extend(v); + tmp.push(u[0].clone()); + + Ok(tmp) + } + + /// This 'witnesses' a point inside the constraint system. + /// It guarantees the point is on the curve. + pub fn witness(mut cs: CS, p: Option) -> Result + where + CS: ConstraintSystem, + { + let p = p.map(|p| p.to_affine()); + + // Allocate u + let u = AllocatedNum::alloc(cs.namespace(|| "u"), || Ok(p.get()?.get_u()))?; + + // Allocate v + let v = AllocatedNum::alloc(cs.namespace(|| "v"), || Ok(p.get()?.get_v()))?; + + Self::interpret(cs.namespace(|| "point interpretation"), &u, &v) + } + + /// Returns `self` if condition is true, and the neutral + /// element (0, 1) otherwise. + pub fn conditionally_select( + &self, + mut cs: CS, + condition: &Boolean, + ) -> Result + where + CS: ConstraintSystem, + { + // Compute u' = self.u if condition, and 0 otherwise + let u_prime = AllocatedNum::alloc(cs.namespace(|| "u'"), || { + if *condition.get_value().get()? { + Ok(*self.u.get_value().get()?) + } else { + Ok(bls12_381::Scalar::zero()) + } + })?; + + // condition * u = u' + // if condition is 0, u' must be 0 + // if condition is 1, u' must be u + let one = CS::one(); + cs.enforce( + || "u' computation", + |lc| lc + self.u.get_variable(), + |_| condition.lc(one, bls12_381::Scalar::one()), + |lc| lc + u_prime.get_variable(), + ); + + // Compute v' = self.v if condition, and 1 otherwise + let v_prime = AllocatedNum::alloc(cs.namespace(|| "v'"), || { + if *condition.get_value().get()? { + Ok(*self.v.get_value().get()?) + } else { + Ok(bls12_381::Scalar::one()) + } + })?; + + // condition * v = v' - (1 - condition) + // if condition is 0, v' must be 1 + // if condition is 1, v' must be v + cs.enforce( + || "v' computation", + |lc| lc + self.v.get_variable(), + |_| condition.lc(one, bls12_381::Scalar::one()), + |lc| lc + v_prime.get_variable() - &condition.not().lc(one, bls12_381::Scalar::one()), + ); + + Ok(EdwardsPoint { + u: u_prime, + v: v_prime, + }) + } + + /// Performs a scalar multiplication of this twisted Edwards + /// point by a scalar represented as a sequence of booleans + /// in little-endian bit order. + pub fn mul(&self, mut cs: CS, by: &[Boolean]) -> Result + where + CS: ConstraintSystem, + { + // Represents the current "magnitude" of the base + // that we're operating over. Starts at self, + // then 2*self, then 4*self, ... + let mut curbase = None; + + // Represents the result of the multiplication + let mut result = None; + + for (i, bit) in by.iter().enumerate() { + if curbase.is_none() { + curbase = Some(self.clone()); + } else { + // Double the previous value + curbase = Some( + curbase + .unwrap() + .double(cs.namespace(|| format!("doubling {}", i)))?, + ); + } + + // Represents the select base. If the bit for this magnitude + // is true, this will return `curbase`. Otherwise it will + // return the neutral element, which will have no effect on + // the result. + let thisbase = curbase + .as_ref() + .unwrap() + .conditionally_select(cs.namespace(|| format!("selection {}", i)), bit)?; + + if result.is_none() { + result = Some(thisbase); + } else { + result = Some( + result + .unwrap() + .add(cs.namespace(|| format!("addition {}", i)), &thisbase)?, + ); + } + } + + Ok(result.get()?.clone()) + } + + pub fn interpret( + mut cs: CS, + u: &AllocatedNum, + v: &AllocatedNum, + ) -> Result + where + CS: ConstraintSystem, + { + // -u^2 + v^2 = 1 + du^2v^2 + + let u2 = u.square(cs.namespace(|| "u^2"))?; + let v2 = v.square(cs.namespace(|| "v^2"))?; + let u2v2 = u2.mul(cs.namespace(|| "u^2 v^2"), &v2)?; + + let one = CS::one(); + cs.enforce( + || "on curve check", + |lc| lc - u2.get_variable() + v2.get_variable(), + |lc| lc + one, + |lc| lc + one + (EDWARDS_D, u2v2.get_variable()), + ); + + Ok(EdwardsPoint { + u: u.clone(), + v: v.clone(), + }) + } + + pub fn double(&self, mut cs: CS) -> Result + where + CS: ConstraintSystem, + { + // Compute T = (u + v) * (v - EDWARDS_A*u) + // = (u + v) * (u + v) + let t = AllocatedNum::alloc(cs.namespace(|| "T"), || { + let mut t0 = *self.u.get_value().get()?; + t0.add_assign(self.v.get_value().get()?); + + let mut t1 = *self.u.get_value().get()?; + t1.add_assign(self.v.get_value().get()?); + + t0.mul_assign(&t1); + + Ok(t0) + })?; + + cs.enforce( + || "T computation", + |lc| lc + self.u.get_variable() + self.v.get_variable(), + |lc| lc + self.u.get_variable() + self.v.get_variable(), + |lc| lc + t.get_variable(), + ); + + // Compute A = u * v + let a = self.u.mul(cs.namespace(|| "A computation"), &self.v)?; + + // Compute C = d*A*A + let c = AllocatedNum::alloc(cs.namespace(|| "C"), || { + let mut t0 = a.get_value().get()?.square(); + t0.mul_assign(EDWARDS_D); + + Ok(t0) + })?; + + cs.enforce( + || "C computation", + |lc| lc + (EDWARDS_D, a.get_variable()), + |lc| lc + a.get_variable(), + |lc| lc + c.get_variable(), + ); + + // Compute u3 = (2.A) / (1 + C) + let u3 = AllocatedNum::alloc(cs.namespace(|| "u3"), || { + let mut t0 = *a.get_value().get()?; + t0 = t0.double(); + + let mut t1 = bls12_381::Scalar::one(); + t1.add_assign(c.get_value().get()?); + + let res = t1.invert().map(|t1| t0 * t1); + if bool::from(res.is_some()) { + Ok(res.unwrap()) + } else { + Err(SynthesisError::DivisionByZero) + } + })?; + + let one = CS::one(); + cs.enforce( + || "u3 computation", + |lc| lc + one + c.get_variable(), + |lc| lc + u3.get_variable(), + |lc| lc + a.get_variable() + a.get_variable(), + ); + + // Compute v3 = (T + (EDWARDS_A-1)*A) / (1 - C) + // = (T - 2.A) / (1 - C) + let v3 = AllocatedNum::alloc(cs.namespace(|| "v3"), || { + let mut t0 = *a.get_value().get()?; + t0 = t0.double().neg(); + t0.add_assign(t.get_value().get()?); + + let mut t1 = bls12_381::Scalar::one(); + t1.sub_assign(c.get_value().get()?); + + let res = t1.invert().map(|t1| t0 * t1); + if bool::from(res.is_some()) { + Ok(res.unwrap()) + } else { + Err(SynthesisError::DivisionByZero) + } + })?; + + cs.enforce( + || "v3 computation", + |lc| lc + one - c.get_variable(), + |lc| lc + v3.get_variable(), + |lc| lc + t.get_variable() - a.get_variable() - a.get_variable(), + ); + + Ok(EdwardsPoint { u: u3, v: v3 }) + } + + /// Perform addition between any two points + pub fn add(&self, mut cs: CS, other: &Self) -> Result + where + CS: ConstraintSystem, + { + // Compute U = (u1 + v1) * (v2 - EDWARDS_A*u2) + // = (u1 + v1) * (u2 + v2) + // (In hindsight, U was a poor choice of name.) + let uppercase_u = AllocatedNum::alloc(cs.namespace(|| "U"), || { + let mut t0 = *self.u.get_value().get()?; + t0.add_assign(self.v.get_value().get()?); + + let mut t1 = *other.u.get_value().get()?; + t1.add_assign(other.v.get_value().get()?); + + t0.mul_assign(&t1); + + Ok(t0) + })?; + + cs.enforce( + || "U computation", + |lc| lc + self.u.get_variable() + self.v.get_variable(), + |lc| lc + other.u.get_variable() + other.v.get_variable(), + |lc| lc + uppercase_u.get_variable(), + ); + + // Compute A = v2 * u1 + let a = other.v.mul(cs.namespace(|| "A computation"), &self.u)?; + + // Compute B = u2 * v1 + let b = other.u.mul(cs.namespace(|| "B computation"), &self.v)?; + + // Compute C = d*A*B + let c = AllocatedNum::alloc(cs.namespace(|| "C"), || { + let mut t0 = *a.get_value().get()?; + t0.mul_assign(b.get_value().get()?); + t0.mul_assign(EDWARDS_D); + + Ok(t0) + })?; + + cs.enforce( + || "C computation", + |lc| lc + (EDWARDS_D, a.get_variable()), + |lc| lc + b.get_variable(), + |lc| lc + c.get_variable(), + ); + + // Compute u3 = (A + B) / (1 + C) + let u3 = AllocatedNum::alloc(cs.namespace(|| "u3"), || { + let mut t0 = *a.get_value().get()?; + t0.add_assign(b.get_value().get()?); + + let mut t1 = bls12_381::Scalar::one(); + t1.add_assign(c.get_value().get()?); + + let ret = t1.invert().map(|t1| t0 * t1); + if bool::from(ret.is_some()) { + Ok(ret.unwrap()) + } else { + Err(SynthesisError::DivisionByZero) + } + })?; + + let one = CS::one(); + cs.enforce( + || "u3 computation", + |lc| lc + one + c.get_variable(), + |lc| lc + u3.get_variable(), + |lc| lc + a.get_variable() + b.get_variable(), + ); + + // Compute v3 = (U - A - B) / (1 - C) + let v3 = AllocatedNum::alloc(cs.namespace(|| "v3"), || { + let mut t0 = *uppercase_u.get_value().get()?; + t0.sub_assign(a.get_value().get()?); + t0.sub_assign(b.get_value().get()?); + + let mut t1 = bls12_381::Scalar::one(); + t1.sub_assign(c.get_value().get()?); + + let ret = t1.invert().map(|t1| t0 * t1); + if bool::from(ret.is_some()) { + Ok(ret.unwrap()) + } else { + Err(SynthesisError::DivisionByZero) + } + })?; + + cs.enforce( + || "v3 computation", + |lc| lc + one - c.get_variable(), + |lc| lc + v3.get_variable(), + |lc| lc + uppercase_u.get_variable() - a.get_variable() - b.get_variable(), + ); + + Ok(EdwardsPoint { u: u3, v: v3 }) + } +} + +pub struct MontgomeryPoint { + x: Num, + y: Num, +} + +impl MontgomeryPoint { + /// Converts an element in the prime order subgroup into + /// a point in the birationally equivalent twisted + /// Edwards curve. + pub fn into_edwards(self, mut cs: CS) -> Result + where + CS: ConstraintSystem, + { + // Compute u = (scale*x) / y + let u = AllocatedNum::alloc(cs.namespace(|| "u"), || { + let mut t0 = *self.x.get_value().get()?; + t0.mul_assign(MONTGOMERY_SCALE); + + let ret = self.y.get_value().get()?.invert().map(|invy| t0 * invy); + if bool::from(ret.is_some()) { + Ok(ret.unwrap()) + } else { + Err(SynthesisError::DivisionByZero) + } + })?; + + cs.enforce( + || "u computation", + |lc| lc + &self.y.lc(bls12_381::Scalar::one()), + |lc| lc + u.get_variable(), + |lc| lc + &self.x.lc(MONTGOMERY_SCALE), + ); + + // Compute v = (x - 1) / (x + 1) + let v = AllocatedNum::alloc(cs.namespace(|| "v"), || { + let mut t0 = *self.x.get_value().get()?; + let mut t1 = t0; + t0.sub_assign(&bls12_381::Scalar::one()); + t1.add_assign(&bls12_381::Scalar::one()); + + let ret = t1.invert().map(|t1| t0 * t1); + if bool::from(ret.is_some()) { + Ok(ret.unwrap()) + } else { + Err(SynthesisError::DivisionByZero) + } + })?; + + let one = CS::one(); + cs.enforce( + || "v computation", + |lc| lc + &self.x.lc(bls12_381::Scalar::one()) + one, + |lc| lc + v.get_variable(), + |lc| lc + &self.x.lc(bls12_381::Scalar::one()) - one, + ); + + Ok(EdwardsPoint { u, v }) + } + + /// Interprets an (x, y) pair as a point + /// in Montgomery, does not check that it's + /// on the curve. Useful for constants and + /// window table lookups. + pub fn interpret_unchecked(x: Num, y: Num) -> Self { + MontgomeryPoint { x, y } + } + + /// Performs an affine point addition, not defined for + /// points with the same x-coordinate. + pub fn add(&self, mut cs: CS, other: &Self) -> Result + where + CS: ConstraintSystem, + { + // Compute lambda = (y' - y) / (x' - x) + let lambda = AllocatedNum::alloc(cs.namespace(|| "lambda"), || { + let mut n = *other.y.get_value().get()?; + n.sub_assign(self.y.get_value().get()?); + + let mut d = *other.x.get_value().get()?; + d.sub_assign(self.x.get_value().get()?); + + let ret = d.invert().map(|d| n * d); + if bool::from(ret.is_some()) { + Ok(ret.unwrap()) + } else { + Err(SynthesisError::DivisionByZero) + } + })?; + + cs.enforce( + || "evaluate lambda", + |lc| lc + &other.x.lc(bls12_381::Scalar::one()) - &self.x.lc(bls12_381::Scalar::one()), + |lc| lc + lambda.get_variable(), + |lc| lc + &other.y.lc(bls12_381::Scalar::one()) - &self.y.lc(bls12_381::Scalar::one()), + ); + + // Compute x'' = lambda^2 - A - x - x' + let xprime = AllocatedNum::alloc(cs.namespace(|| "xprime"), || { + let mut t0 = lambda.get_value().get()?.square(); + t0.sub_assign(MONTGOMERY_A); + t0.sub_assign(self.x.get_value().get()?); + t0.sub_assign(other.x.get_value().get()?); + + Ok(t0) + })?; + + // (lambda) * (lambda) = (A + x + x' + x'') + let one = CS::one(); + cs.enforce( + || "evaluate xprime", + |lc| lc + lambda.get_variable(), + |lc| lc + lambda.get_variable(), + |lc| { + lc + (MONTGOMERY_A, one) + + &self.x.lc(bls12_381::Scalar::one()) + + &other.x.lc(bls12_381::Scalar::one()) + + xprime.get_variable() + }, + ); + + // Compute y' = -(y + lambda(x' - x)) + let yprime = AllocatedNum::alloc(cs.namespace(|| "yprime"), || { + let mut t0 = *xprime.get_value().get()?; + t0.sub_assign(self.x.get_value().get()?); + t0.mul_assign(lambda.get_value().get()?); + t0.add_assign(self.y.get_value().get()?); + t0 = t0.neg(); + + Ok(t0) + })?; + + // y' + y = lambda(x - x') + cs.enforce( + || "evaluate yprime", + |lc| lc + &self.x.lc(bls12_381::Scalar::one()) - xprime.get_variable(), + |lc| lc + lambda.get_variable(), + |lc| lc + yprime.get_variable() + &self.y.lc(bls12_381::Scalar::one()), + ); + + Ok(MontgomeryPoint { + x: xprime.into(), + y: yprime.into(), + }) + } +} + +#[cfg(test)] +mod test { + use bellman::ConstraintSystem; + use group::{ + ff::{Field, PrimeField, PrimeFieldBits}, + Curve, Group, + }; + use rand_core::{RngCore, SeedableRng}; + use rand_xorshift::XorShiftRng; + + use bellman::gadgets::test::*; + + use super::{fixed_base_multiplication, AllocatedNum, EdwardsPoint, MontgomeryPoint}; + use crate::constants::{to_montgomery_coords, NOTE_COMMITMENT_RANDOMNESS_GENERATOR}; + use bellman::gadgets::boolean::{AllocatedBit, Boolean}; + + #[test] + #[allow(clippy::many_single_char_names)] + fn test_into_edwards() { + let mut rng = XorShiftRng::from_seed([ + 0x59, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, + 0xbc, 0xe5, + ]); + + for _ in 0..100 { + let mut cs = TestConstraintSystem::new(); + + let p = jubjub::ExtendedPoint::random(&mut rng); + let (x, y) = to_montgomery_coords(p).unwrap(); + let p = p.to_affine(); + let (u, v) = (p.get_u(), p.get_v()); + + let numx = AllocatedNum::alloc(cs.namespace(|| "mont x"), || Ok(x)).unwrap(); + let numy = AllocatedNum::alloc(cs.namespace(|| "mont y"), || Ok(y)).unwrap(); + + let p = MontgomeryPoint::interpret_unchecked(numx.into(), numy.into()); + + let q = p.into_edwards(&mut cs).unwrap(); + + assert!(cs.is_satisfied()); + assert!(q.u.get_value().unwrap() == u); + assert!(q.v.get_value().unwrap() == v); + + cs.set("u/num", bls12_381::Scalar::random(&mut rng)); + assert_eq!(cs.which_is_unsatisfied().unwrap(), "u computation"); + cs.set("u/num", u); + assert!(cs.is_satisfied()); + + cs.set("v/num", bls12_381::Scalar::random(&mut rng)); + assert_eq!(cs.which_is_unsatisfied().unwrap(), "v computation"); + cs.set("v/num", v); + assert!(cs.is_satisfied()); + } + } + + #[test] + fn test_interpret() { + let mut rng = XorShiftRng::from_seed([ + 0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, + 0xbc, 0xe5, + ]); + + for _ in 0..100 { + let p = jubjub::ExtendedPoint::random(&mut rng); + + let mut cs = TestConstraintSystem::new(); + let q = EdwardsPoint::witness(&mut cs, Some(p)).unwrap(); + + let p = p.to_affine(); + + assert!(cs.is_satisfied()); + assert_eq!(q.u.get_value().unwrap(), p.get_u()); + assert_eq!(q.v.get_value().unwrap(), p.get_v()); + } + + for _ in 0..100 { + let p = jubjub::ExtendedPoint::random(&mut rng).to_affine(); + let (u, v) = (p.get_u(), p.get_v()); + + let mut cs = TestConstraintSystem::new(); + let numu = AllocatedNum::alloc(cs.namespace(|| "u"), || Ok(u)).unwrap(); + let numv = AllocatedNum::alloc(cs.namespace(|| "v"), || Ok(v)).unwrap(); + + let p = EdwardsPoint::interpret(&mut cs, &numu, &numv).unwrap(); + + assert!(cs.is_satisfied()); + assert_eq!(p.u.get_value().unwrap(), u); + assert_eq!(p.v.get_value().unwrap(), v); + } + + // Random (u, v) are unlikely to be on the curve. + for _ in 0..100 { + let u = bls12_381::Scalar::random(&mut rng); + let v = bls12_381::Scalar::random(&mut rng); + + let mut cs = TestConstraintSystem::new(); + let numu = AllocatedNum::alloc(cs.namespace(|| "u"), || Ok(u)).unwrap(); + let numv = AllocatedNum::alloc(cs.namespace(|| "v"), || Ok(v)).unwrap(); + + EdwardsPoint::interpret(&mut cs, &numu, &numv).unwrap(); + + assert_eq!(cs.which_is_unsatisfied().unwrap(), "on curve check"); + } + } + + #[test] + fn test_edwards_fixed_base_multiplication() { + let mut rng = XorShiftRng::from_seed([ + 0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, + 0xbc, 0xe5, + ]); + + for _ in 0..100 { + let mut cs = TestConstraintSystem::::new(); + + let p = masp_primitives::constants::NOTE_COMMITMENT_RANDOMNESS_GENERATOR; + let s = jubjub::Fr::random(&mut rng); + let q = jubjub::ExtendedPoint::from(p * s).to_affine(); + let (u1, v1) = (q.get_u(), q.get_v()); + + let s_bits = s + .to_le_bits() + .iter() + .by_vals() + .take(jubjub::Fr::NUM_BITS as usize) + .enumerate() + .map(|(i, b)| { + AllocatedBit::alloc(cs.namespace(|| format!("scalar bit {}", i)), Some(b)) + .unwrap() + }) + .map(Boolean::from) + .collect::>(); + + let q = fixed_base_multiplication( + cs.namespace(|| "multiplication"), + &NOTE_COMMITMENT_RANDOMNESS_GENERATOR, + &s_bits, + ) + .unwrap(); + + assert_eq!(q.u.get_value().unwrap(), u1); + assert_eq!(q.v.get_value().unwrap(), v1); + } + } + + #[test] + fn test_edwards_multiplication() { + let mut rng = XorShiftRng::from_seed([ + 0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, + 0xbc, 0xe5, + ]); + + for _ in 0..100 { + let mut cs = TestConstraintSystem::new(); + + let p = jubjub::ExtendedPoint::random(&mut rng); + let s = jubjub::Fr::random(&mut rng); + let q = (p * s).to_affine(); + let p = p.to_affine(); + + let (u0, v0) = (p.get_u(), p.get_v()); + let (u1, v1) = (q.get_u(), q.get_v()); + + let num_u0 = AllocatedNum::alloc(cs.namespace(|| "u0"), || Ok(u0)).unwrap(); + let num_v0 = AllocatedNum::alloc(cs.namespace(|| "v0"), || Ok(v0)).unwrap(); + + let p = EdwardsPoint { + u: num_u0, + v: num_v0, + }; + + let s_bits = s + .to_le_bits() + .iter() + .by_vals() + .take(jubjub::Fr::NUM_BITS as usize) + .enumerate() + .map(|(i, b)| { + AllocatedBit::alloc(cs.namespace(|| format!("scalar bit {}", i)), Some(b)) + .unwrap() + }) + .map(Boolean::from) + .collect::>(); + + let q = p.mul(cs.namespace(|| "scalar mul"), &s_bits).unwrap(); + + assert!(cs.is_satisfied()); + + assert_eq!(q.u.get_value().unwrap(), u1); + + assert_eq!(q.v.get_value().unwrap(), v1); + } + } + + #[test] + fn test_conditionally_select() { + let mut rng = XorShiftRng::from_seed([ + 0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, + 0xbc, 0xe5, + ]); + + for _ in 0..1000 { + let mut cs = TestConstraintSystem::new(); + + let p = jubjub::ExtendedPoint::random(&mut rng).to_affine(); + + let (u0, v0) = (p.get_u(), p.get_v()); + + let num_u0 = AllocatedNum::alloc(cs.namespace(|| "u0"), || Ok(u0)).unwrap(); + let num_v0 = AllocatedNum::alloc(cs.namespace(|| "v0"), || Ok(v0)).unwrap(); + + let p = EdwardsPoint { + u: num_u0, + v: num_v0, + }; + + let mut should_we_select = rng.next_u32() % 2 != 0; + + // Conditionally allocate + let mut b = if rng.next_u32() % 2 != 0 { + Boolean::from( + AllocatedBit::alloc(cs.namespace(|| "condition"), Some(should_we_select)) + .unwrap(), + ) + } else { + Boolean::constant(should_we_select) + }; + + // Conditionally negate + if rng.next_u32() % 2 != 0 { + b = b.not(); + should_we_select = !should_we_select; + } + + let q = p + .conditionally_select(cs.namespace(|| "select"), &b) + .unwrap(); + + assert!(cs.is_satisfied()); + + #[allow(clippy::branches_sharing_code)] + if should_we_select { + assert_eq!(q.u.get_value().unwrap(), u0); + assert_eq!(q.v.get_value().unwrap(), v0); + + cs.set("select/v'/num", bls12_381::Scalar::one()); + assert_eq!(cs.which_is_unsatisfied().unwrap(), "select/v' computation"); + cs.set("select/u'/num", bls12_381::Scalar::zero()); + assert_eq!(cs.which_is_unsatisfied().unwrap(), "select/u' computation"); + } else { + assert_eq!(q.u.get_value().unwrap(), bls12_381::Scalar::zero()); + assert_eq!(q.v.get_value().unwrap(), bls12_381::Scalar::one()); + + cs.set("select/v'/num", u0); + assert_eq!(cs.which_is_unsatisfied().unwrap(), "select/v' computation"); + cs.set("select/u'/num", v0); + assert_eq!(cs.which_is_unsatisfied().unwrap(), "select/u' computation"); + } + } + } + + #[test] + fn test_edwards_addition() { + let mut rng = XorShiftRng::from_seed([ + 0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, + 0xbc, 0xe5, + ]); + + for _ in 0..100 { + let p1 = jubjub::ExtendedPoint::random(&mut rng); + let p2 = jubjub::ExtendedPoint::random(&mut rng); + + let p3 = p1 + p2; + + let p1 = p1.to_affine(); + let p2 = p2.to_affine(); + let p3 = p3.to_affine(); + + let (u0, v0) = (p1.get_u(), p1.get_v()); + let (u1, v1) = (p2.get_u(), p2.get_v()); + let (u2, v2) = (p3.get_u(), p3.get_v()); + + let mut cs = TestConstraintSystem::new(); + + let num_u0 = AllocatedNum::alloc(cs.namespace(|| "u0"), || Ok(u0)).unwrap(); + let num_v0 = AllocatedNum::alloc(cs.namespace(|| "v0"), || Ok(v0)).unwrap(); + + let num_u1 = AllocatedNum::alloc(cs.namespace(|| "u1"), || Ok(u1)).unwrap(); + let num_v1 = AllocatedNum::alloc(cs.namespace(|| "v1"), || Ok(v1)).unwrap(); + + let p1 = EdwardsPoint { + u: num_u0, + v: num_v0, + }; + + let p2 = EdwardsPoint { + u: num_u1, + v: num_v1, + }; + + let p3 = p1.add(cs.namespace(|| "addition"), &p2).unwrap(); + + assert!(cs.is_satisfied()); + + assert!(p3.u.get_value().unwrap() == u2); + assert!(p3.v.get_value().unwrap() == v2); + + let uppercase_u = cs.get("addition/U/num"); + cs.set("addition/U/num", bls12_381::Scalar::random(&mut rng)); + assert_eq!(cs.which_is_unsatisfied(), Some("addition/U computation")); + cs.set("addition/U/num", uppercase_u); + assert!(cs.is_satisfied()); + + let u3 = cs.get("addition/u3/num"); + cs.set("addition/u3/num", bls12_381::Scalar::random(&mut rng)); + assert_eq!(cs.which_is_unsatisfied(), Some("addition/u3 computation")); + cs.set("addition/u3/num", u3); + assert!(cs.is_satisfied()); + + let v3 = cs.get("addition/v3/num"); + cs.set("addition/v3/num", bls12_381::Scalar::random(&mut rng)); + assert_eq!(cs.which_is_unsatisfied(), Some("addition/v3 computation")); + cs.set("addition/v3/num", v3); + assert!(cs.is_satisfied()); + } + } + + #[test] + fn test_edwards_doubling() { + let mut rng = XorShiftRng::from_seed([ + 0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, + 0xbc, 0xe5, + ]); + + for _ in 0..100 { + let p1 = jubjub::ExtendedPoint::random(&mut rng); + let p2 = p1.double(); + + let p1 = p1.to_affine(); + let p2 = p2.to_affine(); + + let (u0, v0) = (p1.get_u(), p1.get_v()); + let (u1, v1) = (p2.get_u(), p2.get_v()); + + let mut cs = TestConstraintSystem::new(); + + let num_u0 = AllocatedNum::alloc(cs.namespace(|| "u0"), || Ok(u0)).unwrap(); + let num_v0 = AllocatedNum::alloc(cs.namespace(|| "v0"), || Ok(v0)).unwrap(); + + let p1 = EdwardsPoint { + u: num_u0, + v: num_v0, + }; + + let p2 = p1.double(cs.namespace(|| "doubling")).unwrap(); + + assert!(cs.is_satisfied()); + + assert!(p2.u.get_value().unwrap() == u1); + assert!(p2.v.get_value().unwrap() == v1); + } + } + + #[test] + fn test_montgomery_addition() { + let mut rng = XorShiftRng::from_seed([ + 0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, + 0xbc, 0xe5, + ]); + + for _ in 0..100 { + let p1 = jubjub::ExtendedPoint::random(&mut rng); + let p2 = jubjub::ExtendedPoint::random(&mut rng); + let p3 = p1 + p2; + + let (x0, y0) = to_montgomery_coords(p1).unwrap(); + let (x1, y1) = to_montgomery_coords(p2).unwrap(); + let (x2, y2) = to_montgomery_coords(p3).unwrap(); + + let mut cs = TestConstraintSystem::new(); + + let num_x0 = AllocatedNum::alloc(cs.namespace(|| "x0"), || Ok(x0)).unwrap(); + let num_y0 = AllocatedNum::alloc(cs.namespace(|| "y0"), || Ok(y0)).unwrap(); + + let num_x1 = AllocatedNum::alloc(cs.namespace(|| "x1"), || Ok(x1)).unwrap(); + let num_y1 = AllocatedNum::alloc(cs.namespace(|| "y1"), || Ok(y1)).unwrap(); + + let p1 = MontgomeryPoint { + x: num_x0.into(), + y: num_y0.into(), + }; + + let p2 = MontgomeryPoint { + x: num_x1.into(), + y: num_y1.into(), + }; + + let p3 = p1.add(cs.namespace(|| "addition"), &p2).unwrap(); + + assert!(cs.is_satisfied()); + + assert!(p3.x.get_value().unwrap() == x2); + assert!(p3.y.get_value().unwrap() == y2); + + cs.set("addition/yprime/num", bls12_381::Scalar::random(&mut rng)); + assert_eq!(cs.which_is_unsatisfied(), Some("addition/evaluate yprime")); + cs.set("addition/yprime/num", y2); + assert!(cs.is_satisfied()); + + cs.set("addition/xprime/num", bls12_381::Scalar::random(&mut rng)); + assert_eq!(cs.which_is_unsatisfied(), Some("addition/evaluate xprime")); + cs.set("addition/xprime/num", x2); + assert!(cs.is_satisfied()); + + cs.set("addition/lambda/num", bls12_381::Scalar::random(&mut rng)); + assert_eq!(cs.which_is_unsatisfied(), Some("addition/evaluate lambda")); + } + } + + #[test] + fn test_assert_not_small_order() { + let check_small_order_from_p = |p: jubjub::ExtendedPoint, is_small_order| { + let mut cs = TestConstraintSystem::new(); + + let p = EdwardsPoint::witness(&mut cs, Some(p)).unwrap(); + assert!(cs.is_satisfied()); + assert!(p.assert_not_small_order(&mut cs).is_err() == is_small_order); + }; + + let check_small_order_from_u64s = |u, v| { + let (u, v) = (bls12_381::Scalar::from(u), bls12_381::Scalar::from(v)); + let p = jubjub::AffinePoint::from_raw_unchecked(u, v); + + check_small_order_from_p(p.into(), true); + }; + + // zero has low order + check_small_order_from_u64s(0, 1); + + // prime subgroup order + let prime_subgroup_order = jubjub::Fr::from_str_vartime( + "6554484396890773809930967563523245729705921265872317281365359162392183254199", + ) + .unwrap(); + let largest_small_subgroup_order = jubjub::Fr::from(8); + + let (zero_u, zero_v) = (bls12_381::Scalar::zero(), bls12_381::Scalar::one()); + + // generator for jubjub + let (u, v) = ( + bls12_381::Scalar::from_str_vartime( + "11076627216317271660298050606127911965867021807910416450833192264015104452986", + ) + .unwrap(), + bls12_381::Scalar::from_str_vartime( + "44412834903739585386157632289020980010620626017712148233229312325549216099227", + ) + .unwrap(), + ); + let g = jubjub::AffinePoint::from_raw_unchecked(u, v).into(); + check_small_order_from_p(g, false); + + // generator for the prime subgroup + let g_prime = g * largest_small_subgroup_order; + check_small_order_from_p(g_prime, false); + let prime_subgroup_order_minus_1 = prime_subgroup_order - jubjub::Fr::one(); + + let should_not_be_zero = g_prime * prime_subgroup_order_minus_1; + assert_ne!(zero_u, should_not_be_zero.to_affine().get_u()); + assert_ne!(zero_v, should_not_be_zero.to_affine().get_v()); + let should_be_zero = should_not_be_zero + g_prime; + assert_eq!(zero_u, should_be_zero.to_affine().get_u()); + assert_eq!(zero_v, should_be_zero.to_affine().get_v()); + + // generator for the small order subgroup + let g_small = g * prime_subgroup_order_minus_1; + let g_small = g_small + g; + check_small_order_from_p(g_small, true); + + // g_small does have order 8 + let largest_small_subgroup_order_minus_1 = largest_small_subgroup_order - jubjub::Fr::one(); + + let should_not_be_zero = g_small * largest_small_subgroup_order_minus_1; + assert_ne!(zero_u, should_not_be_zero.to_affine().get_u()); + assert_ne!(zero_v, should_not_be_zero.to_affine().get_v()); + + let should_be_zero = should_not_be_zero + g_small; + assert_eq!(zero_u, should_be_zero.to_affine().get_u()); + assert_eq!(zero_v, should_be_zero.to_affine().get_v()); + + // take all the points from the script + // assert should be different than multiplying by cofactor, which is the solution + // is user input verified? https://github.com/zcash/librustzcash/blob/f5d2afb4eabac29b1b1cc860d66e45a5b48b4f88/src/rustzcash.rs#L299 + } +} diff --git a/masp_proofs/src/circuit/pedersen_hash.rs b/masp_proofs/src/circuit/pedersen_hash.rs index 60d73996..e3859fcf 100644 --- a/masp_proofs/src/circuit/pedersen_hash.rs +++ b/masp_proofs/src/circuit/pedersen_hash.rs @@ -1,10 +1,10 @@ //! Gadget for Zcash's Pedersen hash. +use super::ecc::{EdwardsPoint, MontgomeryPoint}; use bellman::gadgets::boolean::Boolean; use bellman::gadgets::lookup::*; use bellman::{ConstraintSystem, SynthesisError}; -pub use masp_primitives::pedersen_hash::Personalization; -use zcash_proofs::circuit::ecc::{EdwardsPoint, MontgomeryPoint}; +pub use masp_primitives::sapling::pedersen_hash::Personalization; use crate::constants::PEDERSEN_CIRCUIT_GENERATORS; @@ -107,9 +107,8 @@ mod test { use super::*; use bellman::gadgets::boolean::{AllocatedBit, Boolean}; use bellman::gadgets::test::*; - use ff::PrimeField; - use group::Curve; - use masp_primitives::pedersen_hash; + use group::{ff::PrimeField, Curve}; + use masp_primitives::sapling::pedersen_hash; use rand_core::{RngCore, SeedableRng}; use rand_xorshift::XorShiftRng; diff --git a/masp_proofs/src/circuit/sapling.rs b/masp_proofs/src/circuit/sapling.rs index 99e8a92e..95e2d669 100644 --- a/masp_proofs/src/circuit/sapling.rs +++ b/masp_proofs/src/circuit/sapling.rs @@ -1,31 +1,24 @@ -//! The Sapling circuits. +//! The MASP Spend and Output circuits. -use ff::PrimeField; -use group::Curve; +use group::{ff::PrimeField, Curve}; use bellman::{Circuit, ConstraintSystem, SynthesisError}; use masp_primitives::{ constants, - primitives::{PaymentAddress, ProofGenerationKey, ValueCommitment}, + sapling::{PaymentAddress, ProofGenerationKey, ValueCommitment, SAPLING_COMMITMENT_TREE_DEPTH}, }; +use super::ecc; use super::pedersen_hash; use crate::constants::{ NOTE_COMMITMENT_RANDOMNESS_GENERATOR, NULLIFIER_POSITION_GENERATOR, PROOF_GENERATION_KEY_GENERATOR, SPENDING_KEY_GENERATOR, VALUE_COMMITMENT_RANDOMNESS_GENERATOR, }; -use zcash_proofs::circuit::ecc; - -use bellman::gadgets::blake2s; -use bellman::gadgets::boolean; -use bellman::gadgets::multipack; -use bellman::gadgets::num; -use bellman::gadgets::Assignment; - +use bellman::gadgets::{blake2s, boolean, multipack, num, Assignment}; use itertools::multizip; -pub const TREE_DEPTH: usize = zcash_primitives::sapling::SAPLING_COMMITMENT_TREE_DEPTH; +pub const TREE_DEPTH: usize = SAPLING_COMMITMENT_TREE_DEPTH; /// This is an instance of the `Spend` circuit. pub struct Spend { @@ -603,16 +596,13 @@ impl Circuit for Output { #[test] fn test_input_circuit_with_bls12_381() { use bellman::gadgets::test::*; - use ff::{Field, PrimeField, PrimeFieldBits}; - use group::Group; + use group::{ff::Field, ff::PrimeFieldBits, Group}; use masp_primitives::{ asset_type::AssetType, - pedersen_hash, - primitives::{Diversifier, Note, ProofGenerationKey}, + sapling::{pedersen_hash, Diversifier, Note, ProofGenerationKey, Rseed}, }; use rand_core::{RngCore, SeedableRng}; use rand_xorshift::XorShiftRng; - use zcash_primitives::sapling::Rseed; let mut rng = XorShiftRng::from_seed([ 0x58, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc, @@ -635,10 +625,10 @@ fn test_input_circuit_with_bls12_381() { value_commitment.asset_generator = -value_commitment.asset_generator; } - let nsk = jubjub::Fr::random(&mut rng); - let ak = jubjub::SubgroupPoint::random(&mut rng); - - let proof_generation_key = ProofGenerationKey { ak, nsk }; + let proof_generation_key = ProofGenerationKey { + ak: jubjub::SubgroupPoint::random(&mut rng), + nsk: jubjub::Fr::random(&mut rng), + }; let viewing_key = proof_generation_key.to_viewing_key(); @@ -695,11 +685,11 @@ fn test_input_circuit_with_bls12_381() { cur = jubjub::ExtendedPoint::from(pedersen_hash::pedersen_hash( pedersen_hash::Personalization::MerkleTree(i), lhs.iter() - .by_val() + .by_vals() .take(bls12_381::Scalar::NUM_BITS as usize) .chain( rhs.iter() - .by_val() + .by_vals() .take(bls12_381::Scalar::NUM_BITS as usize), ), )) @@ -711,7 +701,7 @@ fn test_input_circuit_with_bls12_381() { } } - let expected_nf = note.nf(&viewing_key, position); + let expected_nf = note.nf(&viewing_key.nk, position); let expected_nf = multipack::bytes_to_bits_le(&expected_nf.0); let expected_nf = multipack::compute_multipacking(&expected_nf); assert_eq!(expected_nf.len(), 2); @@ -721,7 +711,7 @@ fn test_input_circuit_with_bls12_381() { let instance = Spend { value_commitment: Some(value_commitment.clone()), proof_generation_key: Some(proof_generation_key.clone()), - payment_address: Some(payment_address.clone()), + payment_address: Some(payment_address), commitment_randomness: Some(commitment_randomness), ar: Some(ar), auth_path: auth_path.clone(), @@ -772,16 +762,14 @@ fn test_input_circuit_with_bls12_381() { #[test] fn test_input_circuit_with_bls12_381_external_test_vectors() { use bellman::gadgets::test::*; - use ff::{Field, PrimeField, PrimeFieldBits}; - use group::Group; + use group::{ff::Field, ff::PrimeField, ff::PrimeFieldBits, Group}; use masp_primitives::{ asset_type::AssetType, - pedersen_hash, - primitives::{Diversifier, Note, ProofGenerationKey}, + sapling::pedersen_hash, + sapling::{Diversifier, Note, ProofGenerationKey, Rseed}, }; use rand_core::{RngCore, SeedableRng}; use rand_xorshift::XorShiftRng; - use zcash_primitives::sapling::Rseed; let mut rng = XorShiftRng::from_seed([ 0x59, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc, @@ -894,11 +882,11 @@ fn test_input_circuit_with_bls12_381_external_test_vectors() { cur = jubjub::ExtendedPoint::from(pedersen_hash::pedersen_hash( pedersen_hash::Personalization::MerkleTree(i), lhs.iter() - .by_val() + .by_vals() .take(bls12_381::Scalar::NUM_BITS as usize) .chain( rhs.iter() - .by_val() + .by_vals() .take(bls12_381::Scalar::NUM_BITS as usize), ), )) @@ -910,7 +898,7 @@ fn test_input_circuit_with_bls12_381_external_test_vectors() { } } - let expected_nf = note.nf(&viewing_key, position); + let expected_nf = note.nf(&viewing_key.nk, position); let expected_nf = multipack::bytes_to_bits_le(&expected_nf.0); let expected_nf = multipack::compute_multipacking(&expected_nf); assert_eq!(expected_nf.len(), 2); @@ -920,7 +908,7 @@ fn test_input_circuit_with_bls12_381_external_test_vectors() { let instance = Spend { value_commitment: Some(value_commitment.clone()), proof_generation_key: Some(proof_generation_key.clone()), - payment_address: Some(payment_address.clone()), + payment_address: Some(payment_address), commitment_randomness: Some(commitment_randomness), ar: Some(ar), auth_path: auth_path.clone(), @@ -960,15 +948,13 @@ fn test_input_circuit_with_bls12_381_external_test_vectors() { #[test] fn test_output_circuit_with_bls12_381() { use bellman::gadgets::test::*; - use ff::Field; - use group::Group; + use group::{ff::Field, Group}; use masp_primitives::{ asset_type::AssetType, - primitives::{Diversifier, ProofGenerationKey}, + sapling::{Diversifier, ProofGenerationKey, Rseed}, }; use rand_core::{RngCore, SeedableRng}; use rand_xorshift::XorShiftRng; - use zcash_primitives::sapling::Rseed; let mut rng = XorShiftRng::from_seed([ 0x58, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc, @@ -1019,7 +1005,7 @@ fn test_output_circuit_with_bls12_381() { let instance = Output { value_commitment: Some(value_commitment.clone()), - payment_address: Some(payment_address.clone()), + payment_address: Some(payment_address), commitment_randomness: Some(commitment_randomness), esk: Some(esk), asset_identifier: asset_type.identifier_bits(), diff --git a/masp_proofs/src/constants.rs b/masp_proofs/src/constants.rs index 0e202b82..24db91c2 100644 --- a/masp_proofs/src/constants.rs +++ b/masp_proofs/src/constants.rs @@ -1,13 +1,26 @@ //! Various constants used for the MASP proofs. use bls12_381::Scalar; -use ff::Field; -use group::{Curve, Group}; +use group::{ff::Field, Curve, Group}; use jubjub::ExtendedPoint; use lazy_static::lazy_static; -use masp_primitives::constants::PEDERSEN_HASH_GENERATORS; -use zcash_primitives::constants::PEDERSEN_HASH_CHUNKS_PER_GENERATOR; -use zcash_proofs::constants::{generate_circuit_generator, FixedGeneratorOwned}; +use masp_primitives::constants::{PEDERSEN_HASH_CHUNKS_PER_GENERATOR, PEDERSEN_HASH_GENERATORS}; + +/// The `d` constant of the twisted Edwards curve. +pub(crate) const EDWARDS_D: Scalar = Scalar::from_raw([ + 0x0106_5fd6_d634_3eb1, + 0x292d_7f6d_3757_9d26, + 0xf5fd_9207_e6bd_7fd4, + 0x2a93_18e7_4bfa_2b48, +]); + +/// The `A` constant of the birationally equivalent Montgomery curve. +pub(crate) const MONTGOMERY_A: Scalar = Scalar::from_raw([ + 0x0000_0000_0000_a002, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, +]); /// The scaling factor used for conversion to and from the Montgomery form. pub(crate) const MONTGOMERY_SCALE: Scalar = Scalar::from_raw([ @@ -17,6 +30,16 @@ pub(crate) const MONTGOMERY_SCALE: Scalar = Scalar::from_raw([ 0x2762_de61_e862_645e, ]); +/// The number of chunks needed to represent a full scalar during fixed-base +/// exponentiation. +const FIXED_BASE_CHUNKS_PER_GENERATOR: usize = 84; + +/// Reference to a circuit version of a generator for fixed-base salar multiplication. +pub type FixedGenerator = &'static [Vec<(Scalar, Scalar)>]; + +/// Circuit version of a generator for fixed-base salar multiplication. +pub type FixedGeneratorOwned = Vec>; + lazy_static! { pub static ref PROOF_GENERATION_KEY_GENERATOR: FixedGeneratorOwned = generate_circuit_generator(masp_primitives::constants::PROOF_GENERATION_KEY_GENERATOR); @@ -39,6 +62,28 @@ lazy_static! { generate_pedersen_circuit_generators(); } +/// Creates the 3-bit window table `[0, 1, ..., 8]` for different magnitudes of a fixed +/// generator. +pub fn generate_circuit_generator(mut gen: jubjub::SubgroupPoint) -> FixedGeneratorOwned { + let mut windows = vec![]; + + for _ in 0..FIXED_BASE_CHUNKS_PER_GENERATOR { + let mut coeffs = vec![(Scalar::zero(), Scalar::one())]; + let mut g = gen; + for _ in 0..7 { + let g_affine = jubjub::ExtendedPoint::from(g).to_affine(); + coeffs.push((g_affine.get_u(), g_affine.get_v())); + g += gen; + } + windows.push(coeffs); + + // gen = gen * 8 + gen = g; + } + + windows +} + /// Returns the coordinates of this point's Montgomery curve representation, or `None` if /// it is the point at infinity. #[allow(clippy::many_single_char_names)] diff --git a/masp_proofs/src/downloadreader.rs b/masp_proofs/src/downloadreader.rs new file mode 100644 index 00000000..04f46d1b --- /dev/null +++ b/masp_proofs/src/downloadreader.rs @@ -0,0 +1,84 @@ +//! [`io::Read`] implementations for [`minreq`]. + +use std::io; + +/// A wrapper that implements [`io::Read`] on a [`minreq::ResponseLazy`]. +pub enum ResponseLazyReader { + Request(minreq::Request), + Response(minreq::ResponseLazy), + Complete(Result<(), String>), +} + +impl From for ResponseLazyReader { + fn from(request: minreq::Request) -> ResponseLazyReader { + ResponseLazyReader::Request(request) + } +} + +impl From for ResponseLazyReader { + fn from(response: minreq::ResponseLazy) -> ResponseLazyReader { + ResponseLazyReader::Response(response) + } +} + +impl io::Read for ResponseLazyReader { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + use ResponseLazyReader::{Complete, Request, Response}; + + // Zero-sized buffer. This should never happen. + if buf.is_empty() { + return Ok(0); + } + + loop { + match self { + // Launch a lazy response for this request + Request(request) => match request.clone().send_lazy() { + Ok(response) => *self = Response(response), + Err(error) => { + let error = Err(format!("download request failed: {:?}", error)); + + *self = Complete(error); + } + }, + + // Read from the response + Response(response) => { + for (i, buf_byte) in buf.iter_mut().enumerate() { + match response.next() { + // Load a byte into the buffer. + Some(Ok((byte, _length))) => { + *buf_byte = byte; + } + + // The whole response has been processed. + None => { + *self = Complete(Ok(())); + return Ok(i); + } + + // The response is corrupted. + Some(Err(error)) => { + let error = format!("download response failed: {:?}", error); + + *self = Complete(Err(error.clone())); + return Err(io::Error::new(io::ErrorKind::Other, error)); + } + } + } + + return Ok(buf.len()); + } + + Complete(result) => { + return match result { + // Return a zero-byte read for download success and EOF. + Ok(()) => Ok(0), + // Keep returning the download error, + Err(error) => Err(io::Error::new(io::ErrorKind::Other, error.clone())), + }; + } + } + } + } +} diff --git a/masp_proofs/src/hashreader.rs b/masp_proofs/src/hashreader.rs index f8487b86..47fbc648 100644 --- a/masp_proofs/src/hashreader.rs +++ b/masp_proofs/src/hashreader.rs @@ -1,10 +1,17 @@ +//! Abstraction over a reader which hashes the data being read. + +use std::{ + fmt::Write, + io::{self, Read}, +}; + use blake2b_simd::State; -use std::io::{self, Read}; /// Abstraction over a reader which hashes the data being read. pub struct HashReader { reader: R, hasher: State, + byte_count: usize, } impl HashReader { @@ -13,6 +20,7 @@ impl HashReader { HashReader { reader, hasher: State::new(), + byte_count: 0, } } @@ -22,11 +30,16 @@ impl HashReader { let mut s = String::new(); for c in hash.as_bytes().iter() { - s += &format!("{:02x}", c); + write!(&mut s, "{:02x}", c).expect("writing to a string never fails"); } s } + + /// Return the number of bytes read so far. + pub fn byte_count(&self) -> usize { + self.byte_count + } } impl Read for HashReader { @@ -35,6 +48,7 @@ impl Read for HashReader { if bytes > 0 { self.hasher.update(&buf[0..bytes]); + self.byte_count += bytes; } Ok(bytes) diff --git a/masp_proofs/src/lib.rs b/masp_proofs/src/lib.rs index 61dc42e7..15abbba5 100644 --- a/masp_proofs/src/lib.rs +++ b/masp_proofs/src/lib.rs @@ -1,6 +1,6 @@ -//! *Zcash circuits and proofs.* +//! *MASP circuits and proofs.* //! -//! `zcash_proofs` contains the zk-SNARK circuits used by Zcash, and the APIs for creating +//! `masp_proofs` contains the zk-SNARK circuits used by MASP based on Zcash Sapling, and the APIs for creating //! and verifying proofs. #![cfg_attr(docsrs, feature(doc_cfg))] @@ -21,12 +21,13 @@ use directories::BaseDirs; use std::path::PathBuf; pub mod circuit; -mod constants; +pub mod constants; pub mod hashreader; pub mod sapling; -// #[cfg(feature = "embed-verifying-key")] -// pub mod params; +#[cfg(feature = "embed-verifying-key")] +pub mod params; + #[cfg(any(feature = "local-prover", feature = "bundled-prover"))] #[cfg_attr( docsrs, @@ -34,21 +35,48 @@ pub mod sapling; )] pub mod prover; +#[cfg(feature = "download-params")] +#[cfg_attr(docsrs, doc(cfg(feature = "download-params")))] +mod downloadreader; + // Circuit names -#[cfg(feature = "local-prover")] + +/// The MASP spend parameters file name. const MASP_SPEND_NAME: &str = "masp-spend.params"; -#[cfg(feature = "local-prover")] + +/// The MASP output parameters file name. const MASP_OUTPUT_NAME: &str = "masp-output.params"; -#[cfg(feature = "local-prover")] + +/// The MASP convert parameters file name. const MASP_CONVERT_NAME: &str = "masp-convert.params"; // Circuit hashes -const MASP_SPEND_HASH: &str = "5523057113d7daa078714f9859ea03da3c959f4fe3756a0ace4eb25f7cf41d1e21099dac768c2e0045400fee03c1f8bc14eeac2190c3f282e0092419d3b967e5"; -const MASP_OUTPUT_HASH: &str = "89fe551ad6c0281aebb857eb203dbf35854979503d374c83b12512dcd737e12a255869a34e3ff0f6609b78accc81ea5f5e94202e124a590730494eeeee86e755"; -const MASP_CONVERT_HASH: &str = "7a6b038c45ddd841e500484b1c72fa021d874de5a83bf8bce6c0fd8f3c63d491243495df2661682333728a8b14c439985b63b0d6ed61044286e2f86734d66d9b"; +const MASP_SPEND_HASH: &str = "196e7c717f25e16653431559ce2c8816e750a4490f98696e3c031efca37e25e0647182b7b013660806db11eb2b1e365fb2d6a0f24dbbd9a4a8314fef10a7cba2"; +const MASP_OUTPUT_HASH: &str = "eafc3b1746cccc8b9eed2b69395692c5892f6aca83552a07dceb2dcbaa64dcd0e22434260b3aa3b049b633a08b008988cbe0d31effc77e2bc09bfab690a23724"; +const MASP_CONVERT_HASH: &str = "dc4aaf3c3ce056ab448b6c4a7f43c1d68502c2902ea89ab8769b1524a2e8ace9a5369621a73ee1daa52aec826907a19974a37874391cf8f11bbe0b0420de1ab7"; +// Circuit parameter file sizes +const MASP_SPEND_BYTES: u64 = 49848572; +const MASP_CONVERT_BYTES: u64 = 22570940; +const MASP_OUTPUT_BYTES: u64 = 16398620; + +#[cfg(feature = "download-params")] +const DOWNLOAD_URL: &str = + "https://github.com/anoma/masp-mpc/releases/download/namada-trusted-setup/"; +/// The paths to the Sapling parameter files. #[cfg(feature = "download-params")] -const DOWNLOAD_URL: &str = "https://github.com/anoma/masp/blob/test_parameters"; +#[cfg_attr(docsrs, doc(cfg(feature = "download-params")))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct MASPParameterPaths { + /// The path to the MASP spend parameter file. + pub spend: PathBuf, + + /// The path to the MASP output parameter file. + pub output: PathBuf, + + /// The path to the MASP convert parameter file. + pub convert: PathBuf, +} /// Returns the default folder that the MASP proving parameters are located in. #[cfg(feature = "directories")] @@ -63,56 +91,162 @@ pub fn default_params_folder() -> Option { }) } -/// Download the MASP Sapling parameters, storing them in the default location. +/// Download the MASP parameters if needed, and store them in the default location. +/// Always checks the sizes and hashes of the files, even if they didn't need to be downloaded. /// /// This mirrors the behaviour of the `fetch-params.sh` script from `zcashd`. +/// +/// Use `timeout` to set a timeout in seconds for each file download. +/// If `timeout` is `None`, a timeout can be set using the `MINREQ_TIMEOUT` environmental variable. +/// +/// Returns the paths to the downloaded files. #[cfg(feature = "download-params")] #[cfg_attr(docsrs, doc(cfg(feature = "download-params")))] -pub fn download_parameters() -> Result<(), minreq::Error> { +pub fn download_masp_parameters(timeout: Option) -> Result { + let spend = fetch_params(MASP_SPEND_NAME, MASP_SPEND_HASH, MASP_SPEND_BYTES, timeout)?; + let output = fetch_params( + MASP_OUTPUT_NAME, + MASP_OUTPUT_HASH, + MASP_OUTPUT_BYTES, + timeout, + )?; + let convert = fetch_params( + MASP_CONVERT_NAME, + MASP_CONVERT_HASH, + MASP_CONVERT_BYTES, + timeout, + )?; + + Ok(MASPParameterPaths { + spend, + output, + convert, + }) +} + +/// Download the specified parameters if needed, and store them in the default location. +/// Always checks the size and hash of the file, even if it didn't need to be downloaded. +/// +/// See [`download_sapling_parameters`] for details. +#[cfg(feature = "download-params")] +#[cfg_attr(docsrs, doc(cfg(feature = "download-params")))] +fn fetch_params( + name: &str, + expected_hash: &str, + expected_bytes: u64, + timeout: Option, +) -> Result { // Ensure that the default MASP parameters location exists. let params_dir = default_params_folder().ok_or_else(|| { io::Error::new(io::ErrorKind::Other, "Could not load default params folder") })?; std::fs::create_dir_all(¶ms_dir)?; - let fetch_params = |name: &str, expected_hash: &str| -> Result<(), minreq::Error> { - use std::io::Write; - - // Download the parts directly (Sapling parameters are small enough for this). - let params = minreq::get(format!("{}/{}?raw=true", DOWNLOAD_URL, name)).send()?; - - // Verify parameter file hash. - let hash = blake2b_simd::State::new() - .update(params.as_bytes()) - .finalize() - .to_hex(); - if &hash != expected_hash { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - format!( - "{} failed validation (expected: {}, actual: {}, fetched {} bytes)", - name, - expected_hash, - hash, - params.as_bytes().len() - ), - ) - .into()); + let params_path = params_dir.join(name); + + // Download parameters if needed. + // TODO: use try_exists when it stabilises, to exit early on permissions errors (#83186) + if !params_path.exists() { + let result = stream_params_downloads_to_disk( + ¶ms_path, + name, + expected_hash, + expected_bytes, + timeout, + ); + + // Remove the file on error, and return the download or hash error. + if result.is_err() { + let _ = std::fs::remove_file(¶ms_path); + result?; } + } else { + // TODO: avoid reading the files twice + // Either: + // - return Ok if the paths exist, or + // - always load and return the parameters, for newly downloaded and existing files. + + let file_path_string = params_path.to_string_lossy(); + + // Check the file size is correct before hashing large amounts of data. + verify_file_size(¶ms_path, expected_bytes, name, &file_path_string).expect( + "parameter file size is not correct, \ + please clean your MASP parameters directory and re-run `fetch-params`.", + ); + + // Read the file to verify the hash, + // discarding bytes after they're hashed. + let params_file = File::open(¶ms_path)?; + let params_file = BufReader::with_capacity(1024 * 1024, params_file); + let params_file = hashreader::HashReader::new(params_file); + + verify_hash( + params_file, + io::sink(), + expected_hash, + expected_bytes, + name, + &file_path_string, + )?; + } + + Ok(params_path) +} + +/// Download the specified parameter file, stream it to `params_path`, and check its hash. +/// +/// See [`download_sapling_parameters`] for details. +#[cfg(feature = "download-params")] +#[cfg_attr(docsrs, doc(cfg(feature = "download-params")))] +fn stream_params_downloads_to_disk( + params_path: &Path, + name: &str, + expected_hash: &str, + expected_bytes: u64, + timeout: Option, +) -> Result<(), minreq::Error> { + use downloadreader::ResponseLazyReader; + use std::io::{BufWriter, Read}; - // Write parameter file. - let mut f = File::create(params_dir.join(name))?; - f.write_all(params.as_bytes())?; - Ok(()) - }; + // Fail early if the directory isn't writeable. + let new_params_file = File::create(params_path)?; + let new_params_file = BufWriter::with_capacity(1024 * 1024, new_params_file); - fetch_params(MASP_SPEND_NAME, MASP_SPEND_HASH)?; - fetch_params(MASP_OUTPUT_NAME, MASP_OUTPUT_HASH)?; - fetch_params(MASP_CONVERT_NAME, MASP_CONVERT_HASH)?; + // Set up the download requests. + // + // It's necessary for us to host these files in two parts, + // because of CloudFlare's maximum cached file size limit of 512 MB. + // The files must fit in the cache to prevent "denial of wallet" attacks. + let params_url_1 = format!("{}/{}", DOWNLOAD_URL, name); + + let mut params_download_1 = minreq::get(¶ms_url_1); + if let Some(timeout) = timeout { + params_download_1 = params_download_1.with_timeout(timeout); + } + + // Download the responses and write them to a new file, + // verifying the hash as bytes are read. + let params_download_1 = ResponseLazyReader::from(params_download_1); + + // Limit the download size to avoid DoS. + // This also avoids launching the second request, if the first request provides enough bytes. + let params_download = params_download_1.take(expected_bytes); + let params_download = BufReader::with_capacity(1024 * 1024, params_download); + let params_download = hashreader::HashReader::new(params_download); + + verify_hash( + params_download, + new_params_file, + expected_hash, + expected_bytes, + name, + ¶ms_url_1, + )?; Ok(()) } +/// MASP Sapling groth16 circuit parameters. #[allow(clippy::upper_case_acronyms)] pub struct MASPParameters { pub spend_params: Parameters, @@ -122,15 +256,51 @@ pub struct MASPParameters { pub convert_params: Parameters, pub convert_vk: PreparedVerifyingKey, } + +/// Load the specified parameters, checking the sizes and hashes of the files. +/// +/// Returns the loaded parameters. pub fn load_parameters( spend_path: &Path, output_path: &Path, convert_path: &Path, ) -> MASPParameters { + // Check the file sizes are correct before hashing large amounts of data. + verify_file_size( + spend_path, + MASP_SPEND_BYTES, + "masp spend", + &spend_path.to_string_lossy(), + ) + .expect( + "parameter file size is not correct, \ + please clean your MASP parameters directory and re-run `fetch-params`.", + ); + + verify_file_size( + output_path, + MASP_OUTPUT_BYTES, + "masp output", + &output_path.to_string_lossy(), + ) + .expect( + "parameter file size is not correct, \ + please clean your MASP parameters directory and re-run `fetch-params`.", + ); + verify_file_size( + convert_path, + MASP_CONVERT_BYTES, + "masp convert", + &convert_path.to_string_lossy(), + ) + .expect( + "parameter file size is not correct, \ + please clean your MASP parameters directory and re-run `fetch-params`.", + ); // Load from each of the paths - let spend_fs = File::open(spend_path).expect("couldn't load Sapling spend parameters file"); - let output_fs = File::open(output_path).expect("couldn't load Sapling output parameters file"); - let convert_fs = File::open(convert_path).expect("couldn't load convert parameters file"); + let spend_fs = File::open(spend_path).expect("couldn't load MASP spend parameters file"); + let output_fs = File::open(output_path).expect("couldn't load MASP output parameters file"); + let convert_fs = File::open(convert_path).expect("couldn't load MASP convert parameters file"); parse_parameters( BufReader::with_capacity(1024 * 1024, spend_fs), @@ -149,34 +319,58 @@ pub fn parse_parameters(spend_fs: R, output_fs: R, convert_fs: R) - // Deserialize params let spend_params = Parameters::::read(&mut spend_fs, false) - .expect("couldn't deserialize Sapling spend parameters file"); + .expect("couldn't deserialize MASP spend parameters file"); let output_params = Parameters::::read(&mut output_fs, false) - .expect("couldn't deserialize Sapling output parameters file"); + .expect("couldn't deserialize MASP output parameters file"); let convert_params = Parameters::::read(&mut convert_fs, false) - .expect("couldn't deserialize convert parameters file"); + .expect("couldn't deserialize MASP convert parameters file"); // There is extra stuff (the transcript) at the end of the parameter file which is // used to verify the parameter validity, but we're not interested in that. We do // want to read it, though, so that the BLAKE2b computed afterward is consistent // with `b2sum` on the files. let mut sink = io::sink(); - io::copy(&mut spend_fs, &mut sink) - .expect("couldn't finish reading Sapling spend parameter file"); - io::copy(&mut output_fs, &mut sink) - .expect("couldn't finish reading Sapling output parameter file"); - io::copy(&mut convert_fs, &mut sink).expect("couldn't finish reading convert parameter file"); - - if spend_fs.into_hash() != MASP_SPEND_HASH { - panic!("MASP spend parameter file is not correct, please clean your `~/.masp-params/` and re-run `fetch-params`."); - } - if output_fs.into_hash() != MASP_OUTPUT_HASH { - panic!("MASP output parameter file is not correct, please clean your `~/.masp-params/` and re-run `fetch-params`."); - } + // TODO: use the correct paths for Windows and macOS + // use the actual file paths supplied by the caller + verify_hash( + spend_fs, + &mut sink, + MASP_SPEND_HASH, + MASP_SPEND_BYTES, + MASP_SPEND_NAME, + "a file", + ) + .expect( + "MASP spend parameter file is not correct, \ + please clean your `~/.masp-params/` and re-run `fetch-params`.", + ); - if convert_fs.into_hash() != MASP_CONVERT_HASH { - panic!("MASP convert file is not correct, please clean your `~/.masp-params/` and re-run `fetch-params`."); - } + verify_hash( + output_fs, + &mut sink, + MASP_OUTPUT_HASH, + MASP_OUTPUT_BYTES, + MASP_OUTPUT_NAME, + "a file", + ) + .expect( + "MASP output parameter file is not correct, \ + please clean your `~/.masp-params/` and re-run `fetch-params`.", + ); + + verify_hash( + convert_fs, + &mut sink, + MASP_CONVERT_HASH, + MASP_CONVERT_BYTES, + MASP_CONVERT_NAME, + "a file", + ) + .expect( + "MASP convert parameter file is not correct, \ + please clean your `~/.masp-params/` and re-run `fetch-params`.", + ); // Prepare verifying keys let spend_vk = prepare_verifying_key(&spend_params.vk); @@ -192,3 +386,81 @@ pub fn parse_parameters(spend_fs: R, output_fs: R, convert_fs: R) - convert_vk, } } + +/// Check if the size of the file at `params_path` matches `expected_bytes`, +/// using filesystem metadata. +/// +/// Returns an error containing `name` and `params_source` on failure. +fn verify_file_size( + params_path: &Path, + expected_bytes: u64, + name: &str, + params_source: &str, +) -> Result<(), io::Error> { + let file_size = std::fs::metadata(params_path)?.len(); + + if file_size != expected_bytes { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + format!( + "{} failed validation:\n\ + expected: {} bytes,\n\ + actual: {} bytes from {:?}", + name, expected_bytes, file_size, params_source, + ), + )); + } + + Ok(()) +} + +/// Check if the Blake2b hash from `hash_reader` matches `expected_hash`, +/// while streaming from `hash_reader` into `sink`. +/// +/// `hash_reader` can be used to partially read its inner reader's data, +/// before verifying the hash using this function. +/// +/// Returns an error containing `name` and `params_source` on failure. +fn verify_hash( + mut hash_reader: hashreader::HashReader, + mut sink: W, + expected_hash: &str, + expected_bytes: u64, + name: &str, + params_source: &str, +) -> Result<(), io::Error> { + let read_result = io::copy(&mut hash_reader, &mut sink); + + if let Err(read_error) = read_result { + return Err(io::Error::new( + read_error.kind(), + format!( + "{} failed reading:\n\ + expected: {} bytes,\n\ + actual: {} bytes from {:?},\n\ + error: {:?}", + name, + expected_bytes, + hash_reader.byte_count(), + params_source, + read_error, + ), + )); + } + + let byte_count = hash_reader.byte_count(); + let hash = hash_reader.into_hash(); + if hash != expected_hash { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + format!( + "{} failed validation:\n\ + expected: {} hashing {} bytes,\n\ + actual: {} hashing {} bytes from {:?}", + name, expected_hash, expected_bytes, hash, byte_count, params_source, + ), + )); + } + + Ok(()) +} diff --git a/masp_proofs/src/params.rs b/masp_proofs/src/params.rs index 4e1f1635..cd2b0c34 100644 --- a/masp_proofs/src/params.rs +++ b/masp_proofs/src/params.rs @@ -4,20 +4,19 @@ use lazy_static::lazy_static; lazy_static! { pub static ref SPEND_VK: VerifyingKey:: = - VerifyingKey::::read(&include_bytes!("params/spend_TESTING_vk.params")[..]).unwrap(); + VerifyingKey::::read(&include_bytes!("../params/masp-spend.vk")[..]).unwrap(); pub static ref OUTPUT_VK: VerifyingKey:: = - VerifyingKey::::read(&include_bytes!("params/output_TESTING_vk.params")[..]) - .unwrap(); + VerifyingKey::::read(&include_bytes!("../params/masp-output.vk")[..]).unwrap(); pub static ref CONVERT_VK: VerifyingKey:: = - VerifyingKey::::read(&include_bytes!("params/convert_TESTING_vk.params")[..]) - .unwrap(); + VerifyingKey::::read(&include_bytes!("../params/masp-convert.vk")[..]).unwrap(); } #[cfg(feature = "download-params")] #[test] fn test_serialization() { // Download params first - crate::download_parameters().unwrap(); + #[cfg(feature = "download-params")] + crate::download_masp_parameters(None).unwrap(); if let Some(path) = crate::default_params_folder() { let params = crate::load_parameters( diff --git a/masp_proofs/src/prover.rs b/masp_proofs/src/prover.rs index c6709ccb..b61fbc1b 100644 --- a/masp_proofs/src/prover.rs +++ b/masp_proofs/src/prover.rs @@ -5,13 +5,15 @@ use bls12_381::Bls12; use masp_primitives::{ asset_type::AssetType, convert::AllowedConversion, - primitives::{Diversifier, PaymentAddress, ProofGenerationKey}, - prover::TxProver, - redjubjub::{PublicKey, Signature}, - sapling::Node, + merkle_tree::MerklePath, + sapling::{ + prover::TxProver, + redjubjub::{PublicKey, Signature}, + Diversifier, Node, PaymentAddress, ProofGenerationKey, Rseed, + }, + transaction::components::{Amount, GROTH_PROOF_SIZE}, }; -use zcash_primitives::transaction::components::GROTH_PROOF_SIZE; -use zcash_primitives::{merkle_tree::MerklePath, sapling::Rseed}; +use std::path::Path; use crate::{parse_parameters, sapling::SaplingProvingContext}; @@ -19,8 +21,6 @@ use crate::{parse_parameters, sapling::SaplingProvingContext}; use crate::{ default_params_folder, load_parameters, MASP_CONVERT_NAME, MASP_OUTPUT_NAME, MASP_SPEND_NAME, }; -#[cfg(feature = "local-prover")] -use std::path::Path; /// An implementation of [`TxProver`] using Sapling Spend and Output parameters from /// locally-accessible paths. @@ -42,9 +42,9 @@ impl LocalTxProver { /// use masp_proofs::prover::LocalTxProver; /// /// let tx_prover = LocalTxProver::new( - /// Path::new("/path/to/sapling-spend.params"), - /// Path::new("/path/to/sapling-output.params"), - /// Path::new("/path/to/convert.params"), + /// Path::new("/path/to/masp-spend.params"), + /// Path::new("/path/to/masp-output.params"), + /// Path::new("/path/to/masp-convert.params"), /// ); /// ``` /// @@ -247,7 +247,7 @@ impl TxProver for LocalTxProver { fn binding_sig( &self, ctx: &mut Self::SaplingProvingContext, - assets_and_values: &[(AssetType, i64)], + assets_and_values: &Amount, //&[(AssetType, i64)], sighash: &[u8; 32], ) -> Result { ctx.binding_sig(assets_and_values, sighash) diff --git a/masp_proofs/src/sapling/mod.rs b/masp_proofs/src/sapling/mod.rs index 53cd465a..eb14fc60 100644 --- a/masp_proofs/src/sapling/mod.rs +++ b/masp_proofs/src/sapling/mod.rs @@ -6,7 +6,7 @@ mod prover; mod verifier; pub use self::prover::SaplingProvingContext; -pub use self::verifier::SaplingVerificationContext; +pub use self::verifier::{BatchValidator, SaplingVerificationContext}; // This function computes `value` in the exponent of the value commitment base fn masp_compute_value_balance(asset_type: AssetType, value: i64) -> Option { diff --git a/masp_proofs/src/sapling/prover.rs b/masp_proofs/src/sapling/prover.rs index 7ea4b1dd..ad5eaf7b 100644 --- a/masp_proofs/src/sapling/prover.rs +++ b/masp_proofs/src/sapling/prover.rs @@ -3,19 +3,20 @@ use bellman::{ groth16::{create_random_proof, verify_proof, Parameters, PreparedVerifyingKey, Proof}, }; use bls12_381::Bls12; -use ff::Field; -use group::{Curve, GroupEncoding}; +use group::{ff::Field, Curve, GroupEncoding}; use masp_primitives::{ asset_type::AssetType, constants::{SPENDING_KEY_GENERATOR, VALUE_COMMITMENT_RANDOMNESS_GENERATOR}, convert::AllowedConversion, - primitives::{Diversifier, Note, PaymentAddress, ProofGenerationKey}, - redjubjub::{PrivateKey, PublicKey, Signature}, - sapling::Node, + merkle_tree::MerklePath, + sapling::{ + redjubjub::{PrivateKey, PublicKey, Signature}, + Diversifier, Node, Note, PaymentAddress, ProofGenerationKey, Rseed, + }, + transaction::components::Amount, }; use rand_core::OsRng; use std::ops::{AddAssign, Neg}; -use zcash_primitives::{merkle_tree::MerklePath, sapling::Rseed}; use super::masp_compute_value_balance; use crate::circuit::convert::Convert; @@ -96,7 +97,7 @@ impl SaplingProvingContext { rseed, }; - let nullifier = note.nf(&viewing_key, merkle_path.position); + let nullifier = note.nf(&viewing_key.nk, merkle_path.position); // We now have the full witness for our circuit let instance = Spend { @@ -283,7 +284,7 @@ impl SaplingProvingContext { /// and output_proof() must be completed before calling this function. pub fn binding_sig( &self, - assets_and_values: &[(AssetType, i64)], + assets_and_values: &Amount, sighash: &[u8; 32], ) -> Result { // Initialize secure RNG @@ -300,7 +301,7 @@ impl SaplingProvingContext { // against our derived bvk. { let final_bvk = assets_and_values - .iter() + .components() .map(|(asset_type, value_balance)| { // Compute value balance for each asset // Error for bad value balances (-INT64_MAX value) @@ -320,7 +321,7 @@ impl SaplingProvingContext { // Construct signature message let mut data_to_be_signed = [0u8; 64]; data_to_be_signed[0..32].copy_from_slice(&bvk.0.to_bytes()); - (&mut data_to_be_signed[32..64]).copy_from_slice(&sighash[..]); + data_to_be_signed[32..64].copy_from_slice(&sighash[..]); // Sign Ok(bsk.sign( diff --git a/masp_proofs/src/sapling/verifier.rs b/masp_proofs/src/sapling/verifier.rs index db9da860..22cceafb 100644 --- a/masp_proofs/src/sapling/verifier.rs +++ b/masp_proofs/src/sapling/verifier.rs @@ -1,27 +1,31 @@ #![allow(clippy::new_without_default)] -use super::masp_compute_value_balance; -use bellman::{ - gadgets::multipack, - groth16::{verify_proof, PreparedVerifyingKey, Proof}, -}; + +use bellman::{gadgets::multipack, groth16::Proof}; use bls12_381::Bls12; use group::{Curve, GroupEncoding}; use masp_primitives::{ - asset_type::AssetType, - constants::{SPENDING_KEY_GENERATOR, VALUE_COMMITMENT_RANDOMNESS_GENERATOR}, - redjubjub::{PublicKey, Signature}, + sapling::redjubjub::{PublicKey, Signature}, + transaction::components::Amount, }; +use super::masp_compute_value_balance; + +mod single; +pub use single::SaplingVerificationContext; + +mod batch; +pub use batch::BatchValidator; + /// A context object for verifying the Sapling components of a Zcash transaction. -pub struct SaplingVerificationContext { +struct SaplingVerificationContextInner { // (sum of the Spend value commitments) - (sum of the Output value commitments) cv_sum: jubjub::ExtendedPoint, } -impl SaplingVerificationContext { +impl SaplingVerificationContextInner { /// Construct a new context to be used with a single transaction. - pub fn new() -> Self { - SaplingVerificationContext { + fn new() -> Self { + SaplingVerificationContextInner { cv_sum: jubjub::ExtendedPoint::identity(), } } @@ -29,7 +33,7 @@ impl SaplingVerificationContext { /// Perform consensus checks on a Sapling SpendDescription, while /// accumulating its value commitment inside the context for later use. #[allow(clippy::too_many_arguments)] - pub fn check_spend( + fn check_spend( &mut self, cv: jubjub::ExtendedPoint, anchor: bls12_381::Scalar, @@ -38,7 +42,9 @@ impl SaplingVerificationContext { sighash_value: &[u8; 32], spend_auth_sig: Signature, zkproof: Proof, - verifying_key: &PreparedVerifyingKey, + verifier_ctx: &mut C, + spend_auth_sig_verifier: impl FnOnce(&mut C, PublicKey, [u8; 64], Signature) -> bool, + proof_verifier: impl FnOnce(&mut C, Proof, [bls12_381::Scalar; 7]) -> bool, ) -> bool { if (cv.is_small_order() | rk.0.is_small_order()).into() { return false; @@ -53,22 +59,18 @@ impl SaplingVerificationContext { // Compute the signature's message for rk/spend_auth_sig let mut data_to_be_signed = [0u8; 64]; data_to_be_signed[0..32].copy_from_slice(&rk.0.to_bytes()); - (&mut data_to_be_signed[32..64]).copy_from_slice(&sighash_value[..]); + data_to_be_signed[32..64].copy_from_slice(&sighash_value[..]); // Verify the spend_auth_sig - if !rk.verify_with_zip216( - &data_to_be_signed, - &spend_auth_sig, - SPENDING_KEY_GENERATOR, - true, // zip216_enabled = true - ) { + let rk_affine = rk.0.to_affine(); + if !spend_auth_sig_verifier(verifier_ctx, rk, data_to_be_signed, spend_auth_sig) { return false; } // Construct public input for circuit let mut public_input = [bls12_381::Scalar::zero(); 7]; { - let affine = rk.0.to_affine(); + let affine = rk_affine; let (u, v) = (affine.get_u(), affine.get_v()); public_input[0] = u; public_input[1] = v; @@ -93,91 +95,94 @@ impl SaplingVerificationContext { } // Verify the proof - verify_proof(verifying_key, &zkproof, &public_input[..]).is_ok() + proof_verifier(verifier_ctx, zkproof, public_input) } - /// Perform consensus checks on a Sapling OutputDescription, while + /// Perform consensus checks on a Convert SpendDescription, while /// accumulating its value commitment inside the context for later use. - pub fn check_output( + #[allow(clippy::too_many_arguments)] + fn check_convert( &mut self, cv: jubjub::ExtendedPoint, - cmu: bls12_381::Scalar, - epk: jubjub::ExtendedPoint, + anchor: bls12_381::Scalar, zkproof: Proof, - verifying_key: &PreparedVerifyingKey, + verifier_ctx: &mut C, + proof_verifier: impl FnOnce(&mut C, Proof, [bls12_381::Scalar; 3]) -> bool, ) -> bool { - if (cv.is_small_order() | epk.is_small_order()).into() { + if cv.is_small_order().into() { return false; } // Accumulate the value commitment in the context - self.cv_sum -= cv; + self.cv_sum += cv; // Construct public input for circuit - let mut public_input = [bls12_381::Scalar::zero(); 5]; + let mut public_input = [bls12_381::Scalar::zero(); 3]; { let affine = cv.to_affine(); let (u, v) = (affine.get_u(), affine.get_v()); public_input[0] = u; public_input[1] = v; } - { - let affine = epk.to_affine(); - let (u, v) = (affine.get_u(), affine.get_v()); - public_input[2] = u; - public_input[3] = v; - } - public_input[4] = cmu; + public_input[2] = anchor; // Verify the proof - verify_proof(verifying_key, &zkproof, &public_input[..]).is_ok() + proof_verifier(verifier_ctx, zkproof, public_input) } - /// Perform consensus checks on a Sapling ConvertDescription, while + /// Perform consensus checks on a Sapling OutputDescription, while /// accumulating its value commitment inside the context for later use. - pub fn check_convert( + fn check_output( &mut self, cv: jubjub::ExtendedPoint, - anchor: bls12_381::Scalar, + cmu: bls12_381::Scalar, + epk: jubjub::ExtendedPoint, zkproof: Proof, - verifying_key: &PreparedVerifyingKey, + proof_verifier: impl FnOnce(Proof, [bls12_381::Scalar; 5]) -> bool, ) -> bool { - if cv.is_small_order().into() { + if (cv.is_small_order() | epk.is_small_order()).into() { return false; } // Accumulate the value commitment in the context - self.cv_sum += cv; + self.cv_sum -= cv; // Construct public input for circuit - let mut public_input = [bls12_381::Scalar::zero(); 3]; + let mut public_input = [bls12_381::Scalar::zero(); 5]; { let affine = cv.to_affine(); let (u, v) = (affine.get_u(), affine.get_v()); public_input[0] = u; public_input[1] = v; } - public_input[2] = anchor; + { + let affine = epk.to_affine(); + let (u, v) = (affine.get_u(), affine.get_v()); + public_input[2] = u; + public_input[3] = v; + } + public_input[4] = cmu; // Verify the proof - verify_proof(verifying_key, &zkproof, &public_input[..]).is_ok() + proof_verifier(zkproof, public_input) } /// Perform consensus checks on the valueBalance and bindingSig parts of a /// Sapling transaction. All SpendDescriptions and OutputDescriptions must /// have been checked before calling this function. - pub fn final_check( + fn final_check( &self, - assets_and_values: &[(AssetType, i64)], + value_balance: Amount, sighash_value: &[u8; 32], binding_sig: Signature, + binding_sig_verifier: impl FnOnce(PublicKey, [u8; 64], Signature) -> bool, ) -> bool { // Obtain current cv_sum from the context let mut bvk = PublicKey(self.cv_sum); // Compute value balance - let value_balances = assets_and_values - .iter() + let value_balance = value_balance + .components() .map(|(asset_type, value_balance)| { // Compute value balance for each asset // Error for bad value balances (-INT64_MAX value) @@ -185,7 +190,7 @@ impl SaplingVerificationContext { }) .collect::, _>>(); - bvk.0 = match value_balances { + bvk.0 = match value_balance { Ok(vb) => vb.iter().fold(bvk.0, |tmp, value_balance| { // Compute cv_sum minus sum of all value balances tmp - value_balance @@ -196,14 +201,9 @@ impl SaplingVerificationContext { // Compute the signature's message for bvk/binding_sig let mut data_to_be_signed = [0u8; 64]; data_to_be_signed[0..32].copy_from_slice(&bvk.0.to_bytes()); - (&mut data_to_be_signed[32..64]).copy_from_slice(&sighash_value[..]); + data_to_be_signed[32..64].copy_from_slice(&sighash_value[..]); // Verify the binding_sig - bvk.verify_with_zip216( - &data_to_be_signed, - &binding_sig, - VALUE_COMMITMENT_RANDOMNESS_GENERATOR, - true, // zip216_enabled = true - ) + binding_sig_verifier(bvk, data_to_be_signed, binding_sig) } } diff --git a/masp_proofs/src/sapling/verifier/batch.rs b/masp_proofs/src/sapling/verifier/batch.rs new file mode 100644 index 00000000..bfd22b32 --- /dev/null +++ b/masp_proofs/src/sapling/verifier/batch.rs @@ -0,0 +1,211 @@ +use bellman::groth16; +use bls12_381::Bls12; +use group::GroupEncoding; +use masp_primitives::transaction::components::sapling::{Authorized, Bundle}; +use rand_core::{CryptoRng, RngCore}; + +use super::SaplingVerificationContextInner; + +/// Batch validation context for MASP/Sapling. +/// +/// This batch-validates Spend, Convert, and Output proofs, and RedJubjub signatures. +/// +/// Signatures are verified assuming ZIP 216 is active. +pub struct BatchValidator { + bundles_added: bool, + spend_proofs: groth16::batch::Verifier, + convert_proofs: groth16::batch::Verifier, + output_proofs: groth16::batch::Verifier, + signatures: redjubjub::batch::Verifier, +} + +impl Default for BatchValidator { + fn default() -> Self { + Self::new() + } +} + +impl BatchValidator { + /// Constructs a new batch validation context. + pub fn new() -> Self { + BatchValidator { + bundles_added: false, + spend_proofs: groth16::batch::Verifier::new(), + convert_proofs: groth16::batch::Verifier::new(), + output_proofs: groth16::batch::Verifier::new(), + signatures: redjubjub::batch::Verifier::new(), + } + } + + /// Checks the bundle against Sapling-specific consensus rules, and adds its proof and + /// signatures to the validator. + /// + /// Returns `false` if the bundle doesn't satisfy all of the consensus rules. This + /// `BatchValidator` can continue to be used regardless, but some or all of the proofs + /// and signatures from this bundle may have already been added to the batch even if + /// it fails other consensus rules. + pub fn check_bundle(&mut self, bundle: Bundle, sighash: [u8; 32]) -> bool { + self.bundles_added = true; + + let mut ctx = SaplingVerificationContextInner::new(); + + for spend in bundle.shielded_spends { + // Deserialize the proof + let zkproof = match groth16::Proof::read(&spend.zkproof[..]) { + Ok(p) => p, + Err(_) => return false, + }; + + // Check the Spend consensus rules, and batch its proof and spend + // authorization signature. + let consensus_rules_passed = ctx.check_spend( + spend.cv, + spend.anchor, + &spend.nullifier.0, + spend.rk, + &sighash, + spend.spend_auth_sig, + zkproof, + self, + |this, rk, _, spend_auth_sig| { + let rk = redjubjub::VerificationKeyBytes::::from( + rk.0.to_bytes(), + ); + let spend_auth_sig = { + let mut buf = [0; 64]; + spend_auth_sig.write(&mut buf[..]).unwrap(); + redjubjub::Signature::::from(buf) + }; + + this.signatures.queue((rk, spend_auth_sig, &sighash)); + true + }, + |this, proof, public_inputs| { + this.spend_proofs.queue((proof, public_inputs.to_vec())); + true + }, + ); + if !consensus_rules_passed { + return false; + } + } + for convert in bundle.shielded_converts { + // Deserialize the proof + let zkproof = match groth16::Proof::read(&convert.zkproof[..]) { + Ok(p) => p, + Err(_) => return false, + }; + + // Check the Convert consensus rules, and batch its proof + let consensus_rules_passed = ctx.check_convert( + convert.cv, + convert.anchor, + zkproof, + self, + |this, proof, public_inputs| { + this.convert_proofs.queue((proof, public_inputs.to_vec())); + true + }, + ); + if !consensus_rules_passed { + return false; + } + } + + for output in bundle.shielded_outputs { + // Deserialize the ephemeral key + let epk = match jubjub::ExtendedPoint::from_bytes(&output.ephemeral_key.0).into() { + Some(p) => p, + None => return false, + }; + + // Deserialize the proof + let zkproof = match groth16::Proof::read(&output.zkproof[..]) { + Ok(p) => p, + Err(_) => return false, + }; + + // Check the Output consensus rules, and batch its proof. + let consensus_rules_passed = ctx.check_output( + output.cv, + output.cmu, + epk, + zkproof, + |proof, public_inputs| { + self.output_proofs.queue((proof, public_inputs.to_vec())); + true + }, + ); + if !consensus_rules_passed { + return false; + } + } + + // Check the whole-bundle consensus rules, and batch the binding signature. + ctx.final_check( + bundle.value_balance, + &sighash, + bundle.authorization.binding_sig, + |bvk, _, binding_sig| { + let bvk = + redjubjub::VerificationKeyBytes::::from(bvk.0.to_bytes()); + let binding_sig = { + let mut buf = [0; 64]; + binding_sig.write(&mut buf[..]).unwrap(); + redjubjub::Signature::::from(buf) + }; + + self.signatures.queue((bvk, binding_sig, &sighash)); + true + }, + ) + } + + /// Batch-validates the accumulated bundles. + /// + /// Returns `true` if every proof and signature in every bundle added to the batch + /// validator is valid, or `false` if one or more are invalid. No attempt is made to + /// figure out which of the accumulated bundles might be invalid; if that information + /// is desired, construct separate [`BatchValidator`]s for sub-batches of the bundles. + pub fn validate( + self, + spend_vk: &groth16::VerifyingKey, + convert_vk: &groth16::VerifyingKey, + output_vk: &groth16::VerifyingKey, + mut rng: R, + ) -> bool { + if !self.bundles_added { + // An empty batch is always valid, but is not free to run; skip it. + return true; + } + + if let Err(e) = self.signatures.verify(&mut rng) { + tracing::debug!("Signature batch validation failed: {}", e); + return false; + } + + #[cfg(feature = "multicore")] + let verify_proofs = |batch: groth16::batch::Verifier, vk| batch.verify_multicore(vk); + + #[cfg(not(feature = "multicore"))] + let mut verify_proofs = + |batch: groth16::batch::Verifier, vk| batch.verify(&mut rng, vk); + + if verify_proofs(self.spend_proofs, spend_vk).is_err() { + tracing::debug!("Spend proof batch validation failed"); + return false; + } + + if verify_proofs(self.convert_proofs, convert_vk).is_err() { + tracing::debug!("Convert proof batch validation failed"); + return false; + } + + if verify_proofs(self.output_proofs, output_vk).is_err() { + tracing::debug!("Output proof batch validation failed"); + return false; + } + + true + } +} diff --git a/masp_proofs/src/sapling/verifier/single.rs b/masp_proofs/src/sapling/verifier/single.rs new file mode 100644 index 00000000..2df7dfae --- /dev/null +++ b/masp_proofs/src/sapling/verifier/single.rs @@ -0,0 +1,119 @@ +use bellman::groth16::{verify_proof, PreparedVerifyingKey, Proof}; +use bls12_381::Bls12; +use masp_primitives::{ + constants::{SPENDING_KEY_GENERATOR, VALUE_COMMITMENT_RANDOMNESS_GENERATOR}, + sapling::redjubjub::{PublicKey, Signature}, + transaction::components::Amount, +}; + +use super::SaplingVerificationContextInner; + +/// A context object for verifying the Sapling components of a single Zcash transaction. +pub struct SaplingVerificationContext { + inner: SaplingVerificationContextInner, + zip216_enabled: bool, +} + +impl SaplingVerificationContext { + /// Construct a new context to be used with a single transaction. + pub fn new(zip216_enabled: bool) -> Self { + SaplingVerificationContext { + inner: SaplingVerificationContextInner::new(), + zip216_enabled, + } + } + + /// Perform consensus checks on a Sapling SpendDescription, while + /// accumulating its value commitment inside the context for later use. + #[allow(clippy::too_many_arguments)] + pub fn check_spend( + &mut self, + cv: jubjub::ExtendedPoint, + anchor: bls12_381::Scalar, + nullifier: &[u8; 32], + rk: PublicKey, + sighash_value: &[u8; 32], + spend_auth_sig: Signature, + zkproof: Proof, + verifying_key: &PreparedVerifyingKey, + ) -> bool { + let zip216_enabled = true; + self.inner.check_spend( + cv, + anchor, + nullifier, + rk, + sighash_value, + spend_auth_sig, + zkproof, + &mut (), + |_, rk, msg, spend_auth_sig| { + rk.verify_with_zip216( + &msg, + &spend_auth_sig, + SPENDING_KEY_GENERATOR, + zip216_enabled, + ) + }, + |_, proof, public_inputs| { + verify_proof(verifying_key, &proof, &public_inputs[..]).is_ok() + }, + ) + } + + /// Perform consensus checks on a Sapling SpendDescription, while + /// accumulating its value commitment inside the context for later use. + #[allow(clippy::too_many_arguments)] + pub fn check_convert( + &mut self, + cv: jubjub::ExtendedPoint, + anchor: bls12_381::Scalar, + zkproof: Proof, + verifying_key: &PreparedVerifyingKey, + ) -> bool { + self.inner + .check_convert(cv, anchor, zkproof, &mut (), |_, proof, public_inputs| { + verify_proof(verifying_key, &proof, &public_inputs[..]).is_ok() + }) + } + + /// Perform consensus checks on a Sapling OutputDescription, while + /// accumulating its value commitment inside the context for later use. + pub fn check_output( + &mut self, + cv: jubjub::ExtendedPoint, + cmu: bls12_381::Scalar, + epk: jubjub::ExtendedPoint, + zkproof: Proof, + verifying_key: &PreparedVerifyingKey, + ) -> bool { + self.inner + .check_output(cv, cmu, epk, zkproof, |proof, public_inputs| { + verify_proof(verifying_key, &proof, &public_inputs[..]).is_ok() + }) + } + + /// Perform consensus checks on the valueBalance and bindingSig parts of a + /// Sapling transaction. All SpendDescriptions and OutputDescriptions must + /// have been checked before calling this function. + pub fn final_check( + &self, + value_balance: Amount, + sighash_value: &[u8; 32], + binding_sig: Signature, + ) -> bool { + self.inner.final_check( + value_balance, + sighash_value, + binding_sig, + |bvk, msg, binding_sig| { + bvk.verify_with_zip216( + &msg, + &binding_sig, + VALUE_COMMITMENT_RANDOMNESS_GENERATOR, + self.zip216_enabled, + ) + }, + ) + } +} diff --git a/rust-toolchain b/rust-toolchain deleted file mode 100644 index 4213d88d..00000000 --- a/rust-toolchain +++ /dev/null @@ -1 +0,0 @@ -1.61 diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 00000000..2c8ed87a --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "1.65.0" +components = [ "clippy", "rustfmt" ] \ No newline at end of file From 113f4fbdf14eb82ca83dd9d50f1d585a877c4319 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Tue, 11 Apr 2023 10:46:18 +0200 Subject: [PATCH 03/21] Now publicly export cryptographic crates. --- masp_primitives/src/lib.rs | 5 +++++ masp_proofs/src/lib.rs | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/masp_primitives/src/lib.rs b/masp_primitives/src/lib.rs index 7649170d..0f1acdd5 100644 --- a/masp_primitives/src/lib.rs +++ b/masp_primitives/src/lib.rs @@ -26,5 +26,10 @@ pub mod sapling; pub mod transaction; pub mod zip32; +pub use group; +pub use ff; +pub use jubjub; +pub use bls12_381; + #[cfg(test)] mod test_vectors; diff --git a/masp_proofs/src/lib.rs b/masp_proofs/src/lib.rs index 15abbba5..92ae4934 100644 --- a/masp_proofs/src/lib.rs +++ b/masp_proofs/src/lib.rs @@ -15,6 +15,11 @@ use std::fs::File; use std::io::{self, BufReader}; use std::path::Path; +pub use group; +pub use bellman; +pub use jubjub; +pub use bls12_381; + #[cfg(feature = "directories")] use directories::BaseDirs; #[cfg(feature = "directories")] From 1d495c2e4dcb793e4b6ff984819c036e8bbea798 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Tue, 11 Apr 2023 11:14:10 +0200 Subject: [PATCH 04/21] Implemented Borsh for Transaction. --- masp_primitives/src/lib.rs | 1 + masp_primitives/src/sapling.rs | 5 ++- masp_primitives/src/transaction.rs | 31 +++++++++++++++++-- masp_primitives/src/transaction/components.rs | 2 +- 4 files changed, 32 insertions(+), 7 deletions(-) diff --git a/masp_primitives/src/lib.rs b/masp_primitives/src/lib.rs index 0f1acdd5..8193d7bd 100644 --- a/masp_primitives/src/lib.rs +++ b/masp_primitives/src/lib.rs @@ -30,6 +30,7 @@ pub use group; pub use ff; pub use jubjub; pub use bls12_381; +pub use secp256k1; #[cfg(test)] mod test_vectors; diff --git a/masp_primitives/src/sapling.rs b/masp_primitives/src/sapling.rs index 914894f0..312bd196 100644 --- a/masp_primitives/src/sapling.rs +++ b/masp_primitives/src/sapling.rs @@ -85,8 +85,7 @@ pub struct Node { } impl Node { - #[cfg(test)] - pub(crate) fn new(repr: [u8; 32]) -> Self { + pub fn new(repr: [u8; 32]) -> Self { Node { repr } } @@ -551,7 +550,7 @@ impl From for u64 { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Copy)] pub struct Note { /// The asset type that the note represents pub asset_type: AssetType, diff --git a/masp_primitives/src/transaction.rs b/masp_primitives/src/transaction.rs index 4ba6c5a3..0202eeec 100644 --- a/masp_primitives/src/transaction.rs +++ b/masp_primitives/src/transaction.rs @@ -147,7 +147,7 @@ pub trait Authorization { } #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct Unproven; -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone)] pub struct Authorized; impl Authorization for Authorized { @@ -163,7 +163,7 @@ impl Authorization for Unauthorized { } /// A MASP transaction. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Transaction { txid: TxId, data: TransactionData, @@ -183,7 +183,7 @@ impl PartialEq for Transaction { } } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct TransactionData { version: TxVersion, consensus_branch_id: BranchId, @@ -280,6 +280,31 @@ impl TransactionData { } } +impl BorshSerialize for Transaction { + fn serialize(&self, writer: &mut W) -> borsh::maybestd::io::Result<()> { + self.write(writer) + } +} + +impl BorshDeserialize for Transaction { + fn deserialize(buf: &mut &[u8]) -> borsh::maybestd::io::Result { + Self::read(buf, BranchId::MASP) + } +} + +impl borsh::BorshSchema for Transaction { + fn add_definitions_recursively( + _definitions: &mut std::collections::HashMap< + borsh::schema::Declaration, + borsh::schema::Definition, + >, + ) {} + + fn declaration() -> borsh::schema::Declaration { + "Transaction".into() + } +} + impl Transaction { fn from_data(data: TransactionData) -> io::Result { match data.version { diff --git a/masp_primitives/src/transaction/components.rs b/masp_primitives/src/transaction/components.rs index 44fa4d89..8c3604c9 100644 --- a/masp_primitives/src/transaction/components.rs +++ b/masp_primitives/src/transaction/components.rs @@ -5,7 +5,7 @@ pub mod sapling; pub mod transparent; pub use self::{ amount::Amount, - sapling::{OutputDescription, SpendDescription}, + sapling::{OutputDescription, SpendDescription, ConvertDescription}, transparent::{TxIn, TxOut}, }; From 840136f0b33c01f8e825433b6a2653ef258bc99c Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Thu, 13 Apr 2023 10:25:49 +0200 Subject: [PATCH 05/21] Changed TransparentAddress to hash instead of public key. Makes it easier to use from Namada and decreases difference from librustzcash. --- masp_primitives/src/transaction.rs | 4 +++- masp_primitives/src/transaction/builder.rs | 4 +--- .../src/transaction/components/transparent.rs | 9 ++++----- masp_primitives/src/transaction/txid.rs | 2 +- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/masp_primitives/src/transaction.rs b/masp_primitives/src/transaction.rs index 0202eeec..821c9d67 100644 --- a/masp_primitives/src/transaction.rs +++ b/masp_primitives/src/transaction.rs @@ -10,7 +10,6 @@ use blake2b_simd::Hash as Blake2bHash; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use ff::PrimeField; use memuse::DynamicUsage; -pub use secp256k1::PublicKey as TransparentAddress; use std::{ fmt::{self, Debug}, hash::Hash, @@ -35,6 +34,9 @@ use self::{ txid::{to_txid, BlockTxCommitmentDigester, TxIdDigester}, }; +#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash, Debug)] +pub struct TransparentAddress(pub [u8; 20]); + pub const GROTH_PROOF_SIZE: usize = 48 + 96 + 48; pub type GrothProofBytes = [u8; GROTH_PROOF_SIZE]; diff --git a/masp_primitives/src/transaction/builder.rs b/masp_primitives/src/transaction/builder.rs index 050e2e59..85cbf397 100644 --- a/masp_primitives/src/transaction/builder.rs +++ b/masp_primitives/src/transaction/builder.rs @@ -5,8 +5,6 @@ use std::error; use std::fmt; use std::sync::mpsc::Sender; -use secp256k1::PublicKey as TransparentAddress; - use rand::{rngs::OsRng, CryptoRng, RngCore}; use crate::{ @@ -28,7 +26,7 @@ use crate::{ fees::FeeRule, sighash::{signature_hash, SignableInput}, txid::TxIdDigester, - Transaction, TransactionData, TxVersion, Unauthorized, + Transaction, TransactionData, TxVersion, Unauthorized, TransparentAddress, }, zip32::ExtendedSpendingKey, }; diff --git a/masp_primitives/src/transaction/components/transparent.rs b/masp_primitives/src/transaction/components/transparent.rs index a1c81b56..465aa658 100644 --- a/masp_primitives/src/transaction/components/transparent.rs +++ b/masp_primitives/src/transaction/components/transparent.rs @@ -1,11 +1,11 @@ //! Structs representing the components within Zcash transactions. use borsh::{BorshDeserialize, BorshSerialize}; -use secp256k1::PublicKey as TransparentAddress; use std::fmt::{self, Debug}; use std::io::{self, Read, Write}; use crate::asset_type::AssetType; +use crate::transaction::TransparentAddress; use super::amount::{Amount, BalanceError, MAX_MONEY}; @@ -145,10 +145,9 @@ impl TxOut { )); } - let mut tmp = [0u8; 33]; + let mut tmp = [0u8; 20]; reader.read_exact(&mut tmp)?; - let transparent_address = TransparentAddress::from_slice(&tmp) - .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "bad public key"))?; + let transparent_address = TransparentAddress(tmp); Ok(TxOut { asset_type, @@ -160,7 +159,7 @@ impl TxOut { pub fn write(&self, mut writer: W) -> io::Result<()> { writer.write_all(self.asset_type.get_identifier())?; writer.write_all(&self.value.to_le_bytes())?; - writer.write_all(&self.transparent_address.serialize()) + writer.write_all(&self.transparent_address.0) } /// Returns the address to which the TxOut was sent, if this is a valid P2SH or P2PKH output. pub fn recipient_address(&self) -> TransparentAddress { diff --git a/masp_primitives/src/transaction/txid.rs b/masp_primitives/src/transaction/txid.rs index 2a02b757..e9382e2b 100644 --- a/masp_primitives/src/transaction/txid.rs +++ b/masp_primitives/src/transaction/txid.rs @@ -332,7 +332,7 @@ impl TransactionDigest for BlockTxCommitmentDigester { for txout in &bundle.vout { h.write_all(txout.asset_type.get_identifier()).unwrap(); h.write_all(&txout.value.to_le_bytes()).unwrap(); - h.write_all(&txout.transparent_address.serialize()).unwrap(); + h.write_all(&txout.transparent_address.0).unwrap(); } } h.finalize() From 737a92629d9d45fe6c1c37340786116b700f2e84 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Thu, 13 Apr 2023 13:52:20 +0200 Subject: [PATCH 06/21] Now include the TransparentAddress in the Authorized Bundle. --- masp_primitives/src/transaction/builder.rs | 16 +++--- .../src/transaction/components/transparent.rs | 49 +++++++++++++------ .../components/transparent/builder.rs | 28 +++++++++-- masp_primitives/src/transaction/txid.rs | 5 +- 4 files changed, 72 insertions(+), 26 deletions(-) diff --git a/masp_primitives/src/transaction/builder.rs b/masp_primitives/src/transaction/builder.rs index 85cbf397..a2e1453e 100644 --- a/masp_primitives/src/transaction/builder.rs +++ b/masp_primitives/src/transaction/builder.rs @@ -422,7 +422,7 @@ mod testing { mod tests { use ff::Field; use rand_core::OsRng; - use secp256k1::Secp256k1; + use rand::Rng; use crate::{ asset_type::AssetType, @@ -434,6 +434,7 @@ mod tests { components::amount::{Amount, DEFAULT_FEE, MAX_MONEY}, sapling::builder::{self as build_s}, transparent::builder::{self as build_t}, + TransparentAddress, }, zip32::ExtendedSpendingKey, }; @@ -471,7 +472,9 @@ mod tests { #[test] fn binding_sig_present_if_shielded_spend() { - let (_, transparent_address) = Secp256k1::new().generate_keypair(&mut OsRng); + let mut rng = OsRng; + + let transparent_address = TransparentAddress(rng.gen::<[u8; 20]>()); let extsk = ExtendedSpendingKey::master(&[]); let dfvk = extsk.to_diversifiable_full_viewing_key(); @@ -515,10 +518,9 @@ mod tests { #[test] fn fails_on_negative_transparent_output() { - let secret_key = - secp256k1::SecretKey::from_slice(&[0xcd; 32]).expect("32 bytes, within curve order"); - let transparent_address = - secp256k1::PublicKey::from_secret_key(&secp256k1::Secp256k1::new(), &secret_key); + let mut rng = OsRng; + + let transparent_address = TransparentAddress(rng.gen::<[u8; 20]>()); let tx_height = TEST_NETWORK .activation_height(NetworkUpgrade::MASP) .unwrap(); @@ -533,7 +535,7 @@ mod tests { fn fails_on_negative_change() { let mut rng = OsRng; - let (_, transparent_address) = Secp256k1::new().generate_keypair(&mut OsRng); + let transparent_address = TransparentAddress(rng.gen::<[u8; 20]>()); // Just use the master key as the ExtendedSpendingKey for this test let extsk = ExtendedSpendingKey::master(&[]); let tx_height = TEST_NETWORK diff --git a/masp_primitives/src/transaction/components/transparent.rs b/masp_primitives/src/transaction/components/transparent.rs index 465aa658..cea7b6f8 100644 --- a/masp_primitives/src/transaction/components/transparent.rs +++ b/masp_primitives/src/transaction/components/transparent.rs @@ -20,16 +20,17 @@ pub trait Authorization: fmt::Debug { pub struct Authorized; impl Authorization for Authorized { - type TransparentSig = (); + type TransparentSig = TransparentAddress; } pub trait MapAuth { + fn map_script_sig(&self, s: A::TransparentSig) -> B::TransparentSig; fn map_authorization(&self, s: A) -> B; } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq)] pub struct Bundle { - pub vin: Vec, + pub vin: Vec>, pub vout: Vec, pub authorization: A, } @@ -37,7 +38,15 @@ pub struct Bundle { impl Bundle { pub fn map_authorization>(self, f: F) -> Bundle { Bundle { - vin: self.vin, + vin: self + .vin + .into_iter() + .map(|txin| TxIn { + asset_type: txin.asset_type, + transparent_sig: f.map_script_sig(txin.transparent_sig), + value: txin.value, + }) + .collect(), vout: self.vout, authorization: f.map_authorization(self.authorization), } @@ -84,12 +93,13 @@ impl Bundle { } #[derive(Debug, Clone, PartialEq, Eq)] -pub struct TxIn { +pub struct TxIn { pub asset_type: AssetType, pub value: i64, + pub transparent_sig: A::TransparentSig, } -impl TxIn { +impl TxIn { pub fn read(reader: &mut R) -> io::Result { let asset_type = { let mut tmp = [0u8; 32]; @@ -108,13 +118,19 @@ impl TxIn { "value out of range", )); } + let transparent_sig = { + let mut tmp = [0u8; 20]; + reader.read_exact(&mut tmp)?; + TransparentAddress(tmp) + }; - Ok(TxIn { asset_type, value }) + Ok(TxIn { asset_type, value, transparent_sig }) } pub fn write(&self, mut writer: W) -> io::Result<()> { writer.write_all(self.asset_type.get_identifier())?; - writer.write_all(&self.value.to_le_bytes()) + writer.write_all(&self.value.to_le_bytes())?; + writer.write_all(&self.transparent_sig.0) } } @@ -185,23 +201,28 @@ pub mod testing { use proptest::prelude::*; use crate::transaction::components::amount::testing::arb_nonnegative_amount; + use crate::transaction::TransparentAddress; use super::{Authorized, Bundle, TxIn, TxOut}; prop_compose! { - pub fn arb_txin()(amt in arb_nonnegative_amount()) -> TxIn { + pub fn arb_transparent_address()(value in prop::array::uniform20(prop::num::u8::ANY)) -> TransparentAddress { + TransparentAddress(value) + } + } + + prop_compose! { + pub fn arb_txin()(amt in arb_nonnegative_amount(), addr in arb_transparent_address()) -> TxIn { let (asset_type, value) = amt.components().next().unwrap(); - TxIn { asset_type: *asset_type, value: *value } + TxIn { asset_type: *asset_type, value: *value, transparent_sig: addr } } } prop_compose! { - pub fn arb_txout()(amt in arb_nonnegative_amount()) -> TxOut { - let secp = secp256k1::Secp256k1::new(); - let (_, public_key) = secp.generate_keypair(&mut rand_core::OsRng); + pub fn arb_txout()(amt in arb_nonnegative_amount(), addr in arb_transparent_address()) -> TxOut { let (asset_type, value) = amt.components().next().unwrap(); - TxOut { asset_type: *asset_type, value: *value, transparent_address : public_key } + TxOut { asset_type: *asset_type, value: *value, transparent_address : addr } } } diff --git a/masp_primitives/src/transaction/components/transparent/builder.rs b/masp_primitives/src/transaction/components/transparent/builder.rs index d620b020..6d654ce1 100644 --- a/masp_primitives/src/transaction/components/transparent/builder.rs +++ b/masp_primitives/src/transaction/components/transparent/builder.rs @@ -169,12 +169,13 @@ impl TransparentBuilder { pub fn build(self) -> Option> { #[cfg(feature = "transparent-inputs")] - let vin: Vec = self + let vin: Vec> = self .inputs .iter() - .map(|i| TxIn { + .map(|i| TxIn:: { asset_type: i.coin.asset_type, value: i.coin.value, + transparent_sig: (), }) .collect(); @@ -216,8 +217,29 @@ impl TransparentAuthorizingContext for Unauthorized { impl Bundle { pub fn apply_signatures(self) -> Bundle { + #[cfg(feature = "transparent-inputs")] + let transparent_sigs = self + .authorization + .inputs + .iter() + .map(|info| { + info.coin.transparent_address + }); + + #[cfg(not(feature = "transparent-inputs"))] + let transparent_sigs = std::iter::empty::(); + transparent::Bundle { - vin: self.vin, + vin: self + .vin + .iter() + .zip(transparent_sigs) + .map(|(txin, sig)| TxIn { + asset_type: txin.asset_type, + transparent_sig: sig, + value: txin.value, + }) + .collect(), vout: self.vout, authorization: Authorized, } diff --git a/masp_primitives/src/transaction/txid.rs b/masp_primitives/src/transaction/txid.rs index e9382e2b..b5a8f7b6 100644 --- a/masp_primitives/src/transaction/txid.rs +++ b/masp_primitives/src/transaction/txid.rs @@ -62,10 +62,11 @@ pub(crate) fn transparent_outputs_hash>(vout: &[T]) -> Blake2bH /// to a hash personalized by ZCASH_INPUTS_HASH_PERSONALIZATION. /// In the case that no inputs are provided, this produces a default /// hash from just the personalization string. -pub(crate) fn transparent_inputs_hash>(vin: &[T]) -> Blake2bHash { +pub(crate) fn transparent_inputs_hash>>(vin: &[T]) -> Blake2bHash { let mut h = hasher(ZCASH_INPUTS_HASH_PERSONALIZATION); for t_in in vin { - t_in.borrow().write(&mut h).unwrap(); + h.write(t_in.borrow().asset_type.get_identifier()).unwrap(); + h.write(&t_in.borrow().value.to_le_bytes()).unwrap(); } h.finalize() } From da24df0e12f11d454c84bf67825582166d772cfc Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Thu, 13 Apr 2023 14:06:55 +0200 Subject: [PATCH 07/21] Simplified TransparentAuthorizingContext to ease potential reconstructions of TxIns. --- .../src/transaction/components/transparent/builder.rs | 6 +++--- masp_primitives/src/transaction/sighash.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/masp_primitives/src/transaction/components/transparent/builder.rs b/masp_primitives/src/transaction/components/transparent/builder.rs index 6d654ce1..4c53a013 100644 --- a/masp_primitives/src/transaction/components/transparent/builder.rs +++ b/masp_primitives/src/transaction/components/transparent/builder.rs @@ -199,18 +199,18 @@ impl TransparentBuilder { #[cfg(not(feature = "transparent-inputs"))] impl TransparentAuthorizingContext for Unauthorized { - fn input_amounts(&self) -> Vec { + fn input_amounts(&self) -> Vec<(AssetType, i64)> { vec![] } } #[cfg(feature = "transparent-inputs")] impl TransparentAuthorizingContext for Unauthorized { - fn input_amounts(&self) -> Vec> { + fn input_amounts(&self) -> Vec<(AssetType, i64)> { return self .inputs .iter() - .map(|txin| Amount::from_pair(txin.coin.asset_type, txin.coin.value)) + .map(|txin| (txin.coin.asset_type, txin.coin.value)) .collect(); } } diff --git a/masp_primitives/src/transaction/sighash.rs b/masp_primitives/src/transaction/sighash.rs index 20b3498c..e6c9d3c4 100644 --- a/masp_primitives/src/transaction/sighash.rs +++ b/masp_primitives/src/transaction/sighash.rs @@ -5,7 +5,7 @@ use blake2b_simd::Hash as Blake2bHash; use super::{ components::{ sapling::{self, GrothProofBytes}, - transparent, Amount, + transparent, }, sighash_v5::v5_signature_hash, Authorization, TransactionData, TxDigests, TxVersion, @@ -55,7 +55,7 @@ pub trait TransparentAuthorizingContext: transparent::Authorization { /// so that wallets can commit to the transparent input breakdown /// without requiring the full data of the previous transactions /// providing these inputs. - fn input_amounts(&self) -> Vec>; + fn input_amounts(&self) -> Vec<(AssetType, i64)>; } /// Computes the signature hash for an input to a transaction, given From 1d5a496accbd06d7ac229d51bd7c1b228e617c0b Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Thu, 13 Apr 2023 15:19:23 +0200 Subject: [PATCH 08/21] Added more traits to MASP types. --- masp_primitives/src/sapling.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/masp_primitives/src/sapling.rs b/masp_primitives/src/sapling.rs index 312bd196..aa1df496 100644 --- a/masp_primitives/src/sapling.rs +++ b/masp_primitives/src/sapling.rs @@ -505,7 +505,7 @@ pub enum Rseed { } /// Typesafe wrapper for nullifier values. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, BorshSerialize, BorshDeserialize)] pub struct Nullifier(pub [u8; 32]); impl Nullifier { From 6c222793354c0b8e5c9dd87701f1b7662d441ee6 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Thu, 13 Apr 2023 18:25:17 +0200 Subject: [PATCH 09/21] Add convert descriptors to the transaction builder. --- masp_primitives/Cargo.toml | 3 --- masp_primitives/src/lib.rs | 1 - masp_primitives/src/transaction/builder.rs | 17 ++++++++++++++++- .../transaction/components/sapling/builder.rs | 2 +- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/masp_primitives/Cargo.toml b/masp_primitives/Cargo.toml index daec9491..31dd3c75 100644 --- a/masp_primitives/Cargo.toml +++ b/masp_primitives/Cargo.toml @@ -53,9 +53,6 @@ lazy_static = "1" # - Test dependencies proptest = { version = "1.0.0", optional = true } -# - Transparent inputs -secp256k1 = { version = "0.24.1", features = [ "rand" ] } - # - ZIP 339 bip0039 = { version = "0.9", features = ["std", "all-languages"] } diff --git a/masp_primitives/src/lib.rs b/masp_primitives/src/lib.rs index 8193d7bd..0f1acdd5 100644 --- a/masp_primitives/src/lib.rs +++ b/masp_primitives/src/lib.rs @@ -30,7 +30,6 @@ pub use group; pub use ff; pub use jubjub; pub use bls12_381; -pub use secp256k1; #[cfg(test)] mod test_vectors; diff --git a/masp_primitives/src/transaction/builder.rs b/masp_primitives/src/transaction/builder.rs index a2e1453e..5880afc6 100644 --- a/masp_primitives/src/transaction/builder.rs +++ b/masp_primitives/src/transaction/builder.rs @@ -10,6 +10,7 @@ use rand::{rngs::OsRng, CryptoRng, RngCore}; use crate::{ asset_type::AssetType, consensus::{self, BlockHeight, BranchId}, + convert::AllowedConversion, keys::OutgoingViewingKey, memo::MemoBytes, merkle_tree::MerklePath, @@ -217,6 +218,20 @@ impl Builder { .add_spend(&mut self.rng, extsk, diversifier, note, merkle_path) } + /// Adds a Sapling note to be spent in this transaction. + /// + /// Returns an error if the given Merkle path does not have the same anchor as the + /// paths for previous Sapling notes. + pub fn add_sapling_convert( + &mut self, + allowed: AllowedConversion, + value: u64, + merkle_path: MerklePath, + ) -> Result<(), sapling::builder::Error> { + self.sapling_builder + .add_convert(allowed, value, merkle_path) + } + /// Adds a Sapling address to send funds to. pub fn add_sapling_output( &mut self, @@ -268,7 +283,7 @@ impl Builder { } /// Returns the sum of the transparent, Sapling, and TZE value balances. - fn value_balance(&self) -> Result { + pub fn value_balance(&self) -> Result { let value_balances = [ self.transparent_builder.value_balance()?, self.sapling_builder.value_balance(), diff --git a/masp_primitives/src/transaction/components/sapling/builder.rs b/masp_primitives/src/transaction/components/sapling/builder.rs index 3e5ced86..97885604 100644 --- a/masp_primitives/src/transaction/components/sapling/builder.rs +++ b/masp_primitives/src/transaction/components/sapling/builder.rs @@ -331,7 +331,7 @@ impl SaplingBuilder

{ /// /// Returns an error if the given Merkle path does not have the same anchor as the /// paths for previous convert notes. - pub fn add_convert( + pub fn add_convert( &mut self, allowed: AllowedConversion, value: u64, From dd6ed399a0981fe68f681a6444a4341085164c30 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Sat, 15 Apr 2023 08:56:19 +0200 Subject: [PATCH 10/21] Moved transparent addresses out of the signature slot and into TxIn<_>. Txid now includes transparent addresses. --- .../src/transaction/components/transparent.rs | 31 +++++++++++-------- .../components/transparent/builder.rs | 21 +++---------- .../components/transparent/fees.rs | 2 +- masp_primitives/src/transaction/txid.rs | 8 +++-- 4 files changed, 29 insertions(+), 33 deletions(-) diff --git a/masp_primitives/src/transaction/components/transparent.rs b/masp_primitives/src/transaction/components/transparent.rs index cea7b6f8..ac449b26 100644 --- a/masp_primitives/src/transaction/components/transparent.rs +++ b/masp_primitives/src/transaction/components/transparent.rs @@ -20,7 +20,7 @@ pub trait Authorization: fmt::Debug { pub struct Authorized; impl Authorization for Authorized { - type TransparentSig = TransparentAddress; + type TransparentSig = (); } pub trait MapAuth { @@ -43,6 +43,7 @@ impl Bundle { .into_iter() .map(|txin| TxIn { asset_type: txin.asset_type, + address: txin.address, transparent_sig: f.map_script_sig(txin.transparent_sig), value: txin.value, }) @@ -96,6 +97,7 @@ impl Bundle { pub struct TxIn { pub asset_type: AssetType, pub value: i64, + pub address: TransparentAddress, pub transparent_sig: A::TransparentSig, } @@ -118,19 +120,19 @@ impl TxIn { "value out of range", )); } - let transparent_sig = { + let address = { let mut tmp = [0u8; 20]; reader.read_exact(&mut tmp)?; TransparentAddress(tmp) }; - Ok(TxIn { asset_type, value, transparent_sig }) + Ok(TxIn { asset_type, value, address, transparent_sig: () }) } pub fn write(&self, mut writer: W) -> io::Result<()> { writer.write_all(self.asset_type.get_identifier())?; writer.write_all(&self.value.to_le_bytes())?; - writer.write_all(&self.transparent_sig.0) + writer.write_all(&self.address.0) } } @@ -138,7 +140,7 @@ impl TxIn { pub struct TxOut { pub asset_type: AssetType, pub value: i64, - pub transparent_address: TransparentAddress, + pub address: TransparentAddress, } impl TxOut { @@ -161,25 +163,28 @@ impl TxOut { )); } - let mut tmp = [0u8; 20]; - reader.read_exact(&mut tmp)?; - let transparent_address = TransparentAddress(tmp); + let address = { + let mut tmp = [0u8; 20]; + reader.read_exact(&mut tmp)?; + TransparentAddress(tmp) + }; Ok(TxOut { asset_type, value, - transparent_address, + address, }) } pub fn write(&self, mut writer: W) -> io::Result<()> { writer.write_all(self.asset_type.get_identifier())?; writer.write_all(&self.value.to_le_bytes())?; - writer.write_all(&self.transparent_address.0) + writer.write_all(&self.address.0) } + /// Returns the address to which the TxOut was sent, if this is a valid P2SH or P2PKH output. pub fn recipient_address(&self) -> TransparentAddress { - self.transparent_address + self.address } } @@ -214,7 +219,7 @@ pub mod testing { prop_compose! { pub fn arb_txin()(amt in arb_nonnegative_amount(), addr in arb_transparent_address()) -> TxIn { let (asset_type, value) = amt.components().next().unwrap(); - TxIn { asset_type: *asset_type, value: *value, transparent_sig: addr } + TxIn { asset_type: *asset_type, value: *value, address: addr, transparent_sig: () } } } @@ -222,7 +227,7 @@ pub mod testing { pub fn arb_txout()(amt in arb_nonnegative_amount(), addr in arb_transparent_address()) -> TxOut { let (asset_type, value) = amt.components().next().unwrap(); - TxOut { asset_type: *asset_type, value: *value, transparent_address : addr } + TxOut { asset_type: *asset_type, value: *value, address : addr } } } diff --git a/masp_primitives/src/transaction/components/transparent/builder.rs b/masp_primitives/src/transaction/components/transparent/builder.rs index 4c53a013..2714b09c 100644 --- a/masp_primitives/src/transaction/components/transparent/builder.rs +++ b/masp_primitives/src/transaction/components/transparent/builder.rs @@ -126,7 +126,7 @@ impl TransparentBuilder { self.vout.push(TxOut { asset_type, value, - transparent_address: *to, + address: *to, }); Ok(()) @@ -175,6 +175,7 @@ impl TransparentBuilder { .map(|i| TxIn:: { asset_type: i.coin.asset_type, value: i.coin.value, + address: i.coin.address, transparent_sig: (), }) .collect(); @@ -217,27 +218,15 @@ impl TransparentAuthorizingContext for Unauthorized { impl Bundle { pub fn apply_signatures(self) -> Bundle { - #[cfg(feature = "transparent-inputs")] - let transparent_sigs = self - .authorization - .inputs - .iter() - .map(|info| { - info.coin.transparent_address - }); - - #[cfg(not(feature = "transparent-inputs"))] - let transparent_sigs = std::iter::empty::(); - transparent::Bundle { vin: self .vin .iter() - .zip(transparent_sigs) - .map(|(txin, sig)| TxIn { + .map(|txin| TxIn { asset_type: txin.asset_type, - transparent_sig: sig, + address: txin.address, value: txin.value, + transparent_sig: (), }) .collect(), vout: self.vout, diff --git a/masp_primitives/src/transaction/components/transparent/fees.rs b/masp_primitives/src/transaction/components/transparent/fees.rs index 812e8f0a..6157388d 100644 --- a/masp_primitives/src/transaction/components/transparent/fees.rs +++ b/masp_primitives/src/transaction/components/transparent/fees.rs @@ -26,6 +26,6 @@ impl OutputView for TxOut { } fn transparent_address(&self) -> &TransparentAddress { - &self.transparent_address + &self.address } } diff --git a/masp_primitives/src/transaction/txid.rs b/masp_primitives/src/transaction/txid.rs index b5a8f7b6..eae7e082 100644 --- a/masp_primitives/src/transaction/txid.rs +++ b/masp_primitives/src/transaction/txid.rs @@ -65,8 +65,10 @@ pub(crate) fn transparent_outputs_hash>(vout: &[T]) -> Blake2bH pub(crate) fn transparent_inputs_hash>>(vin: &[T]) -> Blake2bHash { let mut h = hasher(ZCASH_INPUTS_HASH_PERSONALIZATION); for t_in in vin { - h.write(t_in.borrow().asset_type.get_identifier()).unwrap(); - h.write(&t_in.borrow().value.to_le_bytes()).unwrap(); + let t_in = t_in.borrow(); + h.write(t_in.asset_type.get_identifier()).unwrap(); + h.write(&t_in.value.to_le_bytes()).unwrap(); + h.write(&t_in.address.0).unwrap(); } h.finalize() } @@ -333,7 +335,7 @@ impl TransactionDigest for BlockTxCommitmentDigester { for txout in &bundle.vout { h.write_all(txout.asset_type.get_identifier()).unwrap(); h.write_all(&txout.value.to_le_bytes()).unwrap(); - h.write_all(&txout.transparent_address.0).unwrap(); + h.write_all(&txout.address.0).unwrap(); } } h.finalize() From 6dd48e3e765d6098608ad5847667868cab9cf285 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Sat, 15 Apr 2023 15:13:34 +0200 Subject: [PATCH 11/21] Allow (de)serialization for builders in order to capture auxilliary inputs. Also allow builder to be used without spending key. --- masp_primitives/src/consensus.rs | 3 +- masp_primitives/src/sapling/keys.rs | 3 +- masp_primitives/src/transaction/builder.rs | 10 ++- .../transaction/components/sapling/builder.rs | 80 +++++++++++++++++-- .../components/transparent/builder.rs | 1 + 5 files changed, 86 insertions(+), 11 deletions(-) diff --git a/masp_primitives/src/consensus.rs b/masp_primitives/src/consensus.rs index 75fc8b77..7a889541 100644 --- a/masp_primitives/src/consensus.rs +++ b/masp_primitives/src/consensus.rs @@ -5,11 +5,12 @@ use std::cmp::{Ord, Ordering}; use std::convert::TryFrom; use std::fmt; use std::ops::{Add, Bound, RangeBounds, Sub}; +use borsh::{BorshSerialize, BorshDeserialize}; /// A wrapper type representing blockchain heights. Safe conversion from /// various integer types, as well as addition and subtraction, are provided. #[repr(transparent)] -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize)] pub struct BlockHeight(u32); memuse::impl_no_dynamic_usage!(BlockHeight); diff --git a/masp_primitives/src/sapling/keys.rs b/masp_primitives/src/sapling/keys.rs index cb265ee1..45ddaf7d 100644 --- a/masp_primitives/src/sapling/keys.rs +++ b/masp_primitives/src/sapling/keys.rs @@ -17,6 +17,7 @@ use std::{ str::FromStr, }; use subtle::CtOption; +use borsh::{BorshSerialize, BorshDeserialize}; use super::{NullifierDerivingKey, ProofGenerationKey, ViewingKey}; @@ -31,7 +32,7 @@ pub enum DecodingError { } /// An outgoing viewing key -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize)] pub struct OutgoingViewingKey(pub [u8; 32]); /// A Sapling expanded spending key diff --git a/masp_primitives/src/transaction/builder.rs b/masp_primitives/src/transaction/builder.rs index 5880afc6..36c2e7fe 100644 --- a/masp_primitives/src/transaction/builder.rs +++ b/masp_primitives/src/transaction/builder.rs @@ -5,6 +5,8 @@ use std::error; use std::fmt; use std::sync::mpsc::Sender; +use borsh::{BorshSerialize, BorshDeserialize}; + use rand::{rngs::OsRng, CryptoRng, RngCore}; use crate::{ @@ -114,14 +116,16 @@ impl Progress { } /// Generates a [`Transaction`] from its inputs and outputs. -pub struct Builder { +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] +pub struct Builder> { params: P, rng: R, target_height: BlockHeight, expiry_height: BlockHeight, transparent_builder: TransparentBuilder, - sapling_builder: SaplingBuilder

, - progress_notifier: Option>, + sapling_builder: SaplingBuilder, + #[borsh_skip] + progress_notifier: Option, } impl Builder { diff --git a/masp_primitives/src/transaction/components/sapling/builder.rs b/masp_primitives/src/transaction/components/sapling/builder.rs index 97885604..5ade2b2e 100644 --- a/masp_primitives/src/transaction/components/sapling/builder.rs +++ b/masp_primitives/src/transaction/components/sapling/builder.rs @@ -35,6 +35,7 @@ use crate::{ zip32::ExtendedSpendingKey, }; use borsh::{BorshDeserialize, BorshSerialize}; +use borsh::maybestd::io::Write; /// If there are any shielded inputs, always have at least two shielded outputs, padding /// with dummy outputs if necessary. See . @@ -66,14 +67,36 @@ impl fmt::Display for Error { } #[derive(Debug, Clone, PartialEq)] -pub struct SpendDescriptionInfo { - extsk: ExtendedSpendingKey, +pub struct SpendDescriptionInfo { + extsk: Key, diversifier: Diversifier, note: Note, alpha: jubjub::Fr, merkle_path: MerklePath, } +impl BorshSerialize for SpendDescriptionInfo { + fn serialize(&self, writer: &mut W) -> borsh::maybestd::io::Result<()> { + self.extsk.serialize(writer)?; + self.diversifier.serialize(writer)?; + self.note.serialize(writer)?; + self.alpha.to_bytes().serialize(writer)?; + self.merkle_path.serialize(writer) + } +} + +impl BorshDeserialize for SpendDescriptionInfo { + fn deserialize(buf: &mut &[u8]) -> borsh::maybestd::io::Result { + let extsk = Key::deserialize(buf)?; + let diversifier = Diversifier::deserialize(buf)?; + let note = Note::deserialize(buf)?; + let alpha: Option<_> = jubjub::Fr::from_bytes(&<[u8; 32]>::deserialize(buf)?).into(); + let alpha = alpha.ok_or_else(|| std::io::Error::from(std::io::ErrorKind::InvalidData))?; + let merkle_path = MerklePath::::deserialize(buf)?; + Ok(SpendDescriptionInfo { extsk, diversifier, note, alpha, merkle_path }) + } +} + impl fees::InputView<()> for SpendDescriptionInfo { fn note_id(&self) -> &() { // The builder does not make use of note identifiers, so we can just return the unit value. @@ -89,7 +112,7 @@ impl fees::InputView<()> for SpendDescriptionInfo { /// A struct containing the information required in order to construct a /// MASP output to a transaction. -#[derive(Clone)] +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] pub struct SaplingOutputInfo { /// `None` represents the `ovk = ⊥` case. ovk: Option, @@ -226,17 +249,62 @@ impl SaplingMetadata { } } -pub struct SaplingBuilder

{ +#[derive(Clone, Debug)] +pub struct SaplingBuilder { params: P, spend_anchor: Option, target_height: BlockHeight, value_balance: Amount, convert_anchor: Option, - spends: Vec, + spends: Vec>, converts: Vec, outputs: Vec, } +impl BorshSerialize for SaplingBuilder { + fn serialize(&self, writer: &mut W) -> borsh::maybestd::io::Result<()> { + self.params.serialize(writer)?; + self.spend_anchor.map(|x| x.to_bytes()).serialize(writer)?; + self.target_height.serialize(writer)?; + self.value_balance.serialize(writer)?; + self.convert_anchor.map(|x| x.to_bytes()).serialize(writer)?; + self.spends.serialize(writer)?; + self.converts.serialize(writer)?; + self.outputs.serialize(writer) + } +} + +impl BorshDeserialize for SaplingBuilder { + fn deserialize(buf: &mut &[u8]) -> borsh::maybestd::io::Result { + let params = P::deserialize(buf)?; + let spend_anchor: Option> = Option::<[u8; 32]>::deserialize(buf)? + .map(|x| bls12_381::Scalar::from_bytes(&x).into()); + let spend_anchor = spend_anchor + .map(|x| x.ok_or_else(|| std::io::Error::from(std::io::ErrorKind::InvalidData))) + .transpose()?; + let target_height = BlockHeight::deserialize(buf)?; + let value_balance: Amount = Amount::deserialize(buf)?; + let convert_anchor: Option> = Option::<[u8; 32]>::deserialize(buf)? + .map(|x| bls12_381::Scalar::from_bytes(&x).into()); + let convert_anchor = convert_anchor + .map(|x| x.ok_or_else(|| std::io::Error::from(std::io::ErrorKind::InvalidData))) + .transpose()?; + let spends = Vec::>::deserialize(buf)?; + let converts = Vec::::deserialize(buf)?; + let outputs = Vec::::deserialize(buf)?; + Ok(SaplingBuilder { + params, + spend_anchor, + target_height, + value_balance, + convert_anchor, + spends, + converts, + outputs, + }) + } +} + #[derive(Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub struct Unauthorized { tx_metadata: SaplingMetadata, @@ -695,7 +763,7 @@ impl Bundle { /// A struct containing the information required in order to construct a /// MASP conversion in a transaction. -#[derive(Clone)] +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] pub struct ConvertDescriptionInfo { allowed: AllowedConversion, value: u64, diff --git a/masp_primitives/src/transaction/components/transparent/builder.rs b/masp_primitives/src/transaction/components/transparent/builder.rs index 2714b09c..6c0cc642 100644 --- a/masp_primitives/src/transaction/components/transparent/builder.rs +++ b/masp_primitives/src/transaction/components/transparent/builder.rs @@ -57,6 +57,7 @@ impl fees::InputView for TransparentInputInfo { } } +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] pub struct TransparentBuilder { #[cfg(feature = "transparent-inputs")] inputs: Vec, From 1fde652c31a08d6303045a0f3e6e2486b1081e79 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Sat, 15 Apr 2023 20:03:05 +0200 Subject: [PATCH 12/21] Fixed clippy issues. --- masp_primitives/src/consensus.rs | 2 +- masp_primitives/src/lib.rs | 4 +-- masp_primitives/src/sapling.rs | 4 ++- masp_primitives/src/sapling/keys.rs | 2 +- masp_primitives/src/transaction.rs | 7 +++--- masp_primitives/src/transaction/builder.rs | 17 +++++-------- masp_primitives/src/transaction/components.rs | 2 +- .../transaction/components/sapling/builder.rs | 25 ++++++++++++------- .../src/transaction/components/transparent.rs | 9 +++++-- masp_primitives/src/transaction/txid.rs | 13 +++++++--- masp_proofs/src/lib.rs | 4 +-- 11 files changed, 52 insertions(+), 37 deletions(-) diff --git a/masp_primitives/src/consensus.rs b/masp_primitives/src/consensus.rs index 7a889541..1be8a9cb 100644 --- a/masp_primitives/src/consensus.rs +++ b/masp_primitives/src/consensus.rs @@ -1,11 +1,11 @@ //! Consensus logic and parameters. +use borsh::{BorshDeserialize, BorshSerialize}; use memuse::DynamicUsage; use std::cmp::{Ord, Ordering}; use std::convert::TryFrom; use std::fmt; use std::ops::{Add, Bound, RangeBounds, Sub}; -use borsh::{BorshSerialize, BorshDeserialize}; /// A wrapper type representing blockchain heights. Safe conversion from /// various integer types, as well as addition and subtraction, are provided. diff --git a/masp_primitives/src/lib.rs b/masp_primitives/src/lib.rs index 0f1acdd5..d1b2d6d2 100644 --- a/masp_primitives/src/lib.rs +++ b/masp_primitives/src/lib.rs @@ -26,10 +26,10 @@ pub mod sapling; pub mod transaction; pub mod zip32; -pub use group; +pub use bls12_381; pub use ff; +pub use group; pub use jubjub; -pub use bls12_381; #[cfg(test)] mod test_vectors; diff --git a/masp_primitives/src/sapling.rs b/masp_primitives/src/sapling.rs index aa1df496..05b74f97 100644 --- a/masp_primitives/src/sapling.rs +++ b/masp_primitives/src/sapling.rs @@ -505,7 +505,9 @@ pub enum Rseed { } /// Typesafe wrapper for nullifier values. -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, BorshSerialize, BorshDeserialize)] +#[derive( + Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, BorshSerialize, BorshDeserialize, +)] pub struct Nullifier(pub [u8; 32]); impl Nullifier { diff --git a/masp_primitives/src/sapling/keys.rs b/masp_primitives/src/sapling/keys.rs index 45ddaf7d..c73dbe9b 100644 --- a/masp_primitives/src/sapling/keys.rs +++ b/masp_primitives/src/sapling/keys.rs @@ -8,6 +8,7 @@ use crate::{ constants::{PROOF_GENERATION_KEY_GENERATOR, SPENDING_KEY_GENERATOR}, keys::prf_expand, }; +use borsh::{BorshDeserialize, BorshSerialize}; use ff::PrimeField; use group::{Group, GroupEncoding}; use std::{ @@ -17,7 +18,6 @@ use std::{ str::FromStr, }; use subtle::CtOption; -use borsh::{BorshSerialize, BorshDeserialize}; use super::{NullifierDerivingKey, ProofGenerationKey, ViewingKey}; diff --git a/masp_primitives/src/transaction.rs b/masp_primitives/src/transaction.rs index 821c9d67..3938ee30 100644 --- a/masp_primitives/src/transaction.rs +++ b/masp_primitives/src/transaction.rs @@ -297,10 +297,11 @@ impl BorshDeserialize for Transaction { impl borsh::BorshSchema for Transaction { fn add_definitions_recursively( _definitions: &mut std::collections::HashMap< - borsh::schema::Declaration, + borsh::schema::Declaration, borsh::schema::Definition, - >, - ) {} + >, + ) { + } fn declaration() -> borsh::schema::Declaration { "Transaction".into() diff --git a/masp_primitives/src/transaction/builder.rs b/masp_primitives/src/transaction/builder.rs index 36c2e7fe..0702b94b 100644 --- a/masp_primitives/src/transaction/builder.rs +++ b/masp_primitives/src/transaction/builder.rs @@ -5,7 +5,7 @@ use std::error; use std::fmt; use std::sync::mpsc::Sender; -use borsh::{BorshSerialize, BorshDeserialize}; +use borsh::{BorshDeserialize, BorshSerialize}; use rand::{rngs::OsRng, CryptoRng, RngCore}; @@ -29,7 +29,7 @@ use crate::{ fees::FeeRule, sighash::{signature_hash, SignableInput}, txid::TxIdDigester, - Transaction, TransactionData, TxVersion, Unauthorized, TransparentAddress, + Transaction, TransactionData, TransparentAddress, TxVersion, Unauthorized, }, zip32::ExtendedSpendingKey, }; @@ -440,8 +440,8 @@ mod testing { #[cfg(test)] mod tests { use ff::Field; - use rand_core::OsRng; use rand::Rng; + use rand_core::OsRng; use crate::{ asset_type::AssetType, @@ -492,7 +492,7 @@ mod tests { #[test] fn binding_sig_present_if_shielded_spend() { let mut rng = OsRng; - + let transparent_address = TransparentAddress(rng.gen::<[u8; 20]>()); let extsk = ExtendedSpendingKey::master(&[]); @@ -538,7 +538,7 @@ mod tests { #[test] fn fails_on_negative_transparent_output() { let mut rng = OsRng; - + let transparent_address = TransparentAddress(rng.gen::<[u8; 20]>()); let tx_height = TEST_NETWORK .activation_height(NetworkUpgrade::MASP) @@ -622,12 +622,7 @@ mod tests { { let mut builder = Builder::new(TEST_NETWORK, tx_height); builder - .add_sapling_spend( - extsk, - *to.diversifier(), - note1.clone(), - witness1.path().unwrap(), - ) + .add_sapling_spend(extsk, *to.diversifier(), note1, witness1.path().unwrap()) .unwrap(); builder .add_sapling_output(ovk, to, zec(), 30000, MemoBytes::empty()) diff --git a/masp_primitives/src/transaction/components.rs b/masp_primitives/src/transaction/components.rs index 8c3604c9..db07c9a5 100644 --- a/masp_primitives/src/transaction/components.rs +++ b/masp_primitives/src/transaction/components.rs @@ -5,7 +5,7 @@ pub mod sapling; pub mod transparent; pub use self::{ amount::Amount, - sapling::{OutputDescription, SpendDescription, ConvertDescription}, + sapling::{ConvertDescription, OutputDescription, SpendDescription}, transparent::{TxIn, TxOut}, }; diff --git a/masp_primitives/src/transaction/components/sapling/builder.rs b/masp_primitives/src/transaction/components/sapling/builder.rs index 5ade2b2e..37bb2455 100644 --- a/masp_primitives/src/transaction/components/sapling/builder.rs +++ b/masp_primitives/src/transaction/components/sapling/builder.rs @@ -34,8 +34,8 @@ use crate::{ }, zip32::ExtendedSpendingKey, }; -use borsh::{BorshDeserialize, BorshSerialize}; use borsh::maybestd::io::Write; +use borsh::{BorshDeserialize, BorshSerialize}; /// If there are any shielded inputs, always have at least two shielded outputs, padding /// with dummy outputs if necessary. See . @@ -93,7 +93,13 @@ impl BorshDeserialize for SpendDescriptionInfo { let alpha: Option<_> = jubjub::Fr::from_bytes(&<[u8; 32]>::deserialize(buf)?).into(); let alpha = alpha.ok_or_else(|| std::io::Error::from(std::io::ErrorKind::InvalidData))?; let merkle_path = MerklePath::::deserialize(buf)?; - Ok(SpendDescriptionInfo { extsk, diversifier, note, alpha, merkle_path }) + Ok(SpendDescriptionInfo { + extsk, + diversifier, + note, + alpha, + merkle_path, + }) } } @@ -162,8 +168,7 @@ impl SaplingOutputInfo { ctx: &mut Pr::SaplingProvingContext, rng: &mut R, ) -> OutputDescription { - let encryptor = - sapling_note_encryption::

(self.ovk, self.note.clone(), self.to, self.memo); + let encryptor = sapling_note_encryption::

(self.ovk, self.note, self.to, self.memo); let (zkproof, cv) = prover.output_proof( ctx, @@ -267,7 +272,9 @@ impl BorshSerialize for SaplingBuilder

BorshSerialize for SaplingBuilder

BorshDeserialize for SaplingBuilder { fn deserialize(buf: &mut &[u8]) -> borsh::maybestd::io::Result { let params = P::deserialize(buf)?; - let spend_anchor: Option> = Option::<[u8; 32]>::deserialize(buf)? - .map(|x| bls12_381::Scalar::from_bytes(&x).into()); + let spend_anchor: Option> = + Option::<[u8; 32]>::deserialize(buf)?.map(|x| bls12_381::Scalar::from_bytes(&x).into()); let spend_anchor = spend_anchor .map(|x| x.ok_or_else(|| std::io::Error::from(std::io::ErrorKind::InvalidData))) .transpose()?; let target_height = BlockHeight::deserialize(buf)?; let value_balance: Amount = Amount::deserialize(buf)?; - let convert_anchor: Option> = Option::<[u8; 32]>::deserialize(buf)? - .map(|x| bls12_381::Scalar::from_bytes(&x).into()); + let convert_anchor: Option> = + Option::<[u8; 32]>::deserialize(buf)?.map(|x| bls12_381::Scalar::from_bytes(&x).into()); let convert_anchor = convert_anchor .map(|x| x.ok_or_else(|| std::io::Error::from(std::io::ErrorKind::InvalidData))) .transpose()?; diff --git a/masp_primitives/src/transaction/components/transparent.rs b/masp_primitives/src/transaction/components/transparent.rs index ac449b26..06efcaee 100644 --- a/masp_primitives/src/transaction/components/transparent.rs +++ b/masp_primitives/src/transaction/components/transparent.rs @@ -126,7 +126,12 @@ impl TxIn { TransparentAddress(tmp) }; - Ok(TxIn { asset_type, value, address, transparent_sig: () }) + Ok(TxIn { + asset_type, + value, + address, + transparent_sig: (), + }) } pub fn write(&self, mut writer: W) -> io::Result<()> { @@ -181,7 +186,7 @@ impl TxOut { writer.write_all(&self.value.to_le_bytes())?; writer.write_all(&self.address.0) } - + /// Returns the address to which the TxOut was sent, if this is a valid P2SH or P2PKH output. pub fn recipient_address(&self) -> TransparentAddress { self.address diff --git a/masp_primitives/src/transaction/txid.rs b/masp_primitives/src/transaction/txid.rs index eae7e082..2bb14780 100644 --- a/masp_primitives/src/transaction/txid.rs +++ b/masp_primitives/src/transaction/txid.rs @@ -62,13 +62,18 @@ pub(crate) fn transparent_outputs_hash>(vout: &[T]) -> Blake2bH /// to a hash personalized by ZCASH_INPUTS_HASH_PERSONALIZATION. /// In the case that no inputs are provided, this produces a default /// hash from just the personalization string. -pub(crate) fn transparent_inputs_hash>>(vin: &[T]) -> Blake2bHash { +pub(crate) fn transparent_inputs_hash< + TransparentAuth: transparent::Authorization, + T: Borrow>, +>( + vin: &[T], +) -> Blake2bHash { let mut h = hasher(ZCASH_INPUTS_HASH_PERSONALIZATION); for t_in in vin { let t_in = t_in.borrow(); - h.write(t_in.asset_type.get_identifier()).unwrap(); - h.write(&t_in.value.to_le_bytes()).unwrap(); - h.write(&t_in.address.0).unwrap(); + h.write_all(t_in.asset_type.get_identifier()).unwrap(); + h.write_all(&t_in.value.to_le_bytes()).unwrap(); + h.write_all(&t_in.address.0).unwrap(); } h.finalize() } diff --git a/masp_proofs/src/lib.rs b/masp_proofs/src/lib.rs index 92ae4934..f5789821 100644 --- a/masp_proofs/src/lib.rs +++ b/masp_proofs/src/lib.rs @@ -15,10 +15,10 @@ use std::fs::File; use std::io::{self, BufReader}; use std::path::Path; -pub use group; pub use bellman; -pub use jubjub; pub use bls12_381; +pub use group; +pub use jubjub; #[cfg(feature = "directories")] use directories::BaseDirs; From 9678f3bd0853f54f9f591bec71235c17f480326a Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Sun, 16 Apr 2023 11:22:22 +0200 Subject: [PATCH 13/21] Made function to map between Builder types. --- masp_primitives/src/transaction/builder.rs | 24 ++++++++++++++ .../transaction/components/sapling/builder.rs | 33 +++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/masp_primitives/src/transaction/builder.rs b/masp_primitives/src/transaction/builder.rs index 0702b94b..dcdd2946 100644 --- a/masp_primitives/src/transaction/builder.rs +++ b/masp_primitives/src/transaction/builder.rs @@ -405,6 +405,30 @@ impl Builder { } } +pub trait MapBuilder: + sapling::builder::MapBuilder +{ + fn map_rng(&self, s: R1) -> R2; + fn map_notifier(&self, s: N1) -> N2; +} + +impl Builder { + pub fn map_builder>( + self, + f: F, + ) -> Builder { + Builder:: { + params: f.map_params(self.params), + rng: f.map_rng(self.rng), + target_height: self.target_height, + expiry_height: self.expiry_height, + transparent_builder: self.transparent_builder, + progress_notifier: self.progress_notifier.map(|x| f.map_notifier(x)), + sapling_builder: self.sapling_builder.map_builder(f), + } + } +} + #[cfg(any(test, feature = "test-dependencies"))] mod testing { use rand::RngCore; diff --git a/masp_primitives/src/transaction/components/sapling/builder.rs b/masp_primitives/src/transaction/components/sapling/builder.rs index 37bb2455..0bee7b43 100644 --- a/masp_primitives/src/transaction/components/sapling/builder.rs +++ b/masp_primitives/src/transaction/components/sapling/builder.rs @@ -777,6 +777,39 @@ pub struct ConvertDescriptionInfo { merkle_path: MerklePath, } +pub trait MapBuilder { + fn map_params(&self, s: P1) -> P2; + fn map_key(&self, s: K1) -> K2; +} + +impl SaplingBuilder { + pub fn map_builder>( + self, + f: F, + ) -> SaplingBuilder { + SaplingBuilder:: { + params: f.map_params(self.params), + spend_anchor: self.spend_anchor, + target_height: self.target_height, + value_balance: self.value_balance, + convert_anchor: self.convert_anchor, + converts: self.converts, + outputs: self.outputs, + spends: self + .spends + .into_iter() + .map(|x| SpendDescriptionInfo { + extsk: f.map_key(x.extsk), + diversifier: x.diversifier, + note: x.note, + alpha: x.alpha, + merkle_path: x.merkle_path, + }) + .collect(), + } + } +} + #[cfg(any(test, feature = "test-dependencies"))] pub mod testing { use proptest::collection::vec; From 0e2e640da2f6179d80f42832d5fd5cb17b14e67e Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Sun, 16 Apr 2023 15:59:20 +0200 Subject: [PATCH 14/21] Fixed bug in the deserialization of MerklePaths. --- masp_primitives/src/merkle_tree.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/masp_primitives/src/merkle_tree.rs b/masp_primitives/src/merkle_tree.rs index fd1f6801..26ae6635 100644 --- a/masp_primitives/src/merkle_tree.rs +++ b/masp_primitives/src/merkle_tree.rs @@ -771,7 +771,10 @@ impl BorshDeserialize for MerklePath { // Begin to construct the authentication path // Do not use any data in the witness after the expected depth let iter = witness[..33 * depth + 8].chunks_exact(33); - *witness = iter.remainder(); + // Update the witness to its final position + *witness = &witness[33 * depth + 8..]; + // Read the position from the witness + let position = iter.remainder().read_u64::()?; // The vector works in reverse let mut auth_path = iter @@ -798,9 +801,6 @@ impl BorshDeserialize for MerklePath { return Err(std::io::Error::from(std::io::ErrorKind::InvalidData)); } - // Read the position from the witness - let position = witness.read_u64::()?; - // Given the position, let's finish constructing the authentication // path let mut tmp = position; From 163a74d11d3c4f24fbc9f4a139cebcbca70c3415 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Tue, 18 Apr 2023 09:24:47 +0200 Subject: [PATCH 15/21] Increased the generality of Builder struct implementations. Made Sapling Builder views stricter. --- masp_primitives/src/transaction/builder.rs | 2 +- .../transaction/components/sapling/builder.rs | 23 +++++++++++-------- .../transaction/components/sapling/fees.rs | 10 +++++--- .../components/transparent/fees.rs | 15 ++++++++---- 4 files changed, 33 insertions(+), 17 deletions(-) diff --git a/masp_primitives/src/transaction/builder.rs b/masp_primitives/src/transaction/builder.rs index dcdd2946..993f5ddf 100644 --- a/masp_primitives/src/transaction/builder.rs +++ b/masp_primitives/src/transaction/builder.rs @@ -128,7 +128,7 @@ pub struct Builder> progress_notifier: Option, } -impl Builder { +impl Builder { /// Returns the network parameters that the builder has been configured for. pub fn params(&self) -> &P { &self.params diff --git a/masp_primitives/src/transaction/components/sapling/builder.rs b/masp_primitives/src/transaction/components/sapling/builder.rs index 0bee7b43..2881994f 100644 --- a/masp_primitives/src/transaction/components/sapling/builder.rs +++ b/masp_primitives/src/transaction/components/sapling/builder.rs @@ -103,16 +103,18 @@ impl BorshDeserialize for SpendDescriptionInfo { } } -impl fees::InputView<()> for SpendDescriptionInfo { +impl fees::InputView<()> for SpendDescriptionInfo { fn note_id(&self) -> &() { // The builder does not make use of note identifiers, so we can just return the unit value. &() } - fn value(&self) -> Amount { - // An existing note to be spent must have a valid amount value. - Amount::from_pair(self.note.asset_type, self.note.value) - .expect("Existing note value with invalid asset type or value.") + fn value(&self) -> u64 { + self.note.value + } + + fn asset_type(&self) -> AssetType { + self.note.asset_type } } @@ -198,9 +200,12 @@ impl SaplingOutputInfo { } impl fees::OutputView for SaplingOutputInfo { - fn value(&self) -> Amount { - Amount::from_pair(self.note.asset_type, self.note.value) - .expect("Note values should be checked at construction.") + fn value(&self) -> u64 { + self.note.value + } + + fn asset_type(&self) -> AssetType { + self.note.asset_type } } @@ -328,7 +333,7 @@ impl Authorization for Unauthorized { type AuthSig = SpendDescriptionInfo; } -impl

SaplingBuilder

{ +impl SaplingBuilder { pub fn new(params: P, target_height: BlockHeight) -> Self { SaplingBuilder { params, diff --git a/masp_primitives/src/transaction/components/sapling/fees.rs b/masp_primitives/src/transaction/components/sapling/fees.rs index 10d72adb..5bd01d03 100644 --- a/masp_primitives/src/transaction/components/sapling/fees.rs +++ b/masp_primitives/src/transaction/components/sapling/fees.rs @@ -1,7 +1,7 @@ //! Types related to computation of fees and change related to the Sapling components //! of a transaction. -use crate::transaction::components::amount::Amount; +use crate::asset_type::AssetType; /// A trait that provides a minimized view of a Sapling input suitable for use in /// fee and change calculation. @@ -9,12 +9,16 @@ pub trait InputView { /// An identifier for the input being spent. fn note_id(&self) -> &NoteRef; /// The value of the input being spent. - fn value(&self) -> Amount; + fn value(&self) -> u64; + /// The asset type of the input being spent. + fn asset_type(&self) -> AssetType; } /// A trait that provides a minimized view of a Sapling output suitable for use in /// fee and change calculation. pub trait OutputView { /// The value of the output being produced. - fn value(&self) -> Amount; + fn value(&self) -> u64; + /// The asset type of the output being produced. + fn asset_type(&self) -> AssetType; } diff --git a/masp_primitives/src/transaction/components/transparent/fees.rs b/masp_primitives/src/transaction/components/transparent/fees.rs index 6157388d..94836f05 100644 --- a/masp_primitives/src/transaction/components/transparent/fees.rs +++ b/masp_primitives/src/transaction/components/transparent/fees.rs @@ -2,7 +2,8 @@ //! of a transaction. use super::TxOut; -use crate::transaction::{components::amount::Amount, TransparentAddress}; +use crate::transaction::TransparentAddress; +use crate::asset_type::AssetType; /// This trait provides a minimized view of a transparent input suitable for use in /// fee and change computation. @@ -15,14 +16,20 @@ pub trait InputView { /// fee and change computation. pub trait OutputView { /// Returns the value of the output being created. - fn value(&self) -> Amount; + fn value(&self) -> i64; + /// Returns the asset type of the output being created. + fn asset_type(&self) -> AssetType; /// Returns the script corresponding to the newly created output. fn transparent_address(&self) -> &TransparentAddress; } impl OutputView for TxOut { - fn value(&self) -> Amount { - Amount::from_pair(self.asset_type, self.value).unwrap() + fn value(&self) -> i64 { + self.value + } + + fn asset_type(&self) -> AssetType { + self.asset_type } fn transparent_address(&self) -> &TransparentAddress { From 6b19a514938fa01a32b8bfd789f1f75165f2cab3 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Tue, 18 Apr 2023 09:55:34 +0200 Subject: [PATCH 16/21] Exposed some detail about where assets are coming from and where they are going. --- masp_primitives/src/transaction/builder.rs | 2 +- .../src/transaction/components/sapling/builder.rs | 12 ++++++++++-- .../src/transaction/components/sapling/fees.rs | 7 ++++++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/masp_primitives/src/transaction/builder.rs b/masp_primitives/src/transaction/builder.rs index 993f5ddf..ed3c05ce 100644 --- a/masp_primitives/src/transaction/builder.rs +++ b/masp_primitives/src/transaction/builder.rs @@ -153,7 +153,7 @@ impl Builder { /// Returns the set of Sapling inputs currently committed to be consumed /// by the transaction. - pub fn sapling_inputs(&self) -> &[impl sapling::fees::InputView<()>] { + pub fn sapling_inputs(&self) -> &[impl sapling::fees::InputView<(), K>] { self.sapling_builder.inputs() } diff --git a/masp_primitives/src/transaction/components/sapling/builder.rs b/masp_primitives/src/transaction/components/sapling/builder.rs index 2881994f..f51b8c51 100644 --- a/masp_primitives/src/transaction/components/sapling/builder.rs +++ b/masp_primitives/src/transaction/components/sapling/builder.rs @@ -103,7 +103,7 @@ impl BorshDeserialize for SpendDescriptionInfo { } } -impl fees::InputView<()> for SpendDescriptionInfo { +impl fees::InputView<(), K> for SpendDescriptionInfo { fn note_id(&self) -> &() { // The builder does not make use of note identifiers, so we can just return the unit value. &() @@ -116,6 +116,10 @@ impl fees::InputView<()> for SpendDescriptionInfo { fn asset_type(&self) -> AssetType { self.note.asset_type } + + fn key(&self) -> &K { + &self.extsk + } } /// A struct containing the information required in order to construct a @@ -207,6 +211,10 @@ impl fees::OutputView for SaplingOutputInfo { fn asset_type(&self) -> AssetType { self.note.asset_type } + + fn address(&self) -> PaymentAddress { + self.to + } } /// Metadata about a transaction created by a [`SaplingBuilder`]. @@ -349,7 +357,7 @@ impl SaplingBuilder { /// Returns the list of Sapling inputs that will be consumed by the transaction being /// constructed. - pub fn inputs(&self) -> &[impl fees::InputView<()>] { + pub fn inputs(&self) -> &[impl fees::InputView<(), K>] { &self.spends } diff --git a/masp_primitives/src/transaction/components/sapling/fees.rs b/masp_primitives/src/transaction/components/sapling/fees.rs index 5bd01d03..81eea72b 100644 --- a/masp_primitives/src/transaction/components/sapling/fees.rs +++ b/masp_primitives/src/transaction/components/sapling/fees.rs @@ -2,16 +2,19 @@ //! of a transaction. use crate::asset_type::AssetType; +use crate::sapling::PaymentAddress; /// A trait that provides a minimized view of a Sapling input suitable for use in /// fee and change calculation. -pub trait InputView { +pub trait InputView { /// An identifier for the input being spent. fn note_id(&self) -> &NoteRef; /// The value of the input being spent. fn value(&self) -> u64; /// The asset type of the input being spent. fn asset_type(&self) -> AssetType; + /// The spend/view key of the input being spent. + fn key(&self) -> &Key; } /// A trait that provides a minimized view of a Sapling output suitable for use in @@ -21,4 +24,6 @@ pub trait OutputView { fn value(&self) -> u64; /// The asset type of the output being produced. fn asset_type(&self) -> AssetType; + /// The destination of this output + fn address(&self) -> PaymentAddress; } From cfea8c95d3f73077ca3e25380fd27e5b46e828fd Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Wed, 19 Apr 2023 10:15:53 +0200 Subject: [PATCH 17/21] Added functions to expose the convert descriptions inside a Builder. --- masp_primitives/src/transaction/builder.rs | 6 ++++++ .../src/transaction/components/sapling/builder.rs | 12 +++++++++++- .../src/transaction/components/sapling/fees.rs | 10 ++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/masp_primitives/src/transaction/builder.rs b/masp_primitives/src/transaction/builder.rs index ed3c05ce..63681b3b 100644 --- a/masp_primitives/src/transaction/builder.rs +++ b/masp_primitives/src/transaction/builder.rs @@ -162,6 +162,12 @@ impl Builder { pub fn sapling_outputs(&self) -> &[impl sapling::fees::OutputView] { self.sapling_builder.outputs() } + + /// Returns the set of Sapling converts currently set to be produced by + /// the transaction. + pub fn sapling_converts(&self) -> &[impl sapling::fees::ConvertView] { + self.sapling_builder.converts() + } } impl Builder { diff --git a/masp_primitives/src/transaction/components/sapling/builder.rs b/masp_primitives/src/transaction/components/sapling/builder.rs index f51b8c51..381295f0 100644 --- a/masp_primitives/src/transaction/components/sapling/builder.rs +++ b/masp_primitives/src/transaction/components/sapling/builder.rs @@ -361,7 +361,7 @@ impl SaplingBuilder { &self.spends } - pub fn converts(&self) -> &[ConvertDescriptionInfo] { + pub fn converts(&self) -> &[impl fees::ConvertView] { &self.converts } /// Returns the Sapling outputs that will be produced by the transaction being constructed @@ -790,6 +790,16 @@ pub struct ConvertDescriptionInfo { merkle_path: MerklePath, } +impl fees::ConvertView for ConvertDescriptionInfo { + fn value(&self) -> u64 { + self.value + } + + fn conversion(&self) -> &AllowedConversion { + &self.allowed + } +} + pub trait MapBuilder { fn map_params(&self, s: P1) -> P2; fn map_key(&self, s: K1) -> K2; diff --git a/masp_primitives/src/transaction/components/sapling/fees.rs b/masp_primitives/src/transaction/components/sapling/fees.rs index 81eea72b..c43395cb 100644 --- a/masp_primitives/src/transaction/components/sapling/fees.rs +++ b/masp_primitives/src/transaction/components/sapling/fees.rs @@ -3,6 +3,7 @@ use crate::asset_type::AssetType; use crate::sapling::PaymentAddress; +use crate::convert::AllowedConversion; /// A trait that provides a minimized view of a Sapling input suitable for use in /// fee and change calculation. @@ -17,6 +18,15 @@ pub trait InputView { fn key(&self) -> &Key; } +/// A trait that provides a minimized view of a Sapling conversion suitable for use in +/// fee and change calculation. +pub trait ConvertView { + /// The amount of the conversion being used. + fn value(&self) -> u64; + /// The allowed conversion being used. + fn conversion(&self) -> &AllowedConversion; +} + /// A trait that provides a minimized view of a Sapling output suitable for use in /// fee and change calculation. pub trait OutputView { From a53d5d458cf27fc547dd8593dc6d039e8b8a7a3c Mon Sep 17 00:00:00 2001 From: joe <55120843+joebebel@users.noreply.github.com> Date: Thu, 20 Jul 2023 20:18:17 -0700 Subject: [PATCH 18/21] fix convertdescription hashing and outputdescription hashing (#47) --- .../transaction/components/sapling/fees.rs | 2 +- .../components/transparent/fees.rs | 2 +- masp_primitives/src/transaction/txid.rs | 42 ++++++++++++++++--- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/masp_primitives/src/transaction/components/sapling/fees.rs b/masp_primitives/src/transaction/components/sapling/fees.rs index c43395cb..93bf9497 100644 --- a/masp_primitives/src/transaction/components/sapling/fees.rs +++ b/masp_primitives/src/transaction/components/sapling/fees.rs @@ -2,8 +2,8 @@ //! of a transaction. use crate::asset_type::AssetType; -use crate::sapling::PaymentAddress; use crate::convert::AllowedConversion; +use crate::sapling::PaymentAddress; /// A trait that provides a minimized view of a Sapling input suitable for use in /// fee and change calculation. diff --git a/masp_primitives/src/transaction/components/transparent/fees.rs b/masp_primitives/src/transaction/components/transparent/fees.rs index 94836f05..cff0a544 100644 --- a/masp_primitives/src/transaction/components/transparent/fees.rs +++ b/masp_primitives/src/transaction/components/transparent/fees.rs @@ -2,8 +2,8 @@ //! of a transaction. use super::TxOut; -use crate::transaction::TransparentAddress; use crate::asset_type::AssetType; +use crate::transaction::TransparentAddress; /// This trait provides a minimized view of a transparent input suitable for use in /// fee and change computation. diff --git a/masp_primitives/src/transaction/txid.rs b/masp_primitives/src/transaction/txid.rs index 2bb14780..abede069 100644 --- a/masp_primitives/src/transaction/txid.rs +++ b/masp_primitives/src/transaction/txid.rs @@ -11,7 +11,7 @@ use group::GroupEncoding; use crate::consensus::{BlockHeight, BranchId}; use super::{ - sapling::{self, OutputDescription, SpendDescription}, + sapling::{self, ConvertDescription, OutputDescription, SpendDescription}, transparent::{self, TxIn, TxOut}, Authorization, Authorized, TransactionDigest, TransparentDigests, TxDigests, TxId, TxVersion, }; @@ -33,6 +33,8 @@ const ZCASH_SAPLING_SPENDS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSSpendsHash" const ZCASH_SAPLING_SPENDS_COMPACT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSSpendCHash"; const ZCASH_SAPLING_SPENDS_NONCOMPACT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSSpendNHash"; +const ZCASH_SAPLING_CONVERTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdConvertHash"; + const ZCASH_SAPLING_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSOutputHash"; const ZCASH_SAPLING_OUTPUTS_COMPACT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSOutC__Hash"; const ZCASH_SAPLING_OUTPUTS_MEMOS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdSOutM__Hash"; @@ -109,6 +111,24 @@ pub(crate) fn hash_sapling_spends( h.finalize() } +/// Implements ZIP 244-like hashing of MASP convert descriptions. +/// +/// Write disjoint parts of each MASP shielded convert to a hash: +/// * \[(cv, anchor)*\] - personalized with ZCASH_SAPLING_CONVERTS_HASH_PERSONALIZATION +/// +pub(crate) fn hash_sapling_converts( + shielded_converts: &[ConvertDescription], +) -> Blake2bHash { + let mut h = hasher(ZCASH_SAPLING_CONVERTS_HASH_PERSONALIZATION); + if !shielded_converts.is_empty() { + for s_convert in shielded_converts { + h.write_all(&s_convert.cv.to_bytes()).unwrap(); + h.write_all(&s_convert.anchor.to_repr()).unwrap(); + } + } + h.finalize() +} + /// Implements [ZIP 244 section T.3b](https://zips.z.cash/zip-0244#t-3b-sapling-outputs-digest) /// /// Write disjoint parts of each Sapling shielded output as 3 separate hashes: @@ -128,12 +148,18 @@ pub(crate) fn hash_sapling_outputs( for s_out in shielded_outputs { ch.write_all(s_out.cmu.to_repr().as_ref()).unwrap(); ch.write_all(s_out.ephemeral_key.as_ref()).unwrap(); - ch.write_all(&s_out.enc_ciphertext[..52]).unwrap(); + ch.write_all(&s_out.enc_ciphertext[..masp_note_encryption::COMPACT_NOTE_SIZE]) + .unwrap(); - mh.write_all(&s_out.enc_ciphertext[52..564]).unwrap(); + mh.write_all( + &s_out.enc_ciphertext[masp_note_encryption::COMPACT_NOTE_SIZE + ..masp_note_encryption::NOTE_PLAINTEXT_SIZE], + ) + .unwrap(); nh.write_all(&s_out.cv.to_bytes()).unwrap(); - nh.write_all(&s_out.enc_ciphertext[564..]).unwrap(); + nh.write_all(&s_out.enc_ciphertext[masp_note_encryption::NOTE_PLAINTEXT_SIZE..]) + .unwrap(); nh.write_all(&s_out.out_ciphertext).unwrap(); } @@ -194,10 +220,14 @@ fn hash_sapling_txid_data< bundle: &sapling::Bundle, ) -> Blake2bHash { let mut h = hasher(ZCASH_SAPLING_HASH_PERSONALIZATION); - if !(bundle.shielded_spends.is_empty() && bundle.shielded_outputs.is_empty()) { + if !(bundle.shielded_spends.is_empty() + && bundle.shielded_converts.is_empty() + && bundle.shielded_outputs.is_empty()) + { h.write_all(hash_sapling_spends(&bundle.shielded_spends).as_bytes()) .unwrap(); - + h.write_all(hash_sapling_converts(&bundle.shielded_converts).as_bytes()) + .unwrap(); h.write_all(hash_sapling_outputs(&bundle.shielded_outputs).as_bytes()) .unwrap(); From 242316ecfe59184320967dde80b7492bccd978ec Mon Sep 17 00:00:00 2001 From: joe <55120843+joebebel@users.noreply.github.com> Date: Thu, 20 Jul 2023 20:35:13 -0700 Subject: [PATCH 19/21] remove wagyu (#50) --- masp_proofs/Cargo.toml | 3 +-- masp_proofs/src/prover.rs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/masp_proofs/Cargo.toml b/masp_proofs/Cargo.toml index b7b90d08..e7618e95 100644 --- a/masp_proofs/Cargo.toml +++ b/masp_proofs/Cargo.toml @@ -36,7 +36,6 @@ tracing = "0.1" blake2b_simd = "1" directories = { version = "4", optional = true } redjubjub = "0.5" -wagyu-zcash-parameters = { version = "0.2", optional = true } getrandom = { version = "0.2", features = ["js"] } itertools = "0.10.1" @@ -50,7 +49,7 @@ pprof = { version = "0.11", features = ["criterion", "flamegraph"] } # MSRV 1.56 [features] default = ["local-prover", "multicore"] -bundled-prover = ["wagyu-zcash-parameters"] +bundled-prover = [] download-params = ["minreq", "directories"] local-prover = ["directories"] multicore = ["bellman/multicore"] diff --git a/masp_proofs/src/prover.rs b/masp_proofs/src/prover.rs index b61fbc1b..adefc463 100644 --- a/masp_proofs/src/prover.rs +++ b/masp_proofs/src/prover.rs @@ -150,7 +150,7 @@ impl LocalTxProver { // spend_vk: p.spend_vk, // output_params: p.output_params, // } - // } + //} } impl TxProver for LocalTxProver { From e83521d0b4499a564e384d61ef6c1dadbfd64356 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Fri, 11 Aug 2023 03:04:38 +0200 Subject: [PATCH 20/21] Made Amount more generic. (#52) --- masp_primitives/Cargo.toml | 3 + masp_primitives/src/convert.rs | 33 +- masp_primitives/src/sapling/prover.rs | 8 +- masp_primitives/src/transaction.rs | 14 +- masp_primitives/src/transaction/builder.rs | 61 +- masp_primitives/src/transaction/components.rs | 4 +- .../src/transaction/components/amount.rs | 671 +++++++++++++----- .../src/transaction/components/sapling.rs | 8 +- .../transaction/components/sapling/builder.rs | 25 +- .../src/transaction/components/transparent.rs | 36 +- .../components/transparent/builder.rs | 36 +- .../components/transparent/fees.rs | 4 +- masp_primitives/src/transaction/fees.rs | 4 +- masp_primitives/src/transaction/fees/fixed.rs | 10 +- masp_primitives/src/transaction/sighash.rs | 2 +- masp_proofs/benches/convert.rs | 16 +- masp_proofs/src/circuit/convert.rs | 14 +- masp_proofs/src/prover.rs | 4 +- masp_proofs/src/sapling/mod.rs | 11 +- masp_proofs/src/sapling/prover.rs | 6 +- masp_proofs/src/sapling/verifier.rs | 4 +- masp_proofs/src/sapling/verifier/single.rs | 4 +- 22 files changed, 621 insertions(+), 357 deletions(-) diff --git a/masp_primitives/Cargo.toml b/masp_primitives/Cargo.toml index 31dd3c75..9eb2bf48 100644 --- a/masp_primitives/Cargo.toml +++ b/masp_primitives/Cargo.toml @@ -36,6 +36,9 @@ sha2 = "0.9" # - Metrics memuse = "0.2.1" +# - Checked arithmetic +num-traits = "0.2.14" + # - Secret management subtle = "2.2.3" diff --git a/masp_primitives/src/convert.rs b/masp_primitives/src/convert.rs index 82d78a36..5a345cf5 100644 --- a/masp_primitives/src/convert.rs +++ b/masp_primitives/src/convert.rs @@ -3,7 +3,7 @@ use crate::{ pedersen_hash::{pedersen_hash, Personalization}, Node, ValueCommitment, }, - transaction::components::amount::Amount, + transaction::components::amount::{I32Sum, ValueSum}, }; use borsh::{BorshDeserialize, BorshSerialize}; use group::{Curve, GroupEncoding}; @@ -16,7 +16,7 @@ use std::{ #[derive(Clone, Debug, PartialEq, Eq)] pub struct AllowedConversion { /// The asset type that the note represents - assets: Amount, + assets: I32Sum, /// Memorize generator because it's expensive to recompute generator: jubjub::ExtendedPoint, } @@ -71,15 +71,15 @@ impl AllowedConversion { } } -impl From for Amount { - fn from(allowed_conversion: AllowedConversion) -> Amount { +impl From for I32Sum { + fn from(allowed_conversion: AllowedConversion) -> I32Sum { allowed_conversion.assets } } -impl From for AllowedConversion { +impl From for AllowedConversion { /// Produces an asset generator without cofactor cleared - fn from(assets: Amount) -> Self { + fn from(assets: I32Sum) -> Self { let mut asset_generator = jubjub::ExtendedPoint::identity(); for (asset, value) in assets.components() { // Compute the absolute value (failing if -i64::MAX is @@ -123,7 +123,7 @@ impl BorshDeserialize for AllowedConversion { /// computation of checking whether the asset generator corresponds to the /// deserialized amount. fn deserialize(buf: &mut &[u8]) -> borsh::maybestd::io::Result { - let assets = Amount::read(buf)?; + let assets = I32Sum::read(buf)?; let gen_bytes = <::Repr as BorshDeserialize>::deserialize(buf)?; let generator = Option::from(jubjub::ExtendedPoint::from_bytes(&gen_bytes)) @@ -174,7 +174,7 @@ impl SubAssign for AllowedConversion { impl Sum for AllowedConversion { fn sum>(iter: I) -> Self { - iter.fold(AllowedConversion::from(Amount::zero()), Add::add) + iter.fold(AllowedConversion::from(ValueSum::zero()), Add::add) } } @@ -182,7 +182,7 @@ impl Sum for AllowedConversion { mod tests { use crate::asset_type::AssetType; use crate::convert::AllowedConversion; - use crate::transaction::components::amount::Amount; + use crate::transaction::components::amount::ValueSum; /// Generate ZEC asset type fn zec() -> AssetType { @@ -199,11 +199,12 @@ mod tests { #[test] fn test_homomorphism() { // Left operand - let a = Amount::from_pair(zec(), 5).unwrap() - + Amount::from_pair(btc(), 6).unwrap() - + Amount::from_pair(xan(), 7).unwrap(); + let a = ValueSum::from_pair(zec(), 5i32).unwrap() + + ValueSum::from_pair(btc(), 6i32).unwrap() + + ValueSum::from_pair(xan(), 7i32).unwrap(); // Right operand - let b = Amount::from_pair(zec(), 2).unwrap() + Amount::from_pair(xan(), 10).unwrap(); + let b = + ValueSum::from_pair(zec(), 2i32).unwrap() + ValueSum::from_pair(xan(), 10i32).unwrap(); // Test homomorphism assert_eq!( AllowedConversion::from(a.clone() + b.clone()), @@ -213,9 +214,9 @@ mod tests { #[test] fn test_serialization() { // Make conversion - let a: AllowedConversion = (Amount::from_pair(zec(), 5).unwrap() - + Amount::from_pair(btc(), 6).unwrap() - + Amount::from_pair(xan(), 7).unwrap()) + let a: AllowedConversion = (ValueSum::from_pair(zec(), 5i32).unwrap() + + ValueSum::from_pair(btc(), 6i32).unwrap() + + ValueSum::from_pair(xan(), 7i32).unwrap()) .into(); // Serialize conversion let mut data = Vec::new(); diff --git a/masp_primitives/src/sapling/prover.rs b/masp_primitives/src/sapling/prover.rs index 03a442fb..946641d1 100644 --- a/masp_primitives/src/sapling/prover.rs +++ b/masp_primitives/src/sapling/prover.rs @@ -8,7 +8,7 @@ use crate::{ redjubjub::{PublicKey, Signature}, Node, }, - transaction::components::{Amount, GROTH_PROOF_SIZE}, + transaction::components::{I128Sum, GROTH_PROOF_SIZE}, }; use super::{Diversifier, PaymentAddress, ProofGenerationKey, Rseed}; @@ -73,7 +73,7 @@ pub trait TxProver { fn binding_sig( &self, ctx: &mut Self::SaplingProvingContext, - amount: &Amount, + amount: &I128Sum, sighash: &[u8; 32], ) -> Result; } @@ -92,7 +92,7 @@ pub mod mock { redjubjub::{PublicKey, Signature}, Diversifier, Node, PaymentAddress, ProofGenerationKey, Rseed, }, - transaction::components::{Amount, GROTH_PROOF_SIZE}, + transaction::components::{I128Sum, GROTH_PROOF_SIZE}, }; use super::TxProver; @@ -169,7 +169,7 @@ pub mod mock { fn binding_sig( &self, _ctx: &mut Self::SaplingProvingContext, - _value: &Amount, + _value: &I128Sum, _sighash: &[u8; 32], ) -> Result { Err(()) diff --git a/masp_primitives/src/transaction.rs b/masp_primitives/src/transaction.rs index 3938ee30..4525b425 100644 --- a/masp_primitives/src/transaction.rs +++ b/masp_primitives/src/transaction.rs @@ -25,7 +25,7 @@ use crate::{ use self::{ components::{ - amount::Amount, + amount::{I128Sum, ValueSum}, sapling::{ self, ConvertDescriptionV5, OutputDescriptionV5, SpendDescription, SpendDescriptionV5, }, @@ -269,10 +269,10 @@ impl TransactionData { } impl TransactionData { - pub fn sapling_value_balance(&self) -> Amount { + pub fn sapling_value_balance(&self) -> I128Sum { self.sapling_bundle .as_ref() - .map_or(Amount::zero(), |b| b.value_balance.clone()) + .map_or(ValueSum::zero(), |b| b.value_balance.clone()) } } @@ -355,8 +355,8 @@ impl Transaction { }) } - fn read_amount(mut reader: R) -> io::Result { - Amount::read(&mut reader).map_err(|_| { + fn read_i128_sum(mut reader: R) -> io::Result { + I128Sum::read(&mut reader).map_err(|_| { io::Error::new( io::ErrorKind::InvalidData, "Amount valueBalance out of range", @@ -407,9 +407,9 @@ impl Transaction { let n_converts = cd_v5s.len(); let n_outputs = od_v5s.len(); let value_balance = if n_spends > 0 || n_outputs > 0 { - Self::read_amount(&mut reader)? + Self::read_i128_sum(&mut reader)? } else { - Amount::zero() + ValueSum::zero() }; let spend_anchor = if n_spends > 0 { diff --git a/masp_primitives/src/transaction/builder.rs b/masp_primitives/src/transaction/builder.rs index 63681b3b..fc65dcfd 100644 --- a/masp_primitives/src/transaction/builder.rs +++ b/masp_primitives/src/transaction/builder.rs @@ -1,6 +1,5 @@ //! Structs for building transactions. -use std::convert::TryInto; use std::error; use std::fmt; use std::sync::mpsc::Sender; @@ -19,7 +18,7 @@ use crate::{ sapling::{prover::TxProver, Diversifier, Node, Note, PaymentAddress}, transaction::{ components::{ - amount::{Amount, BalanceError, MAX_MONEY}, + amount::{BalanceError, I128Sum, U64Sum, ValueSum, MAX_MONEY}, sapling::{ self, builder::{SaplingBuilder, SaplingMetadata}, @@ -43,10 +42,10 @@ const DEFAULT_TX_EXPIRY_DELTA: u32 = 20; pub enum Error { /// Insufficient funds were provided to the transaction builder; the given /// additional amount is required in order to construct the transaction. - InsufficientFunds(Amount), + InsufficientFunds(I128Sum), /// The transaction has inputs in excess of outputs and fees; the user must /// add a change output. - ChangeRequired(Amount), + ChangeRequired(U64Sum), /// An error occurred in computing the fees for a transaction. Fee(FeeError), /// An overflow or underflow occurred when computing value balances @@ -251,7 +250,7 @@ impl Builder { value: u64, memo: MemoBytes, ) -> Result<(), sapling::builder::Error> { - if value > MAX_MONEY.try_into().unwrap() { + if value > MAX_MONEY { return Err(sapling::builder::Error::InvalidAmount); } self.sapling_builder @@ -273,9 +272,9 @@ impl Builder { &mut self, to: &TransparentAddress, asset_type: AssetType, - value: i64, + value: u64, ) -> Result<(), transparent::builder::Error> { - if value < 0 || value > MAX_MONEY { + if value > MAX_MONEY { return Err(transparent::builder::Error::InvalidAmount); } @@ -293,13 +292,13 @@ impl Builder { } /// Returns the sum of the transparent, Sapling, and TZE value balances. - pub fn value_balance(&self) -> Result { + pub fn value_balance(&self) -> Result { let value_balances = [ self.transparent_builder.value_balance()?, self.sapling_builder.value_balance(), ]; - Ok(value_balances.into_iter().sum::()) + Ok(value_balances.into_iter().sum::()) } /// Builds a transaction from the configured spends and outputs. @@ -326,7 +325,7 @@ impl Builder { fn build_internal( self, prover: &impl TxProver, - fee: Amount, + fee: U64Sum, ) -> Result<(Transaction, SaplingMetadata), Error> { let consensus_branch_id = BranchId::for_height(&self.params, self.target_height); @@ -338,9 +337,9 @@ impl Builder { // // After fees are accounted for, the value balance of the transaction must be zero. - let balance_after_fees = self.value_balance()? - fee; + let balance_after_fees = self.value_balance()? - I128Sum::from_sum(fee); - if balance_after_fees != Amount::zero() { + if balance_after_fees != ValueSum::zero() { return Err(Error::InsufficientFunds(-balance_after_fees)); }; @@ -480,9 +479,8 @@ mod tests { merkle_tree::{CommitmentTree, IncrementalWitness}, sapling::Rseed, transaction::{ - components::amount::{Amount, DEFAULT_FEE, MAX_MONEY}, - sapling::builder::{self as build_s}, - transparent::builder::{self as build_t}, + components::amount::{I128Sum, ValueSum, DEFAULT_FEE}, + sapling::builder as build_s, TransparentAddress, }, zip32::ExtendedSpendingKey, @@ -490,7 +488,7 @@ mod tests { use super::{Builder, Error}; - #[test] + /*#[test] fn fails_on_overflow_output() { let extsk = ExtendedSpendingKey::master(&[]); let dfvk = extsk.to_diversifiable_full_viewing_key(); @@ -507,12 +505,12 @@ mod tests { Some(ovk), to, zec(), - MAX_MONEY as u64 + 1, + MAX_MONEY + 1, MemoBytes::empty() ), Err(build_s::Error::InvalidAmount) ); - } + }*/ /// Generate ZEC asset type fn zec() -> AssetType { @@ -565,21 +563,6 @@ mod tests { ); } - #[test] - fn fails_on_negative_transparent_output() { - let mut rng = OsRng; - - let transparent_address = TransparentAddress(rng.gen::<[u8; 20]>()); - let tx_height = TEST_NETWORK - .activation_height(NetworkUpgrade::MASP) - .unwrap(); - let mut builder = Builder::new(TEST_NETWORK, tx_height); - assert_eq!( - builder.add_transparent_output(&transparent_address, zec(), -1,), - Err(build_t::Error::InvalidAmount) - ); - } - #[test] fn fails_on_negative_change() { let mut rng = OsRng; @@ -597,7 +580,9 @@ mod tests { let builder = Builder::new(TEST_NETWORK, tx_height); assert_eq!( builder.mock_build(), - Err(Error::InsufficientFunds(DEFAULT_FEE.clone())) + Err(Error::InsufficientFunds(I128Sum::from_sum( + DEFAULT_FEE.clone() + ))) ); } @@ -615,7 +600,8 @@ mod tests { assert_eq!( builder.mock_build(), Err(Error::InsufficientFunds( - Amount::from_pair(zec(), 50000).unwrap() + &*DEFAULT_FEE + I128Sum::from_pair(zec(), 50000).unwrap() + + &I128Sum::from_sum(DEFAULT_FEE.clone()) )) ); } @@ -630,7 +616,8 @@ mod tests { assert_eq!( builder.mock_build(), Err(Error::InsufficientFunds( - Amount::from_pair(zec(), 50000).unwrap() + &*DEFAULT_FEE + I128Sum::from_pair(zec(), 50000).unwrap() + + &I128Sum::from_sum(DEFAULT_FEE.clone()) )) ); } @@ -663,7 +650,7 @@ mod tests { assert_eq!( builder.mock_build(), Err(Error::InsufficientFunds( - Amount::from_pair(zec(), 1).unwrap() + ValueSum::from_pair(zec(), 1).unwrap() )) ); } diff --git a/masp_primitives/src/transaction/components.rs b/masp_primitives/src/transaction/components.rs index db07c9a5..cc041955 100644 --- a/masp_primitives/src/transaction/components.rs +++ b/masp_primitives/src/transaction/components.rs @@ -4,7 +4,9 @@ pub mod amount; pub mod sapling; pub mod transparent; pub use self::{ - amount::Amount, + amount::{ + I128Sum, I16Sum, I32Sum, I64Sum, I8Sum, U128Sum, U16Sum, U32Sum, U64Sum, U8Sum, ValueSum, + }, sapling::{ConvertDescription, OutputDescription, SpendDescription}, transparent::{TxIn, TxOut}, }; diff --git a/masp_primitives/src/transaction/components/amount.rs b/masp_primitives/src/transaction/components/amount.rs index 49da8501..fac323c5 100644 --- a/masp_primitives/src/transaction/components/amount.rs +++ b/masp_primitives/src/transaction/components/amount.rs @@ -1,36 +1,62 @@ use crate::asset_type::AssetType; use borsh::{BorshDeserialize, BorshSerialize}; +use num_traits::{CheckedAdd, CheckedMul, CheckedNeg, CheckedSub, One}; use std::cmp::Ordering; use std::collections::btree_map::Keys; use std::collections::btree_map::{IntoIter, Iter}; use std::collections::BTreeMap; -use std::convert::TryInto; use std::hash::Hash; use std::io::{Read, Write}; use std::iter::Sum; use std::ops::{Add, AddAssign, Index, Mul, MulAssign, Neg, Sub, SubAssign}; use zcash_encoding::Vector; -pub const MAX_MONEY: i64 = i64::MAX; +pub const MAX_MONEY: u64 = u64::MAX; lazy_static::lazy_static! { -pub static ref DEFAULT_FEE: Amount = Amount::from_pair(zec(), 1000).unwrap(); +pub static ref DEFAULT_FEE: U64Sum = ValueSum::from_pair(zec(), 1000).unwrap(); } /// A type-safe representation of some quantity of Zcash. /// -/// An Amount can only be constructed from an integer that is within the valid monetary +/// An ValueSum can only be constructed from an integer that is within the valid monetary /// range of `{-MAX_MONEY..MAX_MONEY}` (where `MAX_MONEY` = i64::MAX). /// However, this range is not preserved as an invariant internally; it is possible to -/// add two valid Amounts together to obtain an invalid Amount. It is the user's -/// responsibility to handle the result of serializing potentially-invalid Amounts. In -/// particular, a `Transaction` containing serialized invalid Amounts will be rejected +/// add two valid ValueSums together to obtain an invalid ValueSum. It is the user's +/// responsibility to handle the result of serializing potentially-invalid ValueSums. In +/// particular, a `Transaction` containing serialized invalid ValueSums will be rejected /// by the network consensus rules. /// -#[derive(Clone, Default, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Hash)] -pub struct Amount( - pub BTreeMap, -); -impl memuse::DynamicUsage for Amount { +pub type I8Sum = ValueSum; + +pub type U8Sum = ValueSum; + +pub type I16Sum = ValueSum; + +pub type U16Sum = ValueSum; + +pub type I32Sum = ValueSum; + +pub type U32Sum = ValueSum; + +pub type I64Sum = ValueSum; + +pub type U64Sum = ValueSum; + +pub type I128Sum = ValueSum; + +pub type U128Sum = ValueSum; + +#[derive(Clone, Default, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Hash)] +pub struct ValueSum< + Unit: Hash + Ord + BorshSerialize + BorshDeserialize, + Value: BorshSerialize + BorshDeserialize + PartialEq + Eq, +>(pub BTreeMap); + +impl memuse::DynamicUsage for ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Value: BorshSerialize + BorshDeserialize + PartialEq + Eq + Copy + Default + PartialOrd, +{ #[inline(always)] fn dynamic_usage(&self) -> usize { unimplemented!() @@ -44,72 +70,128 @@ impl memuse::DynamicUsage for Amount { } } -impl Amount { - /// Returns a zero-valued Amount. - pub fn zero() -> Self { - Amount(BTreeMap::new()) - } - - /// Creates a non-negative Amount from an i64. - /// - /// Returns an error if the amount is outside the range `{0..MAX_MONEY}`. - pub fn from_nonnegative>(atype: Unit, amount: Amt) -> Result { - let amount = amount.try_into().map_err(|_| ())?; - if amount == 0 { +impl ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Value: BorshSerialize + BorshDeserialize + PartialEq + Eq + Copy + Default + PartialOrd, +{ + /// Creates a non-negative ValueSum from a Value. + pub fn from_nonnegative(atype: Unit, amount: Value) -> Result { + if amount == Value::default() { Ok(Self::zero()) - } else if 0 <= amount && amount <= MAX_MONEY { + } else if Value::default() <= amount { let mut ret = BTreeMap::new(); ret.insert(atype, amount); - Ok(Amount(ret)) + Ok(ValueSum(ret)) } else { Err(()) } } - /// Creates an Amount from a type convertible to i64. - /// - /// Returns an error if the amount is outside the range `{-MAX_MONEY..MAX_MONEY}`. - pub fn from_pair>(atype: Unit, amount: Amt) -> Result { - let amount = amount.try_into().map_err(|_| ())?; - if amount == 0 { +} + +impl ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Value: BorshSerialize + BorshDeserialize + PartialEq + Eq + Copy + Default, +{ + /// Creates an ValueSum from a Value. + pub fn from_pair(atype: Unit, amount: Value) -> Result { + if amount == Value::default() { Ok(Self::zero()) - } else if -MAX_MONEY <= amount && amount <= MAX_MONEY { + } else { let mut ret = BTreeMap::new(); ret.insert(atype, amount); - Ok(Amount(ret)) - } else { - Err(()) + Ok(ValueSum(ret)) } } + /// Filters out everything but the given AssetType from this ValueSum + pub fn project(&self, index: Unit) -> Self { + let val = self.0.get(&index).copied().unwrap_or_default(); + Self::from_pair(index, val).unwrap() + } + + /// Get the given AssetType within this ValueSum + pub fn get(&self, index: &Unit) -> Value { + *self.0.get(index).unwrap_or(&Value::default()) + } +} + +impl ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Value: BorshSerialize + BorshDeserialize + PartialEq + Eq + Copy, +{ + /// Returns a zero-valued ValueSum. + pub fn zero() -> Self { + ValueSum(BTreeMap::new()) + } + + /// Check if ValueSum is zero + pub fn is_zero(&self) -> bool { + self.0.is_empty() + } + /// Returns an iterator over the amount's non-zero asset-types - pub fn asset_types(&self) -> Keys<'_, Unit, i64> { + pub fn asset_types(&self) -> Keys<'_, Unit, Value> { self.0.keys() } /// Returns an iterator over the amount's non-zero components - pub fn components(&self) -> Iter<'_, Unit, i64> { + pub fn components(&self) -> Iter<'_, Unit, Value> { self.0.iter() } /// Returns an iterator over the amount's non-zero components - pub fn into_components(self) -> IntoIter { + pub fn into_components(self) -> IntoIter { self.0.into_iter() } - /// Filters out everything but the given AssetType from this Amount - pub fn project(&self, index: Unit) -> Self { - let val = self.0.get(&index).copied().unwrap_or(0); - Self::from_pair(index, val).unwrap() + /// Filters out the given AssetType from this ValueSum + pub fn reject(&self, index: Unit) -> Self { + let mut val = self.clone(); + val.0.remove(&index); + val } +} - /// Filters out the given AssetType from this Amount - pub fn reject(&self, index: Unit) -> Self { - self.clone() - self.project(index) +impl ValueSum { + /// Deserialize an ValueSum object from a list of amounts denominated by + /// different assets + pub fn read(reader: &mut R) -> std::io::Result { + let vec = Vector::read(reader, |reader| { + let mut atype = [0; 32]; + let mut value = [0; 4]; + reader.read_exact(&mut atype)?; + reader.read_exact(&mut value)?; + let atype = AssetType::from_identifier(&atype).ok_or_else(|| { + std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid asset type") + })?; + Ok((atype, i32::from_le_bytes(value))) + })?; + let mut ret = Self::zero(); + for (atype, amt) in vec { + ret += Self::from_pair(atype, amt).map_err(|_| { + std::io::Error::new(std::io::ErrorKind::InvalidData, "amount out of range") + })?; + } + Ok(ret) + } + + /// Serialize an ValueSum object into a list of amounts denominated by + /// distinct asset types + pub fn write(&self, writer: &mut W) -> std::io::Result<()> { + let vec: Vec<_> = self.components().collect(); + Vector::write(writer, vec.as_ref(), |writer, elt| { + writer.write_all(elt.0.get_identifier())?; + writer.write_all(elt.1.to_le_bytes().as_ref())?; + Ok(()) + }) } } -impl Amount { - /// Deserialize an Amount object from a list of amounts denominated by +impl ValueSum { + /// Deserialize an ValueSum object from a list of amounts denominated by /// different assets pub fn read(reader: &mut R) -> std::io::Result { let vec = Vector::read(reader, |reader| { @@ -131,7 +213,7 @@ impl Amount { Ok(ret) } - /// Serialize an Amount object into a list of amounts denominated by + /// Serialize an ValueSum object into a list of amounts denominated by /// distinct asset types pub fn write(&self, writer: &mut W) -> std::io::Result<()> { let vec: Vec<_> = self.components().collect(); @@ -143,180 +225,390 @@ impl Amount { } } -impl From for Amount { +impl ValueSum { + /// Deserialize an ValueSum object from a list of amounts denominated by + /// different assets + pub fn read(reader: &mut R) -> std::io::Result { + let vec = Vector::read(reader, |reader| { + let mut atype = [0; 32]; + let mut value = [0; 16]; + reader.read_exact(&mut atype)?; + reader.read_exact(&mut value)?; + let atype = AssetType::from_identifier(&atype).ok_or_else(|| { + std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid asset type") + })?; + Ok((atype, i128::from_le_bytes(value))) + })?; + let mut ret = Self::zero(); + for (atype, amt) in vec { + ret += Self::from_pair(atype, amt).map_err(|_| { + std::io::Error::new(std::io::ErrorKind::InvalidData, "amount out of range") + })?; + } + Ok(ret) + } + + /// Serialize an ValueSum object into a list of amounts denominated by + /// distinct asset types + pub fn write(&self, writer: &mut W) -> std::io::Result<()> { + let vec: Vec<_> = self.components().collect(); + Vector::write(writer, vec.as_ref(), |writer, elt| { + writer.write_all(elt.0.get_identifier())?; + writer.write_all(elt.1.to_le_bytes().as_ref())?; + Ok(()) + }) + } +} + +impl From for ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize, + Value: BorshSerialize + BorshDeserialize + PartialEq + Eq + Copy + One, +{ fn from(atype: Unit) -> Self { let mut ret = BTreeMap::new(); - ret.insert(atype, 1); - Amount(ret) + ret.insert(atype, Value::one()); + ValueSum(ret) } } -impl PartialOrd for Amount { - /// One Amount is more than or equal to another if each corresponding - /// coordinate is more than the other's. +impl PartialOrd for ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Value: BorshSerialize + BorshDeserialize + PartialEq + Eq + Copy + Default + PartialOrd, +{ + /// One ValueSum is more than or equal to another if each corresponding + /// coordinate is more than or equal to the other's. fn partial_cmp(&self, other: &Self) -> Option { - let mut diff = other.clone(); - for (atype, amount) in self.components() { - let ent = diff[atype] - amount; - if ent == 0 { - diff.0.remove(atype); - } else { - diff.0.insert(atype.clone(), ent); + let zero = Value::default(); + let mut ordering = Some(Ordering::Equal); + for k in self.0.keys().chain(other.0.keys()) { + let v1 = self.0.get(k).unwrap_or(&zero); + let v2 = other.0.get(k).unwrap_or(&zero); + match (v1.partial_cmp(v2), ordering) { + // Sums cannot be compared if even a single coordinate cannot be + // compared + (None, _) => ordering = None, + // If sums are uncomparable, less, greater, or equal, another + // equal coordinate will not change that + (Some(Ordering::Equal), _) => {} + // A lesser coordinate is inconsistent with the sum being + // greater, and vice-versa + (Some(Ordering::Less), Some(Ordering::Greater) | None) => ordering = None, + (Some(Ordering::Greater), Some(Ordering::Less) | None) => ordering = None, + // It only takes one lesser coordinate, to make a sum that + // otherwise would have been equal, to be lesser + (Some(Ordering::Less), Some(Ordering::Less | Ordering::Equal)) => { + ordering = Some(Ordering::Less) + } + (Some(Ordering::Greater), Some(Ordering::Greater | Ordering::Equal)) => { + ordering = Some(Ordering::Greater) + } } } - if diff.0.values().all(|x| *x == 0) { - Some(Ordering::Equal) - } else if diff.0.values().all(|x| *x >= 0) { - Some(Ordering::Less) - } else if diff.0.values().all(|x| *x <= 0) { - Some(Ordering::Greater) - } else { - None - } - } -} - -impl Index<&Unit> for Amount { - type Output = i64; - /// Query how much of the given asset this amount contains - fn index(&self, index: &Unit) -> &Self::Output { - self.0.get(index).unwrap_or(&0) + ordering } } -impl MulAssign for Amount { - fn mul_assign(&mut self, rhs: i64) { - for (_atype, amount) in self.0.iter_mut() { - let ent = *amount * rhs; - if -MAX_MONEY <= ent && ent <= MAX_MONEY { - *amount = ent; - } else { - panic!("multiplication should remain in range"); +macro_rules! impl_index { + ($struct_type:ty) => { + impl Index<&Unit> for ValueSum + where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize, + { + type Output = $struct_type; + /// Query how much of the given asset this amount contains + fn index(&self, index: &Unit) -> &Self::Output { + self.0.get(index).unwrap_or(&0) } } - } + }; } -impl Mul for Amount { - type Output = Self; +impl_index!(i8); + +impl_index!(u8); + +impl_index!(i16); + +impl_index!(u16); - fn mul(mut self, rhs: i64) -> Self { - self *= rhs; - self +impl_index!(i32); + +impl_index!(u32); + +impl_index!(i64); + +impl_index!(u64); + +impl_index!(i128); + +impl_index!(u128); + +impl MulAssign for ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Value: BorshSerialize + + BorshDeserialize + + PartialEq + + Eq + + Copy + + Default + + PartialOrd + + CheckedMul, +{ + fn mul_assign(&mut self, rhs: Value) { + *self = self.clone() * rhs; } } -impl AddAssign<&Amount> - for Amount +impl Mul for ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Value: BorshSerialize + + BorshDeserialize + + PartialEq + + Eq + + Copy + + Default + + PartialOrd + + CheckedMul, { - fn add_assign(&mut self, rhs: &Self) { - for (atype, amount) in rhs.components() { - let ent = self[atype] + amount; - if ent == 0 { - self.0.remove(atype); - } else if -MAX_MONEY <= ent && ent <= MAX_MONEY { - self.0.insert(atype.clone(), ent); - } else { - panic!("addition should remain in range"); - } + type Output = ValueSum; + + fn mul(self, rhs: Value) -> Self::Output { + let mut comps = BTreeMap::new(); + for (atype, amount) in self.0.iter() { + comps.insert( + atype.clone(), + amount.checked_mul(&rhs).expect("overflow detected"), + ); } + comps.retain(|_, v| *v != Value::default()); + ValueSum(comps) } } -impl AddAssign> - for Amount +impl AddAssign<&ValueSum> for ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Value: BorshSerialize + + BorshDeserialize + + PartialEq + + Eq + + Copy + + Default + + PartialOrd + + CheckedAdd, { - fn add_assign(&mut self, rhs: Self) { + fn add_assign(&mut self, rhs: &ValueSum) { + *self = self.clone() + rhs; + } +} + +impl AddAssign> for ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Value: BorshSerialize + + BorshDeserialize + + PartialEq + + Eq + + Copy + + Default + + PartialOrd + + CheckedAdd, +{ + fn add_assign(&mut self, rhs: ValueSum) { *self += &rhs } } -impl Add<&Amount> - for Amount +impl Add<&ValueSum> for ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Value: BorshSerialize + + BorshDeserialize + + PartialEq + + Eq + + Copy + + Default + + PartialOrd + + CheckedAdd, { - type Output = Self; + type Output = ValueSum; - fn add(mut self, rhs: &Self) -> Self { - self += rhs; - self + fn add(self, rhs: &ValueSum) -> Self::Output { + let mut comps = self.0.clone(); + for (atype, amount) in rhs.components() { + comps.insert( + atype.clone(), + self.get(atype) + .checked_add(amount) + .expect("overflow detected"), + ); + } + comps.retain(|_, v| *v != Value::default()); + ValueSum(comps) } } -impl Add> - for Amount +impl Add> for ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Value: BorshSerialize + + BorshDeserialize + + PartialEq + + Eq + + Copy + + Default + + PartialOrd + + CheckedAdd, { - type Output = Self; + type Output = ValueSum; - fn add(mut self, rhs: Self) -> Self { - self += &rhs; - self + fn add(self, rhs: ValueSum) -> Self::Output { + self + &rhs } } -impl SubAssign<&Amount> - for Amount +impl SubAssign<&ValueSum> for ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Value: BorshSerialize + + BorshDeserialize + + PartialEq + + Eq + + Copy + + Default + + PartialOrd + + CheckedSub, { - fn sub_assign(&mut self, rhs: &Self) { - for (atype, amount) in rhs.components() { - let ent = self[atype] - amount; - if ent == 0 { - self.0.remove(atype); - } else if -MAX_MONEY <= ent && ent <= MAX_MONEY { - self.0.insert(atype.clone(), ent); - } else { - panic!("subtraction should remain in range"); - } - } + fn sub_assign(&mut self, rhs: &ValueSum) { + *self = self.clone() - rhs } } -impl SubAssign> - for Amount +impl SubAssign> for ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Value: BorshSerialize + + BorshDeserialize + + PartialEq + + Eq + + Copy + + Default + + PartialOrd + + CheckedSub, { - fn sub_assign(&mut self, rhs: Self) { + fn sub_assign(&mut self, rhs: ValueSum) { *self -= &rhs } } -impl Neg for Amount { - type Output = Self; - - fn neg(mut self) -> Self { - for (_, amount) in self.0.iter_mut() { - *amount = -*amount; +impl Neg for ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Value: BorshSerialize + + BorshDeserialize + + PartialEq + + Eq + + Copy + + Default + + PartialOrd + + CheckedNeg, +{ + type Output = ValueSum; + + fn neg(mut self) -> Self::Output { + let mut comps = BTreeMap::new(); + for (atype, amount) in self.0.iter_mut() { + comps.insert( + atype.clone(), + amount.checked_neg().expect("overflow detected"), + ); } - self + comps.retain(|_, v| *v != Value::default()); + ValueSum(comps) } } -impl Sub<&Amount> - for Amount +impl Sub<&ValueSum> for ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Value: BorshSerialize + BorshDeserialize + PartialEq + Eq + Copy + Default + CheckedSub, { - type Output = Self; + type Output = ValueSum; - fn sub(mut self, rhs: &Self) -> Self { - self -= rhs; - self + fn sub(self, rhs: &ValueSum) -> Self::Output { + let mut comps = self.0.clone(); + for (atype, amount) in rhs.components() { + comps.insert( + atype.clone(), + self.get(atype) + .checked_sub(amount) + .expect("overflow detected"), + ); + } + comps.retain(|_, v| *v != Value::default()); + ValueSum(comps) } } -impl Sub> - for Amount +impl Sub> for ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Value: BorshSerialize + BorshDeserialize + PartialEq + Eq + Copy + Default + CheckedSub, { - type Output = Self; + type Output = ValueSum; - fn sub(mut self, rhs: Self) -> Self { - self -= &rhs; - self + fn sub(self, rhs: ValueSum) -> Self::Output { + self - &rhs } } -impl Sum for Amount { +impl Sum for ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Value: BorshSerialize + BorshDeserialize + PartialEq + Eq + Copy + Default + PartialOrd, + Self: Add, +{ fn sum>(iter: I) -> Self { iter.fold(Self::zero(), Add::add) } } +impl ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Output: BorshSerialize + BorshDeserialize + PartialEq + Eq + Copy, +{ + pub fn try_from_sum( + x: ValueSum, + ) -> Result>::Error> + where + Value: BorshSerialize + BorshDeserialize + PartialEq + Eq + Copy, + Output: TryFrom, + { + let mut comps = BTreeMap::new(); + for (atype, amount) in x.0 { + comps.insert(atype, amount.try_into()?); + } + Ok(Self(comps)) + } + + pub fn from_sum(x: ValueSum) -> Self + where + Value: BorshSerialize + BorshDeserialize + PartialEq + Eq + Copy, + Output: From, + { + let mut comps = BTreeMap::new(); + for (atype, amount) in x.0 { + comps.insert(atype, amount.into()); + } + Self(comps) + } +} + /// A type for balance violations in amount addition and subtraction /// (overflow and underflow of allowed ranges) #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -331,12 +623,12 @@ impl std::fmt::Display for BalanceError { BalanceError::Overflow => { write!( f, - "Amount addition resulted in a value outside the valid range." + "ValueSum addition resulted in a value outside the valid range." ) } BalanceError::Underflow => write!( f, - "Amount subtraction resulted in a value outside the valid range." + "ValueSum subtraction resulted in a value outside the valid range." ), } } @@ -346,102 +638,105 @@ pub fn zec() -> AssetType { AssetType::new(b"ZEC").unwrap() } -pub fn default_fee() -> Amount { - Amount::from_pair(zec(), 10000).unwrap() +pub fn default_fee() -> ValueSum { + ValueSum::from_pair(zec(), 10000).unwrap() } #[cfg(any(test, feature = "test-dependencies"))] pub mod testing { use proptest::prelude::prop_compose; - use super::{Amount, MAX_MONEY}; + use super::{I128Sum, I64Sum, U64Sum, ValueSum, MAX_MONEY}; use crate::asset_type::testing::arb_asset_type; prop_compose! { - pub fn arb_amount()(asset_type in arb_asset_type(), amt in -MAX_MONEY..MAX_MONEY) -> Amount { - Amount::from_pair(asset_type, amt).unwrap() + pub fn arb_i64_sum()(asset_type in arb_asset_type(), amt in i64::MIN..i64::MAX) -> I64Sum { + ValueSum::from_pair(asset_type, amt).unwrap() } } prop_compose! { - pub fn arb_nonnegative_amount()(asset_type in arb_asset_type(), amt in 0i64..MAX_MONEY) -> Amount { - Amount::from_pair(asset_type, amt).unwrap() + pub fn arb_i128_sum()(asset_type in arb_asset_type(), amt in i128::MIN..i128::MAX) -> I128Sum { + ValueSum::from_pair(asset_type, amt as i128).unwrap() } } prop_compose! { - pub fn arb_positive_amount()(asset_type in arb_asset_type(), amt in 1i64..MAX_MONEY) -> Amount { - Amount::from_pair(asset_type, amt).unwrap() + pub fn arb_nonnegative_amount()(asset_type in arb_asset_type(), amt in 0u64..MAX_MONEY) -> U64Sum { + ValueSum::from_pair(asset_type, amt).unwrap() + } + } + + prop_compose! { + pub fn arb_positive_amount()(asset_type in arb_asset_type(), amt in 1u64..MAX_MONEY) -> U64Sum { + ValueSum::from_pair(asset_type, amt).unwrap() } } } #[cfg(test)] mod tests { - use super::{zec, Amount, MAX_MONEY}; + use super::{zec, I64Sum, ValueSum, MAX_MONEY}; #[test] fn amount_in_range() { let zero = b"\x01\x94\xf3O\xfdd\xef\n\xc3i\x08\xfd\xdf\xec\x05hX\x06)\xc4Vq\x0f\xa1\x86\x83\x12\xa8\x7f\xbf\n\xa5\t\x00\x00\x00\x00\x00\x00\x00\x00"; - assert_eq!(Amount::read(&mut zero.as_ref()).unwrap(), Amount::zero()); + assert_eq!(I64Sum::read(&mut zero.as_ref()).unwrap(), ValueSum::zero()); let neg_one = b"\x01\x94\xf3O\xfdd\xef\n\xc3i\x08\xfd\xdf\xec\x05hX\x06)\xc4Vq\x0f\xa1\x86\x83\x12\xa8\x7f\xbf\n\xa5\t\xff\xff\xff\xff\xff\xff\xff\xff"; assert_eq!( - Amount::read(&mut neg_one.as_ref()).unwrap(), - Amount::from_pair(zec(), -1).unwrap() + I64Sum::read(&mut neg_one.as_ref()).unwrap(), + I64Sum::from_pair(zec(), -1).unwrap() ); let max_money = b"\x01\x94\xf3O\xfdd\xef\n\xc3i\x08\xfd\xdf\xec\x05hX\x06)\xc4Vq\x0f\xa1\x86\x83\x12\xa8\x7f\xbf\n\xa5\t\xff\xff\xff\xff\xff\xff\xff\x7f"; assert_eq!( - Amount::read(&mut max_money.as_ref()).unwrap(), - Amount::from_pair(zec(), MAX_MONEY).unwrap() + I64Sum::read(&mut max_money.as_ref()).unwrap(), + I64Sum::from_pair(zec(), i64::MAX).unwrap() ); //let max_money_p1 = b"\x01\x94\xf3O\xfdd\xef\n\xc3i\x08\xfd\xdf\xec\x05hX\x06)\xc4Vq\x0f\xa1\x86\x83\x12\xa8\x7f\xbf\n\xa5\t\x01\x40\x07\x5a\xf0\x75\x07\x00"; - //assert!(Amount::read(&mut max_money_p1.as_ref()).is_err()); + //assert!(ValueSum::read(&mut max_money_p1.as_ref()).is_err()); //let mut neg_max_money = [0u8; 41]; - //let mut amount = Amount::from_pair(zec(), -MAX_MONEY).unwrap(); + //let mut amount = ValueSum::from_pair(zec(), -MAX_MONEY).unwrap(); //*amount.0.get_mut(&zec()).unwrap() = i64::MIN; //amount.write(&mut neg_max_money.as_mut()); //dbg!(std::str::from_utf8(&neg_max_money.as_ref().iter().map(|b| std::ascii::escape_default(*b)).flatten().collect::>()).unwrap()); let neg_max_money = b"\x01\x94\xf3O\xfdd\xef\n\xc3i\x08\xfd\xdf\xec\x05hX\x06)\xc4Vq\x0f\xa1\x86\x83\x12\xa8\x7f\xbf\n\xa5\t\x01\x00\x00\x00\x00\x00\x00\x80"; assert_eq!( - Amount::read(&mut neg_max_money.as_ref()).unwrap(), - Amount::from_pair(zec(), -MAX_MONEY).unwrap() + I64Sum::read(&mut neg_max_money.as_ref()).unwrap(), + I64Sum::from_pair(zec(), -i64::MAX).unwrap() ); - - let neg_max_money_m1 = b"\x01\x94\xf3O\xfdd\xef\n\xc3i\x08\xfd\xdf\xec\x05hX\x06)\xc4Vq\x0f\xa1\x86\x83\x12\xa8\x7f\xbf\n\xa5\t\x00\x00\x00\x00\x00\x00\x00\x80"; - assert!(Amount::read(&mut neg_max_money_m1.as_ref()).is_err()); } #[test] #[should_panic] fn add_panics_on_overflow() { - let v = Amount::from_pair(zec(), MAX_MONEY).unwrap(); - let _sum = v + Amount::from_pair(zec(), 1).unwrap(); + let v = ValueSum::from_pair(zec(), MAX_MONEY).unwrap(); + let _sum = v + ValueSum::from_pair(zec(), 1).unwrap(); } #[test] #[should_panic] fn add_assign_panics_on_overflow() { - let mut a = Amount::from_pair(zec(), MAX_MONEY).unwrap(); - a += Amount::from_pair(zec(), 1).unwrap(); + let mut a = ValueSum::from_pair(zec(), MAX_MONEY).unwrap(); + a += ValueSum::from_pair(zec(), 1).unwrap(); } #[test] #[should_panic] fn sub_panics_on_underflow() { - let v = Amount::from_pair(zec(), -MAX_MONEY).unwrap(); - let _diff = v - Amount::from_pair(zec(), 1).unwrap(); + let v = ValueSum::from_pair(zec(), 0u64).unwrap(); + let _diff = v - ValueSum::from_pair(zec(), 1).unwrap(); } #[test] #[should_panic] fn sub_assign_panics_on_underflow() { - let mut a = Amount::from_pair(zec(), -MAX_MONEY).unwrap(); - a -= Amount::from_pair(zec(), 1).unwrap(); + let mut a = ValueSum::from_pair(zec(), 0u64).unwrap(); + a -= ValueSum::from_pair(zec(), 1).unwrap(); } } diff --git a/masp_primitives/src/transaction/components/sapling.rs b/masp_primitives/src/transaction/components/sapling.rs index 2048abd9..7ce04153 100644 --- a/masp_primitives/src/transaction/components/sapling.rs +++ b/masp_primitives/src/transaction/components/sapling.rs @@ -23,7 +23,7 @@ use crate::{ }, }; -use super::{amount::Amount, GROTH_PROOF_SIZE}; +use super::{amount::I128Sum, GROTH_PROOF_SIZE}; pub type GrothProofBytes = [u8; GROTH_PROOF_SIZE]; @@ -90,7 +90,7 @@ pub struct Bundle>, pub shielded_converts: Vec>, pub shielded_outputs: Vec>, - pub value_balance: Amount, + pub value_balance: I128Sum, pub authorization: A, } @@ -535,7 +535,7 @@ pub mod testing { Nullifier, }, transaction::{ - components::{amount::testing::arb_amount, GROTH_PROOF_SIZE}, + components::{amount::testing::arb_i128_sum, GROTH_PROOF_SIZE}, TxVersion, }, }; @@ -614,7 +614,7 @@ pub mod testing { shielded_spends in vec(arb_spend_description(), 0..30), shielded_converts in vec(arb_convert_description(), 0..30), shielded_outputs in vec(arb_output_description(), 0..30), - value_balance in arb_amount(), + value_balance in arb_i128_sum(), rng_seed in prop::array::uniform32(prop::num::u8::ANY), fake_bvk_bytes in prop::array::uniform32(prop::num::u8::ANY), ) -> Option> { diff --git a/masp_primitives/src/transaction/components/sapling/builder.rs b/masp_primitives/src/transaction/components/sapling/builder.rs index 381295f0..9d5594ca 100644 --- a/masp_primitives/src/transaction/components/sapling/builder.rs +++ b/masp_primitives/src/transaction/components/sapling/builder.rs @@ -25,7 +25,7 @@ use crate::{ transaction::{ builder::Progress, components::{ - amount::{Amount, MAX_MONEY}, + amount::{I128Sum, I32Sum, ValueSum, MAX_MONEY}, sapling::{ fees, Authorization, Authorized, Bundle, ConvertDescription, GrothProofBytes, OutputDescription, SpendDescription, @@ -146,7 +146,7 @@ impl SaplingOutputInfo { memo: MemoBytes, ) -> Result { let g_d = to.g_d().ok_or(Error::InvalidAddress)?; - if value > MAX_MONEY.try_into().unwrap() { + if value > MAX_MONEY { return Err(Error::InvalidAmount); } @@ -272,7 +272,7 @@ pub struct SaplingBuilder { params: P, spend_anchor: Option, target_height: BlockHeight, - value_balance: Amount, + value_balance: I128Sum, convert_anchor: Option, spends: Vec>, converts: Vec, @@ -303,7 +303,7 @@ impl BorshDeserialize for SaplingBui .map(|x| x.ok_or_else(|| std::io::Error::from(std::io::ErrorKind::InvalidData))) .transpose()?; let target_height = BlockHeight::deserialize(buf)?; - let value_balance: Amount = Amount::deserialize(buf)?; + let value_balance = I128Sum::deserialize(buf)?; let convert_anchor: Option> = Option::<[u8; 32]>::deserialize(buf)?.map(|x| bls12_381::Scalar::from_bytes(&x).into()); let convert_anchor = convert_anchor @@ -347,7 +347,7 @@ impl SaplingBuilder { params, spend_anchor: None, target_height, - value_balance: Amount::zero(), + value_balance: ValueSum::zero(), convert_anchor: None, spends: vec![], converts: vec![], @@ -370,7 +370,7 @@ impl SaplingBuilder { } /// Returns the net value represented by the spends and outputs added to this builder. - pub fn value_balance(&self) -> Amount { + pub fn value_balance(&self) -> I128Sum { self.value_balance.clone() } } @@ -401,8 +401,8 @@ impl SaplingBuilder

{ let alpha = jubjub::Fr::random(&mut rng); - self.value_balance += - Amount::from_pair(note.asset_type, note.value).map_err(|_| Error::InvalidAmount)?; + self.value_balance += ValueSum::from_pair(note.asset_type, note.value.into()) + .map_err(|_| Error::InvalidAmount)?; self.spends.push(SpendDescriptionInfo { extsk, @@ -437,8 +437,8 @@ impl SaplingBuilder

{ self.convert_anchor = Some(merkle_path.root(node).into()) } - let allowed_amt: Amount = allowed.clone().into(); - self.value_balance += allowed_amt * value.try_into().unwrap(); + let allowed_amt: I32Sum = allowed.clone().into(); + self.value_balance += I128Sum::from_sum(allowed_amt) * (value as i128); self.converts.push(ConvertDescriptionInfo { allowed, @@ -472,7 +472,7 @@ impl SaplingBuilder

{ )?; self.value_balance -= - Amount::from_pair(asset_type, value).map_err(|_| Error::InvalidAmount)?; + ValueSum::from_pair(asset_type, value.into()).map_err(|_| Error::InvalidAmount)?; self.outputs.push(output); @@ -488,6 +488,7 @@ impl SaplingBuilder

{ progress_notifier: Option<&Sender>, ) -> Result>, Error> { // Record initial positions of spends and outputs + let value_balance = self.value_balance(); let params = self.params; let mut indexed_spends: Vec<_> = self.spends.into_iter().enumerate().collect(); let mut indexed_converts: Vec<_> = self.converts.into_iter().enumerate().collect(); @@ -723,7 +724,7 @@ impl SaplingBuilder

{ shielded_spends, shielded_converts, shielded_outputs, - value_balance: self.value_balance, + value_balance, authorization: Unauthorized { tx_metadata }, }) }; diff --git a/masp_primitives/src/transaction/components/transparent.rs b/masp_primitives/src/transaction/components/transparent.rs index 06efcaee..85391e8c 100644 --- a/masp_primitives/src/transaction/components/transparent.rs +++ b/masp_primitives/src/transaction/components/transparent.rs @@ -7,7 +7,7 @@ use std::io::{self, Read, Write}; use crate::asset_type::AssetType; use crate::transaction::TransparentAddress; -use super::amount::{Amount, BalanceError, MAX_MONEY}; +use super::amount::{BalanceError, I128Sum, ValueSum, MAX_MONEY}; pub mod builder; pub mod fees; @@ -58,34 +58,22 @@ impl Bundle { /// transferred out of the transparent pool into shielded pools or to fees; a negative value /// means that the containing transaction has funds being transferred into the transparent pool /// from the shielded pools. - pub fn value_balance(&self) -> Result + pub fn value_balance(&self) -> Result where E: From, { let input_sum = self .vin .iter() - .map(|p| { - if p.value >= 0 { - Amount::from_pair(p.asset_type, p.value) - } else { - Err(()) - } - }) - .sum::>() + .map(|p| ValueSum::from_pair(p.asset_type, p.value as i128)) + .sum::>() .map_err(|_| BalanceError::Overflow)?; let output_sum = self .vout .iter() - .map(|p| { - if p.value >= 0 { - Amount::from_pair(p.asset_type, p.value) - } else { - Err(()) - } - }) - .sum::>() + .map(|p| ValueSum::from_pair(p.asset_type, p.value as i128)) + .sum::>() .map_err(|_| BalanceError::Overflow)?; // Cannot panic when subtracting two positive i64 @@ -96,7 +84,7 @@ impl Bundle { #[derive(Debug, Clone, PartialEq, Eq)] pub struct TxIn { pub asset_type: AssetType, - pub value: i64, + pub value: u64, pub address: TransparentAddress, pub transparent_sig: A::TransparentSig, } @@ -112,9 +100,9 @@ impl TxIn { let value = { let mut tmp = [0u8; 8]; reader.read_exact(&mut tmp)?; - i64::from_le_bytes(tmp) + u64::from_le_bytes(tmp) }; - if value < 0 || value > MAX_MONEY { + if value > MAX_MONEY { return Err(io::Error::new( io::ErrorKind::InvalidData, "value out of range", @@ -144,7 +132,7 @@ impl TxIn { #[derive(Clone, Debug, Hash, PartialOrd, PartialEq, Ord, Eq)] pub struct TxOut { pub asset_type: AssetType, - pub value: i64, + pub value: u64, pub address: TransparentAddress, } @@ -159,9 +147,9 @@ impl TxOut { let value = { let mut tmp = [0u8; 8]; reader.read_exact(&mut tmp)?; - i64::from_le_bytes(tmp) + u64::from_le_bytes(tmp) }; - if value < 0 || value > MAX_MONEY { + if value > MAX_MONEY { return Err(io::Error::new( io::ErrorKind::InvalidData, "value out of range", diff --git a/masp_primitives/src/transaction/components/transparent/builder.rs b/masp_primitives/src/transaction/components/transparent/builder.rs index 6c0cc642..f97eb491 100644 --- a/masp_primitives/src/transaction/components/transparent/builder.rs +++ b/masp_primitives/src/transaction/components/transparent/builder.rs @@ -6,7 +6,7 @@ use crate::{ asset_type::AssetType, transaction::{ components::{ - amount::{Amount, BalanceError, MAX_MONEY}, + amount::{BalanceError, I128Sum, ValueSum, MAX_MONEY}, transparent::{self, fees, Authorization, Authorized, Bundle, TxIn, TxOut}, }, sighash::TransparentAuthorizingContext, @@ -105,10 +105,6 @@ impl TransparentBuilder { /// Adds a coin (the output of a previous transaction) to be spent to the transaction. #[cfg(feature = "transparent-inputs")] pub fn add_input(&mut self, coin: TxOut) -> Result<(), Error> { - if coin.value.is_negative() { - return Err(Error::InvalidAmount); - } - self.inputs.push(TransparentInputInfo { coin }); Ok(()) @@ -118,9 +114,9 @@ impl TransparentBuilder { &mut self, to: &TransparentAddress, asset_type: AssetType, - value: i64, + value: u64, ) -> Result<(), Error> { - if value < 0 || value > MAX_MONEY { + if value > MAX_MONEY { return Err(Error::InvalidAmount); } @@ -133,35 +129,23 @@ impl TransparentBuilder { Ok(()) } - pub fn value_balance(&self) -> Result { + pub fn value_balance(&self) -> Result { #[cfg(feature = "transparent-inputs")] let input_sum = self .inputs .iter() - .map(|input| { - if input.coin.value >= 0 { - Amount::from_pair(input.coin.asset_type, input.coin.value) - } else { - Err(()) - } - }) - .sum::>() + .map(|input| ValueSum::from_pair(input.coin.asset_type, input.coin.value as i128)) + .sum::>() .map_err(|_| BalanceError::Overflow)?; #[cfg(not(feature = "transparent-inputs"))] - let input_sum = Amount::zero(); + let input_sum = ValueSum::zero(); let output_sum = self .vout .iter() - .map(|vo| { - if vo.value >= 0 { - Amount::from_pair(vo.asset_type, vo.value) - } else { - Err(()) - } - }) - .sum::>() + .map(|vo| ValueSum::from_pair(vo.asset_type, vo.value as i128)) + .sum::>() .map_err(|_| BalanceError::Overflow)?; // Cannot panic when subtracting two positive i64 @@ -208,7 +192,7 @@ impl TransparentAuthorizingContext for Unauthorized { #[cfg(feature = "transparent-inputs")] impl TransparentAuthorizingContext for Unauthorized { - fn input_amounts(&self) -> Vec<(AssetType, i64)> { + fn input_amounts(&self) -> Vec<(AssetType, u64)> { return self .inputs .iter() diff --git a/masp_primitives/src/transaction/components/transparent/fees.rs b/masp_primitives/src/transaction/components/transparent/fees.rs index cff0a544..99522494 100644 --- a/masp_primitives/src/transaction/components/transparent/fees.rs +++ b/masp_primitives/src/transaction/components/transparent/fees.rs @@ -16,7 +16,7 @@ pub trait InputView { /// fee and change computation. pub trait OutputView { /// Returns the value of the output being created. - fn value(&self) -> i64; + fn value(&self) -> u64; /// Returns the asset type of the output being created. fn asset_type(&self) -> AssetType; /// Returns the script corresponding to the newly created output. @@ -24,7 +24,7 @@ pub trait OutputView { } impl OutputView for TxOut { - fn value(&self) -> i64 { + fn value(&self) -> u64 { self.value } diff --git a/masp_primitives/src/transaction/fees.rs b/masp_primitives/src/transaction/fees.rs index 0108e20f..24d50aad 100644 --- a/masp_primitives/src/transaction/fees.rs +++ b/masp_primitives/src/transaction/fees.rs @@ -2,7 +2,7 @@ use crate::{ consensus::{self, BlockHeight}, - transaction::components::{amount::Amount, transparent::fees as transparent}, + transaction::components::{amount::U64Sum, transparent::fees as transparent}, }; pub mod fixed; @@ -24,5 +24,5 @@ pub trait FeeRule { transparent_outputs: &[impl transparent::OutputView], sapling_input_count: usize, sapling_output_count: usize, - ) -> Result; + ) -> Result; } diff --git a/masp_primitives/src/transaction/fees/fixed.rs b/masp_primitives/src/transaction/fees/fixed.rs index 02e3bd93..8ea28c72 100644 --- a/masp_primitives/src/transaction/fees/fixed.rs +++ b/masp_primitives/src/transaction/fees/fixed.rs @@ -1,7 +1,7 @@ use crate::{ consensus::{self, BlockHeight}, transaction::components::{ - amount::{Amount, DEFAULT_FEE}, + amount::{U64Sum, DEFAULT_FEE}, transparent::fees as transparent, }, }; @@ -10,12 +10,12 @@ use crate::{ /// the transaction being constructed. #[derive(Clone, Debug)] pub struct FeeRule { - fixed_fee: Amount, + fixed_fee: U64Sum, } impl FeeRule { /// Creates a new nonstandard fixed fee rule with the specified fixed fee. - pub fn non_standard(fixed_fee: Amount) -> Self { + pub fn non_standard(fixed_fee: U64Sum) -> Self { Self { fixed_fee } } @@ -27,7 +27,7 @@ impl FeeRule { } /// Returns the fixed fee amount which which this rule was configured. - pub fn fixed_fee(&self) -> Amount { + pub fn fixed_fee(&self) -> U64Sum { self.fixed_fee.clone() } } @@ -42,7 +42,7 @@ impl super::FeeRule for FeeRule { _transparent_outputs: &[impl transparent::OutputView], _sapling_input_count: usize, _sapling_output_count: usize, - ) -> Result { + ) -> Result { Ok(self.fixed_fee.clone()) } } diff --git a/masp_primitives/src/transaction/sighash.rs b/masp_primitives/src/transaction/sighash.rs index e6c9d3c4..c4d4f5d7 100644 --- a/masp_primitives/src/transaction/sighash.rs +++ b/masp_primitives/src/transaction/sighash.rs @@ -55,7 +55,7 @@ pub trait TransparentAuthorizingContext: transparent::Authorization { /// so that wallets can commit to the transparent input breakdown /// without requiring the full data of the previous transactions /// providing these inputs. - fn input_amounts(&self) -> Vec<(AssetType, i64)>; + fn input_amounts(&self) -> Vec<(AssetType, u64)>; } /// Computes the signature hash for an input to a transaction, given diff --git a/masp_proofs/benches/convert.rs b/masp_proofs/benches/convert.rs index da496ad1..60fbbc74 100644 --- a/masp_proofs/benches/convert.rs +++ b/masp_proofs/benches/convert.rs @@ -6,7 +6,7 @@ use bls12_381::Bls12; use criterion::Criterion; use group::ff::Field; use masp_primitives::{ - asset_type::AssetType, convert::AllowedConversion, transaction::components::Amount, + asset_type::AssetType, convert::AllowedConversion, transaction::components::ValueSum, }; use masp_proofs::circuit::convert::{Convert, TREE_DEPTH}; use rand_core::{RngCore, SeedableRng}; @@ -29,19 +29,19 @@ fn criterion_benchmark(c: &mut Criterion) { .unwrap(); c.bench_function("convert", |b| { - let i = rng.next_u32(); + let i = rng.next_u32() >> 1; let spend_asset = AssetType::new(format!("asset {}", i).as_bytes()).unwrap(); let output_asset = AssetType::new(format!("asset {}", i + 1).as_bytes()).unwrap(); let mint_asset = AssetType::new(b"reward").unwrap(); - let spend_value = -(i as i64 + 1); - let output_value = i as i64 + 1; - let mint_value = i as i64 + 1; + let spend_value = -(i as i32 + 1); + let output_value = i as i32 + 1; + let mint_value = i as i32 + 1; - let allowed_conversion: AllowedConversion = (Amount::from_pair(spend_asset, spend_value) + let allowed_conversion: AllowedConversion = (ValueSum::from_pair(spend_asset, spend_value) .unwrap() - + Amount::from_pair(output_asset, output_value).unwrap() - + Amount::from_pair(mint_asset, mint_value).unwrap()) + + ValueSum::from_pair(output_asset, output_value).unwrap() + + ValueSum::from_pair(mint_asset, mint_value).unwrap()) .into(); let value = rng.next_u64(); diff --git a/masp_proofs/src/circuit/convert.rs b/masp_proofs/src/circuit/convert.rs index d555ed67..c8bcb300 100644 --- a/masp_proofs/src/circuit/convert.rs +++ b/masp_proofs/src/circuit/convert.rs @@ -132,7 +132,7 @@ fn test_convert_circuit_with_bls12_381() { use group::{ff::Field, ff::PrimeField, ff::PrimeFieldBits, Curve}; use masp_primitives::{ asset_type::AssetType, convert::AllowedConversion, sapling::pedersen_hash, - transaction::components::Amount, + transaction::components::ValueSum, }; use rand_core::{RngCore, SeedableRng}; use rand_xorshift::XorShiftRng; @@ -150,14 +150,14 @@ fn test_convert_circuit_with_bls12_381() { let output_asset = AssetType::new(format!("asset {}", i + 1).as_bytes()).unwrap(); let mint_asset = AssetType::new(b"reward").unwrap(); - let spend_value = -(i as i64 + 1); - let output_value = i as i64 + 1; - let mint_value = i as i64 + 1; + let spend_value = -(i as i32 + 1); + let output_value = i as i32 + 1; + let mint_value = i as i32 + 1; - let allowed_conversion: AllowedConversion = (Amount::from_pair(spend_asset, spend_value) + let allowed_conversion: AllowedConversion = (ValueSum::from_pair(spend_asset, spend_value) .unwrap() - + Amount::from_pair(output_asset, output_value).unwrap() - + Amount::from_pair(mint_asset, mint_value).unwrap()) + + ValueSum::from_pair(output_asset, output_value).unwrap() + + ValueSum::from_pair(mint_asset, mint_value).unwrap()) .into(); let value = rng.next_u64(); diff --git a/masp_proofs/src/prover.rs b/masp_proofs/src/prover.rs index adefc463..355fb350 100644 --- a/masp_proofs/src/prover.rs +++ b/masp_proofs/src/prover.rs @@ -11,7 +11,7 @@ use masp_primitives::{ redjubjub::{PublicKey, Signature}, Diversifier, Node, PaymentAddress, ProofGenerationKey, Rseed, }, - transaction::components::{Amount, GROTH_PROOF_SIZE}, + transaction::components::{I128Sum, GROTH_PROOF_SIZE}, }; use std::path::Path; @@ -247,7 +247,7 @@ impl TxProver for LocalTxProver { fn binding_sig( &self, ctx: &mut Self::SaplingProvingContext, - assets_and_values: &Amount, //&[(AssetType, i64)], + assets_and_values: &I128Sum, //&[(AssetType, i64)], sighash: &[u8; 32], ) -> Result { ctx.binding_sig(assets_and_values, sighash) diff --git a/masp_proofs/src/sapling/mod.rs b/masp_proofs/src/sapling/mod.rs index eb14fc60..16d90e6e 100644 --- a/masp_proofs/src/sapling/mod.rs +++ b/masp_proofs/src/sapling/mod.rs @@ -9,11 +9,11 @@ pub use self::prover::SaplingProvingContext; pub use self::verifier::{BatchValidator, SaplingVerificationContext}; // This function computes `value` in the exponent of the value commitment base -fn masp_compute_value_balance(asset_type: AssetType, value: i64) -> Option { - // Compute the absolute value (failing if -i64::MAX is +fn masp_compute_value_balance(asset_type: AssetType, value: i128) -> Option { + // Compute the absolute value (failing if -i128::MAX is // the value) let abs = match value.checked_abs() { - Some(a) => a as u64, + Some(a) => a as u128, None => return None, }; @@ -21,7 +21,10 @@ fn masp_compute_value_balance(asset_type: AssetType, value: i64) -> Option Result { // Initialize secure RNG @@ -304,7 +304,7 @@ impl SaplingProvingContext { .components() .map(|(asset_type, value_balance)| { // Compute value balance for each asset - // Error for bad value balances (-INT64_MAX value) + // Error for bad value balances (-INT128_MAX value) masp_compute_value_balance(*asset_type, *value_balance) }) .try_fold(self.cv_sum, |tmp, value_balance| { diff --git a/masp_proofs/src/sapling/verifier.rs b/masp_proofs/src/sapling/verifier.rs index 22cceafb..c9c297dd 100644 --- a/masp_proofs/src/sapling/verifier.rs +++ b/masp_proofs/src/sapling/verifier.rs @@ -5,7 +5,7 @@ use bls12_381::Bls12; use group::{Curve, GroupEncoding}; use masp_primitives::{ sapling::redjubjub::{PublicKey, Signature}, - transaction::components::Amount, + transaction::components::I128Sum, }; use super::masp_compute_value_balance; @@ -172,7 +172,7 @@ impl SaplingVerificationContextInner { /// have been checked before calling this function. fn final_check( &self, - value_balance: Amount, + value_balance: I128Sum, sighash_value: &[u8; 32], binding_sig: Signature, binding_sig_verifier: impl FnOnce(PublicKey, [u8; 64], Signature) -> bool, diff --git a/masp_proofs/src/sapling/verifier/single.rs b/masp_proofs/src/sapling/verifier/single.rs index 2df7dfae..8abedb48 100644 --- a/masp_proofs/src/sapling/verifier/single.rs +++ b/masp_proofs/src/sapling/verifier/single.rs @@ -3,7 +3,7 @@ use bls12_381::Bls12; use masp_primitives::{ constants::{SPENDING_KEY_GENERATOR, VALUE_COMMITMENT_RANDOMNESS_GENERATOR}, sapling::redjubjub::{PublicKey, Signature}, - transaction::components::Amount, + transaction::components::I128Sum, }; use super::SaplingVerificationContextInner; @@ -98,7 +98,7 @@ impl SaplingVerificationContext { /// have been checked before calling this function. pub fn final_check( &self, - value_balance: Amount, + value_balance: I128Sum, sighash_value: &[u8; 32], binding_sig: Signature, ) -> bool { From d130e7011665ed28575e77942db50d1a73a15e24 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Thu, 10 Aug 2023 21:26:08 -0400 Subject: [PATCH 21/21] masp_proofs: export parameter-related constants (#48) Co-authored-by: joe <55120843+joebebel@users.noreply.github.com> --- masp_proofs/src/lib.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/masp_proofs/src/lib.rs b/masp_proofs/src/lib.rs index f5789821..d9cb7bd1 100644 --- a/masp_proofs/src/lib.rs +++ b/masp_proofs/src/lib.rs @@ -47,22 +47,22 @@ mod downloadreader; // Circuit names /// The MASP spend parameters file name. -const MASP_SPEND_NAME: &str = "masp-spend.params"; +pub const MASP_SPEND_NAME: &str = "masp-spend.params"; /// The MASP output parameters file name. -const MASP_OUTPUT_NAME: &str = "masp-output.params"; +pub const MASP_OUTPUT_NAME: &str = "masp-output.params"; /// The MASP convert parameters file name. -const MASP_CONVERT_NAME: &str = "masp-convert.params"; +pub const MASP_CONVERT_NAME: &str = "masp-convert.params"; // Circuit hashes -const MASP_SPEND_HASH: &str = "196e7c717f25e16653431559ce2c8816e750a4490f98696e3c031efca37e25e0647182b7b013660806db11eb2b1e365fb2d6a0f24dbbd9a4a8314fef10a7cba2"; -const MASP_OUTPUT_HASH: &str = "eafc3b1746cccc8b9eed2b69395692c5892f6aca83552a07dceb2dcbaa64dcd0e22434260b3aa3b049b633a08b008988cbe0d31effc77e2bc09bfab690a23724"; -const MASP_CONVERT_HASH: &str = "dc4aaf3c3ce056ab448b6c4a7f43c1d68502c2902ea89ab8769b1524a2e8ace9a5369621a73ee1daa52aec826907a19974a37874391cf8f11bbe0b0420de1ab7"; +pub const MASP_SPEND_HASH: &str = "196e7c717f25e16653431559ce2c8816e750a4490f98696e3c031efca37e25e0647182b7b013660806db11eb2b1e365fb2d6a0f24dbbd9a4a8314fef10a7cba2"; +pub const MASP_OUTPUT_HASH: &str = "eafc3b1746cccc8b9eed2b69395692c5892f6aca83552a07dceb2dcbaa64dcd0e22434260b3aa3b049b633a08b008988cbe0d31effc77e2bc09bfab690a23724"; +pub const MASP_CONVERT_HASH: &str = "dc4aaf3c3ce056ab448b6c4a7f43c1d68502c2902ea89ab8769b1524a2e8ace9a5369621a73ee1daa52aec826907a19974a37874391cf8f11bbe0b0420de1ab7"; // Circuit parameter file sizes -const MASP_SPEND_BYTES: u64 = 49848572; -const MASP_CONVERT_BYTES: u64 = 22570940; -const MASP_OUTPUT_BYTES: u64 = 16398620; +pub const MASP_SPEND_BYTES: u64 = 49848572; +pub const MASP_CONVERT_BYTES: u64 = 22570940; +pub const MASP_OUTPUT_BYTES: u64 = 16398620; #[cfg(feature = "download-params")] const DOWNLOAD_URL: &str =