├── .github └── workflows │ ├── bench.yml │ ├── book.yml │ ├── ci.yml │ ├── lints-beta.yml │ └── lints-stable.yml ├── .gitignore ├── CHANGELOG.md ├── COPYING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── benches ├── circuit.rs ├── note_decryption.rs └── small.rs ├── book ├── .gitignore ├── book.toml ├── macros.txt └── src │ ├── IDENTIFIERS.json │ ├── README.md │ ├── SUMMARY.md │ ├── concepts.md │ ├── concepts │ └── preliminaries.md │ ├── design.md │ ├── design │ ├── actions.md │ ├── circuit.md │ ├── circuit │ │ ├── commit-ivk.md │ │ ├── gadgets.md │ │ └── note-commit.md │ ├── commitment-tree.md │ ├── commitments.md │ ├── keys.md │ ├── nullifiers.md │ └── signatures.md │ ├── user.md │ └── user │ ├── creating-notes.md │ ├── integration.md │ ├── keys.md │ └── spending-notes.md ├── katex-header.html ├── proptest-regressions └── constants │ └── util.txt ├── rust-toolchain.toml ├── src ├── action.rs ├── address.rs ├── builder.rs ├── bundle.rs ├── bundle │ ├── batch.rs │ └── commitments.rs ├── circuit.rs ├── circuit │ ├── commit_ivk.rs │ ├── gadget.rs │ ├── gadget │ │ └── add_chip.rs │ └── note_commit.rs ├── circuit_description ├── circuit_proof_test_case.bin ├── constants.rs ├── constants │ ├── fixed_bases.rs │ ├── fixed_bases │ │ ├── commit_ivk_r.rs │ │ ├── note_commit_r.rs │ │ ├── nullifier_k.rs │ │ ├── spend_auth_g.rs │ │ ├── value_commit_r.rs │ │ └── value_commit_v.rs │ ├── load.rs │ ├── sinsemilla.rs │ └── util.rs ├── keys.rs ├── lib.rs ├── note.rs ├── note │ ├── commitment.rs │ └── nullifier.rs ├── note_encryption.rs ├── pczt.rs ├── pczt │ ├── io_finalizer.rs │ ├── parse.rs │ ├── prover.rs │ ├── signer.rs │ ├── tx_extractor.rs │ ├── updater.rs │ └── verify.rs ├── primitives.rs ├── primitives │ └── redpallas.rs ├── spec.rs ├── test_vectors.rs ├── test_vectors │ ├── commitment_tree.rs │ ├── keys.rs │ ├── merkle_path.rs │ ├── note_encryption.rs │ └── zip32.rs ├── tree.rs ├── value.rs └── zip32.rs └── tests └── builder.rs /.github/workflows/bench.yml: -------------------------------------------------------------------------------- 1 | name: Benchmarks 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | permissions: 8 | contents: write 9 | deployments: write 10 | 11 | jobs: 12 | benchmark: 13 | name: Performance regression check 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Run benchmark 18 | run: cargo bench -- --output-format bencher | tee output.txt 19 | - name: Store benchmark result 20 | uses: benchmark-action/github-action-benchmark@v1 21 | with: 22 | name: Orchard Benchmarks 23 | tool: 'cargo' 24 | output-file-path: output.txt 25 | github-token: ${{ secrets.GITHUB_TOKEN }} 26 | auto-push: true 27 | # Show alert with commit comment on detecting possible performance regression 28 | alert-threshold: '200%' 29 | comment-on-alert: true 30 | fail-on-alert: true 31 | alert-comment-cc-users: '@str4d' 32 | -------------------------------------------------------------------------------- /.github/workflows/book.yml: -------------------------------------------------------------------------------- 1 | name: Orchard book 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | with: 14 | fetch-depth: 0 15 | 16 | - name: Install mdbook and mdbook-katex 17 | run: | 18 | mkdir mdbook 19 | curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.35/mdbook-v0.4.35-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=./mdbook 20 | curl -sSL https://github.com/lzanini/mdbook-katex/releases/download/v0.5.8pub/mdbook-katex-v0.5.8-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=./mdbook 21 | echo `pwd`/mdbook >> $GITHUB_PATH 22 | 23 | - name: Build Orchard book 24 | run: mdbook build book/ 25 | 26 | - name: Deploy to GitHub Pages 27 | uses: peaceiris/actions-gh-pages@v3 28 | with: 29 | github_token: ${{ secrets.GITHUB_TOKEN }} 30 | publish_dir: ./book/book 31 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI checks 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | name: Test on ${{ matrix.os }} 8 | runs-on: ${{ matrix.os }} 9 | strategy: 10 | matrix: 11 | os: [ubuntu-latest, windows-latest, macOS-latest] 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Run tests 15 | run: cargo test --verbose 16 | - name: Verify working directory is clean 17 | run: git diff --exit-code 18 | 19 | build-latest: 20 | name: Latest build on ${{ matrix.os }} 21 | runs-on: ${{ matrix.os }} 22 | strategy: 23 | matrix: 24 | os: [ubuntu-latest, windows-latest, macOS-latest] 25 | steps: 26 | - uses: actions/checkout@v4 27 | - uses: dtolnay/rust-toolchain@stable 28 | id: toolchain 29 | - run: rustup override set ${{steps.toolchain.outputs.name}} 30 | - if: matrix.os == 'ubuntu-latest' 31 | run: sudo apt-get -y install libfontconfig1-dev 32 | - name: Remove lockfile to build with latest dependencies 33 | run: rm Cargo.lock 34 | - name: Build crate 35 | run: cargo build --all-features --verbose 36 | - name: Verify working directory is clean (excluding lockfile) 37 | run: git diff --exit-code ':!Cargo.lock' 38 | 39 | build-nostd: 40 | name: Build no_std for target ${{ matrix.target }} 41 | runs-on: ubuntu-latest 42 | strategy: 43 | matrix: 44 | target: 45 | - wasm32-wasi 46 | - thumbv7em-none-eabihf 47 | steps: 48 | - uses: actions/checkout@v4 49 | with: 50 | path: crate_root 51 | # We use a synthetic crate to ensure no dev-dependencies are enabled, which can 52 | # be incompatible with some of these targets. 53 | - name: Create synthetic crate for testing 54 | run: cargo init --edition 2021 --lib ci-build 55 | - name: Copy Rust version into synthetic crate 56 | run: cp crate_root/rust-toolchain.toml ci-build/ 57 | - name: Copy patch directives into synthetic crate 58 | run: | 59 | echo "[patch.crates-io]" >> ./ci-build/Cargo.toml 60 | cat ./crate_root/Cargo.toml | sed "0,/.\+\(patch.crates.\+\)/d" >> ./ci-build/Cargo.toml 61 | - name: Add no_std pragma to lib.rs 62 | run: | 63 | echo "#![no_std]" > ./ci-build/src/lib.rs 64 | - name: Add dependencies of the synthetic crate (e.g. sapling-crypto) 65 | working-directory: ./ci-build 66 | run: cargo add --no-default-features --path ../crate_root 67 | - name: Add lazy_static with the spin_no_std feature 68 | working-directory: ./ci-build 69 | run: cargo add lazy_static --no-default-features --features "spin_no_std" 70 | - name: Add typenum with the no_std feature 71 | working-directory: ./ci-build 72 | run: cargo add typenum --no-default-features --features "no_std" 73 | - name: Show Cargo.toml for the synthetic crate 74 | working-directory: ./ci-build 75 | run: cat Cargo.toml 76 | - name: Add target 77 | working-directory: ./ci-build 78 | run: rustup target add ${{ matrix.target }} 79 | - name: Build no_std for target 80 | working-directory: ./ci-build 81 | run: cargo build --verbose --target ${{ matrix.target }} 82 | 83 | bitrot: 84 | name: Bitrot check 85 | runs-on: ubuntu-latest 86 | steps: 87 | - uses: actions/checkout@v4 88 | # Build benchmarks to prevent bitrot 89 | - name: Build benchmarks 90 | run: cargo build --benches 91 | 92 | book: 93 | name: Book tests 94 | runs-on: ubuntu-latest 95 | steps: 96 | - uses: actions/checkout@v4 97 | - name: Install mdbook 98 | run: | 99 | mkdir mdbook 100 | curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.35/mdbook-v0.4.35-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=./mdbook 101 | echo `pwd`/mdbook >> $GITHUB_PATH 102 | - name: Test Orchard book 103 | run: mdbook test book/ 104 | 105 | codecov: 106 | name: Code coverage 107 | runs-on: ubuntu-latest 108 | container: 109 | image: xd009642/tarpaulin:develop-nightly 110 | options: --security-opt seccomp=unconfined 111 | steps: 112 | - uses: actions/checkout@v4 113 | - name: Generate coverage report 114 | run: cargo tarpaulin --engine llvm --all-features --release --timeout 600 --out xml 115 | - name: Upload coverage to Codecov 116 | uses: codecov/codecov-action@v3.1.4 117 | 118 | doc-links: 119 | name: Intra-doc links 120 | runs-on: ubuntu-latest 121 | steps: 122 | - uses: actions/checkout@v4 123 | - run: cargo fetch 124 | # Requires #![deny(rustdoc::broken_intra_doc_links)] in crates. 125 | - run: sudo apt-get -y install libfontconfig1-dev 126 | - name: Check intra-doc links 127 | run: cargo doc --all-features --document-private-items 128 | 129 | fmt: 130 | name: Rustfmt 131 | timeout-minutes: 30 132 | runs-on: ubuntu-latest 133 | steps: 134 | - uses: actions/checkout@v4 135 | - run: cargo fmt -- --check 136 | -------------------------------------------------------------------------------- /.github/workflows/lints-beta.yml: -------------------------------------------------------------------------------- 1 | name: Nightly lints 2 | 3 | # These lints are only informative, so we only run them directly on branches 4 | # and not trial-merges of PRs, to reduce noise. 5 | on: push 6 | 7 | jobs: 8 | clippy-beta: 9 | name: Clippy (beta) 10 | timeout-minutes: 30 11 | runs-on: ubuntu-latest 12 | continue-on-error: true 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: dtolnay/rust-toolchain@beta 16 | id: toolchain 17 | with: 18 | components: clippy 19 | - run: rustup override set ${{steps.toolchain.outputs.name}} 20 | - name: Run Clippy 21 | uses: auguwu/clippy-action@1.3.0 22 | continue-on-error: true 23 | with: 24 | token: ${{ secrets.GITHUB_TOKEN }} 25 | working-directory: ${{ inputs.target }} 26 | warn: clippy::all 27 | -------------------------------------------------------------------------------- /.github/workflows/lints-stable.yml: -------------------------------------------------------------------------------- 1 | name: Stable lints 2 | 3 | # We only run these lints on trial-merges of PRs to reduce noise. 4 | on: pull_request 5 | 6 | jobs: 7 | clippy: 8 | name: Clippy (MSRV) 9 | timeout-minutes: 30 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: Run Clippy 14 | uses: auguwu/clippy-action@1.3.0 15 | with: 16 | token: ${{ secrets.GITHUB_TOKEN }} 17 | working-directory: ${{ inputs.target }} 18 | deny: warnings 19 | 20 | clippy-nostd: 21 | name: Clippy (MSRV) no_std for ${{ matrix.target }} 22 | runs-on: ubuntu-latest 23 | strategy: 24 | matrix: 25 | target: 26 | - wasm32-wasi 27 | steps: 28 | - uses: actions/checkout@v4 29 | with: 30 | path: crate_root 31 | # We use a synthetic crate to ensure no dev-dependencies are enabled, which can 32 | # be incompatible with some of these targets. 33 | - name: Create synthetic crate for testing 34 | run: cargo init --edition 2021 --lib ci-build 35 | - name: Copy Rust version into synthetic crate 36 | run: cp crate_root/rust-toolchain.toml ci-build/ 37 | - name: Copy patch directives into synthetic crate 38 | run: | 39 | echo "[patch.crates-io]" >> ./ci-build/Cargo.toml 40 | cat ./crate_root/Cargo.toml | sed "0,/.\+\(patch.crates.\+\)/d" >> ./ci-build/Cargo.toml 41 | - name: Add no_std pragma to lib.rs 42 | run: | 43 | echo "#![no_std]" > ./ci-build/src/lib.rs 44 | - name: Add dependencies of the synthetic crate (e.g. sapling-crypto) 45 | working-directory: ./ci-build 46 | run: cargo add --no-default-features --path ../crate_root 47 | - name: Add lazy_static with the spin_no_std feature 48 | working-directory: ./ci-build 49 | run: cargo add lazy_static --no-default-features --features "spin_no_std" 50 | - name: Add typenum with the no_std feature 51 | working-directory: ./ci-build 52 | run: cargo add typenum --no-default-features --features "no_std" 53 | - name: Show Cargo.toml for the synthetic crate 54 | working-directory: ./ci-build 55 | run: cat Cargo.toml 56 | - name: Add target 57 | working-directory: ./ci-build 58 | run: rustup target add ${{ matrix.target }} 59 | - name: Clippy no_std for target 60 | working-directory: ./ci-build 61 | run: cargo clippy --verbose --target ${{ matrix.target }} 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | .vscode 4 | action-circuit-layout.png 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to Rust's notion of 6 | [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [0.11.0] - 2025-02-20 11 | 12 | ### Added 13 | - `orchard::pczt::Zip32Derivation::extract_account_index` 14 | 15 | ### Changed 16 | - MSRV is now 1.70 17 | - Migrated to `nonempty 0.11`, `incrementalmerkletree 0.8`, `shardtree 0.6`, 18 | `zcash_spec 0.2`, `zip32 0.2` 19 | - `orchard::builder::Builder::add_output` now takes a `[u8; 512]` for its 20 | `memo` argument instead of an optional value. 21 | 22 | ## [0.10.1] - 2024-12-16 23 | 24 | ### Added 25 | - Support for Partially-Created Zcash Transactions: 26 | - `orchard::builder::Builder::build_for_pczt` 27 | - `orchard::note_encryption`: 28 | - `OrchardDomain::for_pczt_action` 29 | - `impl ShieldedOutput for orchard::pczt::Action` 30 | - `orchard::pczt` module. 31 | - `orchard::bundle::EffectsOnly` 32 | - `orchard::tree::MerklePath::{position, auth_path}` 33 | - `orchard::value`: 34 | - `Sign` 35 | - `ValueSum::magnitude_sign` 36 | - `ValueCommitTrapdoor::to_bytes` 37 | - `impl Clone for orchard::tree::MerklePath` 38 | 39 | ## [0.10.0] - 2024-10-02 40 | 41 | ### Changed 42 | - Migrated to `incrementalmerkletree 0.7`. 43 | 44 | ## [0.9.1] - 2024-08-13 45 | 46 | ### Changed 47 | - Migrated to `visibility 0.1.1`. 48 | 49 | ## [0.9.0] - 2024-08-12 50 | 51 | ### Added 52 | - `orchard::keys::SpendValidatingKey::{from_bytes, to_bytes}` behind the 53 | `unstable-frost` feature flag. These are temporary APIs exposed for development 54 | purposes, and will be replaced by type-safe FROST APIs once ZIP 312 key 55 | generation is specified (https://github.com/zcash/zips/pull/883). 56 | 57 | ### Changed 58 | - Migrated to `incrementalmerkletree 0.6`. 59 | 60 | ## [0.8.0] - 2024-03-25 61 | 62 | ### Added 63 | - `orchard::keys::IncomingViewingKey::prepare` 64 | - `orchard::note::Rho` 65 | - `orchard::action::Action::rho` 66 | - `orchard::note_encryption::CompactAction::rho` 67 | - `orchard::note_encryption::OrchardDomain::for_compact_action` 68 | - Additions under the `test-dependencies` feature flag: 69 | - `orchard::tree::MerkleHashOrchard::random` 70 | - `impl Distribution for Standard` 71 | 72 | ### Changed 73 | - The following methods have their `Nullifier`-typed argument or return value 74 | now take or return `note::Rho` instead: 75 | - `orchard::note::RandomSeed::from_bytes` 76 | - `orchard::note::Note::from_parts` 77 | - `orchard::note::Note::rho` 78 | 79 | ### Removed 80 | - `orchard::note_encryption::OrchardDomain::for_nullifier` (use `for_action` 81 | or `for_compact_action` instead). 82 | 83 | ## [0.7.1] - 2024-02-29 84 | ### Added 85 | - `impl subtle::ConstantTimeEq for orchard::note::Nullifier` 86 | - `orchard::note_encryption`: 87 | - `CompactAction::cmx` 88 | - `impl Clone for CompactAction` 89 | 90 | ## [0.7.0] - 2024-01-26 91 | ### Licensing 92 | - The license for this crate is now "MIT OR Apache-2.0". The license 93 | exception that applied to the Zcash and Zebra projects, other projects 94 | designed to integrate with Zcash, and certain forks of Zcash, is no longer 95 | necessary. For clarity, this is intended to be a strict relaxation of the 96 | previous licensing, i.e. it permits all usage that was previously possible 97 | with or without use of the license exception. 98 | 99 | ### Added 100 | - `orchard::builder`: 101 | - `bundle` 102 | - `BundleMetadata` 103 | - `BundleType` 104 | - `OutputInfo` 105 | - `orchard::bundle::Flags::{ENABLED, SPENDS_DISABLED, OUTPUTS_DISABLED}` 106 | - `orchard::tree::Anchor::empty_tree` 107 | 108 | ### Changed 109 | - Migrated to the `zip32` crate. The following types have been replaced by the 110 | equivalent ones in that crate are now re-exported from there: 111 | - `orchard::keys::{DiversifierIndex, Scope}` 112 | - `orchard::zip32::ChildIndex` 113 | - `orchard::builder`: 114 | - `Builder::new` now takes the bundle type to be used in bundle construction, 115 | instead of taking the flags and anchor separately. 116 | - `Builder::add_recipient` has been renamed to `add_output` in order to 117 | clarify than more than one output of a given transaction may be sent to the 118 | same recipient. 119 | - `Builder::build` now takes an additional `BundleType` argument that 120 | specifies how actions should be padded, instead of using hardcoded padding. 121 | It also now returns a `Result, BundleMetadata)>, ...>` 122 | instead of a `Result, ...>`. 123 | - `BuildError` has additional variants: 124 | - `SpendsDisabled` 125 | - `OutputsDisabled` 126 | - `AnchorMismatch` 127 | - `SpendInfo::new` now returns a `Result` instead of an 128 | `Option`. 129 | - `orchard::keys::SpendingKey::from_zip32_seed` now takes a `zip32::AccountId`. 130 | 131 | ### Removed 132 | - `orchard::bundle::Flags::from_parts` 133 | 134 | ## [0.6.0] - 2023-09-08 135 | ### Changed 136 | - MSRV is now 1.65.0. 137 | - Migrated to `incrementalmerkletree 0.5`. 138 | 139 | ## [0.5.0] - 2023-06-06 140 | ### Changed 141 | - Migrated to `zcash_note_encryption 0.4`, `incrementalmerkletree 0.4`, `bridgetree 0.3`. 142 | `bridgetree` is now exclusively a test dependency. 143 | 144 | ## [0.4.0] - 2023-04-11 145 | ### Added 146 | - `orchard::builder`: 147 | - `{SpendInfo::new, InputView, OutputView}` 148 | - `Builder::{spends, outputs}` 149 | - `SpendError` 150 | - `OutputError` 151 | - `orchard::keys`: 152 | - `PreparedEphemeralPublicKey` 153 | - `PreparedIncomingViewingKey` 154 | - impls of `memuse::DynamicUsage` for: 155 | - `orchard::note::Nullifier` 156 | - `orchard::note_encryption::OrchardDomain` 157 | - impls of `Eq` for: 158 | - `orchard::zip32::ChildIndex` 159 | - `orchard::value::ValueSum` 160 | 161 | ### Changed 162 | - MSRV is now 1.60.0. 163 | - Migrated to `ff 0.13`, `group 0.13`, `pasta_curves 0.5`, `halo2_proofs 0.3`, 164 | `halo2_gadgets 0.3`, `reddsa 0.5`, `zcash_note_encryption 0.3`. 165 | - `orchard::builder`: 166 | - `Builder::{add_spend, add_output}` now use concrete error types instead of 167 | `&'static str`s. 168 | - `Error` has been renamed to `BuildError` to differentiate from new error 169 | types. 170 | - `BuildError` now implements `std::error::Error` and `std::fmt::Display`. 171 | 172 | ### Fixed 173 | - Several bugs have been fixed that were preventing Orchard bundles from being 174 | created or verified on 32-bit platforms, or with recent versions of Rust. 175 | 176 | ## [0.3.0] - 2022-10-19 177 | ### Added 178 | - `orchard::Proof::add_to_batch` 179 | - `orchard::address::Address::diversifier` 180 | - `orchard::keys::Diversifier::from_bytes` 181 | - `orchard::note`: 182 | - `RandomSeed` 183 | - `Note::{from_parts, rseed}` 184 | - `orchard::circuit::Circuit::from_action_context` 185 | 186 | ### Changed 187 | - Migrated to `zcash_note_encryption 0.2`. 188 | 189 | ## [0.2.0] - 2022-06-24 190 | ### Added 191 | - `orchard::bundle::BatchValidator` 192 | - `orchard::builder::Builder::value_balance` 193 | - `orchard::note_encryption`: 194 | - `CompactAction::from_parts` 195 | - `CompactAction::nullifier` 196 | - `OrchardDomain::for_nullifier` 197 | - Low-level APIs in `orchard::value` for handling `ValueCommitment`s. 198 | These are useful in code that constructs proof witnesses itself, but 199 | note that doing so requires a detailed knowledge of the Zcash protocol 200 | to avoid privacy and correctness pitfalls. 201 | - `ValueCommitTrapdoor` 202 | - `ValueCommitment::derive` 203 | 204 | ### Changed 205 | - Migrated to `halo2_proofs 0.2`. 206 | 207 | ## [0.1.0] - 2022-05-10 208 | ### Changed 209 | - Migrated to `bitvec 1`, `ff 0.12`, `group 0.12`, `incrementalmerkletree 0.3`, 210 | `pasta_curves 0.4`, `halo2_proofs 0.1`, `reddsa 0.3`. 211 | - `orchard::bundle`: 212 | - `Action` has been moved to `orchard::Action`. 213 | - `Bundle::{try_}authorize` have been renamed to 214 | `Bundle::{try_}map_authorization`. 215 | - `Flags::from_byte` now returns `Option` instead of 216 | `io::Result`. 217 | - `impl Sub for orchard::value::NoteValue` now returns `ValueSum` instead of 218 | `Option`, as the result is guaranteed to be within the valid range 219 | of `ValueSum`. 220 | 221 | ## [0.1.0-beta.3] - 2022-04-06 222 | ### Added 223 | - `orchard::keys`: 224 | - `Scope` enum, for distinguishing external and internal scopes for viewing 225 | keys and addresses. 226 | - `FullViewingKey::{to_ivk, to_ovk}`, which each take a `Scope` argument. 227 | - `FullViewingKey::scope_for_address` 228 | 229 | ### Changed 230 | - Migrated to `halo2_proofs 0.1.0-beta.4`, `incrementalmerkletree 0.3.0-beta.2`. 231 | - `orchard::builder`: 232 | - `Builder::add_spend` now requires that the `FullViewingKey` matches the 233 | given `Note`, and handles any scoping itself (instead of requiring the 234 | caller to pass the `FullViewingKey` for the correct scope). 235 | - `orchard::keys`: 236 | - `FullViewingKey::{address, address_at}` now each take a `Scope` argument. 237 | 238 | ### Removed 239 | - `orchard::keys`: 240 | - `FullViewingKey::derive_internal` 241 | - `impl From<&FullViewingKey> for IncomingViewingKey` (use 242 | `FullViewingKey::to_ivk` instead). 243 | - `impl From<&FullViewingKey> for OutgoingViewingKey` (use 244 | `FullViewingKey::to_ovk` instead). 245 | 246 | ## [0.1.0-beta.2] - 2022-03-22 247 | ### Added 248 | - `orchard::keys`: 249 | - `DiversifierIndex::to_bytes` 250 | - `FullViewingKey::derive_internal` 251 | - `IncomingViewingKey::diversifier_index` 252 | - `orchard::note`: 253 | - `impl PartialEq, Eq, PartialOrd, Ord for Nullifier` 254 | - `orchard::primitives::redpallas::VerificationKey::verify` 255 | - `orchard::tree`: 256 | - `MerklePath::from_parts` 257 | - `impl PartialEq, Eq, PartialOrd, Ord for MerkleHashOrchard` 258 | - `impl From for [u8; 32]` 259 | - `Clone` impls for various structs: 260 | - `orchard::Bundle::{recover_outputs_with_ovks, recover_output_with_ovk}` 261 | - `orchard::builder`: 262 | - `InProgress, SigningMetadata, SigningParts, Unauthorized, Unproven` 263 | - `orchard::circuit::Circuit` 264 | - `orchard::keys::SpendAuthorizingKey` 265 | - `orchard::primitives::redpallas::SigningKey` 266 | 267 | ### Changed 268 | - MSRV is now 1.56.1. 269 | - Bumped dependencies to `pasta_curves 0.3`, `halo2_proofs 0.1.0-beta.3`. 270 | - The following methods now have an additional `rng: impl RngCore` argument: 271 | - `orchard::builder::Bundle::create_proof` 272 | - `orchard::builder::InProgress::create_proof` 273 | - `orchard::circuit::Proof::create` 274 | - `orchard::Bundle::commitment` now requires the bound `V: Copy + Into` 275 | instead of `i64: From<&'a V>`. 276 | - `orchard::Bundle::binding_validating_key` now requires the bound 277 | `V: Into` instead of `V: Into`. 278 | - `orchard::builder::InProgressSignatures` and `orchard::bundle::Authorization` 279 | now have `Debug` bounds on themselves and their associated types. 280 | 281 | ### Removed 282 | - `orchard::bundle`: 283 | - `commitments::hash_bundle_txid_data` (use `Bundle::commitment` instead). 284 | - `commitments::hash_bundle_auth_data` (use `Bundle::authorizing_commitment` 285 | instead). 286 | - `orchard::keys`: 287 | - `FullViewingKey::default_address` 288 | - `IncomingViewingKey::default_address` 289 | - `DiversifierKey` (use the APIs on `FullViewingKey` and `IncomingViewingKey` 290 | instead). 291 | - `impl std::hash::Hash for orchard::tree::MerkleHashOrchard` (use `BTreeMap` 292 | instead of `HashMap`). 293 | - `orchard::value::ValueSum::from_raw` 294 | 295 | ## [0.1.0-beta.1] - 2021-12-17 296 | Initial release! 297 | -------------------------------------------------------------------------------- /COPYING.md: -------------------------------------------------------------------------------- 1 | # License 2 | 3 | Licensed under either of 4 | 5 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 7 | 8 | at your option. 9 | 10 | # Contribution 11 | 12 | Unless you explicitly state otherwise, any contribution intentionally 13 | submitted for inclusion in the work by you, as defined in the Apache-2.0 14 | license, shall be dual licensed as above, without any additional terms or 15 | conditions. 16 | 17 | # Note on license change 18 | 19 | The licensing of this crate changed from the Bootstrap Open Source License 20 | to the above "MIT OR Apache-2.0" dual license in release 0.7.0. The license 21 | exception that applied to the Zcash and Zebra projects, other projects 22 | designed to integrate with Zcash, and certain forks of Zcash, is no longer 23 | necessary. For clarity, this is intended to be a strict relaxation of the 24 | previous licensing, i.e. it permits all usage that was previously possible 25 | with or without use of the license exception. 26 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "orchard" 3 | version = "0.11.0" 4 | authors = [ 5 | "Sean Bowe ", 6 | "Jack Grigg ", 7 | "Daira-Emma Hopwood ", 8 | "Ying Tong Lai", 9 | "Kris Nuttycombe ", 10 | ] 11 | edition = "2021" 12 | rust-version = "1.70" 13 | description = "The Orchard shielded transaction protocol" 14 | license = "MIT OR Apache-2.0" 15 | repository = "https://github.com/zcash/orchard" 16 | documentation = "https://docs.rs/orchard" 17 | readme = "README.md" 18 | categories = ["cryptography::cryptocurrencies"] 19 | keywords = ["zcash"] 20 | 21 | [package.metadata.docs.rs] 22 | all-features = true 23 | rustdoc-args = ["--cfg", "docsrs", "--html-in-header", "katex-header.html"] 24 | 25 | [dependencies] 26 | aes = "0.8" 27 | bitvec = { version = "1", default-features = false } 28 | blake2b_simd = { version = "1", default-features = false } 29 | ff = { version = "0.13", default-features = false } 30 | fpe = { version = "0.6", default-features = false, features = ["alloc"] } 31 | group = "0.13" 32 | hex = { version = "0.4", default-features = false, features = ["alloc"] } 33 | lazy_static = "1" 34 | memuse = { version = "0.2.2", default-features = false } 35 | pasta_curves = "0.5" 36 | proptest = { version = "1.0.0", optional = true } 37 | rand = { version = "0.8", default-features = false } 38 | reddsa = { version = "0.5", default-features = false } 39 | nonempty = { version = "0.11", default-features = false } 40 | poseidon = { package = "halo2_poseidon", version = "0.1" } 41 | serde = { version = "1.0", default-features = false, features = ["derive"] } 42 | sinsemilla = "0.1" 43 | subtle = { version = "2.3", default-features = false } 44 | zcash_note_encryption = "0.4" 45 | incrementalmerkletree = "0.8.1" 46 | zcash_spec = "0.2.1" 47 | zip32 = { version = "0.2.0", default-features = false } 48 | visibility = "0.1.1" 49 | 50 | # Circuit 51 | halo2_gadgets = { version = "0.3", optional = true, default-features = false } 52 | halo2_proofs = { version = "0.3", optional = true, default-features = false, features = ["batch", "floor-planner-v1-legacy-pdqsort"] } 53 | 54 | # Boilerplate 55 | getset = "0.1" 56 | 57 | # Logging 58 | tracing = { version = "0.1", default-features = false } 59 | 60 | # No-std support 61 | core2 = { version = "0.3", default-features = false, features = ["alloc"] } 62 | 63 | # Developer tooling dependencies 64 | image = { version = "0.24", optional = true } 65 | plotters = { version = "0.3.0", optional = true } 66 | 67 | [dev-dependencies] 68 | criterion = "0.4" # 0.5 depends on clap 4 which has MSRV 1.70 69 | halo2_gadgets = { version = "0.3", features = ["test-dependencies"] } 70 | hex = "0.4" 71 | proptest = "1.0.0" 72 | zcash_note_encryption = { version = "0.4", features = ["pre-zip-212"] } 73 | incrementalmerkletree = { version = "0.8.1", features = ["test-dependencies"] } 74 | shardtree = "0.6" 75 | 76 | [target.'cfg(unix)'.dev-dependencies] 77 | inferno = { version = "0.11", default-features = false, features = ["multithreaded", "nameattr"] } 78 | pprof = { version = "0.11", features = ["criterion", "flamegraph"] } 79 | 80 | [lib] 81 | bench = false 82 | 83 | [features] 84 | default = ["circuit", "multicore", "std"] 85 | std = ["core2/std", "group/wnaf-memuse", "reddsa/std"] 86 | circuit = ["dep:halo2_gadgets", "dep:halo2_proofs", "std"] 87 | unstable-frost = [] 88 | multicore = ["halo2_proofs?/multicore"] 89 | dev-graph = ["halo2_proofs?/dev-graph", "image", "plotters"] 90 | test-dependencies = ["proptest", "rand/std"] 91 | 92 | [[bench]] 93 | name = "note_decryption" 94 | harness = false 95 | 96 | [[bench]] 97 | name = "small" 98 | harness = false 99 | 100 | [[bench]] 101 | name = "circuit" 102 | harness = false 103 | 104 | [profile.release] 105 | debug = true 106 | 107 | [profile.bench] 108 | debug = true 109 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020-2023 The Electric Coin Company 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # orchard [![Crates.io](https://img.shields.io/crates/v/orchard.svg)](https://crates.io/crates/orchard) # 2 | 3 | Requires Rust 1.66+. 4 | 5 | ## Documentation 6 | 7 | - [The Orchard Book](https://zcash.github.io/orchard/) 8 | - [Crate documentation](https://docs.rs/orchard) 9 | 10 | ## `no_std` compatibility 11 | 12 | In order to take advantage of `no_std` builds, downstream users of this crate 13 | must enable: 14 | 15 | * the `spin_no_std` feature of the `lazy_static` crate; and 16 | * the `no_std` feature of the `typenum` crate. 17 | 18 | This is needed because the `--no-default-features` builds of these crates still 19 | rely on `std`. 20 | 21 | ## License 22 | 23 | Copyright 2020-2023 The Electric Coin Company. 24 | 25 | All code in this workspace is licensed under either of 26 | 27 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 28 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 29 | 30 | at your option. 31 | 32 | ### Contribution 33 | 34 | Unless you explicitly state otherwise, any contribution intentionally 35 | submitted for inclusion in the work by you, as defined in the Apache-2.0 36 | license, shall be dual licensed as above, without any additional terms or 37 | conditions. 38 | -------------------------------------------------------------------------------- /benches/circuit.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate criterion; 3 | 4 | use criterion::{BenchmarkId, Criterion}; 5 | 6 | #[cfg(unix)] 7 | use pprof::criterion::{Output, PProfProfiler}; 8 | 9 | use orchard::{ 10 | builder::{Builder, BundleType}, 11 | circuit::{ProvingKey, VerifyingKey}, 12 | keys::{FullViewingKey, Scope, SpendingKey}, 13 | value::NoteValue, 14 | Anchor, Bundle, 15 | }; 16 | use rand::rngs::OsRng; 17 | 18 | fn criterion_benchmark(c: &mut Criterion) { 19 | let rng = OsRng; 20 | 21 | let sk = SpendingKey::from_bytes([7; 32]).unwrap(); 22 | let recipient = FullViewingKey::from(&sk).address_at(0u32, Scope::External); 23 | 24 | let vk = VerifyingKey::build(); 25 | let pk = ProvingKey::build(); 26 | 27 | let create_bundle = |num_recipients| { 28 | let mut builder = Builder::new(BundleType::DEFAULT, Anchor::from_bytes([0; 32]).unwrap()); 29 | for _ in 0..num_recipients { 30 | builder 31 | .add_output(None, recipient, NoteValue::from_raw(10), [0; 512]) 32 | .unwrap(); 33 | } 34 | let bundle: Bundle<_, i64> = builder.build(rng).unwrap().unwrap().0; 35 | 36 | let instances: Vec<_> = bundle 37 | .actions() 38 | .iter() 39 | .map(|a| a.to_instance(*bundle.flags(), *bundle.anchor())) 40 | .collect(); 41 | 42 | (bundle, instances) 43 | }; 44 | 45 | let recipients_range = 1..=4; 46 | 47 | { 48 | let mut group = c.benchmark_group("proving"); 49 | group.sample_size(10); 50 | for num_recipients in recipients_range.clone() { 51 | let (bundle, instances) = create_bundle(num_recipients); 52 | group.bench_function(BenchmarkId::new("bundle", num_recipients), |b| { 53 | b.iter(|| { 54 | bundle 55 | .authorization() 56 | .create_proof(&pk, &instances, rng) 57 | .unwrap() 58 | }); 59 | }); 60 | } 61 | } 62 | 63 | { 64 | let mut group = c.benchmark_group("verifying"); 65 | for num_recipients in recipients_range { 66 | let (bundle, instances) = create_bundle(num_recipients); 67 | let bundle = bundle 68 | .create_proof(&pk, rng) 69 | .unwrap() 70 | .apply_signatures(rng, [0; 32], &[]) 71 | .unwrap(); 72 | assert!(bundle.verify_proof(&vk).is_ok()); 73 | group.bench_function(BenchmarkId::new("bundle", num_recipients), |b| { 74 | b.iter(|| bundle.authorization().proof().verify(&vk, &instances)); 75 | }); 76 | } 77 | } 78 | } 79 | 80 | #[cfg(unix)] 81 | criterion_group! { 82 | name = benches; 83 | config = Criterion::default().with_profiler(PProfProfiler::new(100, Output::Flamegraph(None))); 84 | targets = criterion_benchmark 85 | } 86 | #[cfg(windows)] 87 | criterion_group! { 88 | name = benches; 89 | config = Criterion::default(); 90 | targets = criterion_benchmark 91 | } 92 | criterion_main!(benches); 93 | -------------------------------------------------------------------------------- /benches/note_decryption.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; 2 | use orchard::{ 3 | builder::{Builder, BundleType}, 4 | circuit::ProvingKey, 5 | keys::{FullViewingKey, PreparedIncomingViewingKey, Scope, SpendingKey}, 6 | note_encryption::{CompactAction, OrchardDomain}, 7 | value::NoteValue, 8 | Anchor, Bundle, 9 | }; 10 | use rand::rngs::OsRng; 11 | use zcash_note_encryption::{batch, try_compact_note_decryption, try_note_decryption}; 12 | 13 | #[cfg(unix)] 14 | use pprof::criterion::{Output, PProfProfiler}; 15 | 16 | fn bench_note_decryption(c: &mut Criterion) { 17 | let rng = OsRng; 18 | let pk = ProvingKey::build(); 19 | 20 | let fvk = FullViewingKey::from(&SpendingKey::from_bytes([7; 32]).unwrap()); 21 | let valid_ivk = fvk.to_ivk(Scope::External); 22 | let recipient = valid_ivk.address_at(0u32); 23 | let valid_ivk = PreparedIncomingViewingKey::new(&valid_ivk); 24 | 25 | // Compact actions don't have the full AEAD ciphertext, so ZIP 307 trial-decryption 26 | // relies on an invalid ivk resulting in random noise for which the note commitment 27 | // is invalid. However, in practice we still get early rejection: 28 | // - The version byte will be invalid in 255/256 instances. 29 | // - If the version byte is valid, one of either the note commitment check or the esk 30 | // check will be invalid, saving us at least one scalar mul. 31 | // 32 | // Our fixed (action, invalid ivk) tuple will always fall into a specific rejection 33 | // case. In order to reflect the real behaviour in the benchmarks, we trial-decrypt 34 | // with 10240 invalid ivks (each of which will result in a different uniformly-random 35 | // plaintext); this is equivalent to trial-decrypting 10240 different actions with the 36 | // same ivk, but is faster to set up. 37 | let invalid_ivks: Vec<_> = (0u32..10240) 38 | .map(|i| { 39 | let mut sk = [0; 32]; 40 | sk[..4].copy_from_slice(&i.to_le_bytes()); 41 | let fvk = FullViewingKey::from(&SpendingKey::from_bytes(sk).unwrap()); 42 | PreparedIncomingViewingKey::new(&fvk.to_ivk(Scope::External)) 43 | }) 44 | .collect(); 45 | 46 | let bundle = { 47 | let mut builder = Builder::new(BundleType::DEFAULT, Anchor::from_bytes([0; 32]).unwrap()); 48 | // The builder pads to two actions, and shuffles their order. Add two recipients 49 | // so the first action is always decryptable. 50 | builder 51 | .add_output(None, recipient, NoteValue::from_raw(10), [0; 512]) 52 | .unwrap(); 53 | builder 54 | .add_output(None, recipient, NoteValue::from_raw(10), [0; 512]) 55 | .unwrap(); 56 | let bundle: Bundle<_, i64> = builder.build(rng).unwrap().unwrap().0; 57 | bundle 58 | .create_proof(&pk, rng) 59 | .unwrap() 60 | .apply_signatures(rng, [0; 32], &[]) 61 | .unwrap() 62 | }; 63 | let action = bundle.actions().first(); 64 | 65 | let domain = OrchardDomain::for_action(action); 66 | 67 | let compact = { 68 | let mut group = c.benchmark_group("note-decryption"); 69 | group.throughput(Throughput::Elements(1)); 70 | 71 | group.bench_function("valid", |b| { 72 | b.iter(|| try_note_decryption(&domain, &valid_ivk, action).unwrap()) 73 | }); 74 | 75 | // Non-compact actions will always early-reject at the same point: AEAD decryption. 76 | group.bench_function("invalid", |b| { 77 | b.iter(|| try_note_decryption(&domain, &invalid_ivks[0], action)) 78 | }); 79 | 80 | let compact = CompactAction::from(action); 81 | 82 | group.bench_function("compact-valid", |b| { 83 | b.iter(|| try_compact_note_decryption(&domain, &valid_ivk, &compact).unwrap()) 84 | }); 85 | 86 | compact 87 | }; 88 | 89 | { 90 | let mut group = c.benchmark_group("compact-note-decryption"); 91 | group.throughput(Throughput::Elements(invalid_ivks.len() as u64)); 92 | group.bench_function("invalid", |b| { 93 | b.iter(|| { 94 | for ivk in &invalid_ivks { 95 | try_compact_note_decryption(&domain, ivk, &compact); 96 | } 97 | }) 98 | }); 99 | } 100 | 101 | { 102 | // Benchmark with 2 IVKs to emulate a wallet with two pools of funds. 103 | let ivks = 2; 104 | let valid_ivks = vec![valid_ivk; ivks]; 105 | let actions: Vec<_> = (0..100) 106 | .map(|_| (OrchardDomain::for_action(action), action.clone())) 107 | .collect(); 108 | let compact: Vec<_> = (0..100) 109 | .map(|_| { 110 | ( 111 | OrchardDomain::for_action(action), 112 | CompactAction::from(action), 113 | ) 114 | }) 115 | .collect(); 116 | 117 | let mut group = c.benchmark_group("batch-note-decryption"); 118 | 119 | for size in [10, 50, 100] { 120 | group.throughput(Throughput::Elements((ivks * size) as u64)); 121 | 122 | group.bench_function(BenchmarkId::new("valid", size), |b| { 123 | b.iter(|| batch::try_note_decryption(&valid_ivks, &actions[..size])) 124 | }); 125 | 126 | group.bench_function(BenchmarkId::new("invalid", size), |b| { 127 | b.iter(|| batch::try_note_decryption(&invalid_ivks[..ivks], &actions[..size])) 128 | }); 129 | 130 | group.bench_function(BenchmarkId::new("compact-valid", size), |b| { 131 | b.iter(|| batch::try_compact_note_decryption(&valid_ivks, &compact[..size])) 132 | }); 133 | 134 | group.bench_function(BenchmarkId::new("compact-invalid", size), |b| { 135 | b.iter(|| { 136 | batch::try_compact_note_decryption(&invalid_ivks[..ivks], &compact[..size]) 137 | }) 138 | }); 139 | } 140 | } 141 | } 142 | 143 | #[cfg(unix)] 144 | criterion_group! { 145 | name = benches; 146 | config = Criterion::default().with_profiler(PProfProfiler::new(100, Output::Flamegraph(None))); 147 | targets = bench_note_decryption 148 | } 149 | #[cfg(not(unix))] 150 | criterion_group!(benches, bench_note_decryption); 151 | criterion_main!(benches); 152 | -------------------------------------------------------------------------------- /benches/small.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | use orchard::keys::{FullViewingKey, Scope, SpendingKey}; 3 | 4 | fn key_derivation(c: &mut Criterion) { 5 | // Meaningless random spending key. 6 | let sk = SpendingKey::from_bytes([ 7 | 0x2e, 0x0f, 0xd6, 0xc0, 0xed, 0x0b, 0xcf, 0xd8, 0x07, 0xf5, 0xdb, 0xff, 0x47, 0x4e, 0xdc, 8 | 0x78, 0x8c, 0xe0, 0x09, 0x30, 0x66, 0x10, 0x1e, 0x95, 0x82, 0x87, 0xb1, 0x00, 0x50, 0x9b, 9 | 0xf7, 0x9a, 10 | ]) 11 | .unwrap(); 12 | let fvk = FullViewingKey::from(&sk); 13 | 14 | c.bench_function("derive_fvk", |b| b.iter(|| FullViewingKey::from(&sk))); 15 | c.bench_function("default_address", |b| { 16 | b.iter(|| fvk.address_at(0u32, Scope::External)) 17 | }); 18 | } 19 | 20 | criterion_group!(benches, key_derivation); 21 | criterion_main!(benches); 22 | -------------------------------------------------------------------------------- /book/.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | -------------------------------------------------------------------------------- /book/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Jack Grigg"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "The Orchard Book" 7 | 8 | [preprocessor.katex] 9 | macros = "macros.txt" 10 | -------------------------------------------------------------------------------- /book/macros.txt: -------------------------------------------------------------------------------- 1 | # Conventions 2 | 3 | \bconcat:{\mathop{\kern 0.1em||\kern 0.1em}} 4 | \Repr:{\star} 5 | 6 | # Conversions 7 | 8 | \ItoLEBSP:{\mathsf{I2LEBSP}_{#1}} 9 | 10 | # Fields and curves 11 | 12 | \BaseLength:{\ell^\mathsf{#1\vphantom{p}}_{\mathsf{base}}} 13 | 14 | # Key components 15 | 16 | \AuthSignPublic:{\mathsf{ak}} 17 | \NullifierKey:{\mathsf{nk}} 18 | \InViewingKey:{\mathsf{ivk}} 19 | \DiversifiedTransmitBase:{\mathsf{g_d}} 20 | \DiversifiedTransmitBaseRepr:{\mathsf{g\Repr_d}} 21 | \DiversifiedTransmitPublic:{\mathsf{pk_d}} 22 | \DiversifiedTransmitPublicRepr:{\mathsf{pk\Repr_d}} 23 | 24 | # Commitments and hashes 25 | 26 | \SinsemillaHash:{\mathsf{SinsemillaHash}} 27 | \SinsemillaCommit:{\mathsf{SinsemillaCommit}} 28 | \SinsemillaShortCommit:{\mathsf{SinsemillaShortCommit}} 29 | \CommitIvk:{\mathsf{Commit}^{\InViewingKey}} 30 | \NoteCommit:{\mathsf{NoteCommit}} 31 | 32 | # Circuit constraint helper methods 33 | 34 | \BoolCheck:{\texttt{bool\_check}({#1})} 35 | \RangeCheck:{\texttt{range\_check}({#1, #2})} 36 | \ShortLookupRangeCheck:{\texttt{short\_lookup\_range\_check}({#1})} 37 | -------------------------------------------------------------------------------- /book/src/IDENTIFIERS.json: -------------------------------------------------------------------------------- 1 | { 2 | "commit-ivk-bit-lengths": "design/circuit/commit-ivk.html#bit-length-constraints", 3 | "commit-ivk-canonicity-ak": "design/circuit/commit-ivk.html#canonicity-ak", 4 | "commit-ivk-canonicity-nk": "design/circuit/commit-ivk.html#canonicity-nk", 5 | "commit-ivk-decompositions": "design/circuit/commit-ivk.html#constrain-bit-lengths", 6 | "commit-ivk-region-layout": "design/circuit/commit-ivk.html#region-layout", 7 | "note-commit-canonicity-g_d": "design/circuit/note-commit.html#canonicity-g_d", 8 | "note-commit-canonicity-pk_d": "design/circuit/note-commit.html#canonicity-pk_d", 9 | "note-commit-canonicity-psi": "design/circuit/note-commit.html#canonicity-psi", 10 | "note-commit-canonicity-rho": "design/circuit/note-commit.html#canonicity-rho", 11 | "note-commit-canonicity-v": "design/circuit/note-commit.html#canonicity-v", 12 | "note-commit-canonicity-y": "design/circuit/note-commit.html#canonicity-y", 13 | "note-commit-decomposition-b": "design/circuit/note-commit.html#decomposition-b", 14 | "note-commit-decomposition-d": "design/circuit/note-commit.html#decomposition-d", 15 | "note-commit-decomposition-e": "design/circuit/note-commit.html#decomposition-e", 16 | "note-commit-decomposition-g": "design/circuit/note-commit.html#decomposition-g", 17 | "note-commit-decomposition-h": "design/circuit/note-commit.html#decomposition-h", 18 | "note-commit-decomposition-y": "design/circuit/note-commit.html#decomposition-y" 19 | } -------------------------------------------------------------------------------- /book/src/README.md: -------------------------------------------------------------------------------- 1 | {{#include ../../README.md}} 2 | -------------------------------------------------------------------------------- /book/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # The Orchard Book 2 | 3 | [Orchard](README.md) 4 | - [Concepts](concepts.md) 5 | - [Preliminaries](concepts/preliminaries.md) 6 | - [User Documentation](user.md) 7 | - [Creating keys and addresses](user/keys.md) 8 | - [Creating notes](user/creating-notes.md) 9 | - [Spending notes](user/spending-notes.md) 10 | - [Integration into an existing chain](user/integration.md) 11 | - [Design](design.md) 12 | - [Keys and addresses](design/keys.md) 13 | - [Actions](design/actions.md) 14 | - [Commitments](design/commitments.md) 15 | - [Commitment tree](design/commitment-tree.md) 16 | - [Nullifiers](design/nullifiers.md) 17 | - [Signatures](design/signatures.md) 18 | - [Circuit](design/circuit.md) 19 | - [Gadgets](design/circuit/gadgets.md) 20 | - [CommitIvk](design/circuit/commit-ivk.md) 21 | - [NoteCommit](design/circuit/note-commit.md) 22 | -------------------------------------------------------------------------------- /book/src/concepts.md: -------------------------------------------------------------------------------- 1 | # Concepts 2 | -------------------------------------------------------------------------------- /book/src/concepts/preliminaries.md: -------------------------------------------------------------------------------- 1 | # Preliminaries 2 | -------------------------------------------------------------------------------- /book/src/design.md: -------------------------------------------------------------------------------- 1 | # Design 2 | 3 | ## General design notes 4 | 5 | ### Requirements 6 | 7 | - Keep the design close to Sapling, while eliminating aspects we don't like. 8 | 9 | ### Non-requirements 10 | 11 | - Delegated proving with privacy from the prover. 12 | - We know how to do this, but it would require a discrete log equality proof, and the 13 | most efficient way to do this would be to do RedDSA and this at the same time, which 14 | means more work for e.g. hardware wallets. 15 | 16 | ### Open issues 17 | 18 | - Should we have one memo per output, or one memo per transaction, or 0..n memos? 19 | - Variable, or (1 or n), is a potential privacy leak. 20 | - Need to consider the privacy issue related to light clients requesting individual 21 | memos vs being able to fetch all memos. 22 | 23 | ### Note structure 24 | 25 | - TODO: UDAs: arbitrary vs whitelisted 26 | 27 | ### Typed variables vs byte encodings 28 | 29 | For Sapling, we have encountered multiple places where the specification uses typed 30 | variables to define the consensus rules, but the C++ implementation in zcashd relied on 31 | byte encodings to implement them. This resulted in subtly-different consensus rules being 32 | deployed than were intended, for example where a particular type was not round-trip 33 | encodable. 34 | 35 | In Orchard, we avoid this by defining the consensus rules in terms of the byte encodings 36 | of all variables, and being explicit about any types that are not round-trip encodable. 37 | This makes consensus compatibility between strongly-typed implementations (such as this 38 | crate) and byte-oriented implementations easier to achieve. 39 | -------------------------------------------------------------------------------- /book/src/design/actions.md: -------------------------------------------------------------------------------- 1 | # Actions 2 | 3 | In Sprout, we had a single proof that represented two spent notes and two new notes. This 4 | was necessary in order to facilitate spending multiple notes in a single transaction (to 5 | balance value, an output of one JoinSplit could be spent in the next one), but also 6 | provided a minimal level of arity-hiding: single-JoinSplit transactions all looked like 7 | 2-in 2-out transactions, and in multi-JoinSplit transactions each JoinSplit looked like a 8 | 1-in 1-out. 9 | 10 | In Sapling, we switched to using value commitments to balance the transaction, removing 11 | the min-2 arity requirement. We opted for one proof per spent note and one (much simpler) 12 | proof per output note, which greatly improved the performance of generating outputs, but 13 | removed any arity-hiding from the proofs (instead having the transaction builder pad 14 | transactions to 1-in, 2-out). 15 | 16 | For Orchard, we take a combined approach: we define an Orchard transaction as containing a 17 | bundle of actions, where each action is both a spend and an output. This provides the same 18 | inherent arity-hiding as multi-JoinSplit Sprout, but using Sapling value commitments to 19 | balance the transaction without doubling its size. 20 | 21 | ## Memo fields 22 | 23 | Each Orchard action has a memo field for its corresponding output, as with Sprout and 24 | Sapling. We did at one point consider having a single Orchard memo field per transaction, 25 | and/or having a mechanism for enabling multiple recipients to decrypt the same memo, but 26 | these were decided against in order to keep the overall design simpler. 27 | -------------------------------------------------------------------------------- /book/src/design/circuit.md: -------------------------------------------------------------------------------- 1 | # Circuit 2 | 3 | 4 | -------------------------------------------------------------------------------- /book/src/design/circuit/commit-ivk.md: -------------------------------------------------------------------------------- 1 | # $\CommitIvk$ 2 | 3 | ## Message decomposition 4 | 5 | $\SinsemillaShortCommit$ is used in the 6 | [$\CommitIvk$ function](https://zips.z.cash/protocol/protocol.pdf#concretesinsemillacommit). 7 | The input to $\SinsemillaShortCommit$ is: 8 | 9 | $$\ItoLEBSP{\BaseLength{Orchard}}(\AuthSignPublic) \bconcat \ItoLEBSP{\BaseLength{Orchard}}(\NullifierKey),$$ 10 | 11 | where $\AuthSignPublic$, $\NullifierKey$ are Pallas base field elements, and $\BaseLength{Orchard} = 255.$ 12 | 13 | Sinsemilla operates on multiples of 10 bits, so we start by decomposing the message into 14 | chunks: 15 | 16 | $$ 17 | \begin{align} 18 | \ItoLEBSP{\BaseLength{Orchard}}(\AuthSignPublic) &= a \bconcat b_0 \bconcat b_1 \\ 19 | &= (\text{bits 0..=249 of } \AuthSignPublic) \bconcat 20 | (\text{bits 250..=253 of } \AuthSignPublic) \bconcat 21 | (\text{bit 254 of } \AuthSignPublic) \\ 22 | \ItoLEBSP{\BaseLength{Orchard}}(\NullifierKey) &= b_2 \bconcat c \bconcat d_0 \bconcat d_1 \\ 23 | &= (\text{bits 0..=4 of } \NullifierKey) \bconcat 24 | (\text{bits 5..=244 of } \NullifierKey) \bconcat 25 | (\text{bits 245..=253 of } \NullifierKey) \bconcat 26 | (\text{bit 254 of } \NullifierKey) \\ 27 | \end{align} 28 | $$ 29 | 30 | Then we recompose the chunks into message pieces: 31 | 32 | $$ 33 | \begin{array}{|c|l|} 34 | \hline 35 | \text{Length (bits)} & \text{Piece} \\\hline 36 | 250 & a \\ 37 | 10 & b = b_0 \bconcat b_1 \bconcat b_2 \\ 38 | 240 & c \\ 39 | 10 & d = d_0 \bconcat d_1 \\\hline 40 | \end{array} 41 | $$ 42 | 43 | Each message piece is constrained by $\SinsemillaHash$ to its stated length. Additionally, 44 | $\AuthSignPublic$ and $\NullifierKey$ are witnessed as field elements, so we know they are 45 | canonical. However, we need additional constraints to enforce that: 46 | 47 | - The chunks are the correct bit lengths (or else they could overlap in the decompositions 48 | and allow the prover to witness an arbitrary $\SinsemillaShortCommit$ message). 49 | - The chunks contain the canonical decompositions of $\AuthSignPublic$ and $\NullifierKey$ 50 | (or else the prover could witness an input to $\SinsemillaShortCommit$ that is 51 | equivalent to $\AuthSignPublic$ and $\NullifierKey$ but not identical). 52 | 53 | Some of these constraints can be implemented with reusable circuit gadgets. We define a 54 | custom gate controlled by the selector $q_\CommitIvk$ to hold the remaining constraints. 55 | 56 | ## Bit length constraints 57 | 58 | Chunks $a$ and $c$ are directly constrained by Sinsemilla. For the remaining chunks, we 59 | use the following constraints: 60 | 61 | $$ 62 | \begin{array}{|c|l|} 63 | \hline 64 | \text{Degree} & \text{Constraint} \\\hline 65 | & \ShortLookupRangeCheck{b_0, 4} \\\hline 66 | & \ShortLookupRangeCheck{b_2, 5} \\\hline 67 | & \ShortLookupRangeCheck{d_0, 9} \\\hline 68 | 3 & q_\CommitIvk \cdot \BoolCheck{b_1} = 0 \\\hline 69 | 3 & q_\CommitIvk \cdot \BoolCheck{d_1} = 0 \\\hline 70 | \end{array} 71 | $$ 72 | 73 | where $\BoolCheck{x} = x \cdot (1 - x)$ and $\ShortLookupRangeCheck{}$ is a 74 | [short lookup range check](../decomposition.md#short-range-check). 75 | 76 | ## Decomposition constraints 77 | 78 | We have now derived or witnessed every subpiece, and range-constrained every subpiece: 79 | - $a$ ($250$ bits) is witnessed and constrained outside the gate; 80 | - $b_0$ ($4$ bits) is witnessed and constrained outside the gate; 81 | - $b_1$ ($1$ bits) is witnessed and boolean-constrained in the gate; 82 | - $b_2$ ($5$ bits) is witnessed and constrained outside the gate; 83 | - $c$ ($240$ bits) is witnessed and constrained outside the gate; 84 | - $d_0$ ($9$ bits) is witnessed and constrained outside the gate; 85 | - $d_1$ ($1$ bits) is witnessed and boolean-constrained in the gate. 86 | 87 | We can now use them to reconstruct both the (chunked) message pieces, and the original 88 | field element inputs: 89 | 90 | $$ 91 | \begin{align} 92 | b &= b_0 + 2^4 \cdot b_1 + 2^5 \cdot b_2 \\ 93 | d &= d_0 + 2^9 \cdot d_1 \\ 94 | \AuthSignPublic &= a + 2^{250} \cdot b_0 + 2^{254} \cdot b_1 \\ 95 | \NullifierKey &= b_2 + 2^5 \cdot c + 2^{245} \cdot d_0 + 2^{254} \cdot d_1 \\ 96 | \end{align} 97 | $$ 98 | 99 | $$ 100 | \begin{array}{|c|l|} 101 | \hline 102 | \text{Degree} & \text{Constraint} \\\hline 103 | 2 & q_\CommitIvk \cdot (b - (b_0 + b_1 \cdot 2^4 + b_2 \cdot 2^5)) = 0 \\\hline 104 | 2 & q_\CommitIvk \cdot (d - (d_0 + d_1 \cdot 2^9)) = 0 \\\hline 105 | 2 & q_\CommitIvk \cdot (a + b_0 \cdot 2^{250} + b_1 \cdot 2^{254} - \AuthSignPublic) = 0 \\\hline 106 | 2 & q_\CommitIvk \cdot (b_2 + c \cdot 2^5 + d_0 \cdot 2^{245} + d_1 \cdot 2^{254} - \NullifierKey) = 0 \\\hline 107 | \end{array} 108 | $$ 109 | 110 | ## Canonicity checks 111 | 112 | At this point, we have constrained $\ItoLEBSP{\BaseLength{Orchard}}(\AuthSignPublic)$ and 113 | $\ItoLEBSP{\BaseLength{Orchard}}(\NullifierKey)$ to be 255-bit values, with top bits $b_1$ 114 | and $d_1$ respectively. We have also constrained: 115 | 116 | $$ 117 | \begin{align} 118 | \ItoLEBSP{\BaseLength{Orchard}}(\AuthSignPublic) &= \AuthSignPublic \pmod{q_\mathbb{P}} \\ 119 | \ItoLEBSP{\BaseLength{Orchard}}(\NullifierKey) &= \NullifierKey \pmod{q_\mathbb{P}} \\ 120 | \end{align} 121 | $$ 122 | 123 | where $q_\mathbb{P}$ is the Pallas base field modulus. The remaining constraints will 124 | enforce that these are indeed canonically-encoded field elements, i.e. 125 | 126 | $$ 127 | \begin{align} 128 | \ItoLEBSP{\BaseLength{Orchard}}(\AuthSignPublic) &< q_\mathbb{P} \\ 129 | \ItoLEBSP{\BaseLength{Orchard}}(\NullifierKey) &< q_\mathbb{P} \\ 130 | \end{align} 131 | $$ 132 | 133 | The Pallas base field modulus has the form $q_\mathbb{P} = 2^{254} + t_\mathbb{P}$, where 134 | $$t_\mathbb{P} = \mathtt{0x224698fc094cf91b992d30ed00000001}$$ 135 | is 126 bits. We therefore know that if the top bit is not set, then the remaining bits 136 | will always comprise a canonical encoding of a field element. Thus the canonicity checks 137 | below are enforced if and only if $b_1 = 1$ (for $\AuthSignPublic$) or $d_1 = 1$ (for 138 | $\NullifierKey$). 139 | 140 | > In the constraints below we use a base-$2^{10}$ variant of the method used in libsnark 141 | > (originally from [[SVPBABW2012](https://eprint.iacr.org/2012/598.pdf), Appendix C.1]) for 142 | > range constraints $0 \leq x < t$: 143 | > 144 | > - Let $t'$ be the smallest power of $2^{10}$ greater than $t$. 145 | > - Enforce $0 \leq x < t'$. 146 | > - Let $x' = x + t' - t$. 147 | > - Enforce $0 \leq x' < t'$. 148 | 149 | ### $\AuthSignPublic$ with $b_1 = 1 \implies \AuthSignPublic \geq 2^{254}$ 150 | 151 | In these cases, we check that $\textsf{ak}_{0..=253} < t_\mathbb{P}$: 152 | 153 | 1. $b_1 = 1 \implies b_0 = 0.$ 154 | 155 | Since $b_1 = 1 \implies \AuthSignPublic_{0..=253} < t_\mathbb{P} < 2^{126},$ we know that 156 | $\AuthSignPublic_{126..=253} = 0,$ and in particular 157 | $$b_0 := \AuthSignPublic_{250..=253} = 0.$$ 158 | 159 | 2. $b_1 = 1 \implies 0 \leq a < t_\mathbb{P}.$ 160 | 161 | To check that $a < t_\mathbb{P}$, we use two constraints: 162 | 163 | a) $0 \leq a < 2^{130}$. This is expressed in the custom gate as 164 | $$b_1 \cdot z_{a,13} = 0,$$ 165 | where $z_{a,13}$ is the index-13 running sum output by $\SinsemillaHash(a).$ 166 | 167 | b) $0 \leq a + 2^{130} - t_\mathbb{P} < 2^{130}$. To check this, we decompose 168 | $a' = a + 2^{130} - t_\mathbb{P}$ into thirteen 10-bit words (little-endian) using 169 | a running sum $z_{a'}$, looking up each word in a $10$-bit lookup table. We then 170 | enforce in the custom gate that 171 | $$b_1 \cdot z_{a',13} = 0.$$ 172 | 173 | $$ 174 | \begin{array}{|c|l|} 175 | \hline 176 | \text{Degree} & \text{Constraint} \\\hline 177 | 3 & q_\CommitIvk \cdot b_1 \cdot b_0 = 0 \\\hline 178 | 3 & q_\CommitIvk \cdot b_1 \cdot z_{a,13} = 0 \\\hline 179 | 2 & q_\CommitIvk \cdot (a + 2^{130} - t_\mathbb{P} - a') = 0 \\\hline 180 | 3 & q_\CommitIvk \cdot b_1 \cdot z_{a',13} = 0 \\\hline 181 | \end{array} 182 | $$ 183 | 184 | ### $\NullifierKey$ with $d_1 = 1 \implies \NullifierKey \geq 2^{254}$ 185 | 186 | In these cases, we check that $\textsf{nk}_{0..=253} < t_\mathbb{P}$: 187 | 188 | 1. $d_1 = 1 \implies d_0 = 0.$ 189 | 190 | Since $d_1 = 1 \implies \NullifierKey_{0..=253} < t_\mathbb{P} < 2^{126},$ we know that $\NullifierKey_{126..=253} = 0,$ and in particular $$d_0 := \NullifierKey_{245..=253} = 0.$$ 191 | 192 | 2. $d_1 = 1 \implies 0 \leq b_2 + 2^5 \cdot c < t_\mathbb{P}.$ 193 | 194 | To check that $0 \leq b_2 + 2^5 \cdot c < t_\mathbb{P}$, we use two constraints: 195 | 196 | a) $0 \leq b_2 + 2^5 \cdot c < 2^{140}$. $b_2$ is already constrained individually to 197 | be a $5$-bit value. $z_{c,13}$ is the index-13 running sum output by 198 | $\SinsemillaHash(c).$ By constraining $$d_1 \cdot z_{c,13} = 0,$$ we constrain 199 | $b_2 + 2^5 \cdot c < 2^{135} < 2^{140}.$ 200 | 201 | b) $0 \leq b_2 + 2^5 \cdot c + 2^{140} - t_\mathbb{P} < 2^{140}$. To check this, we 202 | decompose ${b_2}c' = b_2 + 2^5 \cdot c + 2^{140} - t_\mathbb{P}$ into fourteen 203 | 10-bit words (little-endian) using a running sum $z_{{b_2}c'}$, looking up each 204 | word in a $10$-bit lookup table. We then enforce in the custom gate that 205 | $$d_1 \cdot z_{{b_2}c',14} = 0.$$ 206 | 207 | $$ 208 | \begin{array}{|c|l|} 209 | \hline 210 | \text{Degree} & \text{Constraint} \\\hline 211 | 3 & q_\CommitIvk \cdot d_1 \cdot d_0 = 0 \\\hline 212 | 3 & q_\CommitIvk \cdot d_1 \cdot z_{c,13} = 0 \\\hline 213 | 2 & q_\CommitIvk \cdot (b_2 + c \cdot 2^5 + 2^{140} - t_\mathbb{P} - {b_2}c') = 0 \\\hline 214 | 3 & q_\CommitIvk \cdot d_1 \cdot z_{{b_2}c',14} = 0 \\\hline 215 | \end{array} 216 | $$ 217 | 218 | ## Region layout 219 | 220 | The constraints controlled by the $q_\CommitIvk$ selector are arranged across 9 221 | advice columns, requiring two rows. 222 | 223 | $$ 224 | \begin{array}{|c|c|c|c|c|c|c|c|c|c} 225 | & & & & & & & & & q_\CommitIvk \\\hline 226 | \AuthSignPublic & a & b & b_0 & b_1 & b_2 & z_{a,13} & a' & z_{a',13} & 1 \\\hline 227 | \NullifierKey & c & d & d_0 & d_1 & & z_{c,13} & {b_2}c' & z_{{b_2}c',14} & 0 \\\hline 228 | \end{array} 229 | $$ 230 | -------------------------------------------------------------------------------- /book/src/design/circuit/gadgets.md: -------------------------------------------------------------------------------- 1 | # Gadgets 2 | 3 | The Orchard circuit makes use of the following gadgets from the `halo2_gadgets` crate: 4 | 5 | - [Elliptic curve](https://zcash.github.io/halo2/design/gadgets/ecc.html): 6 | - `FixedPoint` 7 | - `FixedPointBaseField` 8 | - `FixedPointShort` 9 | - `NonIdentityPoint` 10 | - `Point` 11 | - Poseidon: 12 | - `Hash` 13 | - [Sinsemilla](https://zcash.github.io/halo2/design/gadgets/sinsemilla.html): 14 | - `CommitDomain` 15 | - `Message` 16 | - `MessagePiece` 17 | - [`MerklePath`](https://zcash.github.io/halo2/design/gadgets/sinsemilla/merkle-crh.html) 18 | 19 | It instantiates the instruction sets required for these gadgets with the following chips: 20 | 21 | - `halo2_gadgets::ecc::chip::EccChip` 22 | - `halo2_gadgets::poseidon::Pow5Chip` 23 | - [`halo2_gadgets::sinsemilla::chip::SinsemillaChip`](https://zcash.github.io/halo2/design/gadgets/sinsemilla.html#plonk--halo-2-constraints) 24 | - [`halo2_gadgets::sinsemilla::merkle::chip::MerkleChip`](https://zcash.github.io/halo2/design/gadgets/sinsemilla/merkle-crh.html#circuit-components) 25 | - `halo2_gadgets::utilities::UtilitiesInstructions` 26 | - [`halo2_gadgets::utilities::lookup_range_check::LookupRangeCheckConfig`](https://zcash.github.io/halo2/design/gadgets/decomposition.html#lookup-decomposition) 27 | 28 | It also makes use of the following utility functions for standardising constraints: 29 | - `halo2_gadgets::utilities::{bitrange_subset, bool_check}` 30 | -------------------------------------------------------------------------------- /book/src/design/commitment-tree.md: -------------------------------------------------------------------------------- 1 | # Commitment tree 2 | 3 | The commitment tree structure for Orchard is identical to Sapling: 4 | 5 | - A single global commitment tree of fixed depth 32. 6 | - Note commitments are appended to the tree in-order from the block. 7 | - Valid Orchard anchors correspond to the global tree state at block boundaries (after all 8 | commitments from a block have been appended, and before any commitments from the next 9 | block have been appended). 10 | 11 | The only difference is that we instantiate $\mathsf{MerkleCRH}^\mathsf{Orchard}$ with 12 | Sinsemilla (whereas $\mathsf{MerkleCRH}^\mathsf{Sapling}$ used a Bowe--Hopwood Pedersen 13 | hash). 14 | 15 | ## Uncommitted leaves 16 | 17 | The fixed-depth incremental Merkle trees that we use (in Sprout and Sapling, and again in 18 | Orchard) require specifying an "empty" or "uncommitted" leaf - a value that will never be 19 | appended to the tree as a regular leaf. 20 | 21 | - For Sprout (and trees composed of the outputs of bit-twiddling hash functions), we use 22 | the all-zeroes array; the probability of a real note having a colliding note commitment 23 | is cryptographically negligible. 24 | - For Sapling, where leaves are $u$-coordinates of Jubjub points, we use the value $1$ 25 | which is not the $u$-coordinate of any Jubjub point. 26 | 27 | Orchard note commitments are the $x$-coordinates of Pallas points; thus we take the same 28 | approach as Sapling, using a value that is not the $x$-coordinate of any Pallas point as the 29 | uncommitted leaf value. We use the value $2$ for both Pallas and Vesta, because $2^3 + 5$ is 30 | not a square in either $F_p$ or $F_q$: 31 | 32 | ```python 33 | sage: p = 0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001 34 | sage: q = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001 35 | sage: EllipticCurve(GF(p), [0, 5]).count_points() == q 36 | True 37 | sage: EllipticCurve(GF(q), [0, 5]).count_points() == p 38 | True 39 | sage: Mod(13, p).is_square() 40 | False 41 | sage: Mod(13, q).is_square() 42 | False 43 | ``` 44 | 45 | > Note: There are also no Pallas points with $x$-coordinate $0$, but we map the identity to 46 | > $(0, 0)$ within the circuit. Although $\mathsf{SinsemillaCommit}$ cannot return the identity 47 | > (the incomplete addition would return $\perp$ instead), it would arguably be confusing to 48 | > rely on that. 49 | 50 | ## Considered alternatives 51 | 52 | We considered splitting the commitment tree into several sub-trees: 53 | 54 | - Bundle tree, that accumulates the commitments within a single bundle (and thus a single 55 | transaction). 56 | - Block tree, that accumulates the bundle tree roots within a single block. 57 | - Global tree, that accumulates the block tree roots. 58 | 59 | Each of these trees would have had a fixed depth (necessary for being able to create 60 | proofs). Chains that integrated Orchard could have decoupled the limits on 61 | commitments-per-subtree from higher-layer constraints like block size, by enabling their 62 | blocks and transactions to be structured internally as a series of Orchard blocks or txs 63 | (e.g. a Zcash block would have contained a `Vec`, that each were appended 64 | in-order). 65 | 66 | The motivation for considering this change was to improve the lives of light client wallet 67 | developers. When a new note is received, the wallet derives its incremental witness from 68 | the state of the global tree at the point when the note's commitment is appended; this 69 | incremental state then needs to be updated with every subsequent commitment in the block 70 | in-order. Wallets can't get help from the server to create these for new notes without 71 | leaking the specific note that was received. 72 | 73 | We decided that this was too large a change from Sapling, and that it should be possible 74 | to improve the Incremental Merkle Tree implementation to work around the efficiency issues 75 | without domain-separating the tree. 76 | -------------------------------------------------------------------------------- /book/src/design/commitments.md: -------------------------------------------------------------------------------- 1 | # Commitments 2 | 3 | As in Sapling, we require two kinds of commitment schemes in Orchard: 4 | - $\mathit{HomomorphicCommit}$ is a linearly homomorphic commitment scheme with perfect hiding, 5 | and strong binding reducible to DL. 6 | - $\mathit{Commit}$ and $\mathit{ShortCommit}$ are commitment schemes with perfect hiding, and 7 | strong binding reducible to DL. 8 | 9 | By "strong binding" we mean that the scheme is collision resistant on the input and 10 | randomness. 11 | 12 | We instantiate $\mathit{HomomorphicCommit}$ with a Pedersen commitment, and use it for 13 | value commitments: 14 | 15 | $$\mathsf{cv} = \mathit{HomomorphicCommit}^{\mathsf{cv}}_{\mathsf{rcv}}(v)$$ 16 | 17 | We instantiate $\mathit{Commit}$ and $\mathit{ShortCommit}$ with Sinsemilla, and use them 18 | for all other commitments: 19 | 20 | $$\mathsf{ivk} = \mathit{ShortCommit}^{\mathsf{ivk}}_{\mathsf{rivk}}(\mathsf{ak}, \mathsf{nk})$$ 21 | $$\mathsf{cm} = \mathit{Commit}^{\mathsf{cm}}_{\mathsf{rcm}}(\text{rest of note})$$ 22 | 23 | This is the same split (and rationale) as in Sapling, but using the more PLONK-efficient 24 | Sinsemilla instead of Bowe--Hopwood Pedersen hashes. 25 | 26 | Note that for $\mathsf{ivk}$, we also deviate from Sapling in two ways: 27 | 28 | - We use $\mathit{ShortCommit}$ to derive $\mathsf{ivk}$ instead of a full PRF. This removes an 29 | unnecessary (large) PRF primitive from the circuit, at the cost of requiring $\mathsf{rivk}$ to be 30 | part of the full viewing key. 31 | - We define $\mathsf{ivk}$ as an integer in $[1, q_P)$; that is, we exclude $\mathsf{ivk} = 0$. For 32 | Sapling, we relied on BLAKE2s to make $\mathsf{ivk} = 0$ infeasible to produce, but it was still 33 | technically possible. For Orchard, we get this by construction: 34 | - $0$ is not a valid x-coordinate for any Pallas point. 35 | - $\mathsf{SinsemillaShortCommit}$ internally maps points to field elements by replacing the identity (which 36 | has no affine coordinates) with $0$. But $\mathsf{SinsemillaCommit}$ is defined using incomplete addition, and 37 | thus will never produce the identity. 38 | -------------------------------------------------------------------------------- /book/src/design/keys.md: -------------------------------------------------------------------------------- 1 | # Keys and addresses 2 | 3 | Orchard keys and payment addresses are structurally similar to Sapling. The main change is 4 | that Orchard keys use the Pallas curve instead of Jubjub, in order to enable the future 5 | use of the Pallas-Vesta curve cycle in the Orchard protocol. (We already use Vesta as 6 | the curve on which Halo 2 proofs are computed, but this doesn't yet require a cycle.) 7 | 8 | Using the Pallas curve and making the most efficient use of the Halo 2 proof system 9 | involves corresponding changes to the key derivation process, such as using Sinsemilla 10 | for Pallas-efficient commitments. We also take the opportunity to remove all uses of 11 | expensive general-purpose hashes (such as BLAKE2s) from the circuit. 12 | 13 | We make several structural changes, building on the lessons learned from Sapling: 14 | 15 | - The nullifier private key $\mathsf{nsk}$ is removed. Its purpose in Sapling was as 16 | defense-in-depth, in case RedDSA was found to have weaknesses; an adversary who could 17 | recover $\mathsf{ask}$ would not be able to spend funds. In practice it has not been 18 | feasible to manage $\mathsf{nsk}$ much more securely than a full viewing key, as the 19 | computational power required to generate Sapling proofs has made it necessary to perform 20 | this step on the same device that is creating the overall transaction (rather than on a 21 | more constrained device like a hardware wallet). We are also more confident in RedDSA 22 | now. 23 | 24 | - $\mathsf{nk}$ is now a field element instead of a curve point, making it more efficient 25 | to generate nullifiers. 26 | 27 | - $\mathsf{ovk}$ is now derived from $\mathsf{fvk}$, instead of being derived in parallel. 28 | This places it in a similar position within the key structure to $\mathsf{ivk}$, and 29 | also removes an issue where two full viewing keys could be constructed that have the 30 | same $\mathsf{ivk}$ but different $\mathsf{ovk}$s. Users still have control over whether 31 | $\mathsf{ovk}$ is used when constructing a transaction. 32 | 33 | - All diversifiers now result in valid payment addresses, due to group hashing into Pallas 34 | being specified to be infallible. This removes significant complexity from the use cases 35 | for diversified addresses. 36 | 37 | - The fact that Pallas is a prime-order curve simplifies the protocol and removes the need 38 | for cofactor multiplication in key agreement. Unlike Sapling, we define public (including 39 | ephemeral) and private keys used for note encryption to exclude the zero point and the 40 | zero scalar. Without this change, the implementation of the Orchard Action circuit would 41 | need special cases for the zero point, since Pallas is a short Weierstrass rather than 42 | an Edwards curve. This also has the advantage of ensuring that the key agreement has 43 | "contributory behaviour" — that is, if *either* party contributes a random scalar, then 44 | the shared secret will be random to an observer who does not know that scalar and cannot 45 | break Diffie–Hellman. 46 | 47 | Other than the above, Orchard retains the same design rationale for its keys and addresses 48 | as Sapling. For example, diversifiers remain at 11 bytes, so that a raw Orchard address is 49 | the same length as a raw Sapling address. 50 | 51 | Orchard payment addresses do not have a stand-alone string encoding. Instead, we define 52 | "unified addresses" that can bundle together addresses of different types, including 53 | Orchard. Unified addresses have a Human-Readable Part of "u" on Mainnet, i.e. they will 54 | have the prefix "u1". For specifications of this and other formats (e.g. for Orchard viewing 55 | and spending keys), see section 5.6.4 of the NU5 protocol specification [#NU5-orchardencodings]. 56 | 57 | ## Hierarchical deterministic wallets 58 | 59 | When designing Sapling, we defined a [BIP 32]-like mechanism for generating hierarchical 60 | deterministic wallets in [ZIP 32]. We decided at the time to stick closely to the design 61 | of BIP 32, on the assumption that there were Bitcoin use cases that used both hardened and 62 | non-hardened derivation that we might not be aware of. This decision created significant 63 | complexity for Sapling: we needed to handle derivation separately for each component of 64 | the expanded spending key and full viewing key (whereas for transparent addresses there is 65 | only a single component in the spending key). 66 | 67 | Non-hardened derivation enables creating a multi-level path of child addresses below some 68 | parent address, without involving the parent spending key. The primary use case for this 69 | is HD wallets for transparent addresses, which use the following structure defined in 70 | [BIP 44]: 71 | 72 | - (H) BIP 44 73 | - (H) Coin type: Zcash 74 | - (H) Account 0 75 | - (N) Normal addresses 76 | - (N) Address 0 77 | - (N) Address 1... 78 | - (N) Change addresses 79 | - (N) Change address 0 80 | - (N) Change address 1... 81 | - (H) Account 1... 82 | 83 | Shielded accounts do not require separating change addresses from normal addresses, because 84 | addresses are not revealed in transactions. Similarly, there is also no need to generate 85 | a fresh spending key for every transaction, and in fact this would cause a linear slow-down 86 | in wallet scanning. But for users who do want to generate multiple addresses per account, 87 | they can generate the following structure, which does not use non-hardened derivation: 88 | 89 | - (H) ZIP 32 90 | - (H) Coin type: Zcash 91 | - (H) Account 0 92 | - Diversified address 0 93 | - Diversified address 1... 94 | - (H) Account 1... 95 | 96 | Non-hardened derivation is therefore only required for use-cases that require the ability 97 | to derive more than one child layer of addresses. However, in the years since Sapling was 98 | deployed, we have not seen *any* such use cases appear. 99 | 100 | Therefore, for Orchard we only define hardened derivation, and do so with a much simpler 101 | design than ZIP 32. All derivations produce an opaque binary spending key, from which the 102 | keys and addresses are then derived. As a side benefit, this makes key formats 103 | shorter. (The formats that will actually be used in practice for Orchard will correspond 104 | to the simpler Sapling formats in the protocol specification, rather than the longer 105 | and more complicated "extended" ones defined by ZIP 32.) 106 | 107 | [BIP 32]: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki 108 | [BIP 44]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki 109 | [ZIP 32]: https://zips.z.cash/zip-0032 110 | [NU5-orchardencodings]: https://zips.z.cash/protocol/nu5.pdf#orchardencodings 111 | -------------------------------------------------------------------------------- /book/src/design/signatures.md: -------------------------------------------------------------------------------- 1 | # Signatures 2 | 3 | Orchard signatures are an instantiation of RedDSA with a cofactor of 1. 4 | 5 | TODO: 6 | - Should it be possible to sign partial transactions? 7 | - If we're going to merge down all the signatures into a single one, and also want this, we need to ensure there's a feasible MPC. 8 | -------------------------------------------------------------------------------- /book/src/user.md: -------------------------------------------------------------------------------- 1 | # User Documentation 2 | -------------------------------------------------------------------------------- /book/src/user/creating-notes.md: -------------------------------------------------------------------------------- 1 | # Creating notes 2 | -------------------------------------------------------------------------------- /book/src/user/integration.md: -------------------------------------------------------------------------------- 1 | # Integration into an existing chain 2 | -------------------------------------------------------------------------------- /book/src/user/keys.md: -------------------------------------------------------------------------------- 1 | # Creating keys and addresses 2 | -------------------------------------------------------------------------------- /book/src/user/spending-notes.md: -------------------------------------------------------------------------------- 1 | # Spending notes 2 | -------------------------------------------------------------------------------- /katex-header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /proptest-regressions/constants/util.txt: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc 251d6e9f7ad2f5cd8679dec6b69aa9c879baae8742791b19669c136aef12deac # shrinks to scalar = 0x0000000000000000000000000000000000000000000000000000000000000000, window_num_bits = 6 8 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.70.0" 3 | components = [ "clippy", "rustfmt" ] 4 | -------------------------------------------------------------------------------- /src/action.rs: -------------------------------------------------------------------------------- 1 | use memuse::DynamicUsage; 2 | 3 | use crate::{ 4 | note::{ExtractedNoteCommitment, Nullifier, Rho, TransmittedNoteCiphertext}, 5 | primitives::redpallas::{self, SpendAuth}, 6 | value::ValueCommitment, 7 | }; 8 | 9 | /// An action applied to the global ledger. 10 | /// 11 | /// This both creates a note (adding a commitment to the global ledger), and consumes 12 | /// some note created prior to this action (adding a nullifier to the global ledger). 13 | #[derive(Debug, Clone)] 14 | pub struct Action { 15 | /// The nullifier of the note being spent. 16 | nf: Nullifier, 17 | /// The randomized verification key for the note being spent. 18 | rk: redpallas::VerificationKey, 19 | /// A commitment to the new note being created. 20 | cmx: ExtractedNoteCommitment, 21 | /// The transmitted note ciphertext. 22 | encrypted_note: TransmittedNoteCiphertext, 23 | /// A commitment to the net value created or consumed by this action. 24 | cv_net: ValueCommitment, 25 | /// The authorization for this action. 26 | authorization: A, 27 | } 28 | 29 | impl Action { 30 | /// Constructs an `Action` from its constituent parts. 31 | pub fn from_parts( 32 | nf: Nullifier, 33 | rk: redpallas::VerificationKey, 34 | cmx: ExtractedNoteCommitment, 35 | encrypted_note: TransmittedNoteCiphertext, 36 | cv_net: ValueCommitment, 37 | authorization: T, 38 | ) -> Self { 39 | Action { 40 | nf, 41 | rk, 42 | cmx, 43 | encrypted_note, 44 | cv_net, 45 | authorization, 46 | } 47 | } 48 | 49 | /// Returns the nullifier of the note being spent. 50 | pub fn nullifier(&self) -> &Nullifier { 51 | &self.nf 52 | } 53 | 54 | /// Returns the randomized verification key for the note being spent. 55 | pub fn rk(&self) -> &redpallas::VerificationKey { 56 | &self.rk 57 | } 58 | 59 | /// Returns the commitment to the new note being created. 60 | pub fn cmx(&self) -> &ExtractedNoteCommitment { 61 | &self.cmx 62 | } 63 | 64 | /// Returns the encrypted note ciphertext. 65 | pub fn encrypted_note(&self) -> &TransmittedNoteCiphertext { 66 | &self.encrypted_note 67 | } 68 | 69 | /// Obtains the [`Rho`] value that was used to construct the new note being created. 70 | pub fn rho(&self) -> Rho { 71 | Rho::from_nf_old(self.nf) 72 | } 73 | 74 | /// Returns the commitment to the net value created or consumed by this action. 75 | pub fn cv_net(&self) -> &ValueCommitment { 76 | &self.cv_net 77 | } 78 | 79 | /// Returns the authorization for this action. 80 | pub fn authorization(&self) -> &T { 81 | &self.authorization 82 | } 83 | 84 | /// Transitions this action from one authorization state to another. 85 | pub fn map(self, step: impl FnOnce(T) -> U) -> Action { 86 | Action { 87 | nf: self.nf, 88 | rk: self.rk, 89 | cmx: self.cmx, 90 | encrypted_note: self.encrypted_note, 91 | cv_net: self.cv_net, 92 | authorization: step(self.authorization), 93 | } 94 | } 95 | 96 | /// Transitions this action from one authorization state to another. 97 | pub fn try_map(self, step: impl FnOnce(T) -> Result) -> Result, E> { 98 | Ok(Action { 99 | nf: self.nf, 100 | rk: self.rk, 101 | cmx: self.cmx, 102 | encrypted_note: self.encrypted_note, 103 | cv_net: self.cv_net, 104 | authorization: step(self.authorization)?, 105 | }) 106 | } 107 | } 108 | 109 | impl DynamicUsage for Action> { 110 | #[inline(always)] 111 | fn dynamic_usage(&self) -> usize { 112 | 0 113 | } 114 | 115 | #[inline(always)] 116 | fn dynamic_usage_bounds(&self) -> (usize, Option) { 117 | (0, Some(0)) 118 | } 119 | } 120 | 121 | /// Generators for property testing. 122 | #[cfg(any(test, feature = "test-dependencies"))] 123 | #[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))] 124 | pub(crate) mod testing { 125 | use rand::{rngs::StdRng, SeedableRng}; 126 | use reddsa::orchard::SpendAuth; 127 | 128 | use proptest::prelude::*; 129 | 130 | use crate::{ 131 | note::{ 132 | commitment::ExtractedNoteCommitment, nullifier::testing::arb_nullifier, 133 | testing::arb_note, TransmittedNoteCiphertext, 134 | }, 135 | primitives::redpallas::{ 136 | self, 137 | testing::{arb_spendauth_signing_key, arb_spendauth_verification_key}, 138 | }, 139 | value::{NoteValue, ValueCommitTrapdoor, ValueCommitment}, 140 | }; 141 | 142 | use super::Action; 143 | 144 | prop_compose! { 145 | /// Generate an action without authorization data. 146 | pub fn arb_unauthorized_action(spend_value: NoteValue, output_value: NoteValue)( 147 | nf in arb_nullifier(), 148 | rk in arb_spendauth_verification_key(), 149 | note in arb_note(output_value), 150 | ) -> Action<()> { 151 | let cmx = ExtractedNoteCommitment::from(note.commitment()); 152 | let cv_net = ValueCommitment::derive( 153 | spend_value - output_value, 154 | ValueCommitTrapdoor::zero() 155 | ); 156 | // FIXME: make a real one from the note. 157 | let encrypted_note = TransmittedNoteCiphertext { 158 | epk_bytes: [0u8; 32], 159 | enc_ciphertext: [0u8; 580], 160 | out_ciphertext: [0u8; 80] 161 | }; 162 | Action { 163 | nf, 164 | rk, 165 | cmx, 166 | encrypted_note, 167 | cv_net, 168 | authorization: () 169 | } 170 | } 171 | } 172 | 173 | prop_compose! { 174 | /// Generate an action with invalid (random) authorization data. 175 | pub fn arb_action(spend_value: NoteValue, output_value: NoteValue)( 176 | nf in arb_nullifier(), 177 | sk in arb_spendauth_signing_key(), 178 | note in arb_note(output_value), 179 | rng_seed in prop::array::uniform32(prop::num::u8::ANY), 180 | fake_sighash in prop::array::uniform32(prop::num::u8::ANY), 181 | ) -> Action> { 182 | let cmx = ExtractedNoteCommitment::from(note.commitment()); 183 | let cv_net = ValueCommitment::derive( 184 | spend_value - output_value, 185 | ValueCommitTrapdoor::zero() 186 | ); 187 | 188 | // FIXME: make a real one from the note. 189 | let encrypted_note = TransmittedNoteCiphertext { 190 | epk_bytes: [0u8; 32], 191 | enc_ciphertext: [0u8; 580], 192 | out_ciphertext: [0u8; 80] 193 | }; 194 | 195 | let rng = StdRng::from_seed(rng_seed); 196 | 197 | Action { 198 | nf, 199 | rk: redpallas::VerificationKey::from(&sk), 200 | cmx, 201 | encrypted_note, 202 | cv_net, 203 | authorization: sk.sign(rng, &fake_sighash), 204 | } 205 | } 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/address.rs: -------------------------------------------------------------------------------- 1 | use subtle::CtOption; 2 | 3 | use crate::{ 4 | keys::{DiversifiedTransmissionKey, Diversifier}, 5 | spec::{diversify_hash, NonIdentityPallasPoint}, 6 | }; 7 | 8 | /// A shielded payment address. 9 | /// 10 | /// # Examples 11 | /// 12 | /// ``` 13 | /// use orchard::keys::{SpendingKey, FullViewingKey, Scope}; 14 | /// 15 | /// let sk = SpendingKey::from_bytes([7; 32]).unwrap(); 16 | /// let address = FullViewingKey::from(&sk).address_at(0u32, Scope::External); 17 | /// ``` 18 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 19 | pub struct Address { 20 | d: Diversifier, 21 | pk_d: DiversifiedTransmissionKey, 22 | } 23 | 24 | impl Address { 25 | pub(crate) fn from_parts(d: Diversifier, pk_d: DiversifiedTransmissionKey) -> Self { 26 | // We assume here that pk_d is correctly-derived from d. We ensure this for 27 | // internal APIs. For parsing from raw byte encodings, we assume that users aren't 28 | // modifying internals of encoded address formats. If they do, that can result in 29 | // lost funds, but we can't defend against that from here. 30 | Address { d, pk_d } 31 | } 32 | 33 | /// Returns the [`Diversifier`] for this `Address`. 34 | pub fn diversifier(&self) -> Diversifier { 35 | self.d 36 | } 37 | 38 | pub(crate) fn g_d(&self) -> NonIdentityPallasPoint { 39 | diversify_hash(self.d.as_array()) 40 | } 41 | 42 | pub(crate) fn pk_d(&self) -> &DiversifiedTransmissionKey { 43 | &self.pk_d 44 | } 45 | 46 | /// Serializes this address to its "raw" encoding as specified in [Zcash Protocol Spec § 5.6.4.2: Orchard Raw Payment Addresses][orchardpaymentaddrencoding] 47 | /// 48 | /// [orchardpaymentaddrencoding]: https://zips.z.cash/protocol/protocol.pdf#orchardpaymentaddrencoding 49 | pub fn to_raw_address_bytes(&self) -> [u8; 43] { 50 | let mut result = [0u8; 43]; 51 | result[..11].copy_from_slice(self.d.as_array()); 52 | result[11..].copy_from_slice(&self.pk_d.to_bytes()); 53 | result 54 | } 55 | 56 | /// Parse an address from its "raw" encoding as specified in [Zcash Protocol Spec § 5.6.4.2: Orchard Raw Payment Addresses][orchardpaymentaddrencoding] 57 | /// 58 | /// [orchardpaymentaddrencoding]: https://zips.z.cash/protocol/protocol.pdf#orchardpaymentaddrencoding 59 | pub fn from_raw_address_bytes(bytes: &[u8; 43]) -> CtOption { 60 | DiversifiedTransmissionKey::from_bytes(bytes[11..].try_into().unwrap()).map(|pk_d| { 61 | let d = Diversifier::from_bytes(bytes[..11].try_into().unwrap()); 62 | Self::from_parts(d, pk_d) 63 | }) 64 | } 65 | } 66 | 67 | /// Generators for property testing. 68 | #[cfg(any(test, feature = "test-dependencies"))] 69 | #[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))] 70 | pub mod testing { 71 | use proptest::prelude::*; 72 | 73 | use crate::keys::{ 74 | testing::{arb_diversifier_index, arb_spending_key}, 75 | FullViewingKey, Scope, 76 | }; 77 | 78 | use super::Address; 79 | 80 | prop_compose! { 81 | /// Generates an arbitrary payment address. 82 | pub(crate) fn arb_address()(sk in arb_spending_key(), j in arb_diversifier_index()) -> Address { 83 | let fvk = FullViewingKey::from(&sk); 84 | fvk.address_at(j, Scope::External) 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/bundle/batch.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | 3 | use halo2_proofs::plonk; 4 | use pasta_curves::vesta; 5 | use rand::{CryptoRng, RngCore}; 6 | use tracing::debug; 7 | 8 | use super::{Authorized, Bundle}; 9 | use crate::{ 10 | circuit::VerifyingKey, 11 | primitives::redpallas::{self, Binding, SpendAuth}, 12 | }; 13 | 14 | /// A signature within an authorized Orchard bundle. 15 | #[derive(Debug)] 16 | struct BundleSignature { 17 | /// The signature item for validation. 18 | signature: redpallas::batch::Item, 19 | } 20 | 21 | /// Batch validation context for Orchard. 22 | /// 23 | /// This batch-validates proofs and RedPallas signatures. 24 | #[derive(Debug, Default)] 25 | pub struct BatchValidator { 26 | proofs: plonk::BatchVerifier, 27 | signatures: Vec, 28 | } 29 | 30 | impl BatchValidator { 31 | /// Constructs a new batch validation context. 32 | pub fn new() -> Self { 33 | BatchValidator { 34 | proofs: plonk::BatchVerifier::new(), 35 | signatures: vec![], 36 | } 37 | } 38 | 39 | /// Adds the proof and RedPallas signatures from the given bundle to the validator. 40 | pub fn add_bundle>( 41 | &mut self, 42 | bundle: &Bundle, 43 | sighash: [u8; 32], 44 | ) { 45 | for action in bundle.actions().iter() { 46 | self.signatures.push(BundleSignature { 47 | signature: action 48 | .rk() 49 | .create_batch_item(action.authorization().clone(), &sighash), 50 | }); 51 | } 52 | 53 | self.signatures.push(BundleSignature { 54 | signature: bundle 55 | .binding_validating_key() 56 | .create_batch_item(bundle.authorization().binding_signature().clone(), &sighash), 57 | }); 58 | 59 | bundle 60 | .authorization() 61 | .proof() 62 | .add_to_batch(&mut self.proofs, bundle.to_instances()); 63 | } 64 | 65 | /// Batch-validates the accumulated bundles. 66 | /// 67 | /// Returns `true` if every proof and signature in every bundle added to the batch 68 | /// validator is valid, or `false` if one or more are invalid. No attempt is made to 69 | /// figure out which of the accumulated bundles might be invalid; if that information 70 | /// is desired, construct separate [`BatchValidator`]s for sub-batches of the bundles. 71 | pub fn validate(self, vk: &VerifyingKey, rng: R) -> bool { 72 | // https://p.z.cash/TCR:bad-txns-orchard-binding-signature-invalid?partial 73 | 74 | if self.signatures.is_empty() { 75 | // An empty batch is always valid, but is not free to run; skip it. 76 | // Note that a transaction has at least a binding signature, so if 77 | // there are no signatures, there are also no proofs. 78 | return true; 79 | } 80 | 81 | let mut validator = redpallas::batch::Verifier::new(); 82 | for sig in self.signatures.iter() { 83 | validator.queue(sig.signature.clone()); 84 | } 85 | 86 | match validator.verify(rng) { 87 | // If signatures are valid, check the proofs. 88 | Ok(()) => self.proofs.finalize(&vk.params, &vk.vk), 89 | Err(e) => { 90 | debug!("RedPallas batch validation failed: {}", e); 91 | false 92 | } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/bundle/commitments.rs: -------------------------------------------------------------------------------- 1 | //! Utility functions for computing bundle commitments 2 | 3 | use blake2b_simd::{Hash as Blake2bHash, Params, State}; 4 | 5 | use crate::bundle::{Authorization, Authorized, Bundle}; 6 | 7 | const ZCASH_ORCHARD_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdOrchardHash"; 8 | const ZCASH_ORCHARD_ACTIONS_COMPACT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdOrcActCHash"; 9 | const ZCASH_ORCHARD_ACTIONS_MEMOS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdOrcActMHash"; 10 | const ZCASH_ORCHARD_ACTIONS_NONCOMPACT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdOrcActNHash"; 11 | const ZCASH_ORCHARD_SIGS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxAuthOrchaHash"; 12 | 13 | fn hasher(personal: &[u8; 16]) -> State { 14 | Params::new().hash_length(32).personal(personal).to_state() 15 | } 16 | 17 | /// Write disjoint parts of each Orchard shielded action as 3 separate hashes 18 | /// as defined in [ZIP-244: Transaction Identifier Non-Malleability][zip244]: 19 | /// * \[(nullifier, cmx, ephemeral_key, enc_ciphertext\[..52\])*\] personalized 20 | /// with ZCASH_ORCHARD_ACTIONS_COMPACT_HASH_PERSONALIZATION 21 | /// * \[enc_ciphertext\[52..564\]*\] (memo ciphertexts) personalized 22 | /// with ZCASH_ORCHARD_ACTIONS_MEMOS_HASH_PERSONALIZATION 23 | /// * \[(cv, rk, enc_ciphertext\[564..\], out_ciphertext)*\] personalized 24 | /// with ZCASH_ORCHARD_ACTIONS_NONCOMPACT_HASH_PERSONALIZATION 25 | /// 26 | /// Then, hash these together along with (flags, value_balance_orchard, anchor_orchard), 27 | /// personalized with ZCASH_ORCHARD_ACTIONS_HASH_PERSONALIZATION 28 | /// 29 | /// [zip244]: https://zips.z.cash/zip-0244 30 | pub(crate) fn hash_bundle_txid_data>( 31 | bundle: &Bundle, 32 | ) -> Blake2bHash { 33 | let mut h = hasher(ZCASH_ORCHARD_HASH_PERSONALIZATION); 34 | let mut ch = hasher(ZCASH_ORCHARD_ACTIONS_COMPACT_HASH_PERSONALIZATION); 35 | let mut mh = hasher(ZCASH_ORCHARD_ACTIONS_MEMOS_HASH_PERSONALIZATION); 36 | let mut nh = hasher(ZCASH_ORCHARD_ACTIONS_NONCOMPACT_HASH_PERSONALIZATION); 37 | 38 | for action in bundle.actions().iter() { 39 | ch.update(&action.nullifier().to_bytes()); 40 | ch.update(&action.cmx().to_bytes()); 41 | ch.update(&action.encrypted_note().epk_bytes); 42 | ch.update(&action.encrypted_note().enc_ciphertext[..52]); 43 | 44 | mh.update(&action.encrypted_note().enc_ciphertext[52..564]); 45 | 46 | nh.update(&action.cv_net().to_bytes()); 47 | nh.update(&<[u8; 32]>::from(action.rk())); 48 | nh.update(&action.encrypted_note().enc_ciphertext[564..]); 49 | nh.update(&action.encrypted_note().out_ciphertext); 50 | } 51 | 52 | h.update(ch.finalize().as_bytes()); 53 | h.update(mh.finalize().as_bytes()); 54 | h.update(nh.finalize().as_bytes()); 55 | h.update(&[bundle.flags().to_byte()]); 56 | h.update(&(*bundle.value_balance()).into().to_le_bytes()); 57 | h.update(&bundle.anchor().to_bytes()); 58 | h.finalize() 59 | } 60 | 61 | /// Construct the commitment for the absent bundle as defined in 62 | /// [ZIP-244: Transaction Identifier Non-Malleability][zip244] 63 | /// 64 | /// [zip244]: https://zips.z.cash/zip-0244 65 | pub fn hash_bundle_txid_empty() -> Blake2bHash { 66 | hasher(ZCASH_ORCHARD_HASH_PERSONALIZATION).finalize() 67 | } 68 | 69 | /// Construct the commitment to the authorizing data of an 70 | /// authorized bundle as defined in [ZIP-244: Transaction 71 | /// Identifier Non-Malleability][zip244] 72 | /// 73 | /// [zip244]: https://zips.z.cash/zip-0244 74 | pub(crate) fn hash_bundle_auth_data(bundle: &Bundle) -> Blake2bHash { 75 | let mut h = hasher(ZCASH_ORCHARD_SIGS_HASH_PERSONALIZATION); 76 | h.update(bundle.authorization().proof().as_ref()); 77 | for action in bundle.actions().iter() { 78 | h.update(&<[u8; 64]>::from(action.authorization())); 79 | } 80 | h.update(&<[u8; 64]>::from( 81 | bundle.authorization().binding_signature(), 82 | )); 83 | h.finalize() 84 | } 85 | 86 | /// Construct the commitment for an absent bundle as defined in 87 | /// [ZIP-244: Transaction Identifier Non-Malleability][zip244] 88 | /// 89 | /// [zip244]: https://zips.z.cash/zip-0244 90 | pub fn hash_bundle_auth_empty() -> Blake2bHash { 91 | hasher(ZCASH_ORCHARD_SIGS_HASH_PERSONALIZATION).finalize() 92 | } 93 | -------------------------------------------------------------------------------- /src/circuit/gadget.rs: -------------------------------------------------------------------------------- 1 | //! Gadgets used in the Orchard circuit. 2 | 3 | use ff::Field; 4 | use pasta_curves::pallas; 5 | 6 | use super::{commit_ivk::CommitIvkChip, note_commit::NoteCommitChip}; 7 | use crate::constants::{ 8 | NullifierK, OrchardCommitDomains, OrchardFixedBases, OrchardFixedBasesFull, OrchardHashDomains, 9 | ValueCommitV, 10 | }; 11 | use halo2_gadgets::{ 12 | ecc::{ 13 | chip::EccChip, EccInstructions, FixedPoint, FixedPointBaseField, FixedPointShort, Point, 14 | ScalarFixed, ScalarFixedShort, X, 15 | }, 16 | poseidon::{ 17 | primitives::{self as poseidon, ConstantLength}, 18 | Hash as PoseidonHash, PoseidonSpongeInstructions, Pow5Chip as PoseidonChip, 19 | }, 20 | sinsemilla::{chip::SinsemillaChip, merkle::chip::MerkleChip}, 21 | }; 22 | use halo2_proofs::{ 23 | circuit::{AssignedCell, Chip, Layouter, Value}, 24 | plonk::{self, Advice, Assigned, Column}, 25 | }; 26 | 27 | pub(in crate::circuit) mod add_chip; 28 | 29 | impl super::Config { 30 | pub(super) fn add_chip(&self) -> add_chip::AddChip { 31 | add_chip::AddChip::construct(self.add_config.clone()) 32 | } 33 | 34 | pub(super) fn commit_ivk_chip(&self) -> CommitIvkChip { 35 | CommitIvkChip::construct(self.commit_ivk_config.clone()) 36 | } 37 | 38 | pub(super) fn ecc_chip(&self) -> EccChip { 39 | EccChip::construct(self.ecc_config.clone()) 40 | } 41 | 42 | pub(super) fn sinsemilla_chip_1( 43 | &self, 44 | ) -> SinsemillaChip { 45 | SinsemillaChip::construct(self.sinsemilla_config_1.clone()) 46 | } 47 | 48 | pub(super) fn sinsemilla_chip_2( 49 | &self, 50 | ) -> SinsemillaChip { 51 | SinsemillaChip::construct(self.sinsemilla_config_2.clone()) 52 | } 53 | 54 | pub(super) fn merkle_chip_1( 55 | &self, 56 | ) -> MerkleChip { 57 | MerkleChip::construct(self.merkle_config_1.clone()) 58 | } 59 | 60 | pub(super) fn merkle_chip_2( 61 | &self, 62 | ) -> MerkleChip { 63 | MerkleChip::construct(self.merkle_config_2.clone()) 64 | } 65 | 66 | pub(super) fn poseidon_chip(&self) -> PoseidonChip { 67 | PoseidonChip::construct(self.poseidon_config.clone()) 68 | } 69 | 70 | pub(super) fn note_commit_chip_new(&self) -> NoteCommitChip { 71 | NoteCommitChip::construct(self.new_note_commit_config.clone()) 72 | } 73 | 74 | pub(super) fn note_commit_chip_old(&self) -> NoteCommitChip { 75 | NoteCommitChip::construct(self.old_note_commit_config.clone()) 76 | } 77 | } 78 | 79 | /// An instruction set for adding two circuit words (field elements). 80 | pub(in crate::circuit) trait AddInstruction: Chip { 81 | /// Constraints `a + b` and returns the sum. 82 | fn add( 83 | &self, 84 | layouter: impl Layouter, 85 | a: &AssignedCell, 86 | b: &AssignedCell, 87 | ) -> Result, plonk::Error>; 88 | } 89 | 90 | /// Witnesses the given value in a standalone region. 91 | /// 92 | /// Usages of this helper are technically superfluous, as the single-cell region is only 93 | /// ever used in equality constraints. We could eliminate them with a 94 | /// [write-on-copy abstraction](https://github.com/zcash/halo2/issues/334). 95 | pub(in crate::circuit) fn assign_free_advice( 96 | mut layouter: impl Layouter, 97 | column: Column, 98 | value: Value, 99 | ) -> Result, plonk::Error> 100 | where 101 | for<'v> Assigned: From<&'v V>, 102 | { 103 | layouter.assign_region( 104 | || "load private", 105 | |mut region| region.assign_advice(|| "load private", column, 0, || value), 106 | ) 107 | } 108 | 109 | /// `ValueCommit^Orchard` from [Section 5.4.8.3 Homomorphic Pedersen commitments (Sapling and Orchard)]. 110 | /// 111 | /// [Section 5.4.8.3 Homomorphic Pedersen commitments (Sapling and Orchard)]: https://zips.z.cash/protocol/protocol.pdf#concretehomomorphiccommit 112 | pub(in crate::circuit) fn value_commit_orchard< 113 | EccChip: EccInstructions< 114 | pallas::Affine, 115 | FixedPoints = OrchardFixedBases, 116 | Var = AssignedCell, 117 | >, 118 | >( 119 | mut layouter: impl Layouter, 120 | ecc_chip: EccChip, 121 | v: ScalarFixedShort, 122 | rcv: ScalarFixed, 123 | ) -> Result, plonk::Error> { 124 | // commitment = [v] ValueCommitV 125 | let (commitment, _) = { 126 | let value_commit_v = ValueCommitV; 127 | let value_commit_v = FixedPointShort::from_inner(ecc_chip.clone(), value_commit_v); 128 | value_commit_v.mul(layouter.namespace(|| "[v] ValueCommitV"), v)? 129 | }; 130 | 131 | // blind = [rcv] ValueCommitR 132 | let (blind, _rcv) = { 133 | let value_commit_r = OrchardFixedBasesFull::ValueCommitR; 134 | let value_commit_r = FixedPoint::from_inner(ecc_chip, value_commit_r); 135 | 136 | // [rcv] ValueCommitR 137 | value_commit_r.mul(layouter.namespace(|| "[rcv] ValueCommitR"), rcv)? 138 | }; 139 | 140 | // [v] ValueCommitV + [rcv] ValueCommitR 141 | commitment.add(layouter.namespace(|| "cv"), &blind) 142 | } 143 | 144 | /// `DeriveNullifier` from [Section 4.16: Note Commitments and Nullifiers]. 145 | /// 146 | /// [Section 4.16: Note Commitments and Nullifiers]: https://zips.z.cash/protocol/protocol.pdf#commitmentsandnullifiers 147 | #[allow(clippy::too_many_arguments)] 148 | pub(in crate::circuit) fn derive_nullifier< 149 | PoseidonChip: PoseidonSpongeInstructions, 3, 2>, 150 | AddChip: AddInstruction, 151 | EccChip: EccInstructions< 152 | pallas::Affine, 153 | FixedPoints = OrchardFixedBases, 154 | Var = AssignedCell, 155 | >, 156 | >( 157 | mut layouter: impl Layouter, 158 | poseidon_chip: PoseidonChip, 159 | add_chip: AddChip, 160 | ecc_chip: EccChip, 161 | rho: AssignedCell, 162 | psi: &AssignedCell, 163 | cm: &Point, 164 | nk: AssignedCell, 165 | ) -> Result, plonk::Error> { 166 | // hash = poseidon_hash(nk, rho) 167 | let hash = { 168 | let poseidon_message = [nk, rho]; 169 | let poseidon_hasher = 170 | PoseidonHash::init(poseidon_chip, layouter.namespace(|| "Poseidon init"))?; 171 | poseidon_hasher.hash( 172 | layouter.namespace(|| "Poseidon hash (nk, rho)"), 173 | poseidon_message, 174 | )? 175 | }; 176 | 177 | // Add hash output to psi. 178 | // `scalar` = poseidon_hash(nk, rho) + psi. 179 | let scalar = add_chip.add( 180 | layouter.namespace(|| "scalar = poseidon_hash(nk, rho) + psi"), 181 | &hash, 182 | psi, 183 | )?; 184 | 185 | // Multiply scalar by NullifierK 186 | // `product` = [poseidon_hash(nk, rho) + psi] NullifierK. 187 | // 188 | let product = { 189 | let nullifier_k = FixedPointBaseField::from_inner(ecc_chip, NullifierK); 190 | nullifier_k.mul( 191 | layouter.namespace(|| "[poseidon_output + psi] NullifierK"), 192 | scalar, 193 | )? 194 | }; 195 | 196 | // Add cm to multiplied fixed base to get nf 197 | // cm + [poseidon_output + psi] NullifierK 198 | cm.add(layouter.namespace(|| "nf"), &product) 199 | .map(|res| res.extract_p()) 200 | } 201 | 202 | pub(in crate::circuit) use crate::circuit::commit_ivk::gadgets::commit_ivk; 203 | pub(in crate::circuit) use crate::circuit::note_commit::gadgets::note_commit; 204 | -------------------------------------------------------------------------------- /src/circuit/gadget/add_chip.rs: -------------------------------------------------------------------------------- 1 | use halo2_proofs::{ 2 | circuit::{AssignedCell, Chip, Layouter}, 3 | plonk::{self, Advice, Column, ConstraintSystem, Constraints, Selector}, 4 | poly::Rotation, 5 | }; 6 | use pasta_curves::pallas; 7 | 8 | use super::AddInstruction; 9 | 10 | #[derive(Clone, Debug)] 11 | pub(in crate::circuit) struct AddConfig { 12 | a: Column, 13 | b: Column, 14 | c: Column, 15 | q_add: Selector, 16 | } 17 | 18 | /// A chip implementing a single addition constraint `c = a + b` on a single row. 19 | pub(in crate::circuit) struct AddChip { 20 | config: AddConfig, 21 | } 22 | 23 | impl Chip for AddChip { 24 | type Config = AddConfig; 25 | type Loaded = (); 26 | 27 | fn config(&self) -> &Self::Config { 28 | &self.config 29 | } 30 | 31 | fn loaded(&self) -> &Self::Loaded { 32 | &() 33 | } 34 | } 35 | 36 | impl AddChip { 37 | pub(in crate::circuit) fn configure( 38 | meta: &mut ConstraintSystem, 39 | a: Column, 40 | b: Column, 41 | c: Column, 42 | ) -> AddConfig { 43 | let q_add = meta.selector(); 44 | meta.create_gate("Field element addition: c = a + b", |meta| { 45 | let q_add = meta.query_selector(q_add); 46 | let a = meta.query_advice(a, Rotation::cur()); 47 | let b = meta.query_advice(b, Rotation::cur()); 48 | let c = meta.query_advice(c, Rotation::cur()); 49 | 50 | Constraints::with_selector(q_add, Some(a + b - c)) 51 | }); 52 | 53 | AddConfig { a, b, c, q_add } 54 | } 55 | 56 | pub(in crate::circuit) fn construct(config: AddConfig) -> Self { 57 | Self { config } 58 | } 59 | } 60 | 61 | impl AddInstruction for AddChip { 62 | fn add( 63 | &self, 64 | mut layouter: impl Layouter, 65 | a: &AssignedCell, 66 | b: &AssignedCell, 67 | ) -> Result, plonk::Error> { 68 | layouter.assign_region( 69 | || "c = a + b", 70 | |mut region| { 71 | self.config.q_add.enable(&mut region, 0)?; 72 | 73 | a.copy_advice(|| "copy a", &mut region, self.config.a, 0)?; 74 | b.copy_advice(|| "copy b", &mut region, self.config.b, 0)?; 75 | 76 | let scalar_val = a.value().zip(b.value()).map(|(a, b)| a + b); 77 | region.assign_advice(|| "c", self.config.c, 0, || scalar_val) 78 | }, 79 | ) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/circuit_proof_test_case.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zcash/orchard/fcb14defd75c0dd79512289c17a1ac46b5001d3a/src/circuit_proof_test_case.bin -------------------------------------------------------------------------------- /src/constants.rs: -------------------------------------------------------------------------------- 1 | //! Constants used in the Orchard protocol. 2 | pub mod fixed_bases; 3 | pub mod sinsemilla; 4 | pub mod util; 5 | 6 | #[cfg(feature = "circuit")] 7 | pub use self::sinsemilla::{OrchardCommitDomains, OrchardHashDomains}; 8 | #[cfg(feature = "circuit")] 9 | pub use fixed_bases::{NullifierK, OrchardFixedBases, OrchardFixedBasesFull, ValueCommitV}; 10 | 11 | /// $\mathsf{MerkleDepth^{Orchard}}$ 12 | pub const MERKLE_DEPTH_ORCHARD: usize = 32; 13 | 14 | /// The Pallas scalar field modulus is $q = 2^{254} + \mathsf{t_q}$. 15 | /// 16 | pub(crate) const T_Q: u128 = 45560315531506369815346746415080538113; 17 | 18 | /// The Pallas base field modulus is $p = 2^{254} + \mathsf{t_p}$. 19 | /// 20 | pub(crate) const T_P: u128 = 45560315531419706090280762371685220353; 21 | 22 | /// $\ell^\mathsf{Orchard}_\mathsf{base}$ 23 | pub(crate) const L_ORCHARD_BASE: usize = 255; 24 | 25 | /// $\ell^\mathsf{Orchard}_\mathsf{scalar}$ 26 | pub(crate) const L_ORCHARD_SCALAR: usize = 255; 27 | 28 | /// $\ell_\mathsf{value}$ 29 | pub(crate) const L_VALUE: usize = 64; 30 | 31 | /// SWU hash-to-curve personalization for the group hash for key diversification 32 | pub const KEY_DIVERSIFICATION_PERSONALIZATION: &str = "z.cash:Orchard-gd"; 33 | 34 | #[cfg(test)] 35 | mod tests { 36 | use ff::PrimeField; 37 | use pasta_curves::pallas; 38 | 39 | #[test] 40 | // Orchard uses the Pallas base field as its base field. 41 | fn l_orchard_base() { 42 | assert_eq!(super::L_ORCHARD_BASE, pallas::Base::NUM_BITS as usize); 43 | } 44 | 45 | #[test] 46 | // Orchard uses the Pallas base field as its base field. 47 | fn l_orchard_scalar() { 48 | assert_eq!(super::L_ORCHARD_SCALAR, pallas::Scalar::NUM_BITS as usize); 49 | } 50 | 51 | #[test] 52 | fn t_q() { 53 | let t_q = pallas::Scalar::from_u128(super::T_Q); 54 | let two_pow_254 = pallas::Scalar::from_u128(1 << 127).square(); 55 | assert_eq!(t_q + two_pow_254, pallas::Scalar::zero()); 56 | } 57 | 58 | #[test] 59 | fn t_p() { 60 | let t_p = pallas::Base::from_u128(super::T_P); 61 | let two_pow_254 = pallas::Base::from_u128(1 << 127).square(); 62 | assert_eq!(t_p + two_pow_254, pallas::Base::zero()); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/constants/fixed_bases.rs: -------------------------------------------------------------------------------- 1 | //! Orchard fixed bases. 2 | 3 | #[cfg(feature = "circuit")] 4 | use alloc::vec::Vec; 5 | 6 | use super::{L_ORCHARD_SCALAR, L_VALUE}; 7 | 8 | #[cfg(feature = "circuit")] 9 | use halo2_gadgets::ecc::{ 10 | chip::{BaseFieldElem, FixedPoint, FullScalar, ShortScalar}, 11 | FixedPoints, 12 | }; 13 | 14 | #[cfg(feature = "circuit")] 15 | use pasta_curves::pallas; 16 | 17 | pub mod commit_ivk_r; 18 | pub mod note_commit_r; 19 | pub mod nullifier_k; 20 | pub mod spend_auth_g; 21 | pub mod value_commit_r; 22 | pub mod value_commit_v; 23 | 24 | /// SWU hash-to-curve personalization for the spending key base point and 25 | /// the nullifier base point K^Orchard 26 | pub const ORCHARD_PERSONALIZATION: &str = "z.cash:Orchard"; 27 | 28 | /// SWU hash-to-curve personalization for the value commitment generator 29 | pub const VALUE_COMMITMENT_PERSONALIZATION: &str = "z.cash:Orchard-cv"; 30 | 31 | /// SWU hash-to-curve value for the value commitment generator 32 | pub const VALUE_COMMITMENT_V_BYTES: [u8; 1] = *b"v"; 33 | 34 | /// SWU hash-to-curve value for the value commitment generator 35 | pub const VALUE_COMMITMENT_R_BYTES: [u8; 1] = *b"r"; 36 | 37 | /// SWU hash-to-curve personalization for the note commitment generator 38 | pub const NOTE_COMMITMENT_PERSONALIZATION: &str = "z.cash:Orchard-NoteCommit"; 39 | 40 | /// SWU hash-to-curve personalization for the IVK commitment generator 41 | pub const COMMIT_IVK_PERSONALIZATION: &str = "z.cash:Orchard-CommitIvk"; 42 | 43 | /// Window size for fixed-base scalar multiplication 44 | pub const FIXED_BASE_WINDOW_SIZE: usize = 3; 45 | 46 | /// $2^{`FIXED_BASE_WINDOW_SIZE`}$ 47 | pub const H: usize = 1 << FIXED_BASE_WINDOW_SIZE; 48 | 49 | /// Number of windows for a full-width scalar 50 | pub const NUM_WINDOWS: usize = 51 | (L_ORCHARD_SCALAR + FIXED_BASE_WINDOW_SIZE - 1) / FIXED_BASE_WINDOW_SIZE; 52 | 53 | /// Number of windows for a short signed scalar 54 | pub const NUM_WINDOWS_SHORT: usize = 55 | (L_VALUE + FIXED_BASE_WINDOW_SIZE - 1) / FIXED_BASE_WINDOW_SIZE; 56 | 57 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 58 | // A sum type for both full-width and short bases. This enables us to use the 59 | // shared functionality of full-width and short fixed-base scalar multiplication. 60 | pub enum OrchardFixedBases { 61 | Full(OrchardFixedBasesFull), 62 | NullifierK, 63 | ValueCommitV, 64 | } 65 | 66 | impl From for OrchardFixedBases { 67 | fn from(full_width_base: OrchardFixedBasesFull) -> Self { 68 | Self::Full(full_width_base) 69 | } 70 | } 71 | 72 | impl From for OrchardFixedBases { 73 | fn from(_value_commit_v: ValueCommitV) -> Self { 74 | Self::ValueCommitV 75 | } 76 | } 77 | 78 | impl From for OrchardFixedBases { 79 | fn from(_nullifier_k: NullifierK) -> Self { 80 | Self::NullifierK 81 | } 82 | } 83 | 84 | /// The Orchard fixed bases used in scalar mul with full-width scalars. 85 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 86 | pub enum OrchardFixedBasesFull { 87 | CommitIvkR, 88 | NoteCommitR, 89 | ValueCommitR, 90 | SpendAuthG, 91 | } 92 | 93 | /// NullifierK is used in scalar mul with a base field element. 94 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 95 | pub struct NullifierK; 96 | 97 | /// ValueCommitV is used in scalar mul with a short signed scalar. 98 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 99 | pub struct ValueCommitV; 100 | 101 | #[cfg(feature = "circuit")] 102 | impl FixedPoints for OrchardFixedBases { 103 | type FullScalar = OrchardFixedBasesFull; 104 | type Base = NullifierK; 105 | type ShortScalar = ValueCommitV; 106 | } 107 | 108 | #[cfg(feature = "circuit")] 109 | impl FixedPoint for OrchardFixedBasesFull { 110 | type FixedScalarKind = FullScalar; 111 | 112 | fn generator(&self) -> pallas::Affine { 113 | match self { 114 | Self::CommitIvkR => commit_ivk_r::generator(), 115 | Self::NoteCommitR => note_commit_r::generator(), 116 | Self::ValueCommitR => value_commit_r::generator(), 117 | Self::SpendAuthG => spend_auth_g::generator(), 118 | } 119 | } 120 | 121 | fn u(&self) -> Vec<[[u8; 32]; H]> { 122 | match self { 123 | Self::CommitIvkR => commit_ivk_r::U.to_vec(), 124 | Self::NoteCommitR => note_commit_r::U.to_vec(), 125 | Self::ValueCommitR => value_commit_r::U.to_vec(), 126 | Self::SpendAuthG => spend_auth_g::U.to_vec(), 127 | } 128 | } 129 | 130 | fn z(&self) -> Vec { 131 | match self { 132 | Self::CommitIvkR => commit_ivk_r::Z.to_vec(), 133 | Self::NoteCommitR => note_commit_r::Z.to_vec(), 134 | Self::ValueCommitR => value_commit_r::Z.to_vec(), 135 | Self::SpendAuthG => spend_auth_g::Z.to_vec(), 136 | } 137 | } 138 | } 139 | 140 | #[cfg(feature = "circuit")] 141 | impl FixedPoint for NullifierK { 142 | type FixedScalarKind = BaseFieldElem; 143 | 144 | fn generator(&self) -> pallas::Affine { 145 | nullifier_k::generator() 146 | } 147 | 148 | fn u(&self) -> Vec<[[u8; 32]; H]> { 149 | nullifier_k::U.to_vec() 150 | } 151 | 152 | fn z(&self) -> Vec { 153 | nullifier_k::Z.to_vec() 154 | } 155 | } 156 | 157 | #[cfg(feature = "circuit")] 158 | impl FixedPoint for ValueCommitV { 159 | type FixedScalarKind = ShortScalar; 160 | 161 | fn generator(&self) -> pallas::Affine { 162 | value_commit_v::generator() 163 | } 164 | 165 | fn u(&self) -> Vec<[[u8; 32]; H]> { 166 | value_commit_v::U_SHORT.to_vec() 167 | } 168 | 169 | fn z(&self) -> Vec { 170 | value_commit_v::Z_SHORT.to_vec() 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/constants/load.rs: -------------------------------------------------------------------------------- 1 | use crate::constants::{self, compute_lagrange_coeffs, H, NUM_WINDOWS, NUM_WINDOWS_SHORT}; 2 | use group::ff::PrimeField; 3 | use pasta_curves::pallas; 4 | 5 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 6 | pub enum OrchardFixedBasesFull { 7 | CommitIvkR, 8 | NoteCommitR, 9 | ValueCommitR, 10 | SpendAuthG, 11 | } 12 | 13 | impl OrchardFixedBasesFull { 14 | pub fn generator(&self) -> pallas::Affine { 15 | match self { 16 | OrchardFixedBasesFull::CommitIvkR => super::commit_ivk_r::generator(), 17 | OrchardFixedBasesFull::NoteCommitR => super::note_commit_r::generator(), 18 | OrchardFixedBasesFull::ValueCommitR => super::value_commit_r::generator(), 19 | OrchardFixedBasesFull::SpendAuthG => super::spend_auth_g::generator(), 20 | } 21 | } 22 | 23 | pub fn u(&self) -> U { 24 | match self { 25 | OrchardFixedBasesFull::CommitIvkR => super::commit_ivk_r::U.into(), 26 | OrchardFixedBasesFull::NoteCommitR => super::note_commit_r::U.into(), 27 | OrchardFixedBasesFull::ValueCommitR => super::value_commit_r::U.into(), 28 | OrchardFixedBasesFull::SpendAuthG => super::spend_auth_g::U.into(), 29 | } 30 | } 31 | } 32 | 33 | /// A fixed base to be used in scalar multiplication with a full-width scalar. 34 | #[derive(Clone, Debug, Eq, PartialEq)] 35 | pub struct OrchardFixedBase { 36 | pub generator: pallas::Affine, 37 | pub lagrange_coeffs: LagrangeCoeffs, 38 | pub z: Z, 39 | pub u: U, 40 | } 41 | 42 | impl From for OrchardFixedBase { 43 | fn from(base: OrchardFixedBasesFull) -> Self { 44 | let (generator, z, u) = match base { 45 | OrchardFixedBasesFull::CommitIvkR => ( 46 | super::commit_ivk_r::generator(), 47 | super::commit_ivk_r::Z.into(), 48 | super::commit_ivk_r::U.into(), 49 | ), 50 | OrchardFixedBasesFull::NoteCommitR => ( 51 | super::note_commit_r::generator(), 52 | super::note_commit_r::Z.into(), 53 | super::note_commit_r::U.into(), 54 | ), 55 | OrchardFixedBasesFull::ValueCommitR => ( 56 | super::value_commit_r::generator(), 57 | super::value_commit_r::Z.into(), 58 | super::value_commit_r::U.into(), 59 | ), 60 | OrchardFixedBasesFull::SpendAuthG => ( 61 | super::spend_auth_g::generator(), 62 | super::spend_auth_g::Z.into(), 63 | super::spend_auth_g::U.into(), 64 | ), 65 | }; 66 | 67 | Self { 68 | generator, 69 | lagrange_coeffs: compute_lagrange_coeffs(generator, NUM_WINDOWS).into(), 70 | z, 71 | u, 72 | } 73 | } 74 | } 75 | 76 | /// A fixed base to be used in scalar multiplication with a base field element. 77 | #[derive(Clone, Debug, Eq, PartialEq)] 78 | pub struct ValueCommitV { 79 | pub generator: pallas::Affine, 80 | pub lagrange_coeffs_short: LagrangeCoeffsShort, 81 | pub z_short: ZShort, 82 | pub u_short: UShort, 83 | } 84 | 85 | impl ValueCommitV { 86 | pub fn get() -> Self { 87 | let generator = super::value_commit_v::generator(); 88 | Self { 89 | generator, 90 | lagrange_coeffs_short: compute_lagrange_coeffs(generator, NUM_WINDOWS_SHORT).into(), 91 | z_short: super::value_commit_v::Z_SHORT.into(), 92 | u_short: super::value_commit_v::U_SHORT.into(), 93 | } 94 | } 95 | } 96 | 97 | /// A fixed base to be used in scalar multiplication with a short signed exponent. 98 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 99 | pub struct NullifierK; 100 | 101 | impl From for OrchardFixedBase { 102 | fn from(_nullifier_k: NullifierK) -> Self { 103 | let (generator, z, u) = ( 104 | super::nullifier_k::generator(), 105 | super::nullifier_k::Z.into(), 106 | super::nullifier_k::U.into(), 107 | ); 108 | Self { 109 | generator, 110 | lagrange_coeffs: compute_lagrange_coeffs(generator, NUM_WINDOWS).into(), 111 | z, 112 | u, 113 | } 114 | } 115 | } 116 | 117 | impl NullifierK { 118 | pub fn generator(&self) -> pallas::Affine { 119 | super::nullifier_k::generator() 120 | } 121 | 122 | pub fn u(&self) -> U { 123 | super::nullifier_k::U.into() 124 | } 125 | } 126 | 127 | #[derive(Clone, Debug, Eq, PartialEq)] 128 | // 8 coefficients per window 129 | pub struct WindowLagrangeCoeffs(pub Box<[pallas::Base; H]>); 130 | 131 | impl From<&[pallas::Base; H]> for WindowLagrangeCoeffs { 132 | fn from(array: &[pallas::Base; H]) -> Self { 133 | Self(Box::new(*array)) 134 | } 135 | } 136 | 137 | #[derive(Clone, Debug, Eq, PartialEq)] 138 | // 85 windows per base (with the exception of ValueCommitV) 139 | pub struct LagrangeCoeffs(pub Box<[WindowLagrangeCoeffs; constants::NUM_WINDOWS]>); 140 | 141 | impl From> for LagrangeCoeffs { 142 | fn from(windows: Vec) -> Self { 143 | Self(windows.into_boxed_slice().try_into().unwrap()) 144 | } 145 | } 146 | 147 | impl From> for LagrangeCoeffs { 148 | fn from(arrays: Vec<[pallas::Base; H]>) -> Self { 149 | let windows: Vec = arrays.iter().map(|array| array.into()).collect(); 150 | windows.into() 151 | } 152 | } 153 | 154 | #[derive(Clone, Debug, Eq, PartialEq)] 155 | // 22 windows for ValueCommitV 156 | pub struct LagrangeCoeffsShort(pub Box<[WindowLagrangeCoeffs; NUM_WINDOWS_SHORT]>); 157 | 158 | impl From> for LagrangeCoeffsShort { 159 | fn from(windows: Vec) -> Self { 160 | Self(windows.into_boxed_slice().try_into().unwrap()) 161 | } 162 | } 163 | 164 | impl From> for LagrangeCoeffsShort { 165 | fn from(arrays: Vec<[pallas::Base; H]>) -> Self { 166 | let windows: Vec = arrays.iter().map(|array| array.into()).collect(); 167 | windows.into() 168 | } 169 | } 170 | 171 | #[derive(Clone, Debug, Eq, PartialEq)] 172 | // 85 Z's per base (with the exception of ValueCommitV) 173 | pub struct Z(pub Box<[pallas::Base; NUM_WINDOWS]>); 174 | 175 | impl From<[u64; NUM_WINDOWS]> for Z { 176 | fn from(zs: [u64; NUM_WINDOWS]) -> Self { 177 | Self( 178 | zs.iter() 179 | .map(|z| pallas::Base::from(*z)) 180 | .collect::>() 181 | .into_boxed_slice() 182 | .try_into() 183 | .unwrap(), 184 | ) 185 | } 186 | } 187 | 188 | #[derive(Clone, Debug, Eq, PartialEq)] 189 | // 22 Z's for ValueCommitV 190 | pub struct ZShort(pub Box<[pallas::Base; NUM_WINDOWS_SHORT]>); 191 | 192 | impl From<[u64; NUM_WINDOWS_SHORT]> for ZShort { 193 | fn from(zs: [u64; NUM_WINDOWS_SHORT]) -> Self { 194 | Self( 195 | zs.iter() 196 | .map(|z| pallas::Base::from(*z)) 197 | .collect::>() 198 | .into_boxed_slice() 199 | .try_into() 200 | .unwrap(), 201 | ) 202 | } 203 | } 204 | 205 | #[derive(Clone, Debug, Eq, PartialEq)] 206 | // 8 u's per window 207 | pub struct WindowUs(pub Box<[pallas::Base; H]>); 208 | 209 | impl From<&[[u8; 32]; H]> for WindowUs { 210 | fn from(window_us: &[[u8; 32]; H]) -> Self { 211 | Self( 212 | window_us 213 | .iter() 214 | .map(|u| pallas::Base::from_repr(*u).unwrap()) 215 | .collect::>() 216 | .into_boxed_slice() 217 | .try_into() 218 | .unwrap(), 219 | ) 220 | } 221 | } 222 | 223 | #[derive(Clone, Debug, Eq, PartialEq)] 224 | // 85 windows per base (with the exception of ValueCommitV) 225 | pub struct U(pub Box<[WindowUs; NUM_WINDOWS]>); 226 | 227 | impl From> for U { 228 | fn from(windows: Vec) -> Self { 229 | Self(windows.into_boxed_slice().try_into().unwrap()) 230 | } 231 | } 232 | 233 | impl From<[[[u8; 32]; H]; NUM_WINDOWS]> for U { 234 | fn from(window_us: [[[u8; 32]; H]; NUM_WINDOWS]) -> Self { 235 | let windows: Vec = window_us.iter().map(|us| us.into()).collect(); 236 | windows.into() 237 | } 238 | } 239 | 240 | #[derive(Clone, Debug, Eq, PartialEq)] 241 | // 22 windows for ValueCommitV 242 | pub struct UShort(pub Box<[WindowUs; NUM_WINDOWS_SHORT]>); 243 | 244 | impl From> for UShort { 245 | fn from(windows: Vec) -> Self { 246 | Self(windows.into_boxed_slice().try_into().unwrap()) 247 | } 248 | } 249 | 250 | impl From<[[[u8; 32]; H]; NUM_WINDOWS_SHORT]> for UShort { 251 | fn from(window_us: [[[u8; 32]; H]; NUM_WINDOWS_SHORT]) -> Self { 252 | let windows: Vec = window_us.iter().map(|us| us.into()).collect(); 253 | windows.into() 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /src/constants/sinsemilla.rs: -------------------------------------------------------------------------------- 1 | //! Sinsemilla generators 2 | use crate::spec::i2lebsp; 3 | 4 | #[cfg(feature = "circuit")] 5 | use { 6 | super::{OrchardFixedBases, OrchardFixedBasesFull}, 7 | group::ff::PrimeField, 8 | halo2_gadgets::sinsemilla::{CommitDomains, HashDomains}, 9 | pasta_curves::{arithmetic::CurveAffine, pallas}, 10 | }; 11 | 12 | /// Number of bits of each message piece in $\mathsf{SinsemillaHashToPoint}$ 13 | pub const K: usize = 10; 14 | 15 | /// $\frac{1}{2^K}$ 16 | pub const INV_TWO_POW_K: [u8; 32] = [ 17 | 1, 0, 192, 196, 160, 229, 70, 82, 221, 165, 74, 202, 85, 7, 62, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18 | 0, 0, 0, 0, 0, 240, 63, 19 | ]; 20 | 21 | /// The largest integer such that $2^c \leq (r_P - 1) / 2$, where $r_P$ is the order 22 | /// of Pallas. 23 | pub const C: usize = 253; 24 | 25 | /// $\ell^\mathsf{Orchard}_\mathsf{Merkle}$ 26 | pub(crate) const L_ORCHARD_MERKLE: usize = 255; 27 | 28 | /// SWU hash-to-curve personalization for the Merkle CRH generator 29 | pub const MERKLE_CRH_PERSONALIZATION: &str = "z.cash:Orchard-MerkleCRH"; 30 | 31 | /// Generator used in SinsemillaHashToPoint for note commitment 32 | pub const Q_NOTE_COMMITMENT_M_GENERATOR: ([u8; 32], [u8; 32]) = ( 33 | [ 34 | 93, 116, 168, 64, 9, 186, 14, 50, 42, 221, 70, 253, 90, 15, 150, 197, 93, 237, 176, 121, 35 | 180, 242, 159, 247, 13, 205, 251, 86, 160, 7, 128, 23, 36 | ], 37 | [ 38 | 99, 172, 73, 115, 90, 10, 39, 135, 158, 94, 219, 129, 136, 18, 34, 136, 44, 201, 244, 110, 39 | 217, 194, 190, 78, 131, 112, 198, 138, 147, 88, 160, 50, 40 | ], 41 | ); 42 | 43 | /// Generator used in SinsemillaHashToPoint for IVK commitment 44 | pub const Q_COMMIT_IVK_M_GENERATOR: ([u8; 32], [u8; 32]) = ( 45 | [ 46 | 242, 130, 15, 121, 146, 47, 203, 107, 50, 162, 40, 81, 36, 204, 27, 66, 250, 65, 162, 90, 47 | 184, 129, 204, 125, 17, 200, 169, 74, 241, 12, 188, 5, 48 | ], 49 | [ 50 | 190, 222, 173, 207, 206, 229, 90, 190, 241, 165, 109, 201, 29, 53, 196, 70, 75, 5, 222, 32, 51 | 70, 7, 89, 239, 230, 190, 26, 212, 246, 76, 1, 27, 52 | ], 53 | ); 54 | 55 | /// Generator used in SinsemillaHashToPoint for Merkle collision-resistant hash 56 | pub const Q_MERKLE_CRH: ([u8; 32], [u8; 32]) = ( 57 | [ 58 | 160, 198, 41, 127, 249, 199, 185, 248, 112, 16, 141, 192, 85, 185, 190, 201, 153, 14, 137, 59 | 239, 90, 54, 15, 160, 185, 24, 168, 99, 150, 210, 22, 22, 60 | ], 61 | [ 62 | 98, 234, 242, 37, 206, 174, 233, 134, 150, 21, 116, 5, 234, 150, 28, 226, 121, 89, 163, 79, 63 | 62, 242, 196, 45, 153, 32, 175, 227, 163, 66, 134, 53, 64 | ], 65 | ); 66 | 67 | pub(crate) fn lebs2ip_k(bits: &[bool]) -> u32 { 68 | assert!(bits.len() == K); 69 | bits.iter() 70 | .enumerate() 71 | .fold(0u32, |acc, (i, b)| acc + if *b { 1 << i } else { 0 }) 72 | } 73 | 74 | /// The sequence of K bits in little-endian order representing an integer 75 | /// up to `2^K` - 1. 76 | pub(crate) fn i2lebsp_k(int: usize) -> [bool; K] { 77 | assert!(int < (1 << K)); 78 | i2lebsp(int as u64) 79 | } 80 | 81 | #[derive(Clone, Debug, Eq, PartialEq)] 82 | pub enum OrchardHashDomains { 83 | NoteCommit, 84 | CommitIvk, 85 | MerkleCrh, 86 | } 87 | 88 | #[cfg(feature = "circuit")] 89 | #[allow(non_snake_case)] 90 | impl HashDomains for OrchardHashDomains { 91 | fn Q(&self) -> pallas::Affine { 92 | match self { 93 | OrchardHashDomains::CommitIvk => pallas::Affine::from_xy( 94 | pallas::Base::from_repr(Q_COMMIT_IVK_M_GENERATOR.0).unwrap(), 95 | pallas::Base::from_repr(Q_COMMIT_IVK_M_GENERATOR.1).unwrap(), 96 | ) 97 | .unwrap(), 98 | OrchardHashDomains::NoteCommit => pallas::Affine::from_xy( 99 | pallas::Base::from_repr(Q_NOTE_COMMITMENT_M_GENERATOR.0).unwrap(), 100 | pallas::Base::from_repr(Q_NOTE_COMMITMENT_M_GENERATOR.1).unwrap(), 101 | ) 102 | .unwrap(), 103 | OrchardHashDomains::MerkleCrh => pallas::Affine::from_xy( 104 | pallas::Base::from_repr(Q_MERKLE_CRH.0).unwrap(), 105 | pallas::Base::from_repr(Q_MERKLE_CRH.1).unwrap(), 106 | ) 107 | .unwrap(), 108 | } 109 | } 110 | } 111 | 112 | #[derive(Clone, Debug, Eq, PartialEq)] 113 | pub enum OrchardCommitDomains { 114 | NoteCommit, 115 | CommitIvk, 116 | } 117 | 118 | #[cfg(feature = "circuit")] 119 | impl CommitDomains for OrchardCommitDomains { 120 | fn r(&self) -> OrchardFixedBasesFull { 121 | match self { 122 | Self::NoteCommit => OrchardFixedBasesFull::NoteCommitR, 123 | Self::CommitIvk => OrchardFixedBasesFull::CommitIvkR, 124 | } 125 | } 126 | 127 | fn hash_domain(&self) -> OrchardHashDomains { 128 | match self { 129 | Self::NoteCommit => OrchardHashDomains::NoteCommit, 130 | Self::CommitIvk => OrchardHashDomains::CommitIvk, 131 | } 132 | } 133 | } 134 | 135 | #[cfg(test)] 136 | mod tests { 137 | use super::*; 138 | use crate::constants::{ 139 | fixed_bases::{COMMIT_IVK_PERSONALIZATION, NOTE_COMMITMENT_PERSONALIZATION}, 140 | sinsemilla::MERKLE_CRH_PERSONALIZATION, 141 | }; 142 | use group::{ff::PrimeField, Curve}; 143 | use halo2_gadgets::sinsemilla::primitives::{CommitDomain, HashDomain}; 144 | use halo2_proofs::arithmetic::CurveAffine; 145 | use halo2_proofs::pasta::pallas; 146 | use rand::{self, rngs::OsRng, Rng}; 147 | 148 | #[test] 149 | // Nodes in the Merkle tree are Pallas base field elements. 150 | fn l_orchard_merkle() { 151 | assert_eq!(super::L_ORCHARD_MERKLE, pallas::Base::NUM_BITS as usize); 152 | } 153 | 154 | #[test] 155 | fn lebs2ip_k_round_trip() { 156 | let mut rng = OsRng; 157 | { 158 | let int = rng.gen_range(0..(1 << K)); 159 | assert_eq!(lebs2ip_k(&i2lebsp_k(int)) as usize, int); 160 | } 161 | 162 | assert_eq!(lebs2ip_k(&i2lebsp_k(0)) as usize, 0); 163 | assert_eq!(lebs2ip_k(&i2lebsp_k((1 << K) - 1)) as usize, (1 << K) - 1); 164 | } 165 | 166 | #[test] 167 | fn i2lebsp_k_round_trip() { 168 | { 169 | let bitstring = [0; K].map(|_| rand::random()); 170 | assert_eq!(i2lebsp_k(lebs2ip_k(&bitstring) as usize), bitstring); 171 | } 172 | 173 | { 174 | let bitstring = [false; K]; 175 | assert_eq!(i2lebsp_k(lebs2ip_k(&bitstring) as usize), bitstring); 176 | } 177 | 178 | { 179 | let bitstring = [true; K]; 180 | assert_eq!(i2lebsp_k(lebs2ip_k(&bitstring) as usize), bitstring); 181 | } 182 | } 183 | 184 | #[test] 185 | fn q_note_commitment_m() { 186 | let domain = CommitDomain::new(NOTE_COMMITMENT_PERSONALIZATION); 187 | let point = domain.Q(); 188 | let coords = point.to_affine().coordinates().unwrap(); 189 | 190 | assert_eq!( 191 | *coords.x(), 192 | pallas::Base::from_repr(Q_NOTE_COMMITMENT_M_GENERATOR.0).unwrap() 193 | ); 194 | assert_eq!( 195 | *coords.y(), 196 | pallas::Base::from_repr(Q_NOTE_COMMITMENT_M_GENERATOR.1).unwrap() 197 | ); 198 | } 199 | 200 | #[test] 201 | fn q_commit_ivk_m() { 202 | let domain = CommitDomain::new(COMMIT_IVK_PERSONALIZATION); 203 | let point = domain.Q(); 204 | let coords = point.to_affine().coordinates().unwrap(); 205 | 206 | assert_eq!( 207 | *coords.x(), 208 | pallas::Base::from_repr(Q_COMMIT_IVK_M_GENERATOR.0).unwrap() 209 | ); 210 | assert_eq!( 211 | *coords.y(), 212 | pallas::Base::from_repr(Q_COMMIT_IVK_M_GENERATOR.1).unwrap() 213 | ); 214 | } 215 | 216 | #[test] 217 | fn q_merkle_crh() { 218 | let domain = HashDomain::new(MERKLE_CRH_PERSONALIZATION); 219 | let point = domain.Q(); 220 | let coords = point.to_affine().coordinates().unwrap(); 221 | 222 | assert_eq!( 223 | *coords.x(), 224 | pallas::Base::from_repr(Q_MERKLE_CRH.0).unwrap() 225 | ); 226 | assert_eq!( 227 | *coords.y(), 228 | pallas::Base::from_repr(Q_MERKLE_CRH.1).unwrap() 229 | ); 230 | } 231 | 232 | #[test] 233 | fn inv_two_pow_k() { 234 | let two_pow_k = pallas::Base::from(1u64 << K); 235 | let inv_two_pow_k = pallas::Base::from_repr(INV_TWO_POW_K).unwrap(); 236 | 237 | assert_eq!(two_pow_k * inv_two_pow_k, pallas::Base::one()); 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /src/constants/util.rs: -------------------------------------------------------------------------------- 1 | /// Takes in an FnMut closure and returns a constant-length array with elements of 2 | /// type `Output`. 3 | pub fn gen_const_array( 4 | closure: impl FnMut(usize) -> Output, 5 | ) -> [Output; LEN] { 6 | gen_const_array_with_default(Default::default(), closure) 7 | } 8 | 9 | pub(crate) fn gen_const_array_with_default( 10 | default_value: Output, 11 | closure: impl FnMut(usize) -> Output, 12 | ) -> [Output; LEN] { 13 | let mut ret: [Output; LEN] = [default_value; LEN]; 14 | for (bit, val) in ret.iter_mut().zip((0..LEN).map(closure)) { 15 | *bit = val; 16 | } 17 | ret 18 | } 19 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # orchard 2 | //! 3 | //! ## Nomenclature 4 | //! 5 | //! All types in the `orchard` crate, unless otherwise specified, are Orchard-specific 6 | //! types. For example, [`Address`] is documented as being a shielded payment address; we 7 | //! implicitly mean it is an Orchard payment address (as opposed to e.g. a Sapling payment 8 | //! address, which is also shielded). 9 | 10 | #![no_std] 11 | #![cfg_attr(docsrs, feature(doc_cfg))] 12 | // Temporary until we have more of the crate implemented. 13 | #![allow(dead_code)] 14 | // Catch documentation errors caused by code changes. 15 | #![deny(rustdoc::broken_intra_doc_links)] 16 | #![deny(missing_debug_implementations)] 17 | #![deny(missing_docs)] 18 | #![deny(unsafe_code)] 19 | 20 | #[macro_use] 21 | extern crate alloc; 22 | 23 | #[cfg(feature = "std")] 24 | extern crate std; 25 | 26 | use alloc::vec::Vec; 27 | 28 | mod action; 29 | mod address; 30 | pub mod builder; 31 | pub mod bundle; 32 | #[cfg(feature = "circuit")] 33 | pub mod circuit; 34 | mod constants; 35 | pub mod keys; 36 | pub mod note; 37 | pub mod note_encryption; 38 | pub mod pczt; 39 | pub mod primitives; 40 | mod spec; 41 | pub mod tree; 42 | pub mod value; 43 | pub mod zip32; 44 | 45 | #[cfg(test)] 46 | mod test_vectors; 47 | 48 | pub use action::Action; 49 | pub use address::Address; 50 | pub use bundle::Bundle; 51 | pub use constants::MERKLE_DEPTH_ORCHARD as NOTE_COMMITMENT_TREE_DEPTH; 52 | pub use note::Note; 53 | pub use tree::Anchor; 54 | 55 | /// A proof of the validity of an Orchard [`Bundle`]. 56 | /// 57 | /// [`Bundle`]: crate::bundle::Bundle 58 | #[derive(Clone)] 59 | pub struct Proof(Vec); 60 | 61 | impl core::fmt::Debug for Proof { 62 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 63 | if f.alternate() { 64 | f.debug_tuple("Proof").field(&self.0).finish() 65 | } else { 66 | // By default, only show the proof length, not its contents. 67 | f.debug_tuple("Proof") 68 | .field(&format_args!("{} bytes", self.0.len())) 69 | .finish() 70 | } 71 | } 72 | } 73 | 74 | impl AsRef<[u8]> for Proof { 75 | fn as_ref(&self) -> &[u8] { 76 | &self.0 77 | } 78 | } 79 | 80 | impl memuse::DynamicUsage for Proof { 81 | fn dynamic_usage(&self) -> usize { 82 | self.0.dynamic_usage() 83 | } 84 | 85 | fn dynamic_usage_bounds(&self) -> (usize, Option) { 86 | self.0.dynamic_usage_bounds() 87 | } 88 | } 89 | 90 | impl Proof { 91 | /// Constructs a new Proof value. 92 | pub fn new(bytes: Vec) -> Self { 93 | Proof(bytes) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/note/commitment.rs: -------------------------------------------------------------------------------- 1 | use core::iter; 2 | 3 | use bitvec::{array::BitArray, order::Lsb0}; 4 | use group::ff::{PrimeField, PrimeFieldBits}; 5 | use pasta_curves::pallas; 6 | use subtle::{ConstantTimeEq, CtOption}; 7 | 8 | use crate::{ 9 | constants::{fixed_bases::NOTE_COMMITMENT_PERSONALIZATION, L_ORCHARD_BASE}, 10 | spec::extract_p, 11 | value::NoteValue, 12 | }; 13 | 14 | #[derive(Clone, Debug)] 15 | pub(crate) struct NoteCommitTrapdoor(pub(super) pallas::Scalar); 16 | 17 | impl NoteCommitTrapdoor { 18 | pub(crate) fn inner(&self) -> pallas::Scalar { 19 | self.0 20 | } 21 | } 22 | 23 | /// A commitment to a note. 24 | #[derive(Clone, Debug)] 25 | pub struct NoteCommitment(pub(super) pallas::Point); 26 | 27 | impl NoteCommitment { 28 | pub(crate) fn inner(&self) -> pallas::Point { 29 | self.0 30 | } 31 | } 32 | 33 | impl NoteCommitment { 34 | /// $NoteCommit^Orchard$. 35 | /// 36 | /// Defined in [Zcash Protocol Spec § 5.4.8.4: Sinsemilla commitments][concretesinsemillacommit]. 37 | /// 38 | /// [concretesinsemillacommit]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit 39 | pub(super) fn derive( 40 | g_d: [u8; 32], 41 | pk_d: [u8; 32], 42 | v: NoteValue, 43 | rho: pallas::Base, 44 | psi: pallas::Base, 45 | rcm: NoteCommitTrapdoor, 46 | ) -> CtOption { 47 | let domain = sinsemilla::CommitDomain::new(NOTE_COMMITMENT_PERSONALIZATION); 48 | domain 49 | .commit( 50 | iter::empty() 51 | .chain(BitArray::<_, Lsb0>::new(g_d).iter().by_vals()) 52 | .chain(BitArray::<_, Lsb0>::new(pk_d).iter().by_vals()) 53 | .chain(v.to_le_bits().iter().by_vals()) 54 | .chain(rho.to_le_bits().iter().by_vals().take(L_ORCHARD_BASE)) 55 | .chain(psi.to_le_bits().iter().by_vals().take(L_ORCHARD_BASE)), 56 | &rcm.0, 57 | ) 58 | .map(NoteCommitment) 59 | } 60 | } 61 | 62 | /// The x-coordinate of the commitment to a note. 63 | #[derive(Copy, Clone, Debug)] 64 | pub struct ExtractedNoteCommitment(pub(super) pallas::Base); 65 | 66 | impl ExtractedNoteCommitment { 67 | /// Deserialize the extracted note commitment from a byte array. 68 | /// 69 | /// This method enforces the [consensus rule][cmxcanon] that the 70 | /// byte representation of cmx MUST be canonical. 71 | /// 72 | /// [cmxcanon]: https://zips.z.cash/protocol/protocol.pdf#actionencodingandconsensus 73 | pub fn from_bytes(bytes: &[u8; 32]) -> CtOption { 74 | pallas::Base::from_repr(*bytes).map(ExtractedNoteCommitment) 75 | } 76 | 77 | /// Serialize the value commitment to its canonical byte representation. 78 | pub fn to_bytes(self) -> [u8; 32] { 79 | self.0.to_repr() 80 | } 81 | } 82 | 83 | impl From for ExtractedNoteCommitment { 84 | fn from(cm: NoteCommitment) -> Self { 85 | ExtractedNoteCommitment(extract_p(&cm.0)) 86 | } 87 | } 88 | 89 | impl ExtractedNoteCommitment { 90 | pub(crate) fn inner(&self) -> pallas::Base { 91 | self.0 92 | } 93 | } 94 | 95 | impl From<&ExtractedNoteCommitment> for [u8; 32] { 96 | fn from(cmx: &ExtractedNoteCommitment) -> Self { 97 | cmx.to_bytes() 98 | } 99 | } 100 | 101 | impl ConstantTimeEq for ExtractedNoteCommitment { 102 | fn ct_eq(&self, other: &Self) -> subtle::Choice { 103 | self.0.ct_eq(&other.0) 104 | } 105 | } 106 | 107 | impl PartialEq for ExtractedNoteCommitment { 108 | fn eq(&self, other: &Self) -> bool { 109 | self.ct_eq(other).into() 110 | } 111 | } 112 | 113 | impl Eq for ExtractedNoteCommitment {} 114 | -------------------------------------------------------------------------------- /src/note/nullifier.rs: -------------------------------------------------------------------------------- 1 | use group::{ff::PrimeField, Group}; 2 | use memuse::DynamicUsage; 3 | use pasta_curves::{arithmetic::CurveExt, pallas}; 4 | use rand::RngCore; 5 | use subtle::{ConstantTimeEq, CtOption}; 6 | 7 | use super::NoteCommitment; 8 | use crate::{ 9 | keys::NullifierDerivingKey, 10 | spec::{extract_p, mod_r_p}, 11 | }; 12 | 13 | /// A unique nullifier for a note. 14 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] 15 | pub struct Nullifier(pub(crate) pallas::Base); 16 | 17 | // We know that `pallas::Base` doesn't allocate internally. 18 | memuse::impl_no_dynamic_usage!(Nullifier); 19 | 20 | impl Nullifier { 21 | /// Generates a dummy nullifier for use as $\rho$ in dummy spent notes. 22 | /// 23 | /// Nullifiers are required by consensus to be unique. For dummy output notes, we get 24 | /// this restriction as intended: the note's $\rho$ value is set to the nullifier of 25 | /// the accompanying spent note within the action, which is constrained by consensus 26 | /// to be unique. In the case of dummy spent notes, we get this restriction by 27 | /// following the chain backwards: the nullifier of the dummy spent note will be 28 | /// constrained by consensus to be unique, and the nullifier's uniqueness is derived 29 | /// from the uniqueness of $\rho$. 30 | /// 31 | /// Instead of explicitly sampling for a unique nullifier, we rely here on the size of 32 | /// the base field to make the chance of sampling a colliding nullifier negligible. 33 | pub(crate) fn dummy(rng: &mut impl RngCore) -> Self { 34 | Nullifier(extract_p(&pallas::Point::random(rng))) 35 | } 36 | 37 | /// Deserialize the nullifier from a byte array. 38 | pub fn from_bytes(bytes: &[u8; 32]) -> CtOption { 39 | pallas::Base::from_repr(*bytes).map(Nullifier) 40 | } 41 | 42 | /// Serialize the nullifier to its canonical byte representation. 43 | pub fn to_bytes(self) -> [u8; 32] { 44 | self.0.to_repr() 45 | } 46 | 47 | /// $DeriveNullifier$. 48 | /// 49 | /// Defined in [Zcash Protocol Spec § 4.16: Note Commitments and Nullifiers][commitmentsandnullifiers]. 50 | /// 51 | /// [commitmentsandnullifiers]: https://zips.z.cash/protocol/nu5.pdf#commitmentsandnullifiers 52 | pub(super) fn derive( 53 | nk: &NullifierDerivingKey, 54 | rho: pallas::Base, 55 | psi: pallas::Base, 56 | cm: NoteCommitment, 57 | ) -> Self { 58 | let k = pallas::Point::hash_to_curve("z.cash:Orchard")(b"K"); 59 | 60 | Nullifier(extract_p(&(k * mod_r_p(nk.prf_nf(rho) + psi) + cm.0))) 61 | } 62 | } 63 | 64 | impl ConstantTimeEq for Nullifier { 65 | fn ct_eq(&self, other: &Self) -> subtle::Choice { 66 | self.0.ct_eq(&other.0) 67 | } 68 | } 69 | 70 | /// Generators for property testing. 71 | #[cfg(any(test, feature = "test-dependencies"))] 72 | #[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))] 73 | pub mod testing { 74 | use group::{ff::FromUniformBytes, Group}; 75 | use pasta_curves::pallas; 76 | use proptest::collection::vec; 77 | use proptest::prelude::*; 78 | 79 | use super::Nullifier; 80 | use crate::spec::extract_p; 81 | 82 | prop_compose! { 83 | /// Generate a uniformly distributed nullifier value. 84 | pub fn arb_nullifier()( 85 | bytes in vec(any::(), 64) 86 | ) -> Nullifier { 87 | let point = pallas::Point::generator() * pallas::Scalar::from_uniform_bytes(&<[u8; 64]>::try_from(bytes).unwrap()); 88 | Nullifier(extract_p(&point)) 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/pczt/io_finalizer.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | 3 | use rand::{CryptoRng, RngCore}; 4 | 5 | use crate::{ 6 | keys::SpendAuthorizingKey, 7 | primitives::redpallas, 8 | value::{ValueCommitTrapdoor, ValueCommitment}, 9 | }; 10 | 11 | use super::SignerError; 12 | 13 | impl super::Bundle { 14 | /// Finalizes the IO for this bundle. 15 | pub fn finalize_io( 16 | &mut self, 17 | sighash: [u8; 32], 18 | mut rng: R, 19 | ) -> Result<(), IoFinalizerError> { 20 | // Compute the transaction binding signing key. 21 | let rcvs = self 22 | .actions 23 | .iter() 24 | .map(|a| { 25 | a.rcv 26 | .as_ref() 27 | .ok_or(IoFinalizerError::MissingValueCommitTrapdoor) 28 | }) 29 | .collect::, _>>()?; 30 | let bsk = rcvs.into_iter().sum::().into_bsk(); 31 | 32 | // Verify that bsk and bvk are consistent. 33 | let bvk = (self 34 | .actions 35 | .iter() 36 | .map(|a| a.cv_net()) 37 | .sum::() 38 | - ValueCommitment::derive(self.value_sum, ValueCommitTrapdoor::zero())) 39 | .into_bvk(); 40 | if redpallas::VerificationKey::from(&bsk) != bvk { 41 | return Err(IoFinalizerError::ValueCommitMismatch); 42 | } 43 | self.bsk = Some(bsk); 44 | 45 | // Add signatures to dummy spends. 46 | for action in self.actions.iter_mut() { 47 | // The `Option::take` ensures we don't have any spend authorizing keys in the 48 | // PCZT after the IO Finalizer has run. 49 | if let Some(sk) = action.spend.dummy_sk.take() { 50 | let ask = SpendAuthorizingKey::from(&sk); 51 | action 52 | .sign(sighash, &ask, &mut rng) 53 | .map_err(IoFinalizerError::DummySignature)?; 54 | } 55 | } 56 | 57 | Ok(()) 58 | } 59 | } 60 | 61 | /// Errors that can occur while finalizing the I/O for a PCZT bundle. 62 | #[derive(Debug)] 63 | pub enum IoFinalizerError { 64 | /// An error occurred while signing a dummy spend. 65 | DummySignature(SignerError), 66 | /// The IO Finalizer role requires all `rcv` fields to be set. 67 | MissingValueCommitTrapdoor, 68 | /// The `cv_net`, `rcv`, and `value_sum` values within the Orchard bundle are 69 | /// inconsistent. 70 | ValueCommitMismatch, 71 | } 72 | -------------------------------------------------------------------------------- /src/pczt/prover.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | 3 | use halo2_proofs::plonk; 4 | use rand::{CryptoRng, RngCore}; 5 | 6 | use crate::{ 7 | builder::SpendInfo, 8 | circuit::{Circuit, Instance, ProvingKey}, 9 | note::Rho, 10 | Note, Proof, 11 | }; 12 | 13 | impl super::Bundle { 14 | /// Adds a proof to this PCZT bundle. 15 | pub fn create_proof( 16 | &mut self, 17 | pk: &ProvingKey, 18 | rng: R, 19 | ) -> Result<(), ProverError> { 20 | // If we have no actions, we don't need a proof (and if we still have no actions 21 | // by the time we come to transaction extraction, we will end up with a `None` 22 | // bundle that doesn't even hold a proof field). 23 | if self.actions.is_empty() { 24 | return Ok(()); 25 | } 26 | 27 | let circuits = self 28 | .actions 29 | .iter() 30 | .map(|action| { 31 | let fvk = action 32 | .spend 33 | .fvk 34 | .clone() 35 | .ok_or(ProverError::MissingFullViewingKey)?; 36 | 37 | let note = Note::from_parts( 38 | action 39 | .spend 40 | .recipient 41 | .ok_or(ProverError::MissingRecipient)?, 42 | action.spend.value.ok_or(ProverError::MissingValue)?, 43 | action.spend.rho.ok_or(ProverError::MissingRho)?, 44 | action.spend.rseed.ok_or(ProverError::MissingRandomSeed)?, 45 | ) 46 | .into_option() 47 | .ok_or(ProverError::InvalidSpendNote)?; 48 | 49 | let merkle_path = action 50 | .spend 51 | .witness 52 | .clone() 53 | .ok_or(ProverError::MissingWitness)?; 54 | 55 | let spend = 56 | SpendInfo::new(fvk, note, merkle_path).ok_or(ProverError::WrongFvkForNote)?; 57 | 58 | let output_note = Note::from_parts( 59 | action 60 | .output 61 | .recipient 62 | .ok_or(ProverError::MissingRecipient)?, 63 | action.output.value.ok_or(ProverError::MissingValue)?, 64 | Rho::from_nf_old(action.spend.nullifier), 65 | action.output.rseed.ok_or(ProverError::MissingRandomSeed)?, 66 | ) 67 | .into_option() 68 | .ok_or(ProverError::InvalidOutputNote)?; 69 | 70 | let alpha = action 71 | .spend 72 | .alpha 73 | .ok_or(ProverError::MissingSpendAuthRandomizer)?; 74 | let rcv = action 75 | .rcv 76 | .clone() 77 | .ok_or(ProverError::MissingValueCommitTrapdoor)?; 78 | 79 | Circuit::from_action_context(spend, output_note, alpha, rcv) 80 | .ok_or(ProverError::RhoMismatch) 81 | }) 82 | .collect::, ProverError>>()?; 83 | 84 | let instances = self 85 | .actions 86 | .iter() 87 | .map(|action| { 88 | Instance::from_parts( 89 | self.anchor, 90 | action.cv_net.clone(), 91 | action.spend.nullifier, 92 | action.spend.rk.clone(), 93 | action.output.cmx, 94 | self.flags.spends_enabled(), 95 | self.flags.outputs_enabled(), 96 | ) 97 | }) 98 | .collect::>(); 99 | 100 | let proof = 101 | Proof::create(pk, &circuits, &instances, rng).map_err(ProverError::ProofFailed)?; 102 | 103 | self.zkproof = Some(proof); 104 | 105 | Ok(()) 106 | } 107 | } 108 | 109 | /// Errors that can occur while creating Orchard proofs for a PCZT. 110 | #[derive(Debug)] 111 | pub enum ProverError { 112 | /// The output note's components do not produce a valid note commitment. 113 | InvalidOutputNote, 114 | /// The spent note's components do not produce a valid note commitment. 115 | InvalidSpendNote, 116 | /// The Prover role requires `fvk` to be set. 117 | MissingFullViewingKey, 118 | /// The Prover role requires all `rseed` fields to be set. 119 | MissingRandomSeed, 120 | /// The Prover role requires all `recipient` fields to be set. 121 | MissingRecipient, 122 | /// The Prover role requires `rho` to be set. 123 | MissingRho, 124 | /// The Prover role requires `alpha` to be set. 125 | MissingSpendAuthRandomizer, 126 | /// The Prover role requires all `value` fields to be set. 127 | MissingValue, 128 | /// The Prover role requires `rcv` to be set. 129 | MissingValueCommitTrapdoor, 130 | /// The Prover role requires `witness` to be set. 131 | MissingWitness, 132 | /// An error occurred while creating the proof. 133 | ProofFailed(plonk::Error), 134 | /// The `rho` of the `output_note` is not equal to the nullifier of the spent note. 135 | RhoMismatch, 136 | /// The provided `fvk` does not own the spent note. 137 | WrongFvkForNote, 138 | } 139 | -------------------------------------------------------------------------------- /src/pczt/signer.rs: -------------------------------------------------------------------------------- 1 | use rand::{CryptoRng, RngCore}; 2 | 3 | use crate::{keys::SpendAuthorizingKey, primitives::redpallas}; 4 | 5 | impl super::Action { 6 | /// Signs the Orchard spend with the given spend authorizing key. 7 | /// 8 | /// It is the caller's responsibility to perform any semantic validity checks on the 9 | /// PCZT (for example, comfirming that the change amounts are correct) before calling 10 | /// this method. 11 | pub fn sign( 12 | &mut self, 13 | sighash: [u8; 32], 14 | ask: &SpendAuthorizingKey, 15 | rng: R, 16 | ) -> Result<(), SignerError> { 17 | let alpha = self 18 | .spend 19 | .alpha 20 | .ok_or(SignerError::MissingSpendAuthRandomizer)?; 21 | 22 | let rsk = ask.randomize(&alpha); 23 | let rk = redpallas::VerificationKey::from(&rsk); 24 | 25 | if self.spend.rk == rk { 26 | self.spend.spend_auth_sig = Some(rsk.sign(rng, &sighash)); 27 | Ok(()) 28 | } else { 29 | Err(SignerError::WrongSpendAuthorizingKey) 30 | } 31 | } 32 | } 33 | 34 | /// Errors that can occur while signing an Orchard action in a PCZT. 35 | #[derive(Debug)] 36 | pub enum SignerError { 37 | /// The Signer role requires `alpha` to be set. 38 | MissingSpendAuthRandomizer, 39 | /// The provided `ask` does not own the action's spent note. 40 | WrongSpendAuthorizingKey, 41 | } 42 | -------------------------------------------------------------------------------- /src/pczt/tx_extractor.rs: -------------------------------------------------------------------------------- 1 | use nonempty::NonEmpty; 2 | use rand::{CryptoRng, RngCore}; 3 | 4 | use super::Action; 5 | use crate::{ 6 | bundle::{Authorization, Authorized, EffectsOnly}, 7 | primitives::redpallas::{self, Binding, SpendAuth}, 8 | Proof, 9 | }; 10 | 11 | impl super::Bundle { 12 | /// Extracts the effects of this PCZT bundle as a [regular `Bundle`]. 13 | /// 14 | /// This is used by the Signer role to produce the transaction sighash. 15 | /// 16 | /// [regular `Bundle`]: crate::Bundle 17 | pub fn extract_effects>( 18 | &self, 19 | ) -> Result>, TxExtractorError> { 20 | self.to_tx_data(|_| Ok(()), |_| Ok(EffectsOnly)) 21 | } 22 | 23 | /// Extracts a fully authorized [regular `Bundle`] from this PCZT bundle. 24 | /// 25 | /// This is used by the Transaction Extractor role to produce the final transaction. 26 | /// 27 | /// [regular `Bundle`]: crate::Bundle 28 | pub fn extract>( 29 | self, 30 | ) -> Result>, TxExtractorError> { 31 | self.to_tx_data( 32 | |action| { 33 | action 34 | .spend 35 | .spend_auth_sig 36 | .clone() 37 | .ok_or(TxExtractorError::MissingSpendAuthSig) 38 | }, 39 | |bundle| { 40 | Ok(Unbound { 41 | proof: bundle 42 | .zkproof 43 | .clone() 44 | .ok_or(TxExtractorError::MissingProof)?, 45 | bsk: bundle 46 | .bsk 47 | .clone() 48 | .ok_or(TxExtractorError::MissingBindingSignatureSigningKey)?, 49 | }) 50 | }, 51 | ) 52 | } 53 | 54 | /// Converts this PCZT bundle into a regular bundle with the given authorizations. 55 | fn to_tx_data( 56 | &self, 57 | action_auth: F, 58 | bundle_auth: G, 59 | ) -> Result>, E> 60 | where 61 | A: Authorization, 62 | E: From, 63 | F: Fn(&Action) -> Result<::SpendAuth, E>, 64 | G: FnOnce(&Self) -> Result, 65 | V: TryFrom, 66 | { 67 | let actions = self 68 | .actions 69 | .iter() 70 | .map(|action| { 71 | let authorization = action_auth(action)?; 72 | 73 | Ok(crate::Action::from_parts( 74 | action.spend.nullifier, 75 | action.spend.rk.clone(), 76 | action.output.cmx, 77 | action.output.encrypted_note.clone(), 78 | action.cv_net.clone(), 79 | authorization, 80 | )) 81 | }) 82 | .collect::>()?; 83 | 84 | Ok(if let Some(actions) = NonEmpty::from_vec(actions) { 85 | let value_balance = i64::try_from(self.value_sum) 86 | .ok() 87 | .and_then(|v| v.try_into().ok()) 88 | .ok_or(TxExtractorError::ValueSumOutOfRange)?; 89 | 90 | let authorization = bundle_auth(self)?; 91 | 92 | Some(crate::Bundle::from_parts( 93 | actions, 94 | self.flags, 95 | value_balance, 96 | self.anchor, 97 | authorization, 98 | )) 99 | } else { 100 | None 101 | }) 102 | } 103 | } 104 | 105 | /// Errors that can occur while extracting a regular Orchard bundle from a PCZT bundle. 106 | #[derive(Debug)] 107 | pub enum TxExtractorError { 108 | /// The Transaction Extractor role requires `bsk` to be set. 109 | MissingBindingSignatureSigningKey, 110 | /// The Transaction Extractor role requires `zkproof` to be set. 111 | MissingProof, 112 | /// The Transaction Extractor role requires all `spend_auth_sig` fields to be set. 113 | MissingSpendAuthSig, 114 | /// The value sum does not fit into a `valueBalance`. 115 | ValueSumOutOfRange, 116 | } 117 | 118 | /// Authorizing data for a bundle of actions that is just missing a binding signature. 119 | #[derive(Debug)] 120 | pub struct Unbound { 121 | proof: Proof, 122 | bsk: redpallas::SigningKey, 123 | } 124 | 125 | impl Authorization for Unbound { 126 | type SpendAuth = redpallas::Signature; 127 | } 128 | 129 | impl crate::Bundle { 130 | /// Verifies the given sighash with every `spend_auth_sig`, and then binds the bundle. 131 | /// 132 | /// Returns `None` if the given sighash does not validate against every `spend_auth_sig`. 133 | pub fn apply_binding_signature( 134 | self, 135 | sighash: [u8; 32], 136 | rng: R, 137 | ) -> Option> { 138 | if self 139 | .actions() 140 | .iter() 141 | .all(|action| action.rk().verify(&sighash, action.authorization()).is_ok()) 142 | { 143 | Some(self.map_authorization( 144 | &mut (), 145 | |_, _, a| a, 146 | |_, Unbound { proof, bsk }| Authorized::from_parts(proof, bsk.sign(rng, &sighash)), 147 | )) 148 | } else { 149 | None 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/pczt/updater.rs: -------------------------------------------------------------------------------- 1 | use alloc::string::String; 2 | use alloc::vec::Vec; 3 | 4 | use super::{Action, Bundle, Zip32Derivation}; 5 | 6 | impl Bundle { 7 | /// Updates the bundle with information provided in the given closure. 8 | pub fn update_with(&mut self, f: F) -> Result<(), UpdaterError> 9 | where 10 | F: FnOnce(Updater<'_>) -> Result<(), UpdaterError>, 11 | { 12 | f(Updater(self)) 13 | } 14 | } 15 | 16 | /// An updater for an Orchard PCZT bundle. 17 | #[derive(Debug)] 18 | pub struct Updater<'a>(&'a mut Bundle); 19 | 20 | impl Updater<'_> { 21 | /// Provides read access to the bundle being updated. 22 | pub fn bundle(&self) -> &Bundle { 23 | self.0 24 | } 25 | 26 | /// Updates the action at the given index with information provided in the given 27 | /// closure. 28 | pub fn update_action_with(&mut self, index: usize, f: F) -> Result<(), UpdaterError> 29 | where 30 | F: FnOnce(ActionUpdater<'_>) -> Result<(), UpdaterError>, 31 | { 32 | f(ActionUpdater( 33 | self.0 34 | .actions 35 | .get_mut(index) 36 | .ok_or(UpdaterError::InvalidIndex)?, 37 | )) 38 | } 39 | } 40 | 41 | /// An updater for an Orchard PCZT action. 42 | #[derive(Debug)] 43 | pub struct ActionUpdater<'a>(&'a mut Action); 44 | 45 | impl ActionUpdater<'_> { 46 | /// Sets the ZIP 32 derivation path for the spent note's signing key. 47 | pub fn set_spend_zip32_derivation(&mut self, derivation: Zip32Derivation) { 48 | self.0.spend.zip32_derivation = Some(derivation); 49 | } 50 | 51 | /// Stores the given spend-specific proprietary value at the given key. 52 | pub fn set_spend_proprietary(&mut self, key: String, value: Vec) { 53 | self.0.spend.proprietary.insert(key, value); 54 | } 55 | 56 | /// Sets the ZIP 32 derivation path for the new note's signing key. 57 | pub fn set_output_zip32_derivation(&mut self, derivation: Zip32Derivation) { 58 | self.0.output.zip32_derivation = Some(derivation); 59 | } 60 | 61 | /// Sets the user-facing address that the new note is being sent to. 62 | pub fn set_output_user_address(&mut self, user_address: String) { 63 | self.0.output.user_address = Some(user_address); 64 | } 65 | 66 | /// Stores the given output-specific proprietary value at the given key. 67 | pub fn set_output_proprietary(&mut self, key: String, value: Vec) { 68 | self.0.output.proprietary.insert(key, value); 69 | } 70 | } 71 | 72 | /// Errors that can occur while updating an Orchard bundle in a PCZT. 73 | #[derive(Debug)] 74 | pub enum UpdaterError { 75 | /// An out-of-bounds index was provided when looking up an action. 76 | InvalidIndex, 77 | } 78 | -------------------------------------------------------------------------------- /src/pczt/verify.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | keys::{FullViewingKey, SpendValidatingKey}, 3 | note::{ExtractedNoteCommitment, Rho}, 4 | value::ValueCommitment, 5 | Note, 6 | }; 7 | 8 | impl super::Action { 9 | /// Verifies that the `cv_net` field is consistent with the note fields. 10 | /// 11 | /// Requires that the following optional fields are set: 12 | /// - `spend.value` 13 | /// - `output.value` 14 | /// - `rcv` 15 | pub fn verify_cv_net(&self) -> Result<(), VerifyError> { 16 | let spend_value = self.spend().value.ok_or(VerifyError::MissingValue)?; 17 | let output_value = self.output().value.ok_or(VerifyError::MissingValue)?; 18 | let rcv = self 19 | .rcv 20 | .clone() 21 | .ok_or(VerifyError::MissingValueCommitTrapdoor)?; 22 | 23 | let cv_net = ValueCommitment::derive(spend_value - output_value, rcv); 24 | if cv_net.to_bytes() == self.cv_net.to_bytes() { 25 | Ok(()) 26 | } else { 27 | Err(VerifyError::InvalidValueCommitment) 28 | } 29 | } 30 | } 31 | 32 | impl super::Spend { 33 | /// Returns the [`FullViewingKey`] to use when validating this note. 34 | /// 35 | /// Handles dummy notes when the `value` field is set. 36 | fn fvk_for_validation<'a>( 37 | &'a self, 38 | expected_fvk: Option<&'a FullViewingKey>, 39 | ) -> Result<&'a FullViewingKey, VerifyError> { 40 | match (expected_fvk, self.fvk.as_ref(), self.value.as_ref()) { 41 | (Some(expected_fvk), Some(fvk), _) if fvk == expected_fvk => Ok(fvk), 42 | // `expected_fvk` is ignored if the spent note is a dummy note. 43 | (Some(_), Some(fvk), Some(value)) if value.inner() == 0 => Ok(fvk), 44 | (Some(_), Some(_), _) => Err(VerifyError::MismatchedFullViewingKey), 45 | (Some(expected_fvk), None, _) => Ok(expected_fvk), 46 | (None, Some(fvk), _) => Ok(fvk), 47 | (None, None, _) => Err(VerifyError::MissingFullViewingKey), 48 | } 49 | } 50 | 51 | /// Verifies that the `nullifier` field is consistent with the note fields. 52 | /// 53 | /// Requires that the following optional fields are set: 54 | /// - `recipient` 55 | /// - `value` 56 | /// - `rho` 57 | /// - `rseed` 58 | /// 59 | /// In addition, at least one of the `fvk` field or `expected_fvk` must be provided. 60 | /// 61 | /// The provided [`FullViewingKey`] is ignored if the spent note is a dummy note. 62 | /// Otherwise, it will be checked against the `fvk` field (if both are set). 63 | pub fn verify_nullifier( 64 | &self, 65 | expected_fvk: Option<&FullViewingKey>, 66 | ) -> Result<(), VerifyError> { 67 | let fvk = self.fvk_for_validation(expected_fvk)?; 68 | 69 | let note = Note::from_parts( 70 | self.recipient.ok_or(VerifyError::MissingRecipient)?, 71 | self.value.ok_or(VerifyError::MissingValue)?, 72 | self.rho.ok_or(VerifyError::MissingRho)?, 73 | self.rseed.ok_or(VerifyError::MissingRandomSeed)?, 74 | ) 75 | .into_option() 76 | .ok_or(VerifyError::InvalidSpendNote)?; 77 | 78 | // We need both the note and the FVK to verify the nullifier; we have everything 79 | // needed to also verify that the correct FVK was provided (the nullifier check 80 | // itself only constrains `nk` within the FVK). 81 | fvk.scope_for_address(¬e.recipient()) 82 | .ok_or(VerifyError::WrongFvkForNote)?; 83 | 84 | if note.nullifier(fvk) == self.nullifier { 85 | Ok(()) 86 | } else { 87 | Err(VerifyError::InvalidNullifier) 88 | } 89 | } 90 | 91 | /// Verifies that the `rk` field is consistent with the given FVK. 92 | /// 93 | /// Requires that the following optional fields are set: 94 | /// - `alpha` 95 | /// 96 | /// The provided [`FullViewingKey`] is ignored if the spent note is a dummy note 97 | /// (which can only be determined if the `value` field is set). Otherwise, it will be 98 | /// checked against the `fvk` field (if set). 99 | pub fn verify_rk(&self, expected_fvk: Option<&FullViewingKey>) -> Result<(), VerifyError> { 100 | let fvk = self.fvk_for_validation(expected_fvk)?; 101 | 102 | let ak = SpendValidatingKey::from(fvk.clone()); 103 | 104 | let alpha = self 105 | .alpha 106 | .as_ref() 107 | .ok_or(VerifyError::MissingSpendAuthRandomizer)?; 108 | 109 | if ak.randomize(alpha) == self.rk { 110 | Ok(()) 111 | } else { 112 | Err(VerifyError::InvalidRandomizedVerificationKey) 113 | } 114 | } 115 | } 116 | 117 | impl super::Output { 118 | /// Verifies that the `cmx` field is consistent with the note fields. 119 | /// 120 | /// Requires that the following optional fields are set: 121 | /// - `recipient` 122 | /// - `value` 123 | /// - `rseed` 124 | /// 125 | /// `spend` must be the Spend from the same Orchard action. 126 | pub fn verify_note_commitment(&self, spend: &super::Spend) -> Result<(), VerifyError> { 127 | let note = Note::from_parts( 128 | self.recipient.ok_or(VerifyError::MissingRecipient)?, 129 | self.value.ok_or(VerifyError::MissingValue)?, 130 | Rho::from_nf_old(spend.nullifier), 131 | self.rseed.ok_or(VerifyError::MissingRandomSeed)?, 132 | ) 133 | .into_option() 134 | .ok_or(VerifyError::InvalidOutputNote)?; 135 | 136 | if ExtractedNoteCommitment::from(note.commitment()) == self.cmx { 137 | Ok(()) 138 | } else { 139 | Err(VerifyError::InvalidExtractedNoteCommitment) 140 | } 141 | } 142 | } 143 | 144 | /// Errors that can occur while verifying a PCZT bundle. 145 | #[derive(Debug)] 146 | pub enum VerifyError { 147 | /// The output note's components do not produce the expected `cmx`. 148 | InvalidExtractedNoteCommitment, 149 | /// The spent note's components do not produce the expected `nullifier`. 150 | InvalidNullifier, 151 | /// The output note's components do not produce a valid note commitment. 152 | InvalidOutputNote, 153 | /// The Spend's FVK and `alpha` do not produce the expected `rk`. 154 | InvalidRandomizedVerificationKey, 155 | /// The spent note's components do not produce a valid note commitment. 156 | InvalidSpendNote, 157 | /// The action's `cv_net` does not match the provided note values and `rcv`. 158 | InvalidValueCommitment, 159 | /// The spend or output's `fvk` field does not match the provided FVK. 160 | MismatchedFullViewingKey, 161 | /// Dummy notes must have their `fvk` field set in order to be verified. 162 | MissingFullViewingKey, 163 | /// `nullifier` verification requires `rseed` to be set. 164 | MissingRandomSeed, 165 | /// `nullifier` verification requires `recipient` to be set. 166 | MissingRecipient, 167 | /// `nullifier` verification requires `rho` to be set. 168 | MissingRho, 169 | /// `rk` verification requires `alpha` to be set. 170 | MissingSpendAuthRandomizer, 171 | /// Verification requires all `value` fields to be set. 172 | MissingValue, 173 | /// `cv_net` verification requires `rcv` to be set. 174 | MissingValueCommitTrapdoor, 175 | /// The provided `fvk` does not own the spent note. 176 | WrongFvkForNote, 177 | } 178 | -------------------------------------------------------------------------------- /src/primitives.rs: -------------------------------------------------------------------------------- 1 | //! Primitives used in the Orchard protocol. 2 | // TODO: 3 | // - DH stuff 4 | // - EphemeralPublicKey 5 | // - EphemeralSecretKey 6 | 7 | pub mod redpallas; 8 | -------------------------------------------------------------------------------- /src/primitives/redpallas.rs: -------------------------------------------------------------------------------- 1 | //! A minimal RedPallas implementation for use in Zcash. 2 | 3 | use core::cmp::{Ord, Ordering, PartialOrd}; 4 | 5 | use pasta_curves::pallas; 6 | use rand::{CryptoRng, RngCore}; 7 | 8 | #[cfg(feature = "std")] 9 | pub use reddsa::batch; 10 | 11 | #[cfg(test)] 12 | use rand::rngs::OsRng; 13 | 14 | /// A RedPallas signature type. 15 | pub trait SigType: reddsa::SigType + private::Sealed {} 16 | 17 | /// A type variable corresponding to an Orchard spend authorization signature. 18 | pub type SpendAuth = reddsa::orchard::SpendAuth; 19 | impl SigType for SpendAuth {} 20 | 21 | /// A type variable corresponding to an Orchard binding signature. 22 | pub type Binding = reddsa::orchard::Binding; 23 | impl SigType for Binding {} 24 | 25 | /// A RedPallas signing key. 26 | #[derive(Clone, Debug)] 27 | pub struct SigningKey(reddsa::SigningKey); 28 | 29 | impl From> for [u8; 32] { 30 | fn from(sk: SigningKey) -> [u8; 32] { 31 | sk.0.into() 32 | } 33 | } 34 | 35 | impl From<&SigningKey> for [u8; 32] { 36 | fn from(sk: &SigningKey) -> [u8; 32] { 37 | sk.0.into() 38 | } 39 | } 40 | 41 | impl TryFrom<[u8; 32]> for SigningKey { 42 | type Error = reddsa::Error; 43 | 44 | fn try_from(bytes: [u8; 32]) -> Result { 45 | bytes.try_into().map(SigningKey) 46 | } 47 | } 48 | 49 | impl SigningKey { 50 | /// Randomizes this signing key with the given `randomizer`. 51 | /// 52 | /// Randomization is only supported for `SpendAuth` keys. 53 | pub fn randomize(&self, randomizer: &pallas::Scalar) -> Self { 54 | SigningKey(self.0.randomize(randomizer)) 55 | } 56 | } 57 | 58 | impl SigningKey { 59 | /// Creates a signature of type `T` on `msg` using this `SigningKey`. 60 | pub fn sign(&self, rng: R, msg: &[u8]) -> Signature { 61 | Signature(self.0.sign(rng, msg)) 62 | } 63 | } 64 | 65 | /// A RedPallas verification key. 66 | #[derive(Clone, Debug)] 67 | pub struct VerificationKey(reddsa::VerificationKey); 68 | 69 | impl From> for [u8; 32] { 70 | fn from(vk: VerificationKey) -> [u8; 32] { 71 | vk.0.into() 72 | } 73 | } 74 | 75 | impl From<&VerificationKey> for [u8; 32] { 76 | fn from(vk: &VerificationKey) -> [u8; 32] { 77 | vk.0.into() 78 | } 79 | } 80 | 81 | impl TryFrom<[u8; 32]> for VerificationKey { 82 | type Error = reddsa::Error; 83 | 84 | fn try_from(bytes: [u8; 32]) -> Result { 85 | bytes.try_into().map(VerificationKey) 86 | } 87 | } 88 | 89 | impl<'a, T: SigType> From<&'a SigningKey> for VerificationKey { 90 | fn from(sk: &'a SigningKey) -> VerificationKey { 91 | VerificationKey((&sk.0).into()) 92 | } 93 | } 94 | 95 | impl PartialEq for VerificationKey { 96 | fn eq(&self, other: &Self) -> bool { 97 | <[u8; 32]>::from(self).eq(&<[u8; 32]>::from(other)) 98 | } 99 | } 100 | 101 | impl Eq for VerificationKey {} 102 | 103 | impl PartialOrd for VerificationKey { 104 | fn partial_cmp(&self, other: &Self) -> Option { 105 | Some(self.cmp(other)) 106 | } 107 | } 108 | 109 | impl Ord for VerificationKey { 110 | fn cmp(&self, other: &Self) -> Ordering { 111 | <[u8; 32]>::from(self).cmp(&<[u8; 32]>::from(other)) 112 | } 113 | } 114 | 115 | impl VerificationKey { 116 | /// Used in the note encryption tests. 117 | #[cfg(test)] 118 | pub(crate) fn dummy() -> Self { 119 | VerificationKey((&reddsa::SigningKey::new(OsRng)).into()) 120 | } 121 | 122 | /// Randomizes this verification key with the given `randomizer`. 123 | /// 124 | /// Randomization is only supported for `SpendAuth` keys. 125 | pub fn randomize(&self, randomizer: &pallas::Scalar) -> Self { 126 | VerificationKey(self.0.randomize(randomizer)) 127 | } 128 | 129 | /// Creates a batch validation item from a `SpendAuth` signature. 130 | #[cfg(feature = "std")] 131 | pub fn create_batch_item>( 132 | &self, 133 | sig: Signature, 134 | msg: &M, 135 | ) -> batch::Item { 136 | batch::Item::from_spendauth(self.0.into(), sig.0, msg) 137 | } 138 | } 139 | 140 | #[cfg(feature = "std")] 141 | impl VerificationKey { 142 | /// Creates a batch validation item from a `Binding` signature. 143 | pub fn create_batch_item>( 144 | &self, 145 | sig: Signature, 146 | msg: &M, 147 | ) -> batch::Item { 148 | batch::Item::from_binding(self.0.into(), sig.0, msg) 149 | } 150 | } 151 | 152 | impl VerificationKey { 153 | /// Verifies a purported `signature` over `msg` made by this verification key. 154 | pub fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), reddsa::Error> { 155 | self.0.verify(msg, &signature.0) 156 | } 157 | } 158 | 159 | /// A RedPallas signature. 160 | #[derive(Debug, Clone)] 161 | pub struct Signature(reddsa::Signature); 162 | 163 | impl From<[u8; 64]> for Signature { 164 | fn from(bytes: [u8; 64]) -> Self { 165 | Signature(bytes.into()) 166 | } 167 | } 168 | 169 | impl From<&Signature> for [u8; 64] { 170 | fn from(sig: &Signature) -> Self { 171 | sig.0.into() 172 | } 173 | } 174 | 175 | pub(crate) mod private { 176 | use super::{Binding, SpendAuth}; 177 | 178 | pub trait Sealed {} 179 | 180 | impl Sealed for SpendAuth {} 181 | 182 | impl Sealed for Binding {} 183 | } 184 | 185 | /// Generators for property testing. 186 | #[cfg(any(test, feature = "test-dependencies"))] 187 | #[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))] 188 | pub mod testing { 189 | use proptest::prelude::*; 190 | 191 | use super::{Binding, SigningKey, SpendAuth, VerificationKey}; 192 | 193 | prop_compose! { 194 | /// Generate a uniformly distributed RedDSA spend authorization signing key. 195 | pub fn arb_spendauth_signing_key()( 196 | sk in prop::array::uniform32(prop::num::u8::ANY) 197 | .prop_map(reddsa::SigningKey::try_from) 198 | .prop_filter("Values must be parseable as valid signing keys", |r| r.is_ok()) 199 | ) -> SigningKey { 200 | SigningKey(sk.unwrap()) 201 | } 202 | } 203 | 204 | prop_compose! { 205 | /// Generate a uniformly distributed RedDSA binding signing key. 206 | pub fn arb_binding_signing_key()( 207 | sk in prop::array::uniform32(prop::num::u8::ANY) 208 | .prop_map(reddsa::SigningKey::try_from) 209 | .prop_filter("Values must be parseable as valid signing keys", |r| r.is_ok()) 210 | ) -> SigningKey { 211 | SigningKey(sk.unwrap()) 212 | } 213 | } 214 | 215 | prop_compose! { 216 | /// Generate a uniformly distributed RedDSA spend authorization verification key. 217 | pub fn arb_spendauth_verification_key()(sk in arb_spendauth_signing_key()) -> VerificationKey { 218 | VerificationKey::from(&sk) 219 | } 220 | } 221 | 222 | prop_compose! { 223 | /// Generate a uniformly distributed RedDSA binding verification key. 224 | pub fn arb_binding_verification_key()(sk in arb_binding_signing_key()) -> VerificationKey { 225 | VerificationKey::from(&sk) 226 | } 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/test_vectors.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod commitment_tree; 2 | pub(crate) mod keys; 3 | pub(crate) mod merkle_path; 4 | pub(crate) mod note_encryption; 5 | pub(crate) mod zip32; 6 | -------------------------------------------------------------------------------- /src/test_vectors/commitment_tree.rs: -------------------------------------------------------------------------------- 1 | pub(crate) struct TestVector { 2 | pub empty_roots: [[u8; 32]; 33], 3 | } 4 | 5 | pub(crate) fn test_vectors() -> TestVector { 6 | // From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/orchard_empty_roots.py 7 | TestVector { 8 | empty_roots: [ 9 | [ 10 | 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 11 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 12 | 0x00, 0x00, 0x00, 0x00, 13 | ], 14 | [ 15 | 0xd1, 0xab, 0x25, 0x07, 0xc8, 0x09, 0xc2, 0x71, 0x3c, 0x00, 0x0f, 0x52, 0x5e, 0x9f, 16 | 0xbd, 0xcb, 0x06, 0xc9, 0x58, 0x38, 0x4e, 0x51, 0xb9, 0xcc, 0x7f, 0x79, 0x2d, 0xde, 17 | 0x6c, 0x97, 0xf4, 0x11, 18 | ], 19 | [ 20 | 0xc7, 0x41, 0x3f, 0x46, 0x14, 0xcd, 0x64, 0x04, 0x3a, 0xbb, 0xab, 0x7c, 0xc1, 0x09, 21 | 0x5c, 0x9b, 0xb1, 0x04, 0x23, 0x1c, 0xea, 0x89, 0xe2, 0xc3, 0xe0, 0xdf, 0x83, 0x76, 22 | 0x95, 0x56, 0xd0, 0x30, 23 | ], 24 | [ 25 | 0x21, 0x11, 0xfc, 0x39, 0x77, 0x53, 0xe5, 0xfd, 0x50, 0xec, 0x74, 0x81, 0x6d, 0xf2, 26 | 0x7d, 0x6a, 0xda, 0x7e, 0xd2, 0xa9, 0xac, 0x38, 0x16, 0xaa, 0xb2, 0x57, 0x3c, 0x8f, 27 | 0xac, 0x79, 0x42, 0x04, 28 | ], 29 | [ 30 | 0x80, 0x6a, 0xfb, 0xfe, 0xb4, 0x5c, 0x64, 0xd4, 0xf2, 0x38, 0x4c, 0x51, 0xef, 0xf3, 31 | 0x07, 0x64, 0xb8, 0x45, 0x99, 0xae, 0x56, 0xa7, 0xab, 0x3d, 0x4a, 0x46, 0xd9, 0xce, 32 | 0x3a, 0xea, 0xb4, 0x31, 33 | ], 34 | [ 35 | 0x87, 0x3e, 0x41, 0x57, 0xf2, 0xc0, 0xf0, 0xc6, 0x45, 0xe8, 0x99, 0x36, 0x00, 0x69, 36 | 0xfc, 0xc9, 0xd2, 0xed, 0x9b, 0xc1, 0x1b, 0xf5, 0x98, 0x27, 0xaf, 0x02, 0x30, 0xed, 37 | 0x52, 0xed, 0xab, 0x18, 38 | ], 39 | [ 40 | 0x27, 0xab, 0x13, 0x20, 0x95, 0x3a, 0xe1, 0xad, 0x70, 0xc8, 0xc1, 0x5a, 0x12, 0x53, 41 | 0xa0, 0xa8, 0x6f, 0xbc, 0x8a, 0x0a, 0xa3, 0x6a, 0x84, 0x20, 0x72, 0x93, 0xf8, 0xa4, 42 | 0x95, 0xff, 0xc4, 0x02, 43 | ], 44 | [ 45 | 0x4e, 0x14, 0x56, 0x3d, 0xf1, 0x91, 0xa2, 0xa6, 0x5b, 0x4b, 0x37, 0x11, 0x3b, 0x52, 46 | 0x30, 0x68, 0x05, 0x55, 0x05, 0x1b, 0x22, 0xd7, 0x4a, 0x8e, 0x1f, 0x1d, 0x70, 0x6f, 47 | 0x90, 0xf3, 0x13, 0x3b, 48 | ], 49 | [ 50 | 0xb3, 0xbb, 0xe4, 0xf9, 0x93, 0xd1, 0x8a, 0x0f, 0x4e, 0xb7, 0xf4, 0x17, 0x4b, 0x1d, 51 | 0x85, 0x55, 0xce, 0x33, 0x96, 0x85, 0x5d, 0x04, 0x67, 0x6f, 0x1c, 0xe4, 0xf0, 0x6d, 52 | 0xda, 0x07, 0x37, 0x1f, 53 | ], 54 | [ 55 | 0x4e, 0xf5, 0xbd, 0xe9, 0xc6, 0xf0, 0xd7, 0x6a, 0xeb, 0x9e, 0x27, 0xe9, 0x3f, 0xba, 56 | 0x28, 0xc6, 0x79, 0xdf, 0xcb, 0x99, 0x1c, 0xbc, 0xb8, 0x39, 0x5a, 0x2b, 0x57, 0x92, 57 | 0x4c, 0xbd, 0x17, 0x0e, 58 | ], 59 | [ 60 | 0xa3, 0xc0, 0x25, 0x68, 0xac, 0xeb, 0xf5, 0xca, 0x1e, 0xc3, 0x0d, 0x6a, 0x7d, 0x7c, 61 | 0xd2, 0x17, 0xa4, 0x7d, 0x6a, 0x1b, 0x83, 0x11, 0xbf, 0x94, 0x62, 0xa5, 0xf9, 0x39, 62 | 0xc6, 0xb7, 0x43, 0x07, 63 | ], 64 | [ 65 | 0x3e, 0xf9, 0xb3, 0x0b, 0xae, 0x61, 0x22, 0xda, 0x16, 0x05, 0xba, 0xd6, 0xec, 0x5d, 66 | 0x49, 0xb4, 0x1d, 0x4d, 0x40, 0xca, 0xa9, 0x6c, 0x1c, 0xf6, 0x30, 0x2b, 0x66, 0xc5, 67 | 0xd2, 0xd1, 0x0d, 0x39, 68 | ], 69 | [ 70 | 0x22, 0xae, 0x28, 0x00, 0xcb, 0x93, 0xab, 0xe6, 0x3b, 0x70, 0xc1, 0x72, 0xde, 0x70, 71 | 0x36, 0x2d, 0x98, 0x30, 0xe5, 0x38, 0x00, 0x39, 0x88, 0x84, 0xa7, 0xa6, 0x4f, 0xf6, 72 | 0x8e, 0xd9, 0x9e, 0x0b, 73 | ], 74 | [ 75 | 0x18, 0x71, 0x10, 0xd9, 0x26, 0x72, 0xc2, 0x4c, 0xed, 0xb0, 0x97, 0x9c, 0xdf, 0xc9, 76 | 0x17, 0xa6, 0x05, 0x3b, 0x31, 0x0d, 0x14, 0x5c, 0x03, 0x1c, 0x72, 0x92, 0xbb, 0x1d, 77 | 0x65, 0xb7, 0x66, 0x1b, 78 | ], 79 | [ 80 | 0x3f, 0x98, 0xad, 0xbe, 0x36, 0x4f, 0x14, 0x8b, 0x0c, 0xc2, 0x04, 0x2c, 0xaf, 0xc6, 81 | 0xbe, 0x11, 0x66, 0xfa, 0xe3, 0x90, 0x90, 0xab, 0x4b, 0x35, 0x4b, 0xfb, 0x62, 0x17, 82 | 0xb9, 0x64, 0x45, 0x3b, 83 | ], 84 | [ 85 | 0x63, 0xf8, 0xdb, 0xd1, 0x0d, 0xf9, 0x36, 0xf1, 0x73, 0x49, 0x73, 0xe0, 0xb3, 0xbd, 86 | 0x25, 0xf4, 0xed, 0x44, 0x05, 0x66, 0xc9, 0x23, 0x08, 0x59, 0x03, 0xf6, 0x96, 0xbc, 87 | 0x63, 0x47, 0xec, 0x0f, 88 | ], 89 | [ 90 | 0x21, 0x82, 0x16, 0x3e, 0xac, 0x40, 0x61, 0x88, 0x5a, 0x31, 0x35, 0x68, 0x14, 0x8d, 91 | 0xfa, 0xe5, 0x64, 0xe4, 0x78, 0x06, 0x6d, 0xcb, 0xe3, 0x89, 0xa0, 0xdd, 0xb1, 0xec, 92 | 0xb7, 0xf5, 0xdc, 0x34, 93 | ], 94 | [ 95 | 0xbd, 0x9d, 0xc0, 0x68, 0x19, 0x18, 0xa3, 0xf3, 0xf9, 0xcd, 0x1f, 0x9e, 0x06, 0xaa, 96 | 0x1a, 0xd6, 0x89, 0x27, 0xda, 0x63, 0xac, 0xc1, 0x3b, 0x92, 0xa2, 0x57, 0x8b, 0x27, 97 | 0x38, 0xa6, 0xd3, 0x31, 98 | ], 99 | [ 100 | 0xca, 0x2c, 0xed, 0x95, 0x3b, 0x7f, 0xb9, 0x5e, 0x3b, 0xa9, 0x86, 0x33, 0x3d, 0xa9, 101 | 0xe6, 0x9c, 0xd3, 0x55, 0x22, 0x3c, 0x92, 0x97, 0x31, 0x09, 0x4b, 0x6c, 0x21, 0x74, 102 | 0xc7, 0x63, 0x8d, 0x2e, 103 | ], 104 | [ 105 | 0x55, 0x35, 0x4b, 0x96, 0xb5, 0x6f, 0x9e, 0x45, 0xaa, 0xe1, 0xe0, 0x09, 0x4d, 0x71, 106 | 0xee, 0x24, 0x8d, 0xab, 0xf6, 0x68, 0x11, 0x77, 0x78, 0xbd, 0xc3, 0xc1, 0x9c, 0xa5, 107 | 0x33, 0x1a, 0x4e, 0x1a, 108 | ], 109 | [ 110 | 0x70, 0x97, 0xb0, 0x4c, 0x2a, 0xa0, 0x45, 0xa0, 0xde, 0xff, 0xca, 0xca, 0x41, 0xc5, 111 | 0xac, 0x92, 0xe6, 0x94, 0x46, 0x65, 0x78, 0xf5, 0x90, 0x9e, 0x72, 0xbb, 0x78, 0xd3, 112 | 0x33, 0x10, 0xf7, 0x05, 113 | ], 114 | [ 115 | 0xe8, 0x1d, 0x68, 0x21, 0xff, 0x81, 0x3b, 0xd4, 0x10, 0x86, 0x7a, 0x3f, 0x22, 0xe8, 116 | 0xe5, 0xcb, 0x7a, 0xc5, 0x59, 0x9a, 0x61, 0x0a, 0xf5, 0xc3, 0x54, 0xeb, 0x39, 0x28, 117 | 0x77, 0x36, 0x2e, 0x01, 118 | ], 119 | [ 120 | 0x15, 0x7d, 0xe8, 0x56, 0x7f, 0x7c, 0x49, 0x96, 0xb8, 0xc4, 0xfd, 0xc9, 0x49, 0x38, 121 | 0xfd, 0x80, 0x8c, 0x3b, 0x2a, 0x5c, 0xcb, 0x79, 0xd1, 0xa6, 0x38, 0x58, 0xad, 0xaa, 122 | 0x9a, 0x6d, 0xd8, 0x24, 123 | ], 124 | [ 125 | 0xfe, 0x1f, 0xce, 0x51, 0xcd, 0x61, 0x20, 0xc1, 0x2c, 0x12, 0x46, 0x95, 0xc4, 0xf9, 126 | 0x8b, 0x27, 0x59, 0x18, 0xfc, 0xea, 0xe6, 0xeb, 0x20, 0x98, 0x73, 0xed, 0x73, 0xfe, 127 | 0x73, 0x77, 0x5d, 0x0b, 128 | ], 129 | [ 130 | 0x1f, 0x91, 0x98, 0x29, 0x12, 0x01, 0x26, 0x69, 0xf7, 0x4d, 0x0c, 0xfa, 0x10, 0x30, 131 | 0xff, 0x37, 0xb1, 0x52, 0x32, 0x4e, 0x5b, 0x83, 0x46, 0xb3, 0x33, 0x5a, 0x0a, 0xae, 132 | 0xb6, 0x3a, 0x0a, 0x2d, 133 | ], 134 | [ 135 | 0x5d, 0xec, 0x15, 0xf5, 0x2a, 0xf1, 0x7d, 0xa3, 0x93, 0x13, 0x96, 0x18, 0x3c, 0xbb, 136 | 0xbf, 0xbe, 0xa7, 0xed, 0x95, 0x07, 0x14, 0x54, 0x0a, 0xec, 0x06, 0xc6, 0x45, 0xc7, 137 | 0x54, 0x97, 0x55, 0x22, 138 | ], 139 | [ 140 | 0xe8, 0xae, 0x2a, 0xd9, 0x1d, 0x46, 0x3b, 0xab, 0x75, 0xee, 0x94, 0x1d, 0x33, 0xcc, 141 | 0x58, 0x17, 0xb6, 0x13, 0xc6, 0x3c, 0xda, 0x94, 0x3a, 0x4c, 0x07, 0xf6, 0x00, 0x59, 142 | 0x1b, 0x08, 0x8a, 0x25, 143 | ], 144 | [ 145 | 0xd5, 0x3f, 0xde, 0xe3, 0x71, 0xce, 0xf5, 0x96, 0x76, 0x68, 0x23, 0xf4, 0xa5, 0x18, 146 | 0xa5, 0x83, 0xb1, 0x15, 0x82, 0x43, 0xaf, 0xe8, 0x97, 0x00, 0xf0, 0xda, 0x76, 0xda, 147 | 0x46, 0xd0, 0x06, 0x0f, 148 | ], 149 | [ 150 | 0x15, 0xd2, 0x44, 0x4c, 0xef, 0xe7, 0x91, 0x4c, 0x9a, 0x61, 0xe8, 0x29, 0xc7, 0x30, 151 | 0xec, 0xeb, 0x21, 0x62, 0x88, 0xfe, 0xe8, 0x25, 0xf6, 0xb3, 0xb6, 0x29, 0x8f, 0x6f, 152 | 0x6b, 0x6b, 0xd6, 0x2e, 153 | ], 154 | [ 155 | 0x4c, 0x57, 0xa6, 0x17, 0xa0, 0xaa, 0x10, 0xea, 0x7a, 0x83, 0xaa, 0x6b, 0x6b, 0x0e, 156 | 0xd6, 0x85, 0xb6, 0xa3, 0xd9, 0xe5, 0xb8, 0xfd, 0x14, 0xf5, 0x6c, 0xdc, 0x18, 0x02, 157 | 0x1b, 0x12, 0x25, 0x3f, 158 | ], 159 | [ 160 | 0x3f, 0xd4, 0x91, 0x5c, 0x19, 0xbd, 0x83, 0x1a, 0x79, 0x20, 0xbe, 0x55, 0xd9, 0x69, 161 | 0xb2, 0xac, 0x23, 0x35, 0x9e, 0x25, 0x59, 0xda, 0x77, 0xde, 0x23, 0x73, 0xf0, 0x6c, 162 | 0xa0, 0x14, 0xba, 0x27, 163 | ], 164 | [ 165 | 0x87, 0xd0, 0x63, 0xcd, 0x07, 0xee, 0x49, 0x44, 0x22, 0x2b, 0x77, 0x62, 0x84, 0x0e, 166 | 0xb9, 0x4c, 0x68, 0x8b, 0xec, 0x74, 0x3f, 0xa8, 0xbd, 0xf7, 0x71, 0x5c, 0x8f, 0xe2, 167 | 0x9f, 0x10, 0x4c, 0x2a, 168 | ], 169 | [ 170 | 0xae, 0x29, 0x35, 0xf1, 0xdf, 0xd8, 0xa2, 0x4a, 0xed, 0x7c, 0x70, 0xdf, 0x7d, 0xe3, 171 | 0xa6, 0x68, 0xeb, 0x7a, 0x49, 0xb1, 0x31, 0x98, 0x80, 0xdd, 0xe2, 0xbb, 0xd9, 0x03, 172 | 0x1a, 0xe5, 0xd8, 0x2f, 173 | ], 174 | ], 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/test_vectors/zip32.rs: -------------------------------------------------------------------------------- 1 | //! Test vectors for Orchard ZIP 32 key derivation. 2 | 3 | pub(crate) struct TestVector { 4 | pub(crate) sk: [u8; 32], 5 | pub(crate) c: [u8; 32], 6 | pub(crate) xsk: [u8; 73], 7 | pub(crate) fp: [u8; 32], 8 | } 9 | 10 | // From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/orchard_zip32.py 11 | pub(crate) const TEST_VECTORS: &[TestVector] = &[ 12 | TestVector { 13 | sk: [ 14 | 0x7e, 0xee, 0x3c, 0x10, 0x17, 0x87, 0x09, 0x90, 0xa3, 0xdd, 0x68, 0x91, 0xb8, 0x2f, 15 | 0x80, 0xbe, 0x89, 0x76, 0xc1, 0xe7, 0xdc, 0x20, 0xd6, 0x08, 0x17, 0xa5, 0xe8, 0x8e, 16 | 0x8b, 0x2c, 0xd4, 0xb8, 17 | ], 18 | c: [ 19 | 0xab, 0x8b, 0x7a, 0x00, 0x50, 0x9e, 0xf2, 0x0e, 0x46, 0x9b, 0x52, 0x92, 0xb6, 0x1d, 20 | 0x47, 0x4b, 0x7c, 0xff, 0xcb, 0x16, 0x57, 0x92, 0x4c, 0xda, 0x72, 0x02, 0x50, 0xae, 21 | 0x40, 0x52, 0x66, 0x77, 22 | ], 23 | xsk: [ 24 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xab, 0x8b, 0x7a, 0x00, 0x50, 25 | 0x9e, 0xf2, 0x0e, 0x46, 0x9b, 0x52, 0x92, 0xb6, 0x1d, 0x47, 0x4b, 0x7c, 0xff, 0xcb, 26 | 0x16, 0x57, 0x92, 0x4c, 0xda, 0x72, 0x02, 0x50, 0xae, 0x40, 0x52, 0x66, 0x77, 0x7e, 27 | 0xee, 0x3c, 0x10, 0x17, 0x87, 0x09, 0x90, 0xa3, 0xdd, 0x68, 0x91, 0xb8, 0x2f, 0x80, 28 | 0xbe, 0x89, 0x76, 0xc1, 0xe7, 0xdc, 0x20, 0xd6, 0x08, 0x17, 0xa5, 0xe8, 0x8e, 0x8b, 29 | 0x2c, 0xd4, 0xb8, 30 | ], 31 | fp: [ 32 | 0xff, 0x4c, 0xda, 0x50, 0x02, 0xc8, 0xd1, 0x82, 0x05, 0x88, 0x07, 0xb8, 0x4e, 0x61, 33 | 0x6b, 0x6d, 0x33, 0x9e, 0x1b, 0xbe, 0xec, 0xea, 0x01, 0x65, 0x05, 0x68, 0xd8, 0x91, 34 | 0xa4, 0x38, 0xe7, 0x06, 35 | ], 36 | }, 37 | TestVector { 38 | sk: [ 39 | 0x98, 0xd7, 0x03, 0xfc, 0xb4, 0x05, 0x04, 0xc9, 0x5b, 0x3b, 0x6e, 0xd1, 0x0e, 0xcd, 40 | 0x50, 0x08, 0x2c, 0xff, 0x97, 0xdf, 0xd1, 0xdd, 0x9a, 0xa0, 0x91, 0x3c, 0x78, 0xf9, 41 | 0x77, 0xc9, 0x62, 0xaf, 42 | ], 43 | c: [ 44 | 0x6a, 0x04, 0x1d, 0xfb, 0x9c, 0xfe, 0xbe, 0xe9, 0x7c, 0xb1, 0x85, 0x4f, 0xdc, 0x48, 45 | 0x1c, 0xc0, 0x4f, 0x02, 0xc9, 0x57, 0x7a, 0xa6, 0xf1, 0x3b, 0x2c, 0x44, 0x5b, 0x80, 46 | 0xa9, 0x66, 0x9a, 0x22, 47 | ], 48 | xsk: [ 49 | 0x01, 0xff, 0x4c, 0xda, 0x50, 0x01, 0x00, 0x00, 0x80, 0x6a, 0x04, 0x1d, 0xfb, 0x9c, 50 | 0xfe, 0xbe, 0xe9, 0x7c, 0xb1, 0x85, 0x4f, 0xdc, 0x48, 0x1c, 0xc0, 0x4f, 0x02, 0xc9, 51 | 0x57, 0x7a, 0xa6, 0xf1, 0x3b, 0x2c, 0x44, 0x5b, 0x80, 0xa9, 0x66, 0x9a, 0x22, 0x98, 52 | 0xd7, 0x03, 0xfc, 0xb4, 0x05, 0x04, 0xc9, 0x5b, 0x3b, 0x6e, 0xd1, 0x0e, 0xcd, 0x50, 53 | 0x08, 0x2c, 0xff, 0x97, 0xdf, 0xd1, 0xdd, 0x9a, 0xa0, 0x91, 0x3c, 0x78, 0xf9, 0x77, 54 | 0xc9, 0x62, 0xaf, 55 | ], 56 | fp: [ 57 | 0x32, 0xbb, 0xdc, 0x92, 0x1d, 0x06, 0x6f, 0x23, 0x5d, 0xc9, 0x3e, 0x91, 0x3b, 0x8f, 58 | 0xe1, 0xfd, 0x5b, 0x9f, 0x7f, 0x6a, 0x13, 0xd5, 0x6f, 0x18, 0xec, 0x0d, 0x36, 0x20, 59 | 0xd1, 0xf7, 0xb9, 0xa6, 60 | ], 61 | }, 62 | TestVector { 63 | sk: [ 64 | 0x99, 0xaf, 0xd8, 0x89, 0x4b, 0xaa, 0xd5, 0x87, 0x84, 0xd0, 0xec, 0x08, 0xf5, 0x14, 65 | 0x8e, 0xe2, 0xc2, 0xa1, 0x7b, 0x2b, 0x29, 0x4b, 0x08, 0xef, 0x9e, 0x0a, 0x0c, 0xf1, 66 | 0x4b, 0xcc, 0x09, 0x20, 67 | ], 68 | c: [ 69 | 0x6d, 0xa8, 0xb5, 0x7a, 0x36, 0xc7, 0x7a, 0xd6, 0x41, 0x2a, 0x9d, 0xc0, 0x11, 0x5f, 70 | 0x12, 0xac, 0xed, 0x0e, 0xe0, 0x1c, 0x40, 0x2a, 0x0c, 0xf0, 0xa5, 0x07, 0xcb, 0x17, 71 | 0xfc, 0x7b, 0xbd, 0x1d, 72 | ], 73 | xsk: [ 74 | 0x02, 0x32, 0xbb, 0xdc, 0x92, 0x02, 0x00, 0x00, 0x80, 0x6d, 0xa8, 0xb5, 0x7a, 0x36, 75 | 0xc7, 0x7a, 0xd6, 0x41, 0x2a, 0x9d, 0xc0, 0x11, 0x5f, 0x12, 0xac, 0xed, 0x0e, 0xe0, 76 | 0x1c, 0x40, 0x2a, 0x0c, 0xf0, 0xa5, 0x07, 0xcb, 0x17, 0xfc, 0x7b, 0xbd, 0x1d, 0x99, 77 | 0xaf, 0xd8, 0x89, 0x4b, 0xaa, 0xd5, 0x87, 0x84, 0xd0, 0xec, 0x08, 0xf5, 0x14, 0x8e, 78 | 0xe2, 0xc2, 0xa1, 0x7b, 0x2b, 0x29, 0x4b, 0x08, 0xef, 0x9e, 0x0a, 0x0c, 0xf1, 0x4b, 79 | 0xcc, 0x09, 0x20, 80 | ], 81 | fp: [ 82 | 0x36, 0xa5, 0x7c, 0x4f, 0xc5, 0xb8, 0xb4, 0xa3, 0xd6, 0x2f, 0x22, 0xa5, 0x50, 0x08, 83 | 0x78, 0xf3, 0x93, 0x85, 0x6b, 0x7e, 0xcc, 0xe7, 0x71, 0xad, 0x59, 0x7c, 0xa9, 0x64, 84 | 0xb9, 0x86, 0x37, 0xd9, 85 | ], 86 | }, 87 | TestVector { 88 | sk: [ 89 | 0x96, 0x43, 0x9e, 0xa3, 0x48, 0xa4, 0xb2, 0xce, 0x4e, 0xc7, 0xbe, 0xb4, 0x54, 0x3c, 90 | 0x70, 0x27, 0x4c, 0x8f, 0x76, 0x49, 0x5d, 0x60, 0xc5, 0xfa, 0x5f, 0x01, 0x8b, 0x68, 91 | 0xf3, 0xc3, 0x23, 0x67, 92 | ], 93 | c: [ 94 | 0xb1, 0x96, 0xe9, 0xb5, 0x80, 0x9d, 0x76, 0x57, 0x7a, 0x89, 0x44, 0xc3, 0xf8, 0xc8, 95 | 0xa8, 0x3f, 0x93, 0xf0, 0xc8, 0xf5, 0xac, 0xe6, 0xe7, 0xbc, 0x9c, 0xe4, 0x39, 0x6c, 96 | 0x03, 0x4d, 0x93, 0xfe, 97 | ], 98 | xsk: [ 99 | 0x03, 0x36, 0xa5, 0x7c, 0x4f, 0x03, 0x00, 0x00, 0x80, 0xb1, 0x96, 0xe9, 0xb5, 0x80, 100 | 0x9d, 0x76, 0x57, 0x7a, 0x89, 0x44, 0xc3, 0xf8, 0xc8, 0xa8, 0x3f, 0x93, 0xf0, 0xc8, 101 | 0xf5, 0xac, 0xe6, 0xe7, 0xbc, 0x9c, 0xe4, 0x39, 0x6c, 0x03, 0x4d, 0x93, 0xfe, 0x96, 102 | 0x43, 0x9e, 0xa3, 0x48, 0xa4, 0xb2, 0xce, 0x4e, 0xc7, 0xbe, 0xb4, 0x54, 0x3c, 0x70, 103 | 0x27, 0x4c, 0x8f, 0x76, 0x49, 0x5d, 0x60, 0xc5, 0xfa, 0x5f, 0x01, 0x8b, 0x68, 0xf3, 104 | 0xc3, 0x23, 0x67, 105 | ], 106 | fp: [ 107 | 0xbe, 0x1a, 0x1b, 0x66, 0x1d, 0x2c, 0xa3, 0x19, 0x82, 0x2a, 0x32, 0x55, 0x0d, 0x6d, 108 | 0xc4, 0x88, 0xb6, 0x57, 0x1e, 0x0c, 0xd7, 0x81, 0xd5, 0x07, 0x8b, 0x8f, 0x7b, 0xa3, 109 | 0x66, 0xdd, 0xd3, 0x68, 110 | ], 111 | }, 112 | ]; 113 | -------------------------------------------------------------------------------- /src/zip32.rs: -------------------------------------------------------------------------------- 1 | //! Key structures for Orchard. 2 | 3 | use core::fmt; 4 | 5 | use blake2b_simd::Params as Blake2bParams; 6 | use subtle::{Choice, ConstantTimeEq, CtOption}; 7 | use zcash_spec::VariableLengthSlice; 8 | use zip32::{ 9 | hardened_only::{self, HardenedOnlyKey}, 10 | ChainCode, 11 | }; 12 | 13 | use crate::{ 14 | keys::{FullViewingKey, SpendingKey}, 15 | spec::PrfExpand, 16 | }; 17 | 18 | pub use zip32::ChildIndex; 19 | 20 | const ZIP32_ORCHARD_PERSONALIZATION: &[u8; 16] = b"ZcashIP32Orchard"; 21 | const ZIP32_ORCHARD_FVFP_PERSONALIZATION: &[u8; 16] = b"ZcashOrchardFVFP"; 22 | 23 | /// Errors produced in derivation of extended spending keys 24 | #[derive(Debug, PartialEq, Eq)] 25 | pub enum Error { 26 | /// A seed resulted in an invalid spending key 27 | InvalidSpendingKey, 28 | /// A child index in a derivation path exceeded 2^31 29 | InvalidChildIndex(u32), 30 | } 31 | 32 | impl fmt::Display for Error { 33 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 34 | write!(f, "Seed produced invalid spending key.") 35 | } 36 | } 37 | 38 | //impl std::error::Error for Error {} 39 | 40 | /// An Orchard full viewing key fingerprint 41 | struct FvkFingerprint([u8; 32]); 42 | 43 | impl From<&FullViewingKey> for FvkFingerprint { 44 | fn from(fvk: &FullViewingKey) -> Self { 45 | let mut h = Blake2bParams::new() 46 | .hash_length(32) 47 | .personal(ZIP32_ORCHARD_FVFP_PERSONALIZATION) 48 | .to_state(); 49 | h.update(&fvk.to_bytes()); 50 | let mut fvfp = [0u8; 32]; 51 | fvfp.copy_from_slice(h.finalize().as_bytes()); 52 | FvkFingerprint(fvfp) 53 | } 54 | } 55 | 56 | /// An Orchard full viewing key tag 57 | #[derive(Clone, Copy, Debug, PartialEq)] 58 | struct FvkTag([u8; 4]); 59 | 60 | impl FvkFingerprint { 61 | fn tag(&self) -> FvkTag { 62 | let mut tag = [0u8; 4]; 63 | tag.copy_from_slice(&self.0[..4]); 64 | FvkTag(tag) 65 | } 66 | } 67 | 68 | impl FvkTag { 69 | fn master() -> Self { 70 | FvkTag([0u8; 4]) 71 | } 72 | } 73 | 74 | /// The derivation index associated with a key. 75 | /// 76 | /// Master keys are never derived via the ZIP 32 child derivation process, but they have 77 | /// an index in their encoding. This type allows the encoding to be represented, while 78 | /// also enabling the derivation methods to only accept [`ChildIndex`]. 79 | #[derive(Clone, Copy, Debug)] 80 | struct KeyIndex(CtOption); 81 | 82 | impl ConstantTimeEq for KeyIndex { 83 | fn ct_eq(&self, other: &Self) -> Choice { 84 | // We use a `CtOption` above instead of an enum so that we can implement this. 85 | self.0.ct_eq(&other.0) 86 | } 87 | } 88 | 89 | impl PartialEq for KeyIndex { 90 | fn eq(&self, other: &Self) -> bool { 91 | self.ct_eq(other).into() 92 | } 93 | } 94 | 95 | impl Eq for KeyIndex {} 96 | 97 | impl KeyIndex { 98 | fn master() -> Self { 99 | Self(CtOption::new(ChildIndex::hardened(0), 0.into())) 100 | } 101 | 102 | fn child(i: ChildIndex) -> Self { 103 | Self(CtOption::new(i, 1.into())) 104 | } 105 | 106 | fn new(depth: u8, i: u32) -> Option { 107 | match (depth == 0, i) { 108 | (true, 0) => Some(KeyIndex::master()), 109 | (false, _) => ChildIndex::from_index(i).map(KeyIndex::child), 110 | _ => None, 111 | } 112 | } 113 | 114 | fn index(&self) -> u32 { 115 | if self.0.is_some().into() { 116 | self.0.unwrap().index() 117 | } else { 118 | 0 119 | } 120 | } 121 | } 122 | 123 | #[derive(Clone, Copy, Debug)] 124 | struct Orchard; 125 | 126 | impl hardened_only::Context for Orchard { 127 | const MKG_DOMAIN: [u8; 16] = *ZIP32_ORCHARD_PERSONALIZATION; 128 | const CKD_DOMAIN: PrfExpand<([u8; 32], [u8; 4], [u8; 1], VariableLengthSlice)> = 129 | PrfExpand::ORCHARD_ZIP32_CHILD; 130 | } 131 | 132 | /// An Orchard extended spending key. 133 | /// 134 | /// Defined in [ZIP32: Orchard extended keys][orchardextendedkeys]. 135 | /// 136 | /// [orchardextendedkeys]: https://zips.z.cash/zip-0032#orchard-extended-keys 137 | #[derive(Debug, Clone)] 138 | pub(crate) struct ExtendedSpendingKey { 139 | depth: u8, 140 | parent_fvk_tag: FvkTag, 141 | child_index: KeyIndex, 142 | inner: HardenedOnlyKey, 143 | } 144 | 145 | impl ConstantTimeEq for ExtendedSpendingKey { 146 | fn ct_eq(&self, rhs: &Self) -> Choice { 147 | self.depth.ct_eq(&rhs.depth) 148 | & self.parent_fvk_tag.0.ct_eq(&rhs.parent_fvk_tag.0) 149 | & self.child_index.ct_eq(&rhs.child_index) 150 | & self.inner.ct_eq(&rhs.inner) 151 | } 152 | } 153 | 154 | #[allow(non_snake_case)] 155 | impl ExtendedSpendingKey { 156 | /// Returns the spending key of the child key corresponding to 157 | /// the path derived from the master key 158 | /// 159 | /// # Panics 160 | /// 161 | /// Panics if seed results in invalid spending key. 162 | pub fn from_path(seed: &[u8], path: &[ChildIndex]) -> Result { 163 | let mut xsk = Self::master(seed)?; 164 | for i in path { 165 | xsk = xsk.derive_child(*i)?; 166 | } 167 | Ok(xsk) 168 | } 169 | 170 | /// Generates the master key of an Orchard extended spending key. 171 | /// 172 | /// Defined in [ZIP32: Orchard master key generation][orchardmasterkey]. 173 | /// 174 | /// [orchardmasterkey]: https://zips.z.cash/zip-0032#orchard-master-key-generation 175 | /// 176 | /// # Panics 177 | /// 178 | /// Panics if the seed is shorter than 32 bytes or longer than 252 bytes. 179 | fn master(seed: &[u8]) -> Result { 180 | let m_orchard = HardenedOnlyKey::master(&[seed]); 181 | 182 | let sk = SpendingKey::from_bytes(*m_orchard.parts().0); 183 | if sk.is_none().into() { 184 | return Err(Error::InvalidSpendingKey); 185 | } 186 | 187 | // For the master extended spending key, depth is 0, parent_fvk_tag is 4 zero bytes, and i is 0. 188 | Ok(Self { 189 | depth: 0, 190 | parent_fvk_tag: FvkTag([0; 4]), 191 | child_index: KeyIndex::master(), 192 | inner: m_orchard, 193 | }) 194 | } 195 | 196 | /// Derives a child key from a parent key at a given index. 197 | /// 198 | /// Defined in [ZIP32: Orchard child key derivation][orchardchildkey]. 199 | /// 200 | /// [orchardchildkey]: https://zips.z.cash/zip-0032#orchard-child-key-derivation 201 | /// 202 | /// Discards index if it results in an invalid sk 203 | fn derive_child(&self, index: ChildIndex) -> Result { 204 | let child_i = self.inner.derive_child(index); 205 | 206 | let sk = SpendingKey::from_bytes(*child_i.parts().0); 207 | if sk.is_none().into() { 208 | return Err(Error::InvalidSpendingKey); 209 | } 210 | 211 | let fvk: FullViewingKey = self.into(); 212 | 213 | Ok(Self { 214 | depth: self.depth + 1, 215 | parent_fvk_tag: FvkFingerprint::from(&fvk).tag(), 216 | child_index: KeyIndex::child(index), 217 | inner: child_i, 218 | }) 219 | } 220 | 221 | /// Returns sk of this ExtendedSpendingKey. 222 | pub fn sk(&self) -> SpendingKey { 223 | SpendingKey::from_bytes(*self.inner.parts().0).expect("checked during derivation") 224 | } 225 | 226 | /// Returns the chain code for this ExtendedSpendingKey. 227 | fn chain_code(&self) -> &ChainCode { 228 | self.inner.parts().1 229 | } 230 | } 231 | 232 | #[cfg(test)] 233 | mod tests { 234 | use super::*; 235 | 236 | #[test] 237 | fn derive_child() { 238 | let seed = [0; 32]; 239 | let xsk_m = ExtendedSpendingKey::master(&seed).unwrap(); 240 | 241 | let i_5 = ChildIndex::hardened(5); 242 | let xsk_5 = xsk_m.derive_child(i_5); 243 | 244 | assert!(xsk_5.is_ok()); 245 | } 246 | 247 | #[test] 248 | fn path() { 249 | let seed = [0; 32]; 250 | let xsk_m = ExtendedSpendingKey::master(&seed).unwrap(); 251 | 252 | let xsk_5h = xsk_m.derive_child(ChildIndex::hardened(5)).unwrap(); 253 | assert!(bool::from( 254 | ExtendedSpendingKey::from_path(&seed, &[ChildIndex::hardened(5)]) 255 | .unwrap() 256 | .ct_eq(&xsk_5h) 257 | )); 258 | 259 | let xsk_5h_7 = xsk_5h.derive_child(ChildIndex::hardened(7)).unwrap(); 260 | assert!(bool::from( 261 | ExtendedSpendingKey::from_path( 262 | &seed, 263 | &[ChildIndex::hardened(5), ChildIndex::hardened(7)] 264 | ) 265 | .unwrap() 266 | .ct_eq(&xsk_5h_7) 267 | )); 268 | } 269 | 270 | #[test] 271 | fn test_vectors() { 272 | let test_vectors = crate::test_vectors::zip32::TEST_VECTORS; 273 | 274 | let seed = [ 275 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 276 | 24, 25, 26, 27, 28, 29, 30, 31, 277 | ]; 278 | 279 | let i1h = ChildIndex::hardened(1); 280 | let i2h = ChildIndex::hardened(2); 281 | let i3h = ChildIndex::hardened(3); 282 | 283 | let m = ExtendedSpendingKey::master(&seed).unwrap(); 284 | let m_1h = m.derive_child(i1h).unwrap(); 285 | let m_1h_2h = ExtendedSpendingKey::from_path(&seed, &[i1h, i2h]).unwrap(); 286 | let m_1h_2h_3h = m_1h_2h.derive_child(i3h).unwrap(); 287 | 288 | let xsks = [m, m_1h, m_1h_2h, m_1h_2h_3h]; 289 | assert_eq!(test_vectors.len(), xsks.len()); 290 | 291 | for (xsk, tv) in xsks.iter().zip(test_vectors.iter()) { 292 | assert_eq!(xsk.sk().to_bytes(), &tv.sk); 293 | assert_eq!(xsk.chain_code().as_bytes(), &tv.c); 294 | 295 | assert_eq!(xsk.depth, tv.xsk[0]); 296 | assert_eq!(&xsk.parent_fvk_tag.0, &tv.xsk[1..5]); 297 | assert_eq!(&xsk.child_index.index().to_le_bytes(), &tv.xsk[5..9]); 298 | assert_eq!(xsk.chain_code().as_bytes(), &tv.xsk[9..9 + 32]); 299 | assert_eq!(xsk.sk().to_bytes(), &tv.xsk[9 + 32..]); 300 | 301 | let fvk: FullViewingKey = (&xsk.sk()).into(); 302 | assert_eq!(FvkFingerprint::from(&fvk).0, tv.fp); 303 | } 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /tests/builder.rs: -------------------------------------------------------------------------------- 1 | use incrementalmerkletree::{Hashable, Marking, Retention}; 2 | use orchard::{ 3 | builder::{Builder, BundleType}, 4 | bundle::{Authorized, Flags}, 5 | circuit::{ProvingKey, VerifyingKey}, 6 | keys::{FullViewingKey, PreparedIncomingViewingKey, Scope, SpendAuthorizingKey, SpendingKey}, 7 | note::ExtractedNoteCommitment, 8 | note_encryption::OrchardDomain, 9 | tree::MerkleHashOrchard, 10 | value::NoteValue, 11 | Bundle, 12 | }; 13 | use rand::rngs::OsRng; 14 | use shardtree::{store::memory::MemoryShardStore, ShardTree}; 15 | use zcash_note_encryption::try_note_decryption; 16 | 17 | fn verify_bundle(bundle: &Bundle, vk: &VerifyingKey) { 18 | assert!(matches!(bundle.verify_proof(vk), Ok(()))); 19 | let sighash: [u8; 32] = bundle.commitment().into(); 20 | let bvk = bundle.binding_validating_key(); 21 | for action in bundle.actions() { 22 | assert_eq!(action.rk().verify(&sighash, action.authorization()), Ok(())); 23 | } 24 | assert_eq!( 25 | bvk.verify(&sighash, bundle.authorization().binding_signature()), 26 | Ok(()) 27 | ); 28 | } 29 | 30 | #[test] 31 | fn bundle_chain() { 32 | let mut rng = OsRng; 33 | let pk = ProvingKey::build(); 34 | let vk = VerifyingKey::build(); 35 | 36 | let sk = SpendingKey::from_bytes([0; 32]).unwrap(); 37 | let fvk = FullViewingKey::from(&sk); 38 | let recipient = fvk.address_at(0u32, Scope::External); 39 | 40 | // Create a shielding bundle. 41 | let shielding_bundle: Bundle<_, i64> = { 42 | // Use the empty tree. 43 | let anchor = MerkleHashOrchard::empty_root(32.into()).into(); 44 | 45 | let mut builder = Builder::new( 46 | BundleType::Transactional { 47 | flags: Flags::SPENDS_DISABLED, 48 | bundle_required: false, 49 | }, 50 | anchor, 51 | ); 52 | let note_value = NoteValue::from_raw(5000); 53 | assert_eq!( 54 | builder.add_output(None, recipient, note_value, [0u8; 512]), 55 | Ok(()) 56 | ); 57 | let (unauthorized, bundle_meta) = builder.build(&mut rng).unwrap().unwrap(); 58 | 59 | assert_eq!( 60 | unauthorized 61 | .decrypt_output_with_key( 62 | bundle_meta 63 | .output_action_index(0) 64 | .expect("Output 0 can be found"), 65 | &fvk.to_ivk(Scope::External) 66 | ) 67 | .map(|(note, _, _)| note.value()), 68 | Some(note_value) 69 | ); 70 | 71 | let sighash = unauthorized.commitment().into(); 72 | let proven = unauthorized.create_proof(&pk, &mut rng).unwrap(); 73 | proven.apply_signatures(rng, sighash, &[]).unwrap() 74 | }; 75 | 76 | // Verify the shielding bundle. 77 | verify_bundle(&shielding_bundle, &vk); 78 | 79 | // Create a shielded bundle spending the previous output. 80 | let shielded_bundle: Bundle<_, i64> = { 81 | let ivk = PreparedIncomingViewingKey::new(&fvk.to_ivk(Scope::External)); 82 | let (note, _, _) = shielding_bundle 83 | .actions() 84 | .iter() 85 | .find_map(|action| { 86 | let domain = OrchardDomain::for_action(action); 87 | try_note_decryption(&domain, &ivk, action) 88 | }) 89 | .unwrap(); 90 | 91 | // Use the tree with a single leaf. 92 | let cmx: ExtractedNoteCommitment = note.commitment().into(); 93 | let leaf = MerkleHashOrchard::from_cmx(&cmx); 94 | let mut tree: ShardTree, 32, 16> = 95 | ShardTree::new(MemoryShardStore::empty(), 100); 96 | tree.append( 97 | leaf, 98 | Retention::Checkpoint { 99 | id: 0, 100 | marking: Marking::Marked, 101 | }, 102 | ) 103 | .unwrap(); 104 | let root = tree.root_at_checkpoint_id(&0).unwrap().unwrap(); 105 | let position = tree.max_leaf_position(None).unwrap().unwrap(); 106 | let merkle_path = tree 107 | .witness_at_checkpoint_id(position, &0) 108 | .unwrap() 109 | .unwrap(); 110 | assert_eq!(root, merkle_path.root(MerkleHashOrchard::from_cmx(&cmx))); 111 | 112 | let mut builder = Builder::new(BundleType::DEFAULT, root.into()); 113 | assert_eq!(builder.add_spend(fvk, note, merkle_path.into()), Ok(())); 114 | assert_eq!( 115 | builder.add_output(None, recipient, NoteValue::from_raw(5000), [0u8; 512]), 116 | Ok(()) 117 | ); 118 | let (unauthorized, _) = builder.build(&mut rng).unwrap().unwrap(); 119 | let sighash = unauthorized.commitment().into(); 120 | let proven = unauthorized.create_proof(&pk, &mut rng).unwrap(); 121 | proven 122 | .apply_signatures(rng, sighash, &[SpendAuthorizingKey::from(&sk)]) 123 | .unwrap() 124 | }; 125 | 126 | // Verify the shielded bundle. 127 | verify_bundle(&shielded_bundle, &vk); 128 | } 129 | --------------------------------------------------------------------------------