├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── COPYRIGHT ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── benches ├── circuit.rs └── pedersen_hash.rs ├── rust-toolchain.toml └── src ├── address.rs ├── builder.rs ├── bundle.rs ├── circuit.rs ├── circuit ├── constants.rs ├── ecc.rs └── pedersen_hash.rs ├── constants.rs ├── group_hash.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 ├── pedersen_hash.rs ├── pedersen_hash └── test_vectors.rs ├── prover.rs ├── spec.rs ├── test_vectors.rs ├── test_vectors ├── note_encryption.rs └── signatures.rs ├── tree.rs ├── util.rs ├── value.rs ├── value └── sums.rs ├── verifier.rs ├── verifier ├── batch.rs └── single.rs └── zip32.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | timezone: Etc/UTC 8 | open-pull-requests-limit: 10 9 | labels: 10 | - "A-CI" 11 | -------------------------------------------------------------------------------- /.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 --all-features --verbose --release 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 | - name: Remove lockfile to build with latest dependencies 31 | run: rm Cargo.lock 32 | - name: Build crate 33 | run: cargo build --all-targets --all-features --verbose 34 | - name: Verify working directory is clean (excluding lockfile) 35 | run: git diff --exit-code ':!Cargo.lock' 36 | 37 | build-nodefault: 38 | name: Build target ${{ matrix.target }} 39 | runs-on: ubuntu-latest 40 | strategy: 41 | matrix: 42 | target: 43 | - wasm32-wasi 44 | - thumbv7em-none-eabihf 45 | steps: 46 | - uses: actions/checkout@v4 47 | with: 48 | path: crate_root 49 | # We use a synthetic crate to ensure no dev-dependencies are enabled, which can 50 | # be incompatible with some of these targets. 51 | - name: Create synthetic crate for testing 52 | run: cargo init --lib ci-build 53 | - name: Copy Rust version into synthetic crate 54 | run: cp crate_root/rust-toolchain.toml ci-build/ 55 | - name: Copy patch directives into synthetic crate 56 | run: | 57 | echo "[patch.crates-io]" >> ./ci-build/Cargo.toml 58 | cat ./crate_root/Cargo.toml | sed "0,/.\+\(patch.crates.\+\)/d" >> ./ci-build/Cargo.toml 59 | - name: Add no_std pragma to lib.rs 60 | run: | 61 | echo "#![no_std]" > ./ci-build/src/lib.rs 62 | - name: Add sapling-crypto as a dependency of the synthetic crate 63 | working-directory: ./ci-build 64 | run: cargo add --no-default-features --path ../crate_root 65 | - name: Add lazy_static with the spin_no_std feature 66 | working-directory: ./ci-build 67 | run: cargo add lazy_static --features "spin_no_std" 68 | - name: Add target 69 | working-directory: ./ci-build 70 | run: rustup target add ${{ matrix.target }} 71 | - name: Build for target 72 | working-directory: ./ci-build 73 | run: cargo build --verbose --target ${{ matrix.target }} 74 | 75 | bitrot: 76 | name: Bitrot check 77 | runs-on: ubuntu-latest 78 | steps: 79 | - uses: actions/checkout@v4 80 | # Build benchmarks to prevent bitrot 81 | - name: Build benchmarks 82 | run: cargo build --all --benches 83 | 84 | clippy: 85 | name: Clippy (MSRV) 86 | runs-on: ubuntu-latest 87 | steps: 88 | - uses: actions/checkout@v4 89 | - name: Run Clippy 90 | uses: auguwu/clippy-action@1.4.0 91 | with: 92 | token: ${{ secrets.GITHUB_TOKEN }} 93 | working-directory: ${{ inputs.target }} 94 | deny: warnings 95 | 96 | doc-links: 97 | name: Intra-doc links 98 | runs-on: ubuntu-latest 99 | steps: 100 | - uses: actions/checkout@v4 101 | - run: cargo fetch 102 | # Requires #![deny(rustdoc::broken_intra_doc_links)] in crate. 103 | - name: Check intra-doc links 104 | run: cargo doc --all-features --document-private-items 105 | 106 | fmt: 107 | name: Rustfmt 108 | runs-on: ubuntu-latest 109 | steps: 110 | - uses: actions/checkout@v4 111 | - name: Check formatting 112 | run: cargo fmt -- --check 113 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this library 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 library adheres to Rust's notion of 6 | [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [0.5.0] - 2025-02-20 11 | 12 | ### Added 13 | - `sapling_crypto::pczt::Zip32Derivation::extract_account_index` 14 | - `no_std` compatibility has been introduced by means of a default-enabled 15 | `std` feature flag. 16 | - A default-enabled `circuit` feature is now provided to enable downstream 17 | users to avoid the need to depend upon the `bellman` crate. 18 | 19 | ### Changed 20 | - MSRV is now 1.70 21 | - Updated to `incrementalmerkletree 0.8.1`, `redjubjub 0.8`, `zcash_spec 0.2`, 22 | `zip32 0.2` 23 | - `sapling_crypto::builder::SaplingBuilder::add_output` now takes `[u8; 512]` 24 | for its `memo` argument instead of an optional value. 25 | 26 | ## [0.4.0] - 2024-12-16 27 | 28 | ### Added 29 | - Support for Partially-Created Zcash Transactions: 30 | - `sapling_crypto::builder::Builder::build_for_pczt` 31 | - `sapling_crypto::pczt` module. 32 | - `sapling_crypto::bundle::EffectsOnly` 33 | - `sapling_crypto::keys`: 34 | - `SpendAuthorizingKey::to_bytes` 35 | - `SpendValidatingKey::to_bytes` 36 | - `sapling_crypto::value::ValueSum::to_raw` 37 | - `sapling_crypto::zip32::DiversifiableFullViewingKey::to_internal_fvk` 38 | 39 | ### Fixed 40 | - `sapling_crypto::prover::OutputProver::prepare_circuit` now takes `esk` as an 41 | `sapling_crypto::keys::EphemeralSecretKey`, matching the existing public APIs 42 | that expose it. 43 | 44 | ### Changed 45 | - `sapling_crypto::builder`: 46 | - `SpendInfo::new` now takes a `FullViewingKey` instead of a 47 | `ProofGenerationKey`. 48 | - `Builder::add_spend` now takes a `FullViewingKey` instead of an 49 | `&ExtendedSpendingKey`. 50 | - `Builder::build` and `bundle` now take an `&[ExtendedSpendingKey]` argument. 51 | - `Error` has new variants: 52 | - `MissingSpendingKey` 53 | - `PcztRequiresZip212` 54 | - `WrongSpendingKey` 55 | - `sapling_crypto::bundle::SpendDescriptionV5::into_spend_description` now 56 | supports any `Authorization` for which the `SpendDescription` itself is fully 57 | authorized. 58 | 59 | ## [0.3.0] - 2024-10-02 60 | 61 | ### Changed 62 | - Updated to `incrementalmerkletree` version `0.7`. 63 | 64 | ## [0.2.0] - 2024-08-12 65 | 66 | ### Changed 67 | - Updated to `incrementalmerkletree` version `0.6`. 68 | 69 | ## [0.1.3] - 2024-03-25 70 | 71 | ### Added 72 | - `impl {PartialOrd, Ord} for sapling_crypto::note::Nullifier` 73 | - Additions under the `test-dependencies` feature flag: 74 | - `sapling-crypto::tree::Node::random` 75 | - `impl Distribution for Standard` 76 | 77 | ## [0.1.2] - 2024-03-08 78 | ### Added 79 | - `sapling_crypto::zip32::IncomingViewingKey` 80 | - `sapling_crypto::zip32::DiversifiableFullViewingKey::to_external_ivk` 81 | 82 | ## [0.1.1] - 2024-02-15 83 | ### Fixed 84 | - `sapling_crypto::builder::BundleType::num_outputs` now matches the previous 85 | behaviour for Sapling bundle padding, by including dummy outputs if there are 86 | no requested outputs but some requested spends, and `bundle_required` is set 87 | to `false` (as in `BundleType::DEFAULT`). 88 | 89 | ## [0.1.0] - 2024-01-26 90 | The crate has been completely rewritten. See [`zcash/librustzcash`] for the 91 | history of this rewrite. 92 | 93 | The entries below are relative to the `zcash_primitives::sapling` module as of 94 | `zcash_primitives 0.13.0`. 95 | 96 | ### Added 97 | - `sapling_crypto::Anchor` 98 | - `sapling_crypto::BatchValidator` (moved from `zcash_proofs::sapling`). 99 | - `sapling_crypto::SaplingVerificationContext` (moved from 100 | `zcash_proofs::sapling`). 101 | - `sapling_crypto::builder` (moved from 102 | `zcash_primitives::transaction::components::sapling::builder`). Further 103 | additions to this module: 104 | - `UnauthorizedBundle` 105 | - `InProgress` 106 | - `{InProgressProofs, Unproven, Proven}` 107 | - `{InProgressSignatures, Unsigned, PartiallyAuthorized}` 108 | - `{MaybeSigned, SigningParts}` 109 | - `SpendInfo` 110 | - `OutputInfo` 111 | - `ProverProgress` 112 | - `BundleType` 113 | - `SigningMetadata` 114 | - `bundle` bundle builder function. 115 | - `sapling_crypto::bundle` module: 116 | - The following types moved from 117 | `zcash_primitives::transaction::components::sapling`: 118 | - `Bundle` 119 | - `SpendDescription, SpendDescriptionV5` 120 | - `OutputDescription, OutputDescriptionV5` 121 | - `Authorization, Authorized` 122 | - `GrothProofBytes` 123 | - `Bundle::>::create_proofs` 124 | - `Bundle::>::prepare` 125 | - `Bundle::>::{sign, append_signatures}` 126 | - `Bundle::>::finalize` 127 | - `Bundle::>::apply_signatures` 128 | - `Bundle::try_map_authorization` 129 | - `testing` module, containing the following functions moved from 130 | `zcash_primitives::transaction::components::sapling::testing`: 131 | - `arb_output_description` 132 | - `arb_bundle` 133 | - `sapling_crypto::circuit` module (moved from `zcash_proofs::circuit::sapling`). 134 | Additional additions to this module: 135 | - `{SpendParameters, OutputParameters}` 136 | - `{SpendVerifyingKey, PreparedSpendVerifyingKey}` 137 | - `{OutputVerifyingKey, PreparedOutputVerifyingKey}` 138 | - `sapling_crypto::constants` module. 139 | - `sapling_crypto::keys`: 140 | - `SpendAuthorizingKey` 141 | - `SpendValidatingKey` 142 | - `sapling_crypto::note_encryption`: 143 | - `CompactOutputDescription` (moved from 144 | `zcash_primitives::transaction::components::sapling`). 145 | - `SaplingDomain::new` 146 | - `Zip212Enforcement` 147 | - `sapling_crypto::prover::{SpendProver, OutputProver}` 148 | - `sapling_crypto::tree::Node::{from_bytes, to_bytes}` 149 | - `sapling_crypto::value`: 150 | - `NoteValue::ZERO` 151 | - `ValueCommitTrapdoor::from_bytes` 152 | - `impl Sub for TrapdoorSum` 153 | - `impl Sub for CommitmentSum` 154 | - `sapling_crypto::zip32` module (moved from `zcash_primitives::zip32::sapling`). 155 | - `impl Debug for sapling_crypto::keys::{ExpandedSpendingKey, ProofGenerationKey}` 156 | - Test helpers, behind the `test-dependencies` feature flag: 157 | - `sapling_crypto::prover::mock::{MockSpendProver, MockOutputProver}` 158 | 159 | ### Changed 160 | - `sapling_crypto`: 161 | - `BatchValidator::validate` now takes the `SpendVerifyingKey` and 162 | `OutputVerifyingKey` newtypes. 163 | - `SaplingVerificationContext::new` now always creates a context with ZIP 216 164 | rules enforced, and no longer has a boolean for configuring this. 165 | - `SaplingVerificationContext::{check_spend, final_check}` now use the 166 | `redjubjub` crate types for `rk`, `spend_auth_sig`, and `binding_sig`. 167 | - `SaplingVerificationContext::{check_spend, check_output}` now take 168 | the `PreparedSpendVerifyingKey` and `PreparedOutputVerifyingKey` 169 | newtypes. 170 | - `SaplingVerificationContext::final_check` now takes its `value_balance` 171 | argument as `V: Into` instead of 172 | `zcash_primitives::transaction::components::Amount`. 173 | - `sapling_crypto::address::PaymentAddress::create_note` now takes its `value` 174 | argument as a `NoteValue` instead of as a bare `u64`. 175 | - `sapling_crypto::builder`: 176 | - `SaplingBuilder` has been renamed to `Builder` 177 | - `MaybeSigned::SigningMetadata` has been renamed to `MaybeSigned::SigningParts` 178 | - `Builder` no longer has a `P: zcash_primitives::consensus::Parameters` 179 | type parameter. 180 | - `Builder::new` now takes a `Zip212Enforcement` argument instead of a 181 | `P: zcash_primitives::consensus::Parameters` argument and a target height. 182 | It also now takes as an argument the Sapling anchor to be used for all 183 | spends in the bundle. 184 | - `Builder::add_spend` now takes `extsk` by reference. Also, it no 185 | longer takes a `diversifier` argument as the diversifier may be obtained 186 | from the note. All calls to `add_spend` are now required to use an anchor 187 | that corresponds to the anchor provided at builder construction. 188 | - `Builder::add_output` now takes an `Option<[u8; 512]>` memo instead 189 | of a `MemoBytes`. 190 | - `Builder::build` no longer takes a prover, proving context, progress 191 | notifier, or target height. Instead, it has `SpendProver, OutputProver` 192 | generic parameters and returns `(UnauthorizedBundle, SaplingMetadata)`. The 193 | caller can then use `Bundle::>::create_proofs` to 194 | create spend and output proofs for the bundle. 195 | - `Builder::build` now takes a `BundleType` argument that instructs 196 | it how to pad the bundle with dummy outputs. 197 | - `Error` has new error variants: 198 | - `Error::DuplicateSignature` 199 | - `Error::InvalidExternalSignature` 200 | - `Error::MissingSignatures` 201 | - `Error::BundleTypeNotSatisfiable` 202 | - `sapling_crypto::bundle`: 203 | - `Bundle` now has a second generic parameter `V`. 204 | - `Bundle::value_balance` now returns `&V` instead of 205 | `&zcash_primitives::transaction::components::Amount`. 206 | - `Bundle::map_authorization` now takes a context argument and explicit 207 | functions for each mappable field, rather than a `MapAuth` value, in 208 | order to simplify handling of context values. 209 | - `Authorized::binding_sig` now has type `redjubjub::Signature`. 210 | - `Authorized::AuthSig` now has type `redjubjub::Signature`. 211 | - `SpendDescription::temporary_zcashd_from_parts` now takes `rk` as 212 | `redjubjub::VerificationKey` instead of 213 | `sapling_crypto::redjubjub::PublicKey`. 214 | - `SpendDescription::rk` now returns `&redjubjub::VerificationKey`. 215 | - `SpendDescriptionV5::into_spend_description` now takes `spend_auth_sig` as 216 | `redjubjub::Signature` instead of 217 | `sapling_crypto::redjubjub::Signature`. 218 | - `testing::arb_bundle` now takes a `value_balance: V` argument. 219 | - `sapling_crypto::circuit::ValueCommitmentOpening::value` is now represented as 220 | a `NoteValue` instead of as a bare `u64`. 221 | - `sapling_crypto::keys`: 222 | - `DecodingError` has a new variant `UnsupportedChildIndex`. 223 | - `ExpandedSpendingKey.ask` now has type `SpendAuthorizingKey`. 224 | - `ProofGenerationKey.ak` now has type `SpendValidatingKey`. 225 | - `ViewingKey.ak` now has type `SpendValidatingKey`. 226 | - `sapling_crypto::note_encryption`: 227 | - `SaplingDomain` no longer has a `P: zcash_primitives::consensus::Parameters` 228 | type parameter. 229 | - The following methods now take a `Zip212Enforcement` argument instead of a 230 | `P: zcash_primitives::consensus::Parameters` argument: 231 | - `plaintext_version_is_valid` 232 | - `try_sapling_note_decryption` 233 | - `try_sapling_compact_note_decryption` 234 | - `try_sapling_output_recovery_with_ock` 235 | - `try_sapling_output_recovery` 236 | - `SaplingDomain::Memo` now has type `[u8; 512]` instead of 237 | `zcash_primitives::memo::MemoBytes`. 238 | - `sapling_note_encryption` now takes `memo` as a `[u8; 512]` instead of 239 | `zcash_primitives::memo::MemoBytes`. 240 | - The following methods now return `[u8; 512]` instead of 241 | `zcash_primitives::memo::MemoBytes`: 242 | - `try_sapling_note_decryption` 243 | - `try_sapling_output_recovery_with_ock` 244 | - `try_sapling_output_recovery` 245 | - `sapling_crypto::util::generate_random_rseed` now takes a `Zip212Enforcement` 246 | argument instead of a `P: zcash_primitives::consensus::Parameters` argument 247 | and a height. 248 | - `sapling_crypto::value`: 249 | - `TrapdoorSum::into_bsk` now returns `redjubjub::SigningKey` instead 250 | of `sapling_crypto::redjubjub::PrivateKey`. 251 | - `CommitmentSum::into_bvk` now returns `redjubjub::VerificationKey` 252 | instead of `sapling_crypto::redjubjub::PublicKey`. 253 | 254 | ### Removed 255 | - `sapling_crypto::bundle`: 256 | - `SpendDescription::{read, read_nullifier, read_rk, read_spend_auth_sig}` 257 | - `SpendDescription::{write_v4, write_v5_without_witness_data}` 258 | - `SpendDescriptionV5::read` 259 | - `OutputDescription::read` 260 | - `OutputDescription::{write_v4, write_v5_without_proof}` 261 | - `OutputDescriptionV5::read` 262 | - `MapAuth` trait 263 | - `sapling_crypto::builder`: 264 | - `SpendDescriptionInfo` 265 | - `sapling_crypto::note_encryption::SaplingDomain::for_height` (use 266 | `SaplingDomain::new` instead). 267 | - `sapling_crypto::redjubjub` module (use the `redjubjub` crate instead). 268 | - `sapling_crypto::spend_sig` (use `redjubjub::SigningKey::{randomize, sign}` 269 | instead). 270 | - `sapling_crypto::builder::SaplingBuilder::bundle_output_count` 271 | 272 | ## [0.0.1] - 2017-12-06 273 | Initial release to reserve crate name. 274 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | Copyrights in the "sapling-crypto" library are retained by their contributors. No 2 | copyright assignment is required to contribute to the "sapling-crypto" library. 3 | 4 | The "sapling-crypto" library is licensed under either of 5 | 6 | * Apache License, Version 2.0, (see ./LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) 7 | * MIT license (see ./LICENSE-MIT or http://opensource.org/licenses/MIT) 8 | 9 | at your option. 10 | 11 | Unless you explicitly state otherwise, any contribution intentionally 12 | submitted for inclusion in the work by you, as defined in the Apache-2.0 13 | license, shall be dual licensed as above, without any additional terms or 14 | conditions. 15 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sapling-crypto" 3 | version = "0.5.0" 4 | authors = [ 5 | "Sean Bowe ", 6 | "Jack Grigg ", 7 | "Kris Nuttycombe ", 8 | ] 9 | edition = "2021" 10 | rust-version = "1.70" 11 | description = "Cryptographic library for Zcash Sapling" 12 | homepage = "https://github.com/zcash/sapling-crypto" 13 | repository = "https://github.com/zcash/sapling-crypto" 14 | license = "MIT OR Apache-2.0" 15 | 16 | [package.metadata.docs.rs] 17 | features = ["test-dependencies"] 18 | rustdoc-args = ["--cfg", "docsrs"] 19 | 20 | [dependencies] 21 | ff = { version = "0.13", default-features = false } 22 | group = "0.13" 23 | 24 | bls12_381 = { version = "0.8", default-features = false, features = ["alloc"] } 25 | jubjub = { version = "0.10", default-features = false, features = ["alloc"] } 26 | redjubjub = { version = "0.8", default-features = false } 27 | zcash_spec = "0.2" 28 | 29 | # Boilerplate 30 | getset = "0.1" 31 | 32 | # No-std support 33 | core2 = { version = "0.3", default-features = false, features = ["alloc"] } 34 | 35 | # Circuits 36 | bellman = { version = "0.14", default-features = false, features = ["groth16"], optional = true } 37 | 38 | # CSPRNG 39 | rand = { version = "0.8", default-features = false } 40 | rand_core = { version = "0.6", default-features = false } 41 | 42 | # Digests 43 | blake2b_simd = { version = "1", default-features = false } 44 | blake2s_simd = { version = "1", default-features = false } 45 | 46 | # Documentation 47 | document-features = { version = "0.2", optional = true } 48 | 49 | # Encodings 50 | hex = { version = "0.4", default-features = false, features = ["alloc"] } 51 | 52 | # Logging and metrics 53 | memuse = { version = "0.2.2", default-features = false } 54 | tracing = { version = "0.1", default-features = false } 55 | 56 | # Note Commitment Trees 57 | bitvec = { version = "1", default-features = false } 58 | incrementalmerkletree = { version = "0.8.1", default-features = false, features = ["legacy-api"] } 59 | 60 | # Note encryption 61 | zcash_note_encryption = { version = "0.4", features = ["pre-zip-212"] } 62 | 63 | # Secret management 64 | subtle = { version = "2.2.3", default-features = false } 65 | 66 | # Static constants 67 | lazy_static = "1" 68 | 69 | # Test dependencies 70 | proptest = { version = "1", optional = true } 71 | 72 | # ZIP 32 73 | aes = "0.8" 74 | fpe = { version = "0.6", default-features = false, features = ["alloc"] } 75 | zip32 = { version = "0.2", default-features = false } 76 | 77 | 78 | [dev-dependencies] 79 | chacha20poly1305 = "0.10" 80 | criterion = "0.4" 81 | incrementalmerkletree = { version = "0.8.1", features = ["legacy-api", "test-dependencies"] } 82 | proptest = "1" 83 | rand_xorshift = "0.3" 84 | 85 | [target.'cfg(unix)'.dev-dependencies] 86 | pprof = { version = "0.11", features = ["criterion", "flamegraph"] } # MSRV 1.56 87 | 88 | [features] 89 | default = ["multicore", "circuit"] 90 | std = [ 91 | "core2/std", 92 | "dep:document-features", 93 | "group/wnaf-memuse", 94 | "redjubjub/std" 95 | ] 96 | 97 | ## Enables creation of Sapling proofs 98 | circuit = [ 99 | "dep:bellman", 100 | "bls12_381/bits", 101 | "bls12_381/groups", 102 | "bls12_381/pairings", 103 | "jubjub/bits", 104 | "std" 105 | ] 106 | 107 | ## Enables multithreading support for creating proofs. 108 | multicore = ["bellman?/multicore"] 109 | 110 | ### A temporary feature flag that exposes granular APIs needed by `zcashd`. These APIs 111 | ### should not be relied upon and will be removed in a future release. 112 | temporary-zcashd = [] 113 | 114 | ## Exposes APIs that are useful for testing, such as `proptest` strategies. 115 | test-dependencies = [ 116 | "incrementalmerkletree/test-dependencies", 117 | "dep:proptest", 118 | ] 119 | 120 | [[bench]] 121 | name = "circuit" 122 | harness = false 123 | 124 | [[bench]] 125 | name = "pedersen_hash" 126 | harness = false 127 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sapling-crypto 2 | 3 | This repository contains an implementation of Zcash's "Sapling" cryptography. 4 | 5 | ## `no_std` compatibility 6 | 7 | Downstream users of this crate must enable the `spin_no_std` feature of the 8 | `lazy_static` crate in order to take advantage of `no_std` builds; this is due 9 | to the fact that `--no-default-features` builds of `lazy_static` still rely on 10 | `std`. 11 | 12 | ## License 13 | 14 | Licensed under either of 15 | 16 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 17 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 18 | 19 | at your option. 20 | 21 | ### Contribution 22 | 23 | Unless you explicitly state otherwise, any contribution intentionally 24 | submitted for inclusion in the work by you, as defined in the Apache-2.0 25 | license, shall be dual licensed as above, without any additional terms or 26 | conditions. 27 | -------------------------------------------------------------------------------- /benches/circuit.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate criterion; 3 | 4 | use bellman::groth16::*; 5 | use bls12_381::Bls12; 6 | use criterion::Criterion; 7 | use group::ff::Field; 8 | use rand::{Rng, RngCore, SeedableRng}; 9 | use rand_xorshift::XorShiftRng; 10 | use sapling_crypto::{ 11 | circuit::{Spend, ValueCommitmentOpening}, 12 | keys::ExpandedSpendingKey, 13 | value::NoteValue, 14 | Diversifier, 15 | }; 16 | 17 | #[cfg(unix)] 18 | use pprof::criterion::{Output, PProfProfiler}; 19 | 20 | const TREE_DEPTH: usize = 32; 21 | 22 | fn criterion_benchmark(c: &mut Criterion) { 23 | let mut rng = XorShiftRng::from_seed([ 24 | 0x59, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc, 25 | 0xe5, 26 | ]); 27 | 28 | let groth_params = generate_random_parameters::( 29 | Spend { 30 | value_commitment_opening: None, 31 | proof_generation_key: None, 32 | payment_address: None, 33 | commitment_randomness: None, 34 | ar: None, 35 | auth_path: vec![None; TREE_DEPTH], 36 | anchor: None, 37 | }, 38 | &mut rng, 39 | ) 40 | .unwrap(); 41 | 42 | c.bench_function("sapling-spend-prove", |b| { 43 | let value_commitment = ValueCommitmentOpening { 44 | value: NoteValue::from_raw(1), 45 | randomness: jubjub::Fr::random(&mut rng), 46 | }; 47 | 48 | let sk: [u8; 32] = rng.gen(); 49 | let expsk = ExpandedSpendingKey::from_spending_key(&sk); 50 | 51 | let proof_generation_key = expsk.proof_generation_key(); 52 | 53 | let viewing_key = proof_generation_key.to_viewing_key(); 54 | 55 | let payment_address = loop { 56 | let diversifier = { 57 | let mut d = [0; 11]; 58 | rng.fill_bytes(&mut d); 59 | Diversifier(d) 60 | }; 61 | 62 | if let Some(p) = viewing_key.to_payment_address(diversifier) { 63 | break p; 64 | } 65 | }; 66 | 67 | let commitment_randomness = jubjub::Fr::random(&mut rng); 68 | let auth_path = 69 | vec![Some((bls12_381::Scalar::random(&mut rng), rng.next_u32() % 2 != 0)); TREE_DEPTH]; 70 | let ar = jubjub::Fr::random(&mut rng); 71 | let anchor = bls12_381::Scalar::random(&mut rng); 72 | 73 | b.iter(|| { 74 | create_random_proof( 75 | Spend { 76 | value_commitment_opening: Some(value_commitment.clone()), 77 | proof_generation_key: Some(proof_generation_key.clone()), 78 | payment_address: Some(payment_address), 79 | commitment_randomness: Some(commitment_randomness), 80 | ar: Some(ar), 81 | auth_path: auth_path.clone(), 82 | anchor: Some(anchor), 83 | }, 84 | &groth_params, 85 | &mut rng, 86 | ) 87 | }); 88 | }); 89 | } 90 | 91 | #[cfg(unix)] 92 | criterion_group! { 93 | name = benches; 94 | config = Criterion::default() 95 | .sample_size(10) 96 | .with_profiler(PProfProfiler::new(100, Output::Flamegraph(None))); 97 | targets = criterion_benchmark 98 | } 99 | #[cfg(windows)] 100 | criterion_group! { 101 | name = benches; 102 | config = Criterion::default().sample_size(10); 103 | targets = criterion_benchmark 104 | } 105 | criterion_main!(benches); 106 | -------------------------------------------------------------------------------- /benches/pedersen_hash.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | use rand_core::{OsRng, RngCore}; 3 | use sapling_crypto::pedersen_hash::{pedersen_hash, Personalization}; 4 | 5 | #[cfg(unix)] 6 | use pprof::criterion::{Output, PProfProfiler}; 7 | 8 | fn bench_pedersen_hash(c: &mut Criterion) { 9 | let rng = &mut OsRng; 10 | let bits = (0..510) 11 | .map(|_| (rng.next_u32() % 2) != 0) 12 | .collect::>(); 13 | let personalization = Personalization::MerkleTree(31); 14 | 15 | c.bench_function("pedersen-hash", |b| { 16 | b.iter(|| pedersen_hash(personalization, bits.clone())) 17 | }); 18 | } 19 | 20 | #[cfg(unix)] 21 | criterion_group! { 22 | name = benches; 23 | config = Criterion::default().with_profiler(PProfProfiler::new(100, Output::Flamegraph(None))); 24 | targets = bench_pedersen_hash 25 | } 26 | #[cfg(not(unix))] 27 | criterion_group!(benches, bench_pedersen_hash); 28 | criterion_main!(benches); 29 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.70.0" 3 | components = ["clippy", "rustfmt"] 4 | -------------------------------------------------------------------------------- /src/address.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | keys::{DiversifiedTransmissionKey, Diversifier}, 3 | note::{Note, Rseed}, 4 | value::NoteValue, 5 | }; 6 | 7 | /// A Sapling payment address. 8 | /// 9 | /// # Invariants 10 | /// 11 | /// - `diversifier` is guaranteed to be valid for Sapling (only 50% of diversifiers are). 12 | /// - `pk_d` is guaranteed to be prime-order (i.e. in the prime-order subgroup of Jubjub, 13 | /// and not the identity). 14 | #[derive(Clone, Copy, Debug)] 15 | pub struct PaymentAddress { 16 | pk_d: DiversifiedTransmissionKey, 17 | diversifier: Diversifier, 18 | } 19 | 20 | impl PartialEq for PaymentAddress { 21 | fn eq(&self, other: &Self) -> bool { 22 | self.pk_d == other.pk_d && self.diversifier == other.diversifier 23 | } 24 | } 25 | 26 | impl Eq for PaymentAddress {} 27 | 28 | impl PaymentAddress { 29 | /// Constructs a PaymentAddress from a diversifier and a Jubjub point. 30 | /// 31 | /// Returns None if `diversifier` is not valid for Sapling, or `pk_d` is the identity. 32 | /// Note that we cannot verify in this constructor that `pk_d` is derived from 33 | /// `diversifier`, so addresses for which these values have no known relationship 34 | /// (and therefore no-one can receive funds at them) can still be constructed. 35 | pub fn from_parts(diversifier: Diversifier, pk_d: DiversifiedTransmissionKey) -> Option { 36 | // Check that the diversifier is valid 37 | diversifier.g_d()?; 38 | 39 | Self::from_parts_unchecked(diversifier, pk_d) 40 | } 41 | 42 | /// Constructs a PaymentAddress from a diversifier and a Jubjub point. 43 | /// 44 | /// Returns None if `pk_d` is the identity. The caller must check that `diversifier` 45 | /// is valid for Sapling. 46 | pub(crate) fn from_parts_unchecked( 47 | diversifier: Diversifier, 48 | pk_d: DiversifiedTransmissionKey, 49 | ) -> Option { 50 | if pk_d.is_identity() { 51 | None 52 | } else { 53 | Some(PaymentAddress { pk_d, diversifier }) 54 | } 55 | } 56 | 57 | /// Parses a PaymentAddress from bytes. 58 | pub fn from_bytes(bytes: &[u8; 43]) -> Option { 59 | let diversifier = { 60 | let mut tmp = [0; 11]; 61 | tmp.copy_from_slice(&bytes[0..11]); 62 | Diversifier(tmp) 63 | }; 64 | 65 | let pk_d = DiversifiedTransmissionKey::from_bytes(bytes[11..43].try_into().unwrap()); 66 | if pk_d.is_some().into() { 67 | // The remaining invariants are checked here. 68 | PaymentAddress::from_parts(diversifier, pk_d.unwrap()) 69 | } else { 70 | None 71 | } 72 | } 73 | 74 | /// Returns the byte encoding of this `PaymentAddress`. 75 | pub fn to_bytes(&self) -> [u8; 43] { 76 | let mut bytes = [0; 43]; 77 | bytes[0..11].copy_from_slice(&self.diversifier.0); 78 | bytes[11..].copy_from_slice(&self.pk_d.to_bytes()); 79 | bytes 80 | } 81 | 82 | /// Returns the [`Diversifier`] for this `PaymentAddress`. 83 | pub fn diversifier(&self) -> &Diversifier { 84 | &self.diversifier 85 | } 86 | 87 | /// Returns `pk_d` for this `PaymentAddress`. 88 | pub fn pk_d(&self) -> &DiversifiedTransmissionKey { 89 | &self.pk_d 90 | } 91 | 92 | pub(crate) fn g_d(&self) -> jubjub::SubgroupPoint { 93 | self.diversifier.g_d().expect("checked at construction") 94 | } 95 | 96 | pub fn create_note(&self, value: NoteValue, rseed: Rseed) -> Note { 97 | Note::from_parts(*self, value, rseed) 98 | } 99 | } 100 | 101 | #[cfg(any(test, feature = "test-dependencies"))] 102 | pub(super) mod testing { 103 | use proptest::prelude::*; 104 | 105 | use super::{ 106 | super::keys::{testing::arb_incoming_viewing_key, Diversifier, SaplingIvk}, 107 | PaymentAddress, 108 | }; 109 | 110 | pub fn arb_payment_address() -> impl Strategy { 111 | arb_incoming_viewing_key().prop_flat_map(|ivk: SaplingIvk| { 112 | any::<[u8; 11]>().prop_filter_map( 113 | "Sampled diversifier must generate a valid Sapling payment address.", 114 | move |d| ivk.to_payment_address(Diversifier(d)), 115 | ) 116 | }) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/circuit/constants.rs: -------------------------------------------------------------------------------- 1 | //! Various constants used for the Zcash proofs. 2 | 3 | use crate::constants::{PEDERSEN_HASH_CHUNKS_PER_GENERATOR, PEDERSEN_HASH_GENERATORS}; 4 | 5 | use alloc::vec::Vec; 6 | use bls12_381::Scalar; 7 | use group::{ff::Field, Curve, Group}; 8 | use jubjub::ExtendedPoint; 9 | use lazy_static::lazy_static; 10 | 11 | /// The `d` constant of the twisted Edwards curve. 12 | pub(crate) const EDWARDS_D: Scalar = Scalar::from_raw([ 13 | 0x0106_5fd6_d634_3eb1, 14 | 0x292d_7f6d_3757_9d26, 15 | 0xf5fd_9207_e6bd_7fd4, 16 | 0x2a93_18e7_4bfa_2b48, 17 | ]); 18 | 19 | /// The `A` constant of the birationally equivalent Montgomery curve. 20 | pub(crate) const MONTGOMERY_A: Scalar = Scalar::from_raw([ 21 | 0x0000_0000_0000_a002, 22 | 0x0000_0000_0000_0000, 23 | 0x0000_0000_0000_0000, 24 | 0x0000_0000_0000_0000, 25 | ]); 26 | 27 | /// The scaling factor used for conversion to and from the Montgomery form. 28 | pub(crate) const MONTGOMERY_SCALE: Scalar = Scalar::from_raw([ 29 | 0x8f45_35f7_cf82_b8d9, 30 | 0xce40_6970_3da8_8abd, 31 | 0x31de_341e_77d7_64e5, 32 | 0x2762_de61_e862_645e, 33 | ]); 34 | 35 | /// The number of chunks needed to represent a full scalar during fixed-base 36 | /// exponentiation. 37 | const FIXED_BASE_CHUNKS_PER_GENERATOR: usize = 84; 38 | 39 | /// Reference to a circuit version of a generator for fixed-base salar multiplication. 40 | pub type FixedGenerator = &'static [Vec<(Scalar, Scalar)>]; 41 | 42 | /// Circuit version of a generator for fixed-base salar multiplication. 43 | pub type FixedGeneratorOwned = Vec>; 44 | 45 | lazy_static! { 46 | pub static ref PROOF_GENERATION_KEY_GENERATOR: FixedGeneratorOwned = 47 | generate_circuit_generator(crate::constants::PROOF_GENERATION_KEY_GENERATOR); 48 | 49 | pub static ref NOTE_COMMITMENT_RANDOMNESS_GENERATOR: FixedGeneratorOwned = 50 | generate_circuit_generator(crate::constants::NOTE_COMMITMENT_RANDOMNESS_GENERATOR); 51 | 52 | pub static ref NULLIFIER_POSITION_GENERATOR: FixedGeneratorOwned = 53 | generate_circuit_generator(crate::constants::NULLIFIER_POSITION_GENERATOR); 54 | 55 | pub static ref VALUE_COMMITMENT_VALUE_GENERATOR: FixedGeneratorOwned = 56 | generate_circuit_generator(crate::constants::VALUE_COMMITMENT_VALUE_GENERATOR); 57 | 58 | pub static ref VALUE_COMMITMENT_RANDOMNESS_GENERATOR: FixedGeneratorOwned = 59 | generate_circuit_generator(crate::constants::VALUE_COMMITMENT_RANDOMNESS_GENERATOR); 60 | 61 | pub static ref SPENDING_KEY_GENERATOR: FixedGeneratorOwned = 62 | generate_circuit_generator(crate::constants::SPENDING_KEY_GENERATOR); 63 | 64 | /// The pre-computed window tables `[-4, 3, 2, 1, 1, 2, 3, 4]` of different magnitudes 65 | /// of the Pedersen hash segment generators. 66 | pub(crate) static ref PEDERSEN_CIRCUIT_GENERATORS: Vec>> = 67 | generate_pedersen_circuit_generators(); 68 | } 69 | 70 | /// Creates the 3-bit window table `[0, 1, ..., 8]` for different magnitudes of a fixed 71 | /// generator. 72 | pub fn generate_circuit_generator(mut gen: jubjub::SubgroupPoint) -> FixedGeneratorOwned { 73 | let mut windows = vec![]; 74 | 75 | for _ in 0..FIXED_BASE_CHUNKS_PER_GENERATOR { 76 | let mut coeffs = vec![(Scalar::zero(), Scalar::one())]; 77 | let mut g = gen; 78 | for _ in 0..7 { 79 | let g_affine = jubjub::ExtendedPoint::from(g).to_affine(); 80 | coeffs.push((g_affine.get_u(), g_affine.get_v())); 81 | g += gen; 82 | } 83 | windows.push(coeffs); 84 | 85 | // gen = gen * 8 86 | gen = g; 87 | } 88 | 89 | windows 90 | } 91 | 92 | /// Returns the coordinates of this point's Montgomery curve representation, or `None` if 93 | /// it is the point at infinity. 94 | #[allow(clippy::many_single_char_names)] 95 | pub(crate) fn to_montgomery_coords(g: ExtendedPoint) -> Option<(Scalar, Scalar)> { 96 | let g = g.to_affine(); 97 | let (x, y) = (g.get_u(), g.get_v()); 98 | 99 | if y == Scalar::one() { 100 | // The only solution for y = 1 is x = 0. (0, 1) is the neutral element, so we map 101 | // this to the point at infinity. 102 | None 103 | } else { 104 | // The map from a twisted Edwards curve is defined as 105 | // (x, y) -> (u, v) where 106 | // u = (1 + y) / (1 - y) 107 | // v = u / x 108 | // 109 | // This mapping is not defined for y = 1 and for x = 0. 110 | // 111 | // We have that y != 1 above. If x = 0, the only 112 | // solutions for y are 1 (contradiction) or -1. 113 | if x.is_zero_vartime() { 114 | // (0, -1) is the point of order two which is not 115 | // the neutral element, so we map it to (0, 0) which is 116 | // the only affine point of order 2. 117 | Some((Scalar::zero(), Scalar::zero())) 118 | } else { 119 | // The mapping is defined as above. 120 | // 121 | // (x, y) -> (u, v) where 122 | // u = (1 + y) / (1 - y) 123 | // v = u / x 124 | 125 | let u = (Scalar::one() + y) * (Scalar::one() - y).invert().unwrap(); 126 | let v = u * x.invert().unwrap(); 127 | 128 | // Scale it into the correct curve constants 129 | // scaling factor = sqrt(4 / (a - d)) 130 | Some((u, v * MONTGOMERY_SCALE)) 131 | } 132 | } 133 | } 134 | 135 | /// Creates the 2-bit window table lookups for each 4-bit "chunk" in each segment of the 136 | /// Pedersen hash. 137 | fn generate_pedersen_circuit_generators() -> Vec>> { 138 | // Process each segment 139 | PEDERSEN_HASH_GENERATORS 140 | .iter() 141 | .cloned() 142 | .map(|mut gen| { 143 | let mut windows = vec![]; 144 | 145 | for _ in 0..PEDERSEN_HASH_CHUNKS_PER_GENERATOR { 146 | // Create (x, y) coeffs for this chunk 147 | let mut coeffs = vec![]; 148 | let mut g = gen; 149 | 150 | // coeffs = g, g*2, g*3, g*4 151 | for _ in 0..4 { 152 | coeffs.push( 153 | to_montgomery_coords(g.into()) 154 | .expect("we never encounter the point at infinity"), 155 | ); 156 | g += gen; 157 | } 158 | windows.push(coeffs); 159 | 160 | // Our chunks are separated by 2 bits to prevent overlap. 161 | for _ in 0..4 { 162 | gen = gen.double(); 163 | } 164 | } 165 | 166 | windows 167 | }) 168 | .collect() 169 | } 170 | 171 | #[cfg(test)] 172 | mod tests { 173 | use super::*; 174 | 175 | #[test] 176 | fn edwards_d() { 177 | // d = -(10240/10241) 178 | assert_eq!( 179 | -Scalar::from(10240) * Scalar::from(10241).invert().unwrap(), 180 | EDWARDS_D 181 | ); 182 | } 183 | 184 | #[test] 185 | fn montgomery_a() { 186 | assert_eq!(Scalar::from(40962), MONTGOMERY_A); 187 | } 188 | 189 | #[test] 190 | fn montgomery_scale() { 191 | // scaling factor = sqrt(4 / (a - d)) 192 | assert_eq!( 193 | MONTGOMERY_SCALE.square() * (-Scalar::one() - EDWARDS_D), 194 | Scalar::from(4), 195 | ); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/circuit/pedersen_hash.rs: -------------------------------------------------------------------------------- 1 | //! Gadget for Zcash's Pedersen hash. 2 | 3 | use super::ecc::{EdwardsPoint, MontgomeryPoint}; 4 | pub use crate::pedersen_hash::Personalization; 5 | 6 | use alloc::vec::Vec; 7 | use bellman::gadgets::boolean::Boolean; 8 | use bellman::gadgets::lookup::*; 9 | use bellman::{ConstraintSystem, SynthesisError}; 10 | 11 | use super::constants::PEDERSEN_CIRCUIT_GENERATORS; 12 | 13 | fn get_constant_bools(person: &Personalization) -> Vec { 14 | person 15 | .get_bits() 16 | .into_iter() 17 | .map(Boolean::constant) 18 | .collect() 19 | } 20 | 21 | pub fn pedersen_hash( 22 | mut cs: CS, 23 | personalization: Personalization, 24 | bits: &[Boolean], 25 | ) -> Result 26 | where 27 | CS: ConstraintSystem, 28 | { 29 | let personalization = get_constant_bools(&personalization); 30 | assert_eq!(personalization.len(), 6); 31 | 32 | let mut edwards_result = None; 33 | let mut bits = personalization.iter().chain(bits.iter()).peekable(); 34 | let mut segment_generators = PEDERSEN_CIRCUIT_GENERATORS.iter(); 35 | let boolean_false = Boolean::constant(false); 36 | 37 | let mut segment_i = 0; 38 | while bits.peek().is_some() { 39 | let mut segment_result = None; 40 | let mut segment_windows = &segment_generators.next().expect("enough segments")[..]; 41 | 42 | let mut window_i = 0; 43 | while let Some(a) = bits.next() { 44 | let b = bits.next().unwrap_or(&boolean_false); 45 | let c = bits.next().unwrap_or(&boolean_false); 46 | 47 | let tmp = lookup3_xy_with_conditional_negation( 48 | cs.namespace(|| format!("segment {}, window {}", segment_i, window_i)), 49 | &[a.clone(), b.clone(), c.clone()], 50 | &segment_windows[0], 51 | )?; 52 | 53 | let tmp = MontgomeryPoint::interpret_unchecked(tmp.0, tmp.1); 54 | 55 | match segment_result { 56 | None => { 57 | segment_result = Some(tmp); 58 | } 59 | Some(ref mut segment_result) => { 60 | *segment_result = tmp.add( 61 | cs.namespace(|| { 62 | format!("addition of segment {}, window {}", segment_i, window_i) 63 | }), 64 | segment_result, 65 | )?; 66 | } 67 | } 68 | 69 | segment_windows = &segment_windows[1..]; 70 | 71 | if segment_windows.is_empty() { 72 | break; 73 | } 74 | 75 | window_i += 1; 76 | } 77 | 78 | let segment_result = segment_result.expect( 79 | "bits is not exhausted due to while condition; 80 | thus there must be a segment window; 81 | thus there must be a segment result", 82 | ); 83 | 84 | // Convert this segment into twisted Edwards form. 85 | let segment_result = segment_result.into_edwards( 86 | cs.namespace(|| format!("conversion of segment {} into edwards", segment_i)), 87 | )?; 88 | 89 | match edwards_result { 90 | Some(ref mut edwards_result) => { 91 | *edwards_result = segment_result.add( 92 | cs.namespace(|| format!("addition of segment {} to accumulator", segment_i)), 93 | edwards_result, 94 | )?; 95 | } 96 | None => { 97 | edwards_result = Some(segment_result); 98 | } 99 | } 100 | 101 | segment_i += 1; 102 | } 103 | 104 | Ok(edwards_result.unwrap()) 105 | } 106 | 107 | #[cfg(test)] 108 | mod test { 109 | use super::*; 110 | use crate::pedersen_hash; 111 | 112 | use bellman::gadgets::boolean::{AllocatedBit, Boolean}; 113 | use bellman::gadgets::test::*; 114 | use group::{ff::PrimeField, Curve}; 115 | use rand_core::{RngCore, SeedableRng}; 116 | use rand_xorshift::XorShiftRng; 117 | 118 | /// Predict the number of constraints of a Pedersen hash 119 | fn ph_num_constraints(input_bits: usize) -> usize { 120 | // Account for the 6 personalization bits. 121 | let personalized_bits = 6 + input_bits; 122 | // Constant booleans in the personalization and padding don't need lookup "precomp" constraints. 123 | let precomputed_booleans = 2 + (personalized_bits % 3 == 1) as usize; 124 | 125 | // Count chunks and segments with ceiling division 126 | let chunks = (personalized_bits + 3 - 1) / 3; 127 | let segments = (chunks + 63 - 1) / 63; 128 | let all_but_last_segments = segments - 1; 129 | let last_chunks = chunks - all_but_last_segments * 63; 130 | 131 | // Constraints per operation 132 | let lookup_chunk = 2; 133 | let add_chunks = 3; // Montgomery addition 134 | let convert_segment = 2; // Conversion to Edwards 135 | let add_segments = 6; // Edwards addition 136 | 137 | (chunks) * lookup_chunk - precomputed_booleans 138 | + segments * convert_segment 139 | + all_but_last_segments * ((63 - 1) * add_chunks + add_segments) 140 | + (last_chunks - 1) * add_chunks 141 | } 142 | 143 | #[test] 144 | fn test_pedersen_hash_constraints() { 145 | let mut rng = XorShiftRng::from_seed([ 146 | 0x59, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 147 | 0xbc, 0xe5, 148 | ]); 149 | 150 | let leaves_len = 2 * 255; 151 | let note_len = 64 + 256 + 256; 152 | 153 | for &n_bits in [ 154 | 0, 155 | 3 * 63 - 6, 156 | 3 * 63 - 6 + 1, 157 | 3 * 63 - 6 + 2, 158 | leaves_len, 159 | note_len, 160 | ] 161 | .iter() 162 | { 163 | let mut cs = TestConstraintSystem::new(); 164 | 165 | let input: Vec = (0..n_bits).map(|_| rng.next_u32() % 2 != 0).collect(); 166 | 167 | let input_bools: Vec = input 168 | .iter() 169 | .enumerate() 170 | .map(|(i, b)| { 171 | Boolean::from( 172 | AllocatedBit::alloc(cs.namespace(|| format!("input {}", i)), Some(*b)) 173 | .unwrap(), 174 | ) 175 | }) 176 | .collect(); 177 | 178 | pedersen_hash( 179 | cs.namespace(|| "pedersen hash"), 180 | Personalization::NoteCommitment, 181 | &input_bools, 182 | ) 183 | .unwrap(); 184 | 185 | assert!(cs.is_satisfied()); 186 | 187 | let bitness_constraints = n_bits; 188 | let ph_constraints = ph_num_constraints(n_bits); 189 | assert_eq!(cs.num_constraints(), bitness_constraints + ph_constraints); 190 | // The actual usages 191 | if n_bits == leaves_len { 192 | assert_eq!(cs.num_constraints(), leaves_len + 867) 193 | }; 194 | if n_bits == note_len { 195 | assert_eq!(cs.num_constraints(), note_len + 982) 196 | }; 197 | } 198 | } 199 | 200 | #[test] 201 | fn test_pedersen_hash() { 202 | let mut rng = XorShiftRng::from_seed([ 203 | 0x59, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 204 | 0xbc, 0xe5, 205 | ]); 206 | 207 | for length in 0..751 { 208 | for _ in 0..5 { 209 | let input: Vec = (0..length).map(|_| rng.next_u32() % 2 != 0).collect(); 210 | 211 | let mut cs = TestConstraintSystem::new(); 212 | 213 | let input_bools: Vec = input 214 | .iter() 215 | .enumerate() 216 | .map(|(i, b)| { 217 | Boolean::from( 218 | AllocatedBit::alloc(cs.namespace(|| format!("input {}", i)), Some(*b)) 219 | .unwrap(), 220 | ) 221 | }) 222 | .collect(); 223 | 224 | let res = pedersen_hash( 225 | cs.namespace(|| "pedersen hash"), 226 | Personalization::MerkleTree(1), 227 | &input_bools, 228 | ) 229 | .unwrap(); 230 | 231 | assert!(cs.is_satisfied()); 232 | 233 | let expected = jubjub::ExtendedPoint::from(pedersen_hash::pedersen_hash( 234 | Personalization::MerkleTree(1), 235 | input.clone().into_iter(), 236 | )) 237 | .to_affine(); 238 | 239 | assert_eq!(res.get_u().get_value().unwrap(), expected.get_u()); 240 | assert_eq!(res.get_v().get_value().unwrap(), expected.get_v()); 241 | 242 | // Test against the output of a different personalization 243 | let unexpected = jubjub::ExtendedPoint::from(pedersen_hash::pedersen_hash( 244 | Personalization::MerkleTree(0), 245 | input.into_iter(), 246 | )) 247 | .to_affine(); 248 | 249 | assert!(res.get_u().get_value().unwrap() != unexpected.get_u()); 250 | assert!(res.get_v().get_value().unwrap() != unexpected.get_v()); 251 | } 252 | } 253 | } 254 | 255 | #[test] 256 | fn test_pedersen_hash_external_test_vectors() { 257 | let mut rng = XorShiftRng::from_seed([ 258 | 0x59, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 259 | 0xbc, 0xe5, 260 | ]); 261 | 262 | let expected_us = [ 263 | "28161926966428986673895580777285905189725480206811328272001879986576840909576", 264 | "39669831794597628158501766225645040955899576179071014703006420393381978263045", 265 | ]; 266 | let expected_vs = [ 267 | "26869991781071974894722407757894142583682396277979904369818887810555917099932", 268 | "2112827187110048608327330788910224944044097981650120385961435904443901436107", 269 | ]; 270 | for length in 300..302 { 271 | let input: Vec = (0..length).map(|_| rng.next_u32() % 2 != 0).collect(); 272 | 273 | let mut cs = TestConstraintSystem::new(); 274 | 275 | let input_bools: Vec = input 276 | .iter() 277 | .enumerate() 278 | .map(|(i, b)| { 279 | Boolean::from( 280 | AllocatedBit::alloc(cs.namespace(|| format!("input {}", i)), Some(*b)) 281 | .unwrap(), 282 | ) 283 | }) 284 | .collect(); 285 | 286 | let res = pedersen_hash( 287 | cs.namespace(|| "pedersen hash"), 288 | Personalization::MerkleTree(1), 289 | &input_bools, 290 | ) 291 | .unwrap(); 292 | 293 | assert!(cs.is_satisfied()); 294 | 295 | assert_eq!( 296 | res.get_u().get_value().unwrap(), 297 | bls12_381::Scalar::from_str_vartime(expected_us[length - 300]).unwrap() 298 | ); 299 | assert_eq!( 300 | res.get_v().get_value().unwrap(), 301 | bls12_381::Scalar::from_str_vartime(expected_vs[length - 300]).unwrap() 302 | ); 303 | } 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /src/constants.rs: -------------------------------------------------------------------------------- 1 | //! Various constants used by the Sapling protocol. 2 | 3 | use alloc::vec::Vec; 4 | use ff::PrimeField; 5 | use group::Group; 6 | use jubjub::SubgroupPoint; 7 | use lazy_static::lazy_static; 8 | 9 | /// First 64 bytes of the BLAKE2s input during group hash. 10 | /// This is chosen to be some random string that we couldn't have anticipated when we designed 11 | /// the algorithm, for rigidity purposes. 12 | /// We deliberately use an ASCII hex string of 32 bytes here. 13 | pub const GH_FIRST_BLOCK: &[u8; 64] = 14 | b"096b36a5804bfacef1691e173c366a47ff5ba84a44f26ddd7e8d9f79d5b42df0"; 15 | 16 | // BLAKE2s invocation personalizations 17 | /// BLAKE2s Personalization for CRH^ivk = BLAKE2s(ak | nk) 18 | pub const CRH_IVK_PERSONALIZATION: &[u8; 8] = b"Zcashivk"; 19 | 20 | /// BLAKE2s Personalization for PRF^nf = BLAKE2s(nk | rho) 21 | pub const PRF_NF_PERSONALIZATION: &[u8; 8] = b"Zcash_nf"; 22 | 23 | // Group hash personalizations 24 | /// BLAKE2s Personalization for Pedersen hash generators. 25 | pub const PEDERSEN_HASH_GENERATORS_PERSONALIZATION: &[u8; 8] = b"Zcash_PH"; 26 | 27 | /// BLAKE2s Personalization for the group hash for key diversification 28 | pub const KEY_DIVERSIFICATION_PERSONALIZATION: &[u8; 8] = b"Zcash_gd"; 29 | 30 | /// BLAKE2s Personalization for the spending key base point 31 | pub const SPENDING_KEY_GENERATOR_PERSONALIZATION: &[u8; 8] = b"Zcash_G_"; 32 | 33 | /// BLAKE2s Personalization for the proof generation key base point 34 | pub const PROOF_GENERATION_KEY_BASE_GENERATOR_PERSONALIZATION: &[u8; 8] = b"Zcash_H_"; 35 | 36 | /// BLAKE2s Personalization for the value commitment generator for the value 37 | pub const VALUE_COMMITMENT_GENERATOR_PERSONALIZATION: &[u8; 8] = b"Zcash_cv"; 38 | 39 | /// BLAKE2s Personalization for the nullifier position generator (for computing rho) 40 | pub const NULLIFIER_POSITION_IN_TREE_GENERATOR_PERSONALIZATION: &[u8; 8] = b"Zcash_J_"; 41 | 42 | // π_A + π_B + π_C 43 | pub(crate) const GROTH_PROOF_SIZE: usize = 48 + 96 + 48; 44 | 45 | /// The prover will demonstrate knowledge of discrete log with respect to this base when 46 | /// they are constructing a proof, in order to authorize proof construction. 47 | pub const PROOF_GENERATION_KEY_GENERATOR: SubgroupPoint = SubgroupPoint::from_raw_unchecked( 48 | bls12_381::Scalar::from_raw([ 49 | 0x3af2_dbef_b96e_2571, 50 | 0xadf2_d038_f2fb_b820, 51 | 0x7043_03f1_e890_6081, 52 | 0x1457_a502_31cd_e2df, 53 | ]), 54 | bls12_381::Scalar::from_raw([ 55 | 0x467a_f9f7_e05d_e8e7, 56 | 0x50df_51ea_f5a1_49d2, 57 | 0xdec9_0184_0f49_48cc, 58 | 0x54b6_d107_18df_2a7a, 59 | ]), 60 | ); 61 | 62 | /// The note commitment is randomized over this generator. 63 | pub const NOTE_COMMITMENT_RANDOMNESS_GENERATOR: SubgroupPoint = SubgroupPoint::from_raw_unchecked( 64 | bls12_381::Scalar::from_raw([ 65 | 0xa514_3b34_a8e3_6462, 66 | 0xf091_9d06_ffb1_ecda, 67 | 0xa140_9aa1_f33b_ec2c, 68 | 0x26eb_9f8a_9ec7_2a8c, 69 | ]), 70 | bls12_381::Scalar::from_raw([ 71 | 0xd4fc_6365_796c_77ac, 72 | 0x96b7_8bea_fa9c_c44c, 73 | 0x949d_7747_6e26_2c95, 74 | 0x114b_7501_ad10_4c57, 75 | ]), 76 | ); 77 | 78 | /// The node commitment is randomized again by the position in order to supply the 79 | /// nullifier computation with a unique input w.r.t. the note being spent, to prevent 80 | /// Faerie gold attacks. 81 | pub const NULLIFIER_POSITION_GENERATOR: SubgroupPoint = SubgroupPoint::from_raw_unchecked( 82 | bls12_381::Scalar::from_raw([ 83 | 0x2ce3_3921_888d_30db, 84 | 0xe81c_ee09_a561_229e, 85 | 0xdb56_b6db_8d80_75ed, 86 | 0x2400_c2e2_e336_2644, 87 | ]), 88 | bls12_381::Scalar::from_raw([ 89 | 0xa3f7_fa36_c72b_0065, 90 | 0xe155_b8e8_ffff_2e42, 91 | 0xfc9e_8a15_a096_ba8f, 92 | 0x6136_9d54_40bf_84a5, 93 | ]), 94 | ); 95 | 96 | /// The value commitment is used to check balance between inputs and outputs. The value is 97 | /// placed over this generator. 98 | pub const VALUE_COMMITMENT_VALUE_GENERATOR: SubgroupPoint = SubgroupPoint::from_raw_unchecked( 99 | bls12_381::Scalar::from_raw([ 100 | 0x3618_3b2c_b4d7_ef51, 101 | 0x9472_c89a_c043_042d, 102 | 0xd861_8ed1_d15f_ef4e, 103 | 0x273f_910d_9ecc_1615, 104 | ]), 105 | bls12_381::Scalar::from_raw([ 106 | 0xa77a_81f5_0667_c8d7, 107 | 0xbc33_32d0_fa1c_cd18, 108 | 0xd322_94fd_8977_4ad6, 109 | 0x466a_7e3a_82f6_7ab1, 110 | ]), 111 | ); 112 | 113 | /// The value commitment is randomized over this generator, for privacy. 114 | pub const VALUE_COMMITMENT_RANDOMNESS_GENERATOR: SubgroupPoint = SubgroupPoint::from_raw_unchecked( 115 | bls12_381::Scalar::from_raw([ 116 | 0x3bce_3b77_9366_4337, 117 | 0xd1d8_da41_af03_744e, 118 | 0x7ff6_826a_d580_04b4, 119 | 0x6800_f4fa_0f00_1cfc, 120 | ]), 121 | bls12_381::Scalar::from_raw([ 122 | 0x3cae_fab9_380b_6a8b, 123 | 0xad46_f1b0_473b_803b, 124 | 0xe6fb_2a6e_1e22_ab50, 125 | 0x6d81_d3a9_cb45_dedb, 126 | ]), 127 | ); 128 | 129 | /// The spender proves discrete log with respect to this base at spend time. 130 | pub const SPENDING_KEY_GENERATOR: SubgroupPoint = SubgroupPoint::from_raw_unchecked( 131 | bls12_381::Scalar::from_raw([ 132 | 0x47bf_4692_0a95_a753, 133 | 0xd5b9_a7d3_ef8e_2827, 134 | 0xd418_a7ff_2675_3b6a, 135 | 0x0926_d4f3_2059_c712, 136 | ]), 137 | bls12_381::Scalar::from_raw([ 138 | 0x3056_32ad_aaf2_b530, 139 | 0x6d65_674d_cedb_ddbc, 140 | 0x53bb_37d0_c21c_fd05, 141 | 0x57a1_019e_6de9_b675, 142 | ]), 143 | ); 144 | 145 | /// The generators (for each segment) used in all Pedersen commitments. 146 | pub const PEDERSEN_HASH_GENERATORS: &[SubgroupPoint] = &[ 147 | SubgroupPoint::from_raw_unchecked( 148 | bls12_381::Scalar::from_raw([ 149 | 0x194e_4292_6f66_1b51, 150 | 0x2f0c_718f_6f0f_badd, 151 | 0xb5ea_25de_7ec0_e378, 152 | 0x73c0_16a4_2ded_9578, 153 | ]), 154 | bls12_381::Scalar::from_raw([ 155 | 0x77bf_abd4_3224_3cca, 156 | 0xf947_2e8b_c04e_4632, 157 | 0x79c9_166b_837e_dc5e, 158 | 0x289e_87a2_d352_1b57, 159 | ]), 160 | ), 161 | SubgroupPoint::from_raw_unchecked( 162 | bls12_381::Scalar::from_raw([ 163 | 0xb981_9dc8_2d90_607e, 164 | 0xa361_ee3f_d48f_df77, 165 | 0x52a3_5a8c_1908_dd87, 166 | 0x15a3_6d1f_0f39_0d88, 167 | ]), 168 | bls12_381::Scalar::from_raw([ 169 | 0x7b0d_c53c_4ebf_1891, 170 | 0x1f3a_beeb_98fa_d3e8, 171 | 0xf789_1142_c001_d925, 172 | 0x015d_8c7f_5b43_fe33, 173 | ]), 174 | ), 175 | SubgroupPoint::from_raw_unchecked( 176 | bls12_381::Scalar::from_raw([ 177 | 0x76d6_f7c2_b67f_c475, 178 | 0xbae8_e5c4_6641_ae5c, 179 | 0xeb69_ae39_f5c8_4210, 180 | 0x6643_21a5_8246_e2f6, 181 | ]), 182 | bls12_381::Scalar::from_raw([ 183 | 0x80ed_502c_9793_d457, 184 | 0x8bb2_2a7f_1784_b498, 185 | 0xe000_a46c_8e8c_e853, 186 | 0x362e_1500_d24e_ee9e, 187 | ]), 188 | ), 189 | SubgroupPoint::from_raw_unchecked( 190 | bls12_381::Scalar::from_raw([ 191 | 0x4c76_7804_c1c4_a2cc, 192 | 0x7d02_d50e_654b_87f2, 193 | 0xedc5_f4a9_cff2_9fd5, 194 | 0x323a_6548_ce9d_9876, 195 | ]), 196 | bls12_381::Scalar::from_raw([ 197 | 0x8471_4bec_a335_70e9, 198 | 0x5103_afa1_a11f_6a85, 199 | 0x9107_0acb_d8d9_47b7, 200 | 0x2f7e_e40c_4b56_cad8, 201 | ]), 202 | ), 203 | SubgroupPoint::from_raw_unchecked( 204 | bls12_381::Scalar::from_raw([ 205 | 0x4680_9430_657f_82d1, 206 | 0xefd5_9313_05f2_f0bf, 207 | 0x89b6_4b4e_0336_2796, 208 | 0x3bd2_6660_00b5_4796, 209 | ]), 210 | bls12_381::Scalar::from_raw([ 211 | 0x9996_8299_c365_8aef, 212 | 0xb3b9_d809_5859_d14c, 213 | 0x3978_3238_1406_c9e5, 214 | 0x494b_c521_03ab_9d0a, 215 | ]), 216 | ), 217 | SubgroupPoint::from_raw_unchecked( 218 | bls12_381::Scalar::from_raw([ 219 | 0xcb3c_0232_58d3_2079, 220 | 0x1d9e_5ca2_1135_ff6f, 221 | 0xda04_9746_d76d_3ee5, 222 | 0x6344_7b2b_a31b_b28a, 223 | ]), 224 | bls12_381::Scalar::from_raw([ 225 | 0x4360_8211_9f8d_629a, 226 | 0xa802_00d2_c66b_13a7, 227 | 0x64cd_b107_0a13_6a28, 228 | 0x64ec_4689_e8bf_b6e5, 229 | ]), 230 | ), 231 | ]; 232 | 233 | /// The maximum number of chunks per segment of the Pedersen hash. 234 | pub const PEDERSEN_HASH_CHUNKS_PER_GENERATOR: usize = 63; 235 | 236 | /// The window size for exponentiation of Pedersen hash generators outside the circuit. 237 | pub const PEDERSEN_HASH_EXP_WINDOW_SIZE: u32 = 8; 238 | 239 | lazy_static! { 240 | /// The exp table for [`PEDERSEN_HASH_GENERATORS`]. 241 | pub static ref PEDERSEN_HASH_EXP_TABLE: Vec>> = 242 | generate_pedersen_hash_exp_table(); 243 | } 244 | 245 | /// Creates the exp table for the Pedersen hash generators. 246 | fn generate_pedersen_hash_exp_table() -> Vec>> { 247 | let window = PEDERSEN_HASH_EXP_WINDOW_SIZE; 248 | 249 | PEDERSEN_HASH_GENERATORS 250 | .iter() 251 | .cloned() 252 | .map(|mut g| { 253 | let mut tables = vec![]; 254 | 255 | let mut num_bits = 0; 256 | while num_bits <= jubjub::Fr::NUM_BITS { 257 | let mut table = Vec::with_capacity(1 << window); 258 | let mut base = SubgroupPoint::identity(); 259 | 260 | for _ in 0..(1 << window) { 261 | table.push(base); 262 | base += g; 263 | } 264 | 265 | tables.push(table); 266 | num_bits += window; 267 | 268 | for _ in 0..window { 269 | g = g.double(); 270 | } 271 | } 272 | 273 | tables 274 | }) 275 | .collect() 276 | } 277 | 278 | #[cfg(test)] 279 | mod tests { 280 | use jubjub::SubgroupPoint; 281 | 282 | use super::*; 283 | use crate::group_hash::group_hash; 284 | 285 | fn find_group_hash(m: &[u8], personalization: &[u8; 8]) -> SubgroupPoint { 286 | let mut tag = m.to_vec(); 287 | let i = tag.len(); 288 | tag.push(0u8); 289 | 290 | loop { 291 | let gh = group_hash(&tag, personalization); 292 | 293 | // We don't want to overflow and start reusing generators 294 | assert!(tag[i] != u8::MAX); 295 | tag[i] += 1; 296 | 297 | if let Some(gh) = gh { 298 | break gh; 299 | } 300 | } 301 | } 302 | 303 | #[test] 304 | fn proof_generation_key_base_generator() { 305 | assert_eq!( 306 | find_group_hash(&[], PROOF_GENERATION_KEY_BASE_GENERATOR_PERSONALIZATION), 307 | PROOF_GENERATION_KEY_GENERATOR, 308 | ); 309 | } 310 | 311 | #[test] 312 | fn note_commitment_randomness_generator() { 313 | assert_eq!( 314 | find_group_hash(b"r", PEDERSEN_HASH_GENERATORS_PERSONALIZATION), 315 | NOTE_COMMITMENT_RANDOMNESS_GENERATOR, 316 | ); 317 | } 318 | 319 | #[test] 320 | fn nullifier_position_generator() { 321 | assert_eq!( 322 | find_group_hash(&[], NULLIFIER_POSITION_IN_TREE_GENERATOR_PERSONALIZATION), 323 | NULLIFIER_POSITION_GENERATOR, 324 | ); 325 | } 326 | 327 | #[test] 328 | fn value_commitment_value_generator() { 329 | assert_eq!( 330 | find_group_hash(b"v", VALUE_COMMITMENT_GENERATOR_PERSONALIZATION), 331 | VALUE_COMMITMENT_VALUE_GENERATOR, 332 | ); 333 | } 334 | 335 | #[test] 336 | fn value_commitment_randomness_generator() { 337 | assert_eq!( 338 | find_group_hash(b"r", VALUE_COMMITMENT_GENERATOR_PERSONALIZATION), 339 | VALUE_COMMITMENT_RANDOMNESS_GENERATOR, 340 | ); 341 | } 342 | 343 | #[test] 344 | fn spending_key_generator() { 345 | assert_eq!( 346 | find_group_hash(&[], SPENDING_KEY_GENERATOR_PERSONALIZATION), 347 | SPENDING_KEY_GENERATOR, 348 | ); 349 | } 350 | 351 | #[test] 352 | fn pedersen_hash_generators() { 353 | for (m, actual) in PEDERSEN_HASH_GENERATORS.iter().enumerate() { 354 | assert_eq!( 355 | &find_group_hash( 356 | &(m as u32).to_le_bytes(), 357 | PEDERSEN_HASH_GENERATORS_PERSONALIZATION 358 | ), 359 | actual 360 | ); 361 | } 362 | } 363 | 364 | #[test] 365 | fn no_duplicate_fixed_base_generators() { 366 | let fixed_base_generators = [ 367 | PROOF_GENERATION_KEY_GENERATOR, 368 | NOTE_COMMITMENT_RANDOMNESS_GENERATOR, 369 | NULLIFIER_POSITION_GENERATOR, 370 | VALUE_COMMITMENT_VALUE_GENERATOR, 371 | VALUE_COMMITMENT_RANDOMNESS_GENERATOR, 372 | SPENDING_KEY_GENERATOR, 373 | ]; 374 | 375 | // Check for duplicates, far worse than spec inconsistencies! 376 | for (i, p1) in fixed_base_generators.iter().enumerate() { 377 | if p1.is_identity().into() { 378 | panic!("Neutral element!"); 379 | } 380 | 381 | for p2 in fixed_base_generators.iter().skip(i + 1) { 382 | if p1 == p2 { 383 | panic!("Duplicate generator!"); 384 | } 385 | } 386 | } 387 | } 388 | 389 | /// Check for simple relations between the generators, that make finding collisions easy; 390 | /// far worse than spec inconsistencies! 391 | fn check_consistency_of_pedersen_hash_generators( 392 | pedersen_hash_generators: &[jubjub::SubgroupPoint], 393 | ) { 394 | for (i, p1) in pedersen_hash_generators.iter().enumerate() { 395 | if p1.is_identity().into() { 396 | panic!("Neutral element!"); 397 | } 398 | for p2 in pedersen_hash_generators.iter().skip(i + 1) { 399 | if p1 == p2 { 400 | panic!("Duplicate generator!"); 401 | } 402 | if *p1 == -p2 { 403 | panic!("Inverse generator!"); 404 | } 405 | } 406 | 407 | // check for a generator being the sum of any other two 408 | for (j, p2) in pedersen_hash_generators.iter().enumerate() { 409 | if j == i { 410 | continue; 411 | } 412 | for (k, p3) in pedersen_hash_generators.iter().enumerate() { 413 | if k == j || k == i { 414 | continue; 415 | } 416 | let sum = p2 + p3; 417 | if sum == *p1 { 418 | panic!("Linear relation between generators!"); 419 | } 420 | } 421 | } 422 | } 423 | } 424 | 425 | #[test] 426 | fn pedersen_hash_generators_consistency() { 427 | check_consistency_of_pedersen_hash_generators(PEDERSEN_HASH_GENERATORS); 428 | } 429 | 430 | #[test] 431 | #[should_panic(expected = "Linear relation between generators!")] 432 | fn test_jubjub_bls12_pedersen_hash_generators_consistency_check_linear_relation() { 433 | let mut pedersen_hash_generators = PEDERSEN_HASH_GENERATORS.to_vec(); 434 | 435 | // Test for linear relation 436 | pedersen_hash_generators.push(PEDERSEN_HASH_GENERATORS[0] + PEDERSEN_HASH_GENERATORS[1]); 437 | 438 | check_consistency_of_pedersen_hash_generators(&pedersen_hash_generators); 439 | } 440 | } 441 | -------------------------------------------------------------------------------- /src/group_hash.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of [group hashing into Jubjub][grouphash]. 2 | //! 3 | //! [grouphash]: https://zips.z.cash/protocol/protocol.pdf#concretegrouphashjubjub 4 | 5 | use ff::PrimeField; 6 | use group::{cofactor::CofactorGroup, Group, GroupEncoding}; 7 | 8 | use super::constants; 9 | use blake2s_simd::Params; 10 | 11 | /// Produces a random point in the Jubjub curve. 12 | /// The point is guaranteed to be prime order 13 | /// and not the identity. 14 | #[allow(clippy::assertions_on_constants)] 15 | pub fn group_hash(tag: &[u8], personalization: &[u8]) -> Option { 16 | assert_eq!(personalization.len(), 8); 17 | 18 | // Check to see that scalar field is 255 bits 19 | assert!(bls12_381::Scalar::NUM_BITS == 255); 20 | 21 | let h = Params::new() 22 | .hash_length(32) 23 | .personal(personalization) 24 | .to_state() 25 | .update(constants::GH_FIRST_BLOCK) 26 | .update(tag) 27 | .finalize(); 28 | 29 | let p = jubjub::ExtendedPoint::from_bytes(h.as_array()); 30 | if p.is_some().into() { 31 | // ::clear_cofactor is implemented using 32 | // ExtendedPoint::mul_by_cofactor in the jubjub crate. 33 | let p = CofactorGroup::clear_cofactor(&p.unwrap()); 34 | 35 | if p.is_identity().into() { 36 | None 37 | } else { 38 | Some(p) 39 | } 40 | } else { 41 | None 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # sapling 2 | //! 3 | //! ## Nomenclature 4 | //! 5 | //! All types in the `sapling-crypto` crate, unless otherwise specified, are 6 | //! Sapling-specific types. For example, [`PaymentAddress`] is documented as being a 7 | //! shielded payment address; we implicitly mean it is an Sapling payment address (as 8 | //! opposed to e.g. an Orchard payment address, which is also shielded). 9 | //! 10 | #![cfg_attr(feature = "std", doc = "## Feature flags")] 11 | #![cfg_attr(feature = "std", doc = document_features::document_features!())] 12 | //! 13 | 14 | #![no_std] 15 | #![cfg_attr(docsrs, feature(doc_cfg))] 16 | // Catch documentation errors caused by code changes. 17 | #![deny(rustdoc::broken_intra_doc_links)] 18 | #![deny(unsafe_code)] 19 | 20 | #[macro_use] 21 | extern crate alloc; 22 | 23 | #[cfg(feature = "std")] 24 | extern crate std; 25 | 26 | mod address; 27 | pub mod builder; 28 | pub mod bundle; 29 | 30 | #[cfg(feature = "circuit")] 31 | pub mod circuit; 32 | pub mod constants; 33 | pub mod group_hash; 34 | pub mod keys; 35 | pub mod note; 36 | pub mod note_encryption; 37 | pub mod pczt; 38 | pub mod pedersen_hash; 39 | #[cfg(feature = "circuit")] 40 | pub mod prover; 41 | mod spec; 42 | mod tree; 43 | pub mod util; 44 | pub mod value; 45 | #[cfg(feature = "circuit")] 46 | mod verifier; 47 | pub mod zip32; 48 | 49 | pub use address::PaymentAddress; 50 | pub use bundle::Bundle; 51 | pub use keys::{Diversifier, NullifierDerivingKey, ProofGenerationKey, SaplingIvk, ViewingKey}; 52 | pub use note::{nullifier::Nullifier, Note, Rseed}; 53 | pub use tree::{ 54 | merkle_hash, Anchor, CommitmentTree, IncrementalWitness, MerklePath, Node, 55 | NOTE_COMMITMENT_TREE_DEPTH, 56 | }; 57 | 58 | #[cfg(feature = "circuit")] 59 | pub use verifier::{BatchValidator, SaplingVerificationContext}; 60 | 61 | #[cfg(any(test, feature = "test-dependencies"))] 62 | #[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))] 63 | pub mod testing { 64 | pub use super::{ 65 | address::testing::arb_payment_address, keys::testing::arb_incoming_viewing_key, 66 | note::testing::arb_note, tree::testing::arb_node, 67 | }; 68 | } 69 | 70 | #[cfg(test)] 71 | mod test_vectors; 72 | -------------------------------------------------------------------------------- /src/note.rs: -------------------------------------------------------------------------------- 1 | use group::{ff::Field, GroupEncoding}; 2 | use rand_core::{CryptoRng, RngCore}; 3 | use zcash_spec::PrfExpand; 4 | 5 | use crate::{ 6 | keys::{ExpandedSpendingKey, FullViewingKey}, 7 | zip32::ExtendedSpendingKey, 8 | }; 9 | 10 | use super::{ 11 | keys::EphemeralSecretKey, value::NoteValue, Nullifier, NullifierDerivingKey, PaymentAddress, 12 | }; 13 | 14 | mod commitment; 15 | pub use self::commitment::{ExtractedNoteCommitment, NoteCommitment}; 16 | 17 | pub(super) mod nullifier; 18 | 19 | /// Enum for note randomness before and after [ZIP 212](https://zips.z.cash/zip-0212). 20 | /// 21 | /// Before ZIP 212, the note commitment trapdoor `rcm` must be a scalar value. 22 | /// After ZIP 212, the note randomness `rseed` is a 32-byte sequence, used to derive 23 | /// both the note commitment trapdoor `rcm` and the ephemeral private key `esk`. 24 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 25 | pub enum Rseed { 26 | BeforeZip212(jubjub::Fr), 27 | AfterZip212([u8; 32]), 28 | } 29 | 30 | impl Rseed { 31 | /// Defined in [Zcash Protocol Spec § 4.7.2: Sending Notes (Sapling)][saplingsend]. 32 | /// 33 | /// [saplingsend]: https://zips.z.cash/protocol/protocol.pdf#saplingsend 34 | pub(crate) fn rcm(&self) -> commitment::NoteCommitTrapdoor { 35 | commitment::NoteCommitTrapdoor(match self { 36 | Rseed::BeforeZip212(rcm) => *rcm, 37 | Rseed::AfterZip212(rseed) => { 38 | jubjub::Fr::from_bytes_wide(&PrfExpand::SAPLING_RCM.with(rseed)) 39 | } 40 | }) 41 | } 42 | } 43 | 44 | /// A discrete amount of funds received by an address. 45 | #[derive(Clone, Debug)] 46 | pub struct Note { 47 | /// The recipient of the funds. 48 | recipient: PaymentAddress, 49 | /// The value of this note. 50 | value: NoteValue, 51 | /// The seed randomness for various note components. 52 | rseed: Rseed, 53 | } 54 | 55 | impl PartialEq for Note { 56 | fn eq(&self, other: &Self) -> bool { 57 | // Notes are canonically defined by their commitments. 58 | self.cmu().eq(&other.cmu()) 59 | } 60 | } 61 | 62 | impl Eq for Note {} 63 | 64 | impl Note { 65 | /// Creates a note from its component parts. 66 | /// 67 | /// # Caveats 68 | /// 69 | /// This low-level constructor enforces that the provided arguments produce an 70 | /// internally valid `Note`. However, it allows notes to be constructed in a way that 71 | /// violates required security checks for note decryption, as specified in 72 | /// [Section 4.19] of the Zcash Protocol Specification. Users of this constructor 73 | /// should only call it with note components that have been fully validated by 74 | /// decrypting a received note according to [Section 4.19]. 75 | /// 76 | /// [Section 4.19]: https://zips.z.cash/protocol/protocol.pdf#saplingandorchardinband 77 | pub fn from_parts(recipient: PaymentAddress, value: NoteValue, rseed: Rseed) -> Self { 78 | Note { 79 | recipient, 80 | value, 81 | rseed, 82 | } 83 | } 84 | 85 | /// Returns the recipient of this note. 86 | pub fn recipient(&self) -> PaymentAddress { 87 | self.recipient 88 | } 89 | 90 | /// Returns the value of this note. 91 | pub fn value(&self) -> NoteValue { 92 | self.value 93 | } 94 | 95 | /// Returns the rseed value of this note. 96 | pub fn rseed(&self) -> &Rseed { 97 | &self.rseed 98 | } 99 | 100 | /// Computes the note commitment, returning the full point. 101 | fn cm_full_point(&self) -> NoteCommitment { 102 | NoteCommitment::derive( 103 | self.recipient.g_d().to_bytes(), 104 | self.recipient.pk_d().to_bytes(), 105 | self.value, 106 | self.rseed.rcm(), 107 | ) 108 | } 109 | 110 | /// Computes the nullifier given the nullifier deriving key and 111 | /// note position 112 | pub fn nf(&self, nk: &NullifierDerivingKey, position: u64) -> Nullifier { 113 | Nullifier::derive(nk, self.cm_full_point(), position) 114 | } 115 | 116 | /// Computes the note commitment 117 | pub fn cmu(&self) -> ExtractedNoteCommitment { 118 | self.cm_full_point().into() 119 | } 120 | 121 | /// Defined in [Zcash Protocol Spec § 4.7.2: Sending Notes (Sapling)][saplingsend]. 122 | /// 123 | /// [saplingsend]: https://zips.z.cash/protocol/protocol.pdf#saplingsend 124 | pub fn rcm(&self) -> jubjub::Fr { 125 | self.rseed.rcm().0 126 | } 127 | 128 | /// Derives `esk` from the internal `Rseed` value, or generates a random value if this 129 | /// note was created with a v1 (i.e. pre-ZIP 212) note plaintext. 130 | pub fn generate_or_derive_esk( 131 | &self, 132 | rng: &mut R, 133 | ) -> EphemeralSecretKey { 134 | self.generate_or_derive_esk_internal(rng) 135 | } 136 | 137 | pub(crate) fn generate_or_derive_esk_internal( 138 | &self, 139 | rng: &mut R, 140 | ) -> EphemeralSecretKey { 141 | match self.derive_esk() { 142 | None => EphemeralSecretKey(jubjub::Fr::random(rng)), 143 | Some(esk) => esk, 144 | } 145 | } 146 | 147 | /// Returns the derived `esk` if this note was created after ZIP 212 activated. 148 | pub(crate) fn derive_esk(&self) -> Option { 149 | match self.rseed { 150 | Rseed::BeforeZip212(_) => None, 151 | Rseed::AfterZip212(rseed) => Some(EphemeralSecretKey(jubjub::Fr::from_bytes_wide( 152 | &PrfExpand::SAPLING_ESK.with(&rseed), 153 | ))), 154 | } 155 | } 156 | 157 | /// Generates a dummy spent note. 158 | /// 159 | /// Defined in [Zcash Protocol Spec § 4.8.2: Dummy Notes (Sapling)][saplingdummynotes]. 160 | /// 161 | /// [saplingdummynotes]: https://zips.z.cash/protocol/nu5.pdf#saplingdummynotes 162 | pub(crate) fn dummy(mut rng: R) -> (ExpandedSpendingKey, FullViewingKey, Self) { 163 | let mut sk_bytes = [0; 32]; 164 | rng.fill_bytes(&mut sk_bytes); 165 | 166 | let extsk = ExtendedSpendingKey::master(&sk_bytes[..]); 167 | let fvk = extsk.to_diversifiable_full_viewing_key().fvk().clone(); 168 | let recipient = extsk.default_address(); 169 | 170 | let mut rseed_bytes = [0; 32]; 171 | rng.fill_bytes(&mut rseed_bytes); 172 | let rseed = Rseed::AfterZip212(rseed_bytes); 173 | 174 | let note = Note::from_parts(recipient.1, NoteValue::ZERO, rseed); 175 | 176 | (extsk.expsk, fvk, note) 177 | } 178 | } 179 | 180 | #[cfg(any(test, feature = "test-dependencies"))] 181 | pub(super) mod testing { 182 | use proptest::{collection::vec, prelude::*}; 183 | 184 | use super::{ 185 | super::{testing::arb_payment_address, value::NoteValue}, 186 | ExtractedNoteCommitment, Note, Rseed, 187 | }; 188 | 189 | prop_compose! { 190 | pub fn arb_note(value: NoteValue)( 191 | recipient in arb_payment_address(), 192 | rseed in prop::array::uniform32(prop::num::u8::ANY).prop_map(Rseed::AfterZip212) 193 | ) -> Note { 194 | Note { 195 | recipient, 196 | value, 197 | rseed 198 | } 199 | } 200 | } 201 | 202 | prop_compose! { 203 | pub(crate) fn arb_cmu()( 204 | cmu in vec(any::(), 64) 205 | .prop_map(|v| <[u8;64]>::try_from(v.as_slice()).unwrap()) 206 | .prop_map(|v| bls12_381::Scalar::from_bytes_wide(&v)), 207 | ) -> ExtractedNoteCommitment { 208 | ExtractedNoteCommitment(cmu) 209 | } 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /src/note/commitment.rs: -------------------------------------------------------------------------------- 1 | use core::iter; 2 | 3 | use bitvec::{array::BitArray, order::Lsb0}; 4 | use group::ff::PrimeField; 5 | use subtle::{ConstantTimeEq, CtOption}; 6 | 7 | use crate::{ 8 | pedersen_hash::Personalization, 9 | spec::{extract_p, windowed_pedersen_commit}, 10 | value::NoteValue, 11 | }; 12 | 13 | /// The trapdoor for a Sapling note commitment. 14 | #[derive(Clone, Debug)] 15 | pub(crate) struct NoteCommitTrapdoor(pub(super) jubjub::Fr); 16 | 17 | /// A commitment to a note. 18 | #[derive(Clone, Debug)] 19 | pub struct NoteCommitment(jubjub::SubgroupPoint); 20 | 21 | impl NoteCommitment { 22 | pub(crate) fn inner(&self) -> jubjub::SubgroupPoint { 23 | self.0 24 | } 25 | } 26 | 27 | impl NoteCommitment { 28 | /// Derives a Sapling note commitment. 29 | #[cfg(feature = "temporary-zcashd")] 30 | pub fn temporary_zcashd_derive( 31 | g_d: [u8; 32], 32 | pk_d: [u8; 32], 33 | v: NoteValue, 34 | rcm: jubjub::Fr, 35 | ) -> Self { 36 | Self::derive(g_d, pk_d, v, NoteCommitTrapdoor(rcm)) 37 | } 38 | 39 | /// $NoteCommit^Sapling$. 40 | /// 41 | /// Defined in [Zcash Protocol Spec § 5.4.8.2: Windowed Pedersen commitments][concretewindowedcommit]. 42 | /// 43 | /// [concretewindowedcommit]: https://zips.z.cash/protocol/protocol.pdf#concretewindowedcommit 44 | pub(super) fn derive( 45 | g_d: [u8; 32], 46 | pk_d: [u8; 32], 47 | v: NoteValue, 48 | rcm: NoteCommitTrapdoor, 49 | ) -> Self { 50 | NoteCommitment(windowed_pedersen_commit( 51 | Personalization::NoteCommitment, 52 | iter::empty() 53 | .chain(v.to_le_bits().iter().by_vals()) 54 | .chain(BitArray::<_, Lsb0>::new(g_d).iter().by_vals()) 55 | .chain(BitArray::<_, Lsb0>::new(pk_d).iter().by_vals()), 56 | rcm.0, 57 | )) 58 | } 59 | } 60 | 61 | /// The u-coordinate of the commitment to a note. 62 | #[derive(Copy, Clone, Debug)] 63 | pub struct ExtractedNoteCommitment(pub(super) bls12_381::Scalar); 64 | 65 | impl ExtractedNoteCommitment { 66 | /// Deserialize the extracted note commitment from a byte array. 67 | /// 68 | /// This method enforces the [consensus rule][cmucanon] that the byte representation 69 | /// of cmu MUST be canonical. 70 | /// 71 | /// [cmucanon]: https://zips.z.cash/protocol/protocol.pdf#outputencodingandconsensus 72 | pub fn from_bytes(bytes: &[u8; 32]) -> CtOption { 73 | bls12_381::Scalar::from_repr(*bytes).map(ExtractedNoteCommitment) 74 | } 75 | 76 | /// Serialize the value commitment to its canonical byte representation. 77 | pub fn to_bytes(self) -> [u8; 32] { 78 | self.0.to_repr() 79 | } 80 | 81 | pub(crate) fn inner(&self) -> jubjub::Base { 82 | self.0 83 | } 84 | } 85 | 86 | impl From for ExtractedNoteCommitment { 87 | fn from(cm: NoteCommitment) -> Self { 88 | ExtractedNoteCommitment(extract_p(&cm.0)) 89 | } 90 | } 91 | 92 | impl From<&ExtractedNoteCommitment> for [u8; 32] { 93 | fn from(cmu: &ExtractedNoteCommitment) -> Self { 94 | cmu.to_bytes() 95 | } 96 | } 97 | 98 | impl ConstantTimeEq for ExtractedNoteCommitment { 99 | fn ct_eq(&self, other: &Self) -> subtle::Choice { 100 | self.0.ct_eq(&other.0) 101 | } 102 | } 103 | 104 | impl PartialEq for ExtractedNoteCommitment { 105 | fn eq(&self, other: &Self) -> bool { 106 | self.ct_eq(other).into() 107 | } 108 | } 109 | 110 | impl Eq for ExtractedNoteCommitment {} 111 | -------------------------------------------------------------------------------- /src/note/nullifier.rs: -------------------------------------------------------------------------------- 1 | use alloc::fmt; 2 | use alloc::vec::Vec; 3 | use core::array::TryFromSliceError; 4 | 5 | use subtle::{Choice, ConstantTimeEq}; 6 | 7 | use super::NoteCommitment; 8 | use crate::{ 9 | keys::NullifierDerivingKey, 10 | spec::{mixing_pedersen_hash, prf_nf}, 11 | }; 12 | 13 | /// Typesafe wrapper for nullifier values. 14 | #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] 15 | pub struct Nullifier(pub [u8; 32]); 16 | 17 | impl fmt::Debug for Nullifier { 18 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 19 | f.debug_tuple("Nullifier") 20 | .field(&hex::encode(self.0)) 21 | .finish() 22 | } 23 | } 24 | 25 | impl Nullifier { 26 | pub fn from_slice(bytes: &[u8]) -> Result { 27 | bytes.try_into().map(Nullifier) 28 | } 29 | 30 | pub fn to_vec(&self) -> Vec { 31 | self.0.to_vec() 32 | } 33 | 34 | /// $DeriveNullifier$. 35 | /// 36 | /// Defined in [Zcash Protocol Spec § 4.16: Note Commitments and Nullifiers][commitmentsandnullifiers]. 37 | /// 38 | /// [commitmentsandnullifiers]: https://zips.z.cash/protocol/protocol.pdf#commitmentsandnullifiers 39 | pub(super) fn derive(nk: &NullifierDerivingKey, cm: NoteCommitment, position: u64) -> Self { 40 | let rho = mixing_pedersen_hash(cm.inner(), position); 41 | Nullifier(prf_nf(&nk.0, &rho)) 42 | } 43 | } 44 | 45 | impl AsRef<[u8]> for Nullifier { 46 | fn as_ref(&self) -> &[u8] { 47 | &self.0 48 | } 49 | } 50 | 51 | impl ConstantTimeEq for Nullifier { 52 | fn ct_eq(&self, other: &Self) -> Choice { 53 | self.0.ct_eq(&other.0) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/pczt.rs: -------------------------------------------------------------------------------- 1 | //! PCZT support for Sapling. 2 | 3 | use alloc::collections::BTreeMap; 4 | use alloc::string::String; 5 | use alloc::vec::Vec; 6 | use core::fmt; 7 | 8 | use getset::Getters; 9 | use redjubjub::{Binding, SpendAuth}; 10 | use zcash_note_encryption::{ 11 | EphemeralKeyBytes, OutgoingCipherKey, ENC_CIPHERTEXT_SIZE, OUT_CIPHERTEXT_SIZE, 12 | }; 13 | use zip32::ChildIndex; 14 | 15 | use crate::{ 16 | bundle::GrothProofBytes, 17 | keys::SpendAuthorizingKey, 18 | note::ExtractedNoteCommitment, 19 | value::{NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum}, 20 | Anchor, MerklePath, Nullifier, PaymentAddress, ProofGenerationKey, Rseed, 21 | }; 22 | 23 | mod parse; 24 | pub use parse::ParseError; 25 | 26 | mod verify; 27 | pub use verify::VerifyError; 28 | 29 | mod io_finalizer; 30 | pub use io_finalizer::IoFinalizerError; 31 | 32 | mod updater; 33 | pub use updater::{OutputUpdater, SpendUpdater, Updater, UpdaterError}; 34 | 35 | #[cfg(feature = "circuit")] 36 | mod prover; 37 | #[cfg(feature = "circuit")] 38 | pub use prover::ProverError; 39 | 40 | mod signer; 41 | pub use signer::SignerError; 42 | 43 | mod tx_extractor; 44 | pub use tx_extractor::{TxExtractorError, Unbound}; 45 | 46 | /// PCZT fields that are specific to producing the transaction's Sapling bundle (if any). 47 | /// 48 | /// This struct is for representing Sapling in a partially-created transaction. If you 49 | /// have a fully-created transaction, use [the regular `Bundle` struct]. 50 | /// 51 | /// [the regular `Bundle` struct]: crate::Bundle 52 | #[derive(Debug, Getters)] 53 | #[getset(get = "pub")] 54 | pub struct Bundle { 55 | /// The Sapling spends in this bundle. 56 | /// 57 | /// Entries are added by the Constructor, and modified by an Updater, IO Finalizer, 58 | /// Prover, Signer, Combiner, or Spend Finalizer. 59 | pub(crate) spends: Vec, 60 | 61 | /// The Sapling outputs in this bundle. 62 | /// 63 | /// Entries are added by the Constructor, and modified by an Updater, IO Finalizer, 64 | /// Prover, Signer, Combiner, or Spend Finalizer. 65 | pub(crate) outputs: Vec, 66 | 67 | /// The net value of Sapling `spends` minus `outputs`. 68 | /// 69 | /// This is initialized by the Creator, and updated by the Constructor as spends or 70 | /// outputs are added to the PCZT. It enables per-spend and per-output values to be 71 | /// redacted from the PCZT after they are no longer necessary. 72 | pub(crate) value_sum: ValueSum, 73 | 74 | /// The Sapling anchor for this transaction. 75 | /// 76 | /// Set by the Creator. 77 | pub(crate) anchor: Anchor, 78 | 79 | /// The Sapling binding signature signing key. 80 | /// 81 | /// - This is `None` until it is set by the IO Finalizer. 82 | /// - The Transaction Extractor uses this to produce the binding signature. 83 | pub(crate) bsk: Option>, 84 | } 85 | 86 | impl Bundle { 87 | /// Returns a mutable reference to the spends in this bundle. 88 | /// 89 | /// This is used by Signers to apply signatures with [`Spend::sign`]. 90 | pub fn spends_mut(&mut self) -> &mut [Spend] { 91 | &mut self.spends 92 | } 93 | } 94 | 95 | /// Information about a Sapling spend within a transaction. 96 | /// 97 | /// This struct is for representing Sapling spends in a partially-created transaction. If 98 | /// you have a fully-created transaction, use [the regular `SpendDescription` struct]. 99 | /// 100 | /// [the regular `SpendDescription` struct]: crate::bundle::SpendDescription 101 | #[derive(Debug, Getters)] 102 | #[getset(get = "pub")] 103 | pub struct Spend { 104 | /// A commitment to the value consumed by this spend. 105 | pub(crate) cv: ValueCommitment, 106 | 107 | /// The nullifier of the note being spent. 108 | pub(crate) nullifier: Nullifier, 109 | 110 | /// The randomized verification key for the note being spent. 111 | pub(crate) rk: redjubjub::VerificationKey, 112 | 113 | /// The Spend proof. 114 | /// 115 | /// This is set by the Prover. 116 | pub(crate) zkproof: Option, 117 | 118 | /// The spend authorization signature. 119 | /// 120 | /// This is set by the Signer. 121 | pub(crate) spend_auth_sig: Option>, 122 | 123 | /// The address that received the note being spent. 124 | /// 125 | /// - This is set by the Constructor (or Updater?). 126 | /// - This is required by the Prover. 127 | pub(crate) recipient: Option, 128 | 129 | /// The value of the input being spent. 130 | /// 131 | /// This may be used by Signers to verify that the value matches `cv`, and to confirm 132 | /// the values and change involved in the transaction. 133 | /// 134 | /// This exposes the input value to all participants. For Signers who don't need this 135 | /// information, or after signatures have been applied, this can be redacted. 136 | pub(crate) value: Option, 137 | 138 | /// The seed randomness for the note being spent. 139 | /// 140 | /// - This is set by the Constructor. 141 | /// - This is required by the Prover. 142 | pub(crate) rseed: Option, 143 | 144 | /// The value commitment randomness. 145 | /// 146 | /// - This is set by the Constructor. 147 | /// - The IO Finalizer compresses it into the `bsk`. 148 | /// - This is required by the Prover. 149 | /// - This may be used by Signers to verify that the value correctly matches `cv`. 150 | /// 151 | /// This opens `cv` for all participants. For Signers who don't need this information, 152 | /// or after proofs / signatures have been applied, this can be redacted. 153 | pub(crate) rcv: Option, 154 | 155 | /// The proof generation key `(ak, nsk)` corresponding to the recipient that received 156 | /// the note being spent. 157 | /// 158 | /// - This is set by the Updater. 159 | /// - This is required by the Prover. 160 | pub(crate) proof_generation_key: Option, 161 | 162 | /// A witness from the note to the bundle's anchor. 163 | /// 164 | /// - This is set by the Updater. 165 | /// - This is required by the Prover. 166 | pub(crate) witness: Option, 167 | 168 | /// The spend authorization randomizer. 169 | /// 170 | /// - This is chosen by the Constructor. 171 | /// - This is required by the Signer for creating `spend_auth_sig`, and may be used to 172 | /// validate `rk`. 173 | /// - After`zkproof` / `spend_auth_sig` has been set, this can be redacted. 174 | pub(crate) alpha: Option, 175 | 176 | /// The ZIP 32 derivation path at which the spending key can be found for the note 177 | /// being spent. 178 | pub(crate) zip32_derivation: Option, 179 | 180 | /// The spend authorizing key for this spent note, if it is a dummy note. 181 | /// 182 | /// - This is chosen by the Constructor. 183 | /// - This is required by the IO Finalizer, and is cleared by it once used. 184 | /// - Signers MUST reject PCZTs that contain `dummy_ask` values. 185 | pub(crate) dummy_ask: Option, 186 | 187 | /// Proprietary fields related to the note being spent. 188 | pub(crate) proprietary: BTreeMap>, 189 | } 190 | 191 | /// Information about a Sapling output within a transaction. 192 | /// 193 | /// This struct is for representing Sapling outputs in a partially-created transaction. If 194 | /// you have a fully-created transaction, use [the regular `OutputDescription` struct]. 195 | /// 196 | /// [the regular `OutputDescription` struct]: crate::bundle::OutputDescription 197 | #[derive(Getters)] 198 | #[getset(get = "pub")] 199 | pub struct Output { 200 | /// A commitment to the value created by this output. 201 | pub(crate) cv: ValueCommitment, 202 | 203 | /// A commitment to the new note being created. 204 | pub(crate) cmu: ExtractedNoteCommitment, 205 | 206 | /// The ephemeral key used to encrypt the note plaintext. 207 | pub(crate) ephemeral_key: EphemeralKeyBytes, 208 | 209 | /// The encrypted note plaintext for the output. 210 | /// 211 | /// Once we have memo bundles, we will be able to set memos independently of Outputs. 212 | /// For now, the Constructor sets both at the same time. 213 | pub(crate) enc_ciphertext: [u8; ENC_CIPHERTEXT_SIZE], 214 | 215 | /// The encrypted output plaintext for the output. 216 | pub(crate) out_ciphertext: [u8; OUT_CIPHERTEXT_SIZE], 217 | 218 | /// The Output proof. 219 | /// 220 | /// This is set by the Prover. 221 | pub(crate) zkproof: Option, 222 | 223 | /// The address that will receive the output. 224 | /// 225 | /// - This is set by the Constructor. 226 | /// - This is required by the Prover. 227 | /// - The Signer can use `recipient` and `rseed` (if present) to verify that 228 | /// `enc_ciphertext` is correctly encrypted (and contains a note plaintext matching 229 | /// the public commitments), and to confirm the value of the memo. 230 | pub(crate) recipient: Option, 231 | 232 | /// The value of the output. 233 | /// 234 | /// This may be used by Signers to verify that the value matches `cv`, and to confirm 235 | /// the values and change involved in the transaction. 236 | /// 237 | /// This exposes the output value to all participants. For Signers who don't need this 238 | /// information, or after signatures have been applied, this can be redacted. 239 | pub(crate) value: Option, 240 | 241 | /// The seed randomness for the output. 242 | /// 243 | /// - This is set by the Constructor. 244 | /// - This is required by the Prover. 245 | /// - The Signer can use `recipient` and `rseed` (if present) to verify that 246 | /// `enc_ciphertext` is correctly encrypted (and contains a note plaintext matching 247 | /// the public commitments), and to confirm the value of the memo. 248 | pub(crate) rseed: Option<[u8; 32]>, 249 | 250 | /// The value commitment randomness. 251 | /// 252 | /// - This is set by the Constructor. 253 | /// - The IO Finalizer compresses it into the bsk. 254 | /// - This is required by the Prover. 255 | /// - This may be used by Signers to verify that the value correctly matches `cv`. 256 | /// 257 | /// This opens `cv` for all participants. For Signers who don't need this information, 258 | /// or after proofs / signatures have been applied, this can be redacted. 259 | pub(crate) rcv: Option, 260 | 261 | /// The `ock` value used to encrypt `out_ciphertext`. 262 | /// 263 | /// This enables Signers to verify that `out_ciphertext` is correctly encrypted. 264 | /// 265 | /// This may be `None` if the Constructor added the output using an OVK policy of 266 | /// "None", to make the output unrecoverable from the chain by the sender. 267 | pub(crate) ock: Option, 268 | 269 | /// The ZIP 32 derivation path at which the spending key can be found for the output. 270 | pub(crate) zip32_derivation: Option, 271 | 272 | /// The user-facing address to which this output is being sent, if any. 273 | /// 274 | /// - This is set by an Updater. 275 | /// - Signers must parse this address (if present) and confirm that it contains 276 | /// `recipient` (either directly, or e.g. as a receiver within a Unified Address). 277 | pub(crate) user_address: Option, 278 | 279 | /// Proprietary fields related to the note being created. 280 | pub(crate) proprietary: BTreeMap>, 281 | } 282 | 283 | impl fmt::Debug for Output { 284 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 285 | f.debug_struct("Output") 286 | .field("cv", &self.cv) 287 | .field("cmu", &self.cmu) 288 | .field("ephemeral_key", &self.ephemeral_key) 289 | .field("enc_ciphertext", &self.enc_ciphertext) 290 | .field("out_ciphertext", &self.out_ciphertext) 291 | .field("zkproof", &self.zkproof) 292 | .field("recipient", &self.recipient) 293 | .field("value", &self.value) 294 | .field("rseed", &self.rseed) 295 | .field("rcv", &self.rcv) 296 | .field("zip32_derivation", &self.zip32_derivation) 297 | .field("user_address", &self.user_address) 298 | .field("proprietary", &self.proprietary) 299 | .finish_non_exhaustive() 300 | } 301 | } 302 | 303 | /// The ZIP 32 derivation path at which a key can be found. 304 | #[derive(Debug, Getters, PartialEq, Eq)] 305 | #[getset(get = "pub")] 306 | pub struct Zip32Derivation { 307 | /// The [ZIP 32 seed fingerprint](https://zips.z.cash/zip-0032#seed-fingerprints). 308 | seed_fingerprint: [u8; 32], 309 | 310 | /// The sequence of indices corresponding to the shielded HD path. 311 | derivation_path: Vec, 312 | } 313 | 314 | impl Zip32Derivation { 315 | /// Extracts the ZIP 32 account index from this derivation path. 316 | /// 317 | /// Returns `None` if the seed fingerprints don't match, or if this is a non-standard 318 | /// derivation path. 319 | pub fn extract_account_index( 320 | &self, 321 | seed_fp: &zip32::fingerprint::SeedFingerprint, 322 | expected_coin_type: zip32::ChildIndex, 323 | ) -> Option { 324 | if self.seed_fingerprint == seed_fp.to_bytes() { 325 | match &self.derivation_path[..] { 326 | [purpose, coin_type, account_index] 327 | if purpose == &zip32::ChildIndex::hardened(32) 328 | && coin_type == &expected_coin_type => 329 | { 330 | Some( 331 | zip32::AccountId::try_from(account_index.index() - (1 << 31)) 332 | .expect("zip32::ChildIndex only supports hardened"), 333 | ) 334 | } 335 | _ => None, 336 | } 337 | } else { 338 | None 339 | } 340 | } 341 | } 342 | -------------------------------------------------------------------------------- /src/pczt/io_finalizer.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use rand::{CryptoRng, RngCore}; 3 | 4 | use crate::value::{CommitmentSum, TrapdoorSum}; 5 | 6 | use super::SignerError; 7 | 8 | impl super::Bundle { 9 | /// Finalizes the IO for this bundle. 10 | pub fn finalize_io( 11 | &mut self, 12 | sighash: [u8; 32], 13 | mut rng: R, 14 | ) -> Result<(), IoFinalizerError> { 15 | // Compute the transaction binding signing key. 16 | let bsk = { 17 | let spend_rcvs = self 18 | .spends 19 | .iter() 20 | .map(|spend| { 21 | spend 22 | .rcv 23 | .as_ref() 24 | .ok_or(IoFinalizerError::MissingValueCommitTrapdoor) 25 | }) 26 | .collect::, _>>()?; 27 | 28 | let output_rcvs = self 29 | .outputs 30 | .iter() 31 | .map(|output| { 32 | output 33 | .rcv 34 | .as_ref() 35 | .ok_or(IoFinalizerError::MissingValueCommitTrapdoor) 36 | }) 37 | .collect::, _>>()?; 38 | 39 | let spends: TrapdoorSum = spend_rcvs.into_iter().sum(); 40 | let outputs: TrapdoorSum = output_rcvs.into_iter().sum(); 41 | (spends - outputs).into_bsk() 42 | }; 43 | 44 | // Verify that bsk and bvk are consistent. 45 | let bvk = { 46 | let spends = self 47 | .spends 48 | .iter() 49 | .map(|spend| spend.cv()) 50 | .sum::(); 51 | let outputs = self 52 | .outputs 53 | .iter() 54 | .map(|output| output.cv()) 55 | .sum::(); 56 | (spends - outputs).into_bvk( 57 | i64::try_from(self.value_sum).map_err(|_| IoFinalizerError::InvalidValueSum)?, 58 | ) 59 | }; 60 | if redjubjub::VerificationKey::from(&bsk) != bvk { 61 | return Err(IoFinalizerError::ValueCommitMismatch); 62 | } 63 | self.bsk = Some(bsk); 64 | 65 | // Add signatures to dummy spends. 66 | for spend in self.spends.iter_mut() { 67 | // The `Option::take` ensures we don't have any spend authorizing keys in the 68 | // PCZT after the IO Finalizer has run. 69 | if let Some(ask) = spend.dummy_ask.take() { 70 | spend 71 | .sign(sighash, &ask, &mut rng) 72 | .map_err(IoFinalizerError::DummySignature)?; 73 | } 74 | } 75 | 76 | Ok(()) 77 | } 78 | } 79 | 80 | /// Errors that can occur while finalizing the I/O for a PCZT bundle. 81 | #[derive(Debug)] 82 | pub enum IoFinalizerError { 83 | /// An error occurred while signing a dummy spend. 84 | DummySignature(SignerError), 85 | /// The `value_sum` is too large for the `value_balance` field. 86 | InvalidValueSum, 87 | /// The IO Finalizer role requires all `rcv` fields to be set. 88 | MissingValueCommitTrapdoor, 89 | /// The `cv_net`, `rcv`, and `value_sum` values within the Orchard bundle are 90 | /// inconsistent. 91 | ValueCommitMismatch, 92 | } 93 | -------------------------------------------------------------------------------- /src/pczt/parse.rs: -------------------------------------------------------------------------------- 1 | use alloc::collections::BTreeMap; 2 | use alloc::string::String; 3 | use alloc::vec::Vec; 4 | 5 | use ff::PrimeField; 6 | use zcash_note_encryption::{EphemeralKeyBytes, OutgoingCipherKey}; 7 | use zip32::ChildIndex; 8 | 9 | use super::{Bundle, Output, Spend, Zip32Derivation}; 10 | use crate::{ 11 | bundle::GrothProofBytes, 12 | keys::{SpendAuthorizingKey, SpendValidatingKey}, 13 | note::ExtractedNoteCommitment, 14 | value::{NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum}, 15 | Anchor, MerklePath, Node, Nullifier, PaymentAddress, ProofGenerationKey, Rseed, 16 | }; 17 | 18 | impl Bundle { 19 | /// Parses a PCZT bundle from its component parts. 20 | pub fn parse( 21 | spends: Vec, 22 | outputs: Vec, 23 | value_sum: i128, 24 | anchor: [u8; 32], 25 | bsk: Option<[u8; 32]>, 26 | ) -> Result { 27 | let value_sum = ValueSum::from_raw(value_sum); 28 | 29 | let anchor = Anchor::from_bytes(anchor) 30 | .into_option() 31 | .ok_or(ParseError::InvalidAnchor)?; 32 | 33 | let bsk = bsk 34 | .map(redjubjub::SigningKey::try_from) 35 | .transpose() 36 | .map_err(|_| ParseError::InvalidBindingSignatureSigningKey)?; 37 | 38 | Ok(Self { 39 | spends, 40 | outputs, 41 | value_sum, 42 | anchor, 43 | bsk, 44 | }) 45 | } 46 | } 47 | 48 | impl Spend { 49 | /// Parses a PCZT spend from its component parts. 50 | #[allow(clippy::too_many_arguments)] 51 | pub fn parse( 52 | cv: [u8; 32], 53 | nullifier: [u8; 32], 54 | rk: [u8; 32], 55 | zkproof: Option, 56 | spend_auth_sig: Option<[u8; 64]>, 57 | recipient: Option<[u8; 43]>, 58 | value: Option, 59 | rcm: Option<[u8; 32]>, 60 | rseed: Option<[u8; 32]>, 61 | rcv: Option<[u8; 32]>, 62 | proof_generation_key: Option<([u8; 32], [u8; 32])>, 63 | witness: Option<(u32, [[u8; 32]; 32])>, 64 | alpha: Option<[u8; 32]>, 65 | zip32_derivation: Option, 66 | dummy_ask: Option<[u8; 32]>, 67 | proprietary: BTreeMap>, 68 | ) -> Result { 69 | let cv = ValueCommitment::from_bytes_not_small_order(&cv) 70 | .into_option() 71 | .ok_or(ParseError::InvalidValueCommitment)?; 72 | 73 | let nullifier = Nullifier(nullifier); 74 | 75 | let rk = redjubjub::VerificationKey::try_from(rk) 76 | .map_err(|_| ParseError::InvalidRandomizedKey)?; 77 | 78 | let spend_auth_sig = spend_auth_sig.map(redjubjub::Signature::from); 79 | 80 | let recipient = recipient 81 | .as_ref() 82 | .map(|r| PaymentAddress::from_bytes(r).ok_or(ParseError::InvalidRecipient)) 83 | .transpose()?; 84 | 85 | let value = value.map(NoteValue::from_raw); 86 | 87 | let rseed = match (rcm, rseed) { 88 | (None, None) => Ok(None), 89 | (Some(rcm), None) => jubjub::Scalar::from_repr(rcm) 90 | .into_option() 91 | .ok_or(ParseError::InvalidNoteCommitRandomness) 92 | .map(Rseed::BeforeZip212) 93 | .map(Some), 94 | (None, Some(rseed)) => Ok(Some(Rseed::AfterZip212(rseed))), 95 | (Some(_), Some(_)) => Err(ParseError::MixedNoteCommitRandomnessAndRseed), 96 | }?; 97 | 98 | let rcv = rcv 99 | .map(|rcv| { 100 | ValueCommitTrapdoor::from_bytes(rcv) 101 | .into_option() 102 | .ok_or(ParseError::InvalidValueCommitTrapdoor) 103 | }) 104 | .transpose()?; 105 | 106 | let proof_generation_key = proof_generation_key 107 | .map(|(ak, nsk)| { 108 | Ok(ProofGenerationKey { 109 | ak: SpendValidatingKey::from_bytes(&ak) 110 | .ok_or(ParseError::InvalidProofGenerationKey)?, 111 | nsk: jubjub::Scalar::from_repr(nsk) 112 | .into_option() 113 | .ok_or(ParseError::InvalidProofGenerationKey)?, 114 | }) 115 | }) 116 | .transpose()?; 117 | 118 | let witness = witness 119 | .map(|(position, auth_path_bytes)| { 120 | let path_elems = auth_path_bytes 121 | .into_iter() 122 | .map(|hash| { 123 | Node::from_bytes(hash) 124 | .into_option() 125 | .ok_or(ParseError::InvalidWitness) 126 | }) 127 | .collect::, _>>()?; 128 | 129 | MerklePath::from_parts(path_elems, u64::from(position).into()) 130 | .map_err(|()| ParseError::InvalidWitness) 131 | }) 132 | .transpose()?; 133 | 134 | let alpha = alpha 135 | .map(|alpha| { 136 | jubjub::Scalar::from_repr(alpha) 137 | .into_option() 138 | .ok_or(ParseError::InvalidSpendAuthRandomizer) 139 | }) 140 | .transpose()?; 141 | 142 | let dummy_ask = dummy_ask 143 | .map(|dummy_ask| { 144 | SpendAuthorizingKey::from_bytes(&dummy_ask) 145 | .ok_or(ParseError::InvalidDummySpendAuthorizingKey) 146 | }) 147 | .transpose()?; 148 | 149 | Ok(Self { 150 | cv, 151 | nullifier, 152 | rk, 153 | zkproof, 154 | spend_auth_sig, 155 | recipient, 156 | value, 157 | rseed, 158 | rcv, 159 | proof_generation_key, 160 | witness, 161 | alpha, 162 | zip32_derivation, 163 | dummy_ask, 164 | proprietary, 165 | }) 166 | } 167 | } 168 | 169 | impl Output { 170 | /// Parses a PCZT output from its component parts. 171 | #[allow(clippy::too_many_arguments)] 172 | pub fn parse( 173 | cv: [u8; 32], 174 | cmu: [u8; 32], 175 | ephemeral_key: [u8; 32], 176 | enc_ciphertext: Vec, 177 | out_ciphertext: Vec, 178 | zkproof: Option, 179 | recipient: Option<[u8; 43]>, 180 | value: Option, 181 | rseed: Option<[u8; 32]>, 182 | rcv: Option<[u8; 32]>, 183 | ock: Option<[u8; 32]>, 184 | zip32_derivation: Option, 185 | user_address: Option, 186 | proprietary: BTreeMap>, 187 | ) -> Result { 188 | let cv = ValueCommitment::from_bytes_not_small_order(&cv) 189 | .into_option() 190 | .ok_or(ParseError::InvalidValueCommitment)?; 191 | 192 | let cmu = ExtractedNoteCommitment::from_bytes(&cmu) 193 | .into_option() 194 | .ok_or(ParseError::InvalidExtractedNoteCommitment)?; 195 | 196 | let ephemeral_key = EphemeralKeyBytes(ephemeral_key); 197 | 198 | let enc_ciphertext = enc_ciphertext 199 | .as_slice() 200 | .try_into() 201 | .map_err(|_| ParseError::InvalidEncCiphertext)?; 202 | 203 | let out_ciphertext = out_ciphertext 204 | .as_slice() 205 | .try_into() 206 | .map_err(|_| ParseError::InvalidOutCiphertext)?; 207 | 208 | let recipient = recipient 209 | .as_ref() 210 | .map(|r| PaymentAddress::from_bytes(r).ok_or(ParseError::InvalidRecipient)) 211 | .transpose()?; 212 | 213 | let value = value.map(NoteValue::from_raw); 214 | 215 | let rcv = rcv 216 | .map(|rcv| { 217 | ValueCommitTrapdoor::from_bytes(rcv) 218 | .into_option() 219 | .ok_or(ParseError::InvalidValueCommitTrapdoor) 220 | }) 221 | .transpose()?; 222 | 223 | let ock = ock.map(OutgoingCipherKey); 224 | 225 | Ok(Self { 226 | cv, 227 | cmu, 228 | ephemeral_key, 229 | enc_ciphertext, 230 | out_ciphertext, 231 | zkproof, 232 | recipient, 233 | value, 234 | rseed, 235 | rcv, 236 | ock, 237 | zip32_derivation, 238 | user_address, 239 | proprietary, 240 | }) 241 | } 242 | } 243 | 244 | impl Zip32Derivation { 245 | /// Parses a ZIP 32 derivation path from its component parts. 246 | /// 247 | /// Returns an error if any of the derivation path indices are non-hardened (which 248 | /// this crate does not support, even though Sapling does). 249 | pub fn parse( 250 | seed_fingerprint: [u8; 32], 251 | derivation_path: Vec, 252 | ) -> Result { 253 | Ok(Self { 254 | seed_fingerprint, 255 | derivation_path: derivation_path 256 | .into_iter() 257 | .map(|i| ChildIndex::from_index(i).ok_or(ParseError::InvalidZip32Derivation)) 258 | .collect::>()?, 259 | }) 260 | } 261 | } 262 | 263 | /// Errors that can occur while parsing a PCZT bundle. 264 | #[derive(Debug)] 265 | pub enum ParseError { 266 | /// An invalid `anchor` was provided. 267 | InvalidAnchor, 268 | /// An invalid `bsk` was provided. 269 | InvalidBindingSignatureSigningKey, 270 | /// An invalid `dummy_ask` was provided. 271 | InvalidDummySpendAuthorizingKey, 272 | /// An invalid `enc_ciphertext` was provided. 273 | InvalidEncCiphertext, 274 | /// An invalid `cmu` was provided. 275 | InvalidExtractedNoteCommitment, 276 | /// An invalid `rcm` was provided. 277 | InvalidNoteCommitRandomness, 278 | /// An invalid `out_ciphertext` was provided. 279 | InvalidOutCiphertext, 280 | /// An invalid `proof_generation_key` was provided. 281 | InvalidProofGenerationKey, 282 | /// An invalid `rk` was provided. 283 | InvalidRandomizedKey, 284 | /// An invalid `recipient` was provided. 285 | InvalidRecipient, 286 | /// An invalid `alpha` was provided. 287 | InvalidSpendAuthRandomizer, 288 | /// An invalid `cv` was provided. 289 | InvalidValueCommitment, 290 | /// An invalid `rcv` was provided. 291 | InvalidValueCommitTrapdoor, 292 | /// An invalid `witness` was provided. 293 | InvalidWitness, 294 | /// An invalid `zip32_derivation` was provided. 295 | InvalidZip32Derivation, 296 | /// Both `rcm` and `rseed` were provided for a Spend. 297 | MixedNoteCommitRandomnessAndRseed, 298 | } 299 | -------------------------------------------------------------------------------- /src/pczt/prover.rs: -------------------------------------------------------------------------------- 1 | use rand::{CryptoRng, RngCore}; 2 | 3 | use crate::{ 4 | prover::{OutputProver, SpendProver}, 5 | Note, Rseed, 6 | }; 7 | 8 | impl super::Bundle { 9 | /// Adds a proof to this PCZT bundle. 10 | pub fn create_proofs( 11 | &mut self, 12 | spend_prover: &S, 13 | output_prover: &O, 14 | mut rng: R, 15 | ) -> Result<(), ProverError> 16 | where 17 | S: SpendProver, 18 | O: OutputProver, 19 | { 20 | for spend in &mut self.spends { 21 | let proof_generation_key = spend 22 | .proof_generation_key 23 | .clone() 24 | .ok_or(ProverError::MissingProofGenerationKey)?; 25 | 26 | let note = Note::from_parts( 27 | spend.recipient.ok_or(ProverError::MissingRecipient)?, 28 | spend.value.ok_or(ProverError::MissingValue)?, 29 | spend.rseed.ok_or(ProverError::MissingRandomSeed)?, 30 | ); 31 | 32 | let alpha = spend.alpha.ok_or(ProverError::MissingSpendAuthRandomizer)?; 33 | 34 | let rcv = spend 35 | .rcv 36 | .clone() 37 | .ok_or(ProverError::MissingValueCommitTrapdoor)?; 38 | 39 | let merkle_path = spend.witness.clone().ok_or(ProverError::MissingWitness)?; 40 | 41 | let circuit = S::prepare_circuit( 42 | proof_generation_key, 43 | *note.recipient().diversifier(), 44 | *note.rseed(), 45 | note.value(), 46 | alpha, 47 | rcv, 48 | self.anchor.inner(), 49 | merkle_path, 50 | ) 51 | .ok_or(ProverError::InvalidDiversifier)?; 52 | 53 | let proof = spend_prover.create_proof(circuit, &mut rng); 54 | spend.zkproof = Some(S::encode_proof(proof)); 55 | } 56 | 57 | for output in &mut self.outputs { 58 | let recipient = output.recipient.ok_or(ProverError::MissingRecipient)?; 59 | let value = output.value.ok_or(ProverError::MissingValue)?; 60 | 61 | let note = Note::from_parts( 62 | recipient, 63 | value, 64 | output 65 | .rseed 66 | .map(Rseed::AfterZip212) 67 | .ok_or(ProverError::MissingRandomSeed)?, 68 | ); 69 | 70 | let esk = note.generate_or_derive_esk(&mut rng); 71 | let rcm = note.rcm(); 72 | 73 | let rcv = output 74 | .rcv 75 | .clone() 76 | .ok_or(ProverError::MissingValueCommitTrapdoor)?; 77 | 78 | let circuit = O::prepare_circuit(&esk, recipient, rcm, value, rcv); 79 | let proof = output_prover.create_proof(circuit, &mut rng); 80 | output.zkproof = Some(O::encode_proof(proof)); 81 | } 82 | 83 | Ok(()) 84 | } 85 | } 86 | 87 | /// Errors that can occur while creating Sapling proofs for a PCZT. 88 | #[derive(Debug)] 89 | pub enum ProverError { 90 | InvalidDiversifier, 91 | /// The Prover role requires all `proof_generation_key` fields to be set. 92 | MissingProofGenerationKey, 93 | /// The Prover role requires all `rseed` fields to be set. 94 | MissingRandomSeed, 95 | /// The Prover role requires all `recipient` fields to be set. 96 | MissingRecipient, 97 | /// The Prover role requires all `alpha` fields to be set. 98 | MissingSpendAuthRandomizer, 99 | /// The Prover role requires all `value` fields to be set. 100 | MissingValue, 101 | /// The Prover role requires all `rcv` fields to be set. 102 | MissingValueCommitTrapdoor, 103 | /// The Prover role requires all `witness` fields to be set. 104 | MissingWitness, 105 | } 106 | -------------------------------------------------------------------------------- /src/pczt/signer.rs: -------------------------------------------------------------------------------- 1 | use rand::{CryptoRng, RngCore}; 2 | 3 | use crate::keys::SpendAuthorizingKey; 4 | 5 | impl super::Spend { 6 | /// Signs the Sapling 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.alpha.ok_or(SignerError::MissingSpendAuthRandomizer)?; 18 | 19 | let rsk = ask.randomize(&alpha); 20 | let rk = redjubjub::VerificationKey::from(&rsk); 21 | 22 | if self.rk == rk { 23 | self.spend_auth_sig = Some(rsk.sign(rng, &sighash)); 24 | Ok(()) 25 | } else { 26 | Err(SignerError::WrongSpendAuthorizingKey) 27 | } 28 | } 29 | } 30 | 31 | /// Errors that can occur while signing an Orchard action in a PCZT. 32 | #[derive(Debug)] 33 | pub enum SignerError { 34 | /// The Signer role requires `alpha` to be set. 35 | MissingSpendAuthRandomizer, 36 | /// The provided `ask` does not own the action's spent note. 37 | WrongSpendAuthorizingKey, 38 | } 39 | -------------------------------------------------------------------------------- /src/pczt/tx_extractor.rs: -------------------------------------------------------------------------------- 1 | use rand::{CryptoRng, RngCore}; 2 | 3 | use crate::{ 4 | bundle::{ 5 | Authorization, Authorized, EffectsOnly, GrothProofBytes, OutputDescription, 6 | SpendDescription, 7 | }, 8 | Bundle, 9 | }; 10 | 11 | use super::{Output, Spend}; 12 | 13 | impl super::Bundle { 14 | /// Extracts the effects of this PCZT bundle as a [regular `Bundle`]. 15 | /// 16 | /// This is used by the Signer role to produce the transaction sighash. 17 | /// 18 | /// [regular `Bundle`]: crate::Bundle 19 | pub fn extract_effects>( 20 | &self, 21 | ) -> Result>, TxExtractorError> { 22 | self.to_tx_data(|_| Ok(()), |_| Ok(()), |_| Ok(()), |_| Ok(EffectsOnly)) 23 | } 24 | 25 | /// Extracts a fully authorized [regular `Bundle`] from this PCZT bundle. 26 | /// 27 | /// This is used by the Transaction Extractor role to produce the final transaction. 28 | /// 29 | /// [regular `Bundle`]: crate::Bundle 30 | pub fn extract>( 31 | self, 32 | ) -> Result>, TxExtractorError> { 33 | self.to_tx_data( 34 | |spend| spend.zkproof.ok_or(TxExtractorError::MissingProof), 35 | |spend| { 36 | spend 37 | .spend_auth_sig 38 | .ok_or(TxExtractorError::MissingSpendAuthSig) 39 | }, 40 | |output| output.zkproof.ok_or(TxExtractorError::MissingProof), 41 | |bundle| { 42 | Ok(Unbound { 43 | bsk: bundle 44 | .bsk 45 | .ok_or(TxExtractorError::MissingBindingSignatureSigningKey)?, 46 | }) 47 | }, 48 | ) 49 | } 50 | 51 | fn to_tx_data( 52 | &self, 53 | spend_proof: F, 54 | spend_auth: G, 55 | output_proof: H, 56 | bundle_auth: I, 57 | ) -> Result>, E> 58 | where 59 | A: Authorization, 60 | E: From, 61 | F: Fn(&Spend) -> Result<::SpendProof, E>, 62 | G: Fn(&Spend) -> Result<::AuthSig, E>, 63 | H: Fn(&Output) -> Result<::OutputProof, E>, 64 | I: FnOnce(&Self) -> Result, 65 | V: TryFrom, 66 | { 67 | let spends = self 68 | .spends 69 | .iter() 70 | .map(|spend| { 71 | Ok(SpendDescription::from_parts( 72 | spend.cv.clone(), 73 | self.anchor.inner(), 74 | spend.nullifier, 75 | spend.rk, 76 | spend_proof(spend)?, 77 | spend_auth(spend)?, 78 | )) 79 | }) 80 | .collect::>()?; 81 | 82 | let outputs = self 83 | .outputs 84 | .iter() 85 | .map(|output| { 86 | Ok(OutputDescription::from_parts( 87 | output.cv.clone(), 88 | output.cmu, 89 | output.ephemeral_key.clone(), 90 | output.enc_ciphertext, 91 | output.out_ciphertext, 92 | output_proof(output)?, 93 | )) 94 | }) 95 | .collect::>()?; 96 | 97 | let value_balance = i64::try_from(self.value_sum) 98 | .ok() 99 | .and_then(|v| v.try_into().ok()) 100 | .ok_or(TxExtractorError::ValueSumOutOfRange)?; 101 | 102 | let authorization = bundle_auth(self)?; 103 | 104 | Ok(Bundle::from_parts( 105 | spends, 106 | outputs, 107 | value_balance, 108 | authorization, 109 | )) 110 | } 111 | } 112 | 113 | /// Errors that can occur while extracting a regular Sapling bundle from a PCZT bundle. 114 | #[derive(Debug)] 115 | pub enum TxExtractorError { 116 | /// The Transaction Extractor role requires `bsk` to be set. 117 | MissingBindingSignatureSigningKey, 118 | /// The Transaction Extractor role requires all `zkproof` fields to be set. 119 | MissingProof, 120 | /// The Transaction Extractor role requires all `spend_auth_sig` fields to be set. 121 | MissingSpendAuthSig, 122 | /// The value sum does not fit into a `valueBalance`. 123 | ValueSumOutOfRange, 124 | } 125 | 126 | /// Authorizing data for a bundle of actions that is just missing a binding signature. 127 | #[derive(Debug)] 128 | pub struct Unbound { 129 | bsk: redjubjub::SigningKey, 130 | } 131 | 132 | impl Authorization for Unbound { 133 | type SpendProof = GrothProofBytes; 134 | type OutputProof = GrothProofBytes; 135 | type AuthSig = redjubjub::Signature; 136 | } 137 | 138 | impl crate::Bundle { 139 | /// Verifies the given sighash with every `spend_auth_sig`, and then binds the bundle. 140 | /// 141 | /// Returns `None` if the given sighash does not validate against every `spend_auth_sig`. 142 | pub fn apply_binding_signature( 143 | self, 144 | sighash: [u8; 32], 145 | rng: R, 146 | ) -> Option> { 147 | if self 148 | .shielded_spends() 149 | .iter() 150 | .all(|spend| spend.rk().verify(&sighash, spend.spend_auth_sig()).is_ok()) 151 | { 152 | Some(self.map_authorization( 153 | &mut (), 154 | |_, p| p, 155 | |_, p| p, 156 | |_, s| s, 157 | |_, Unbound { bsk }| Authorized { 158 | binding_sig: bsk.sign(rng, &sighash), 159 | }, 160 | )) 161 | } else { 162 | None 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/pczt/updater.rs: -------------------------------------------------------------------------------- 1 | use alloc::string::String; 2 | use alloc::vec::Vec; 3 | 4 | use crate::ProofGenerationKey; 5 | 6 | use super::{Bundle, Output, Spend, Zip32Derivation}; 7 | 8 | impl Bundle { 9 | /// Updates the bundle with information provided in the given closure. 10 | pub fn update_with(&mut self, f: F) -> Result<(), UpdaterError> 11 | where 12 | F: FnOnce(Updater<'_>) -> Result<(), UpdaterError>, 13 | { 14 | f(Updater(self)) 15 | } 16 | } 17 | 18 | /// An updater for a Sapling PCZT bundle. 19 | pub struct Updater<'a>(&'a mut Bundle); 20 | 21 | impl Updater<'_> { 22 | /// Provides read access to the bundle being updated. 23 | pub fn bundle(&self) -> &Bundle { 24 | self.0 25 | } 26 | 27 | /// Updates the spend at the given index with information provided in the given 28 | /// closure. 29 | pub fn update_spend_with(&mut self, index: usize, f: F) -> Result<(), UpdaterError> 30 | where 31 | F: FnOnce(SpendUpdater<'_>) -> Result<(), UpdaterError>, 32 | { 33 | f(SpendUpdater( 34 | self.0 35 | .spends 36 | .get_mut(index) 37 | .ok_or(UpdaterError::InvalidIndex)?, 38 | )) 39 | } 40 | 41 | /// Updates the output at the given index with information provided in the given 42 | /// closure. 43 | pub fn update_output_with(&mut self, index: usize, f: F) -> Result<(), UpdaterError> 44 | where 45 | F: FnOnce(OutputUpdater<'_>) -> Result<(), UpdaterError>, 46 | { 47 | f(OutputUpdater( 48 | self.0 49 | .outputs 50 | .get_mut(index) 51 | .ok_or(UpdaterError::InvalidIndex)?, 52 | )) 53 | } 54 | } 55 | 56 | /// An updater for a Sapling PCZT spend. 57 | pub struct SpendUpdater<'a>(&'a mut Spend); 58 | 59 | impl SpendUpdater<'_> { 60 | /// Sets the proof generation key for this spend. 61 | /// 62 | /// Returns an error if the proof generation key does not match the spend. 63 | pub fn set_proof_generation_key( 64 | &mut self, 65 | proof_generation_key: ProofGenerationKey, 66 | ) -> Result<(), UpdaterError> { 67 | // TODO: Verify that the proof generation key matches the spend, if possible. 68 | self.0.proof_generation_key = Some(proof_generation_key); 69 | Ok(()) 70 | } 71 | 72 | /// Sets the ZIP 32 derivation path for the spent note's signing key. 73 | pub fn set_zip32_derivation(&mut self, derivation: Zip32Derivation) { 74 | self.0.zip32_derivation = Some(derivation); 75 | } 76 | 77 | /// Stores the given proprietary value at the given key. 78 | pub fn set_proprietary(&mut self, key: String, value: Vec) { 79 | self.0.proprietary.insert(key, value); 80 | } 81 | } 82 | 83 | /// An updater for a Sapling PCZT output. 84 | pub struct OutputUpdater<'a>(&'a mut Output); 85 | 86 | impl OutputUpdater<'_> { 87 | /// Sets the ZIP 32 derivation path for the new note's signing key. 88 | pub fn set_zip32_derivation(&mut self, derivation: Zip32Derivation) { 89 | self.0.zip32_derivation = Some(derivation); 90 | } 91 | 92 | /// Sets the user-facing address that the new note is being sent to. 93 | pub fn set_user_address(&mut self, user_address: String) { 94 | self.0.user_address = Some(user_address); 95 | } 96 | 97 | /// Stores the given proprietary value at the given key. 98 | pub fn set_proprietary(&mut self, key: String, value: Vec) { 99 | self.0.proprietary.insert(key, value); 100 | } 101 | } 102 | 103 | /// Errors that can occur while updating a Sapling bundle in a PCZT. 104 | #[derive(Debug)] 105 | pub enum UpdaterError { 106 | /// An out-of-bounds index was provided when looking up a spend or output. 107 | InvalidIndex, 108 | /// The provided `proof_generation_key` does not match the spend. 109 | WrongProofGenerationKey, 110 | } 111 | -------------------------------------------------------------------------------- /src/pczt/verify.rs: -------------------------------------------------------------------------------- 1 | use crate::{keys::FullViewingKey, value::ValueCommitment, Note, ViewingKey}; 2 | 3 | impl super::Spend { 4 | /// Verifies that the `cv` field is consistent with the note fields. 5 | /// 6 | /// Requires that the following optional fields are set: 7 | /// - `value` 8 | /// - `rcv` 9 | pub fn verify_cv(&self) -> Result<(), VerifyError> { 10 | let value = self.value.ok_or(VerifyError::MissingValue)?; 11 | let rcv = self 12 | .rcv 13 | .clone() 14 | .ok_or(VerifyError::MissingValueCommitTrapdoor)?; 15 | 16 | let cv_net = ValueCommitment::derive(value, rcv); 17 | if cv_net.to_bytes() == self.cv.to_bytes() { 18 | Ok(()) 19 | } else { 20 | Err(VerifyError::InvalidValueCommitment) 21 | } 22 | } 23 | 24 | /// Returns the [`ViewingKey`] to use when validating this note. 25 | /// 26 | /// Handles dummy notes when the `value` field is set. 27 | fn vk_for_validation( 28 | &self, 29 | expected_fvk: Option<&FullViewingKey>, 30 | ) -> Result { 31 | let vk = self 32 | .proof_generation_key 33 | .as_ref() 34 | .map(|proof_generation_key| proof_generation_key.to_viewing_key()); 35 | 36 | match (expected_fvk, vk, self.value.as_ref()) { 37 | (Some(expected_fvk), Some(vk), _) 38 | if vk.ak == expected_fvk.vk.ak && vk.nk == expected_fvk.vk.nk => 39 | { 40 | Ok(vk) 41 | } 42 | // `expected_fvk` is ignored if the spent note is a dummy note. 43 | (Some(_), Some(vk), Some(value)) if value.inner() == 0 => Ok(vk), 44 | (Some(_), Some(_), _) => Err(VerifyError::MismatchedFullViewingKey), 45 | (Some(expected_fvk), None, _) => Ok(expected_fvk.vk.clone()), 46 | (None, Some(vk), _) => Ok(vk), 47 | (None, None, _) => Err(VerifyError::MissingProofGenerationKey), 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 | /// - `rseed` 57 | /// - `witness` 58 | /// 59 | /// In addition, at least one of the `proof_generation_key` field or `expected_fvk` 60 | /// must be provided. 61 | /// 62 | /// The provided [`FullViewingKey`] is ignored if the spent note is a dummy note. 63 | /// Otherwise, it will be checked against the `proof_generation_key` field (if both 64 | /// are set). 65 | pub fn verify_nullifier( 66 | &self, 67 | expected_fvk: Option<&FullViewingKey>, 68 | ) -> Result<(), VerifyError> { 69 | let vk = self.vk_for_validation(expected_fvk)?; 70 | 71 | let note = Note::from_parts( 72 | self.recipient.ok_or(VerifyError::MissingRecipient)?, 73 | self.value.ok_or(VerifyError::MissingValue)?, 74 | self.rseed.ok_or(VerifyError::MissingRandomSeed)?, 75 | ); 76 | 77 | // We need both the note and the VK to verify the nullifier; we have everything 78 | // needed to also verify that the correct VK was provided (the nullifier check 79 | // itself only constrains `nk` within the VK). 80 | if vk.to_payment_address(*note.recipient().diversifier()) != Some(note.recipient()) { 81 | return Err(VerifyError::WrongFvkForNote); 82 | } 83 | 84 | let merkle_path = self.witness().as_ref().ok_or(VerifyError::MissingWitness)?; 85 | 86 | if note.nf(&vk.nk, merkle_path.position().into()) == self.nullifier { 87 | Ok(()) 88 | } else { 89 | Err(VerifyError::InvalidNullifier) 90 | } 91 | } 92 | 93 | /// Verifies that the `rk` field is consistent with the given FVK. 94 | /// 95 | /// Requires that the following optional fields are set: 96 | /// - `alpha` 97 | /// 98 | /// The provided [`FullViewingKey`] is ignored if the spent note is a dummy note 99 | /// (which can only be determined if the `value` field is set). Otherwise, it will be 100 | /// checked against the `proof_generation_key` field (if set). 101 | pub fn verify_rk(&self, expected_fvk: Option<&FullViewingKey>) -> Result<(), VerifyError> { 102 | let vk = self.vk_for_validation(expected_fvk)?; 103 | 104 | let alpha = self 105 | .alpha 106 | .as_ref() 107 | .ok_or(VerifyError::MissingSpendAuthRandomizer)?; 108 | 109 | if vk.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 `cv` field is consistent with the note fields. 119 | /// 120 | /// Requires that the following optional fields are set: 121 | /// - `value` 122 | /// - `rcv` 123 | pub fn verify_cv(&self) -> Result<(), VerifyError> { 124 | let value = self.value.ok_or(VerifyError::MissingValue)?; 125 | let rcv = self 126 | .rcv 127 | .clone() 128 | .ok_or(VerifyError::MissingValueCommitTrapdoor)?; 129 | 130 | let cv_net = ValueCommitment::derive(value, rcv); 131 | if cv_net.to_bytes() == self.cv.to_bytes() { 132 | Ok(()) 133 | } else { 134 | Err(VerifyError::InvalidValueCommitment) 135 | } 136 | } 137 | 138 | /// Verifies that the `cmu` field is consistent with the note fields. 139 | /// 140 | /// Requires that the following optional fields are set: 141 | /// - `recipient` 142 | /// - `value` 143 | /// - `rseed` 144 | pub fn verify_note_commitment(&self) -> Result<(), VerifyError> { 145 | let note = Note::from_parts( 146 | self.recipient.ok_or(VerifyError::MissingRecipient)?, 147 | self.value.ok_or(VerifyError::MissingValue)?, 148 | crate::Rseed::AfterZip212(self.rseed.ok_or(VerifyError::MissingRandomSeed)?), 149 | ); 150 | 151 | if note.cmu() == self.cmu { 152 | Ok(()) 153 | } else { 154 | Err(VerifyError::InvalidExtractedNoteCommitment) 155 | } 156 | } 157 | } 158 | 159 | /// Errors that can occur while verifying a PCZT bundle. 160 | #[derive(Debug)] 161 | pub enum VerifyError { 162 | /// The output note's components do not produce the expected `cmx`. 163 | InvalidExtractedNoteCommitment, 164 | /// The spent note's components do not produce the expected `nullifier`. 165 | InvalidNullifier, 166 | /// The Spend's FVK and `alpha` do not produce the expected `rk`. 167 | InvalidRandomizedVerificationKey, 168 | /// The action's `cv_net` does not match the provided note values and `rcv`. 169 | InvalidValueCommitment, 170 | /// The spend or output's `fvk` field does not match the provided FVK. 171 | MismatchedFullViewingKey, 172 | /// Dummy notes must have their `proof_generation_key` field set in order to be verified. 173 | MissingProofGenerationKey, 174 | /// `nullifier` verification requires `rseed` to be set. 175 | MissingRandomSeed, 176 | /// `nullifier` verification requires `recipient` to be set. 177 | MissingRecipient, 178 | /// `rk` verification requires `alpha` to be set. 179 | MissingSpendAuthRandomizer, 180 | /// Verification requires all `value` fields to be set. 181 | MissingValue, 182 | /// `cv_net` verification requires `rcv` to be set. 183 | MissingValueCommitTrapdoor, 184 | /// `nullifier` verification requires `witness` to be set. 185 | MissingWitness, 186 | /// The provided `fvk` does not own the spent note. 187 | WrongFvkForNote, 188 | } 189 | -------------------------------------------------------------------------------- /src/pedersen_hash.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of the Pedersen hash function used in Sapling. 2 | 3 | #[cfg(test)] 4 | pub(crate) mod test_vectors; 5 | 6 | use alloc::vec::Vec; 7 | use core::ops::{AddAssign, Neg}; 8 | use ff::PrimeField; 9 | use group::Group; 10 | 11 | use super::constants::{PEDERSEN_HASH_CHUNKS_PER_GENERATOR, PEDERSEN_HASH_EXP_WINDOW_SIZE}; 12 | 13 | #[derive(Copy, Clone)] 14 | pub enum Personalization { 15 | NoteCommitment, 16 | MerkleTree(usize), 17 | } 18 | 19 | impl Personalization { 20 | pub fn get_bits(&self) -> Vec { 21 | match *self { 22 | Personalization::NoteCommitment => vec![true, true, true, true, true, true], 23 | Personalization::MerkleTree(num) => { 24 | assert!(num < 63); 25 | 26 | (0..6).map(|i| (num >> i) & 1 == 1).collect() 27 | } 28 | } 29 | } 30 | } 31 | 32 | pub fn pedersen_hash(personalization: Personalization, bits: I) -> jubjub::SubgroupPoint 33 | where 34 | I: IntoIterator, 35 | { 36 | let mut bits = personalization.get_bits().into_iter().chain(bits); 37 | 38 | let mut result = jubjub::SubgroupPoint::identity(); 39 | let mut generators = crate::constants::PEDERSEN_HASH_EXP_TABLE.iter(); 40 | 41 | loop { 42 | let mut acc = jubjub::Fr::zero(); 43 | let mut cur = jubjub::Fr::one(); 44 | let mut chunks_remaining = PEDERSEN_HASH_CHUNKS_PER_GENERATOR; 45 | let mut encountered_bits = false; 46 | 47 | // Grab three bits from the input 48 | while let Some(a) = bits.next() { 49 | encountered_bits = true; 50 | 51 | let b = bits.next().unwrap_or(false); 52 | let c = bits.next().unwrap_or(false); 53 | 54 | // Start computing this portion of the scalar 55 | let mut tmp = cur; 56 | if a { 57 | tmp.add_assign(&cur); 58 | } 59 | cur = cur.double(); // 2^1 * cur 60 | if b { 61 | tmp.add_assign(&cur); 62 | } 63 | 64 | // conditionally negate 65 | if c { 66 | tmp = tmp.neg(); 67 | } 68 | 69 | acc.add_assign(&tmp); 70 | 71 | chunks_remaining -= 1; 72 | 73 | if chunks_remaining == 0 { 74 | break; 75 | } else { 76 | cur = cur.double().double().double(); // 2^4 * cur 77 | } 78 | } 79 | 80 | if !encountered_bits { 81 | break; 82 | } 83 | 84 | let mut table: &[Vec] = 85 | generators.next().expect("we don't have enough generators"); 86 | let window = PEDERSEN_HASH_EXP_WINDOW_SIZE as usize; 87 | let window_mask = (1u64 << window) - 1; 88 | 89 | let acc = acc.to_repr(); 90 | let num_limbs: usize = acc.as_ref().len() / 8; 91 | let mut limbs = vec![0u64; num_limbs + 1]; 92 | for (src, dst) in acc.chunks_exact(8).zip(limbs[..num_limbs].iter_mut()) { 93 | *dst = u64::from_le_bytes(src.try_into().expect("correct length")); 94 | } 95 | 96 | let mut tmp = jubjub::SubgroupPoint::identity(); 97 | 98 | let mut pos = 0; 99 | while pos < jubjub::Fr::NUM_BITS as usize { 100 | let u64_idx = pos / 64; 101 | let bit_idx = pos % 64; 102 | let i = (if bit_idx + window < 64 { 103 | // This window's bits are contained in a single u64. 104 | limbs[u64_idx] >> bit_idx 105 | } else { 106 | // Combine the current u64's bits with the bits from the next u64. 107 | (limbs[u64_idx] >> bit_idx) | (limbs[u64_idx + 1] << (64 - bit_idx)) 108 | } & window_mask) as usize; 109 | 110 | tmp += table[0][i]; 111 | 112 | pos += window; 113 | table = &table[1..]; 114 | } 115 | 116 | result += tmp; 117 | } 118 | 119 | result 120 | } 121 | 122 | #[cfg(test)] 123 | pub mod test { 124 | use alloc::string::ToString; 125 | use group::Curve; 126 | 127 | use super::*; 128 | 129 | pub struct TestVector<'a> { 130 | pub personalization: Personalization, 131 | pub input_bits: Vec, 132 | pub hash_u: &'a str, 133 | pub hash_v: &'a str, 134 | } 135 | 136 | #[test] 137 | fn test_pedersen_hash_points() { 138 | let test_vectors = test_vectors::get_vectors(); 139 | 140 | assert!(!test_vectors.is_empty()); 141 | 142 | for v in test_vectors.iter() { 143 | let input_bools: Vec = v.input_bits.iter().map(|&i| i == 1).collect(); 144 | 145 | // The 6 bits prefix is handled separately 146 | assert_eq!(v.personalization.get_bits(), &input_bools[..6]); 147 | 148 | let p = jubjub::ExtendedPoint::from(pedersen_hash( 149 | v.personalization, 150 | input_bools.into_iter().skip(6), 151 | )) 152 | .to_affine(); 153 | 154 | assert_eq!(p.get_u().to_string(), v.hash_u); 155 | assert_eq!(p.get_v().to_string(), v.hash_v); 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/prover.rs: -------------------------------------------------------------------------------- 1 | //! Abstractions over the proving system and parameters. 2 | 3 | use bellman::groth16::{create_random_proof, Proof}; 4 | use bls12_381::Bls12; 5 | use rand_core::RngCore; 6 | 7 | use crate::{ 8 | bundle::GrothProofBytes, 9 | circuit, 10 | constants::GROTH_PROOF_SIZE, 11 | keys::EphemeralSecretKey, 12 | value::{NoteValue, ValueCommitTrapdoor}, 13 | MerklePath, 14 | }; 15 | 16 | use super::{ 17 | circuit::{Output, OutputParameters, Spend, SpendParameters, ValueCommitmentOpening}, 18 | Diversifier, Note, PaymentAddress, ProofGenerationKey, Rseed, 19 | }; 20 | 21 | /// Interface for creating Sapling Spend proofs. 22 | pub trait SpendProver { 23 | /// The proof type created by this prover. 24 | type Proof; 25 | 26 | /// Prepares an instance of the Sapling Spend circuit for the given inputs. 27 | /// 28 | /// Returns `None` if `diversifier` is not a valid Sapling diversifier. 29 | #[allow(clippy::too_many_arguments)] 30 | fn prepare_circuit( 31 | proof_generation_key: ProofGenerationKey, 32 | diversifier: Diversifier, 33 | rseed: Rseed, 34 | value: NoteValue, 35 | alpha: jubjub::Fr, 36 | rcv: ValueCommitTrapdoor, 37 | anchor: bls12_381::Scalar, 38 | merkle_path: MerklePath, 39 | ) -> Option; 40 | 41 | /// Create the proof for a Sapling [`SpendDescription`]. 42 | /// 43 | /// [`SpendDescription`]: crate::bundle::SpendDescription 44 | fn create_proof(&self, circuit: circuit::Spend, rng: &mut R) -> Self::Proof; 45 | 46 | /// Encodes the given Sapling [`SpendDescription`] proof, erasing its type. 47 | /// 48 | /// [`SpendDescription`]: crate::bundle::SpendDescription 49 | fn encode_proof(proof: Self::Proof) -> GrothProofBytes; 50 | } 51 | 52 | /// Interface for creating Sapling Output proofs. 53 | pub trait OutputProver { 54 | /// The proof type created by this prover. 55 | type Proof; 56 | 57 | /// Prepares an instance of the Sapling Output circuit for the given inputs. 58 | /// 59 | /// Returns `None` if `diversifier` is not a valid Sapling diversifier. 60 | fn prepare_circuit( 61 | esk: &EphemeralSecretKey, 62 | payment_address: PaymentAddress, 63 | rcm: jubjub::Fr, 64 | value: NoteValue, 65 | rcv: ValueCommitTrapdoor, 66 | ) -> circuit::Output; 67 | 68 | /// Create the proof for a Sapling [`OutputDescription`]. 69 | /// 70 | /// [`OutputDescription`]: crate::bundle::OutputDescription 71 | fn create_proof(&self, circuit: circuit::Output, rng: &mut R) -> Self::Proof; 72 | 73 | /// Encodes the given Sapling [`OutputDescription`] proof, erasing its type. 74 | /// 75 | /// [`OutputDescription`]: crate::bundle::OutputDescription 76 | fn encode_proof(proof: Self::Proof) -> GrothProofBytes; 77 | } 78 | 79 | impl SpendProver for SpendParameters { 80 | type Proof = Proof; 81 | 82 | fn prepare_circuit( 83 | proof_generation_key: ProofGenerationKey, 84 | diversifier: Diversifier, 85 | rseed: Rseed, 86 | value: NoteValue, 87 | alpha: jubjub::Fr, 88 | rcv: ValueCommitTrapdoor, 89 | anchor: bls12_381::Scalar, 90 | merkle_path: MerklePath, 91 | ) -> Option { 92 | // Construct the value commitment 93 | let value_commitment_opening = ValueCommitmentOpening { 94 | value, 95 | randomness: rcv.inner(), 96 | }; 97 | 98 | // Construct the viewing key 99 | let viewing_key = proof_generation_key.to_viewing_key(); 100 | 101 | // Construct the payment address with the viewing key / diversifier 102 | let payment_address = viewing_key.to_payment_address(diversifier)?; 103 | 104 | let note = Note::from_parts(payment_address, value, rseed); 105 | 106 | // We now have the full witness for our circuit 107 | let pos: u64 = merkle_path.position().into(); 108 | Some(Spend { 109 | value_commitment_opening: Some(value_commitment_opening), 110 | proof_generation_key: Some(proof_generation_key), 111 | payment_address: Some(payment_address), 112 | commitment_randomness: Some(note.rcm()), 113 | ar: Some(alpha), 114 | auth_path: merkle_path 115 | .path_elems() 116 | .iter() 117 | .enumerate() 118 | .map(|(i, node)| Some(((*node).into(), (pos >> i) & 0x1 == 1))) 119 | .collect(), 120 | anchor: Some(anchor), 121 | }) 122 | } 123 | 124 | fn create_proof(&self, circuit: Spend, rng: &mut R) -> Self::Proof { 125 | create_random_proof(circuit, &self.0, rng).expect("proving should not fail") 126 | } 127 | 128 | fn encode_proof(proof: Self::Proof) -> GrothProofBytes { 129 | let mut zkproof = [0u8; GROTH_PROOF_SIZE]; 130 | proof 131 | .write(&mut zkproof[..]) 132 | .expect("should be able to serialize a proof"); 133 | zkproof 134 | } 135 | } 136 | 137 | impl OutputProver for OutputParameters { 138 | type Proof = Proof; 139 | 140 | fn prepare_circuit( 141 | esk: &EphemeralSecretKey, 142 | payment_address: PaymentAddress, 143 | rcm: jubjub::Fr, 144 | value: NoteValue, 145 | rcv: ValueCommitTrapdoor, 146 | ) -> Output { 147 | // Construct the value commitment for the proof instance 148 | let value_commitment_opening = ValueCommitmentOpening { 149 | value, 150 | randomness: rcv.inner(), 151 | }; 152 | 153 | // We now have a full witness for the output proof. 154 | Output { 155 | value_commitment_opening: Some(value_commitment_opening), 156 | payment_address: Some(payment_address), 157 | commitment_randomness: Some(rcm), 158 | esk: Some(esk.0), 159 | } 160 | } 161 | 162 | fn create_proof(&self, circuit: Output, rng: &mut R) -> Self::Proof { 163 | create_random_proof(circuit, &self.0, rng).expect("proving should not fail") 164 | } 165 | 166 | fn encode_proof(proof: Self::Proof) -> GrothProofBytes { 167 | let mut zkproof = [0u8; GROTH_PROOF_SIZE]; 168 | proof 169 | .write(&mut zkproof[..]) 170 | .expect("should be able to serialize a proof"); 171 | zkproof 172 | } 173 | } 174 | 175 | #[cfg(any(test, feature = "test-dependencies"))] 176 | #[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))] 177 | pub mod mock { 178 | use ff::Field; 179 | 180 | use super::{OutputProver, SpendProver}; 181 | use crate::{ 182 | bundle::GrothProofBytes, 183 | circuit::{self, ValueCommitmentOpening}, 184 | constants::GROTH_PROOF_SIZE, 185 | keys::EphemeralSecretKey, 186 | value::{NoteValue, ValueCommitTrapdoor}, 187 | Diversifier, MerklePath, PaymentAddress, ProofGenerationKey, Rseed, 188 | }; 189 | 190 | pub struct MockSpendProver; 191 | 192 | impl SpendProver for MockSpendProver { 193 | type Proof = GrothProofBytes; 194 | 195 | fn prepare_circuit( 196 | proof_generation_key: ProofGenerationKey, 197 | diversifier: Diversifier, 198 | _rseed: Rseed, 199 | value: NoteValue, 200 | alpha: jubjub::Fr, 201 | rcv: ValueCommitTrapdoor, 202 | anchor: bls12_381::Scalar, 203 | _merkle_path: MerklePath, 204 | ) -> Option { 205 | let payment_address = proof_generation_key 206 | .to_viewing_key() 207 | .ivk() 208 | .to_payment_address(diversifier); 209 | Some(circuit::Spend { 210 | value_commitment_opening: Some(ValueCommitmentOpening { 211 | value, 212 | randomness: rcv.inner(), 213 | }), 214 | proof_generation_key: Some(proof_generation_key), 215 | payment_address, 216 | commitment_randomness: Some(jubjub::Scalar::ZERO), 217 | ar: Some(alpha), 218 | auth_path: vec![], 219 | anchor: Some(anchor), 220 | }) 221 | } 222 | 223 | fn create_proof( 224 | &self, 225 | _circuit: circuit::Spend, 226 | _rng: &mut R, 227 | ) -> Self::Proof { 228 | [0u8; GROTH_PROOF_SIZE] 229 | } 230 | 231 | fn encode_proof(proof: Self::Proof) -> GrothProofBytes { 232 | proof 233 | } 234 | } 235 | 236 | pub struct MockOutputProver; 237 | 238 | impl OutputProver for MockOutputProver { 239 | type Proof = GrothProofBytes; 240 | 241 | fn prepare_circuit( 242 | esk: &EphemeralSecretKey, 243 | payment_address: PaymentAddress, 244 | rcm: jubjub::Fr, 245 | value: NoteValue, 246 | rcv: ValueCommitTrapdoor, 247 | ) -> circuit::Output { 248 | circuit::Output { 249 | value_commitment_opening: Some(ValueCommitmentOpening { 250 | value, 251 | randomness: rcv.inner(), 252 | }), 253 | payment_address: Some(payment_address), 254 | commitment_randomness: Some(rcm), 255 | esk: Some(esk.0), 256 | } 257 | } 258 | 259 | fn create_proof( 260 | &self, 261 | _circuit: circuit::Output, 262 | _rng: &mut R, 263 | ) -> Self::Proof { 264 | [0u8; GROTH_PROOF_SIZE] 265 | } 266 | 267 | fn encode_proof(proof: Self::Proof) -> GrothProofBytes { 268 | proof 269 | } 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /src/spec.rs: -------------------------------------------------------------------------------- 1 | //! Helper functions defined in the Zcash Protocol Specification. 2 | 3 | use blake2s_simd::Params as Blake2sParams; 4 | use group::{cofactor::CofactorGroup, ff::PrimeField, Curve, GroupEncoding, WnafBase, WnafScalar}; 5 | 6 | use super::{ 7 | constants::{ 8 | CRH_IVK_PERSONALIZATION, KEY_DIVERSIFICATION_PERSONALIZATION, 9 | NOTE_COMMITMENT_RANDOMNESS_GENERATOR, NULLIFIER_POSITION_GENERATOR, PRF_NF_PERSONALIZATION, 10 | }, 11 | group_hash::group_hash, 12 | pedersen_hash::{pedersen_hash, Personalization}, 13 | }; 14 | 15 | const PREPARED_WINDOW_SIZE: usize = 4; 16 | pub(crate) type PreparedBase = WnafBase; 17 | pub(crate) type PreparedBaseSubgroup = WnafBase; 18 | pub(crate) type PreparedScalar = WnafScalar; 19 | 20 | /// $CRH^\mathsf{ivk}(ak, nk)$ 21 | /// 22 | /// Defined in [Zcash Protocol Spec § 5.4.1.5: CRH^ivk Hash Function][concretecrhivk]. 23 | /// 24 | /// [concretecrhivk]: https://zips.z.cash/protocol/protocol.pdf#concretecrhivk 25 | pub(crate) fn crh_ivk(ak: [u8; 32], nk: [u8; 32]) -> jubjub::Scalar { 26 | let mut h: [u8; 32] = Blake2sParams::new() 27 | .hash_length(32) 28 | .personal(CRH_IVK_PERSONALIZATION) 29 | .to_state() 30 | .update(&ak) 31 | .update(&nk) 32 | .finalize() 33 | .as_bytes() 34 | .try_into() 35 | .expect("output length is correct"); 36 | 37 | // Drop the most significant five bits, so it can be interpreted as a scalar. 38 | h[31] &= 0b0000_0111; 39 | 40 | jubjub::Fr::from_repr(h).unwrap() 41 | } 42 | 43 | /// Defined in [Zcash Protocol Spec § 5.4.1.6: DiversifyHash^Sapling and DiversifyHash^Orchard Hash Functions][concretediversifyhash]. 44 | /// 45 | /// [concretediversifyhash]: https://zips.z.cash/protocol/protocol.pdf#concretediversifyhash 46 | pub(crate) fn diversify_hash(d: &[u8; 11]) -> Option { 47 | group_hash(d, KEY_DIVERSIFICATION_PERSONALIZATION) 48 | } 49 | 50 | /// $MixingPedersenHash$. 51 | /// 52 | /// Defined in [Zcash Protocol Spec § 5.4.1.8: Mixing Pedersen Hash Function][concretemixinghash]. 53 | /// 54 | /// [concretemixinghash]: https://zips.z.cash/protocol/protocol.pdf#concretemixinghash 55 | pub(crate) fn mixing_pedersen_hash( 56 | cm: jubjub::SubgroupPoint, 57 | position: u64, 58 | ) -> jubjub::SubgroupPoint { 59 | cm + (NULLIFIER_POSITION_GENERATOR * jubjub::Fr::from(position)) 60 | } 61 | 62 | /// $PRF^\mathsf{nfSapling}_{nk}(\rho)$ 63 | /// 64 | /// Defined in [Zcash Protocol Spec § 5.4.2: Pseudo Random Functions][concreteprfs]. 65 | /// 66 | /// [concreteprfs]: https://zips.z.cash/protocol/protocol.pdf#concreteprfs 67 | pub(crate) fn prf_nf(nk: &jubjub::SubgroupPoint, rho: &jubjub::SubgroupPoint) -> [u8; 32] { 68 | Blake2sParams::new() 69 | .hash_length(32) 70 | .personal(PRF_NF_PERSONALIZATION) 71 | .to_state() 72 | .update(&nk.to_bytes()) 73 | .update(&rho.to_bytes()) 74 | .finalize() 75 | .as_bytes() 76 | .try_into() 77 | .expect("output length is correct") 78 | } 79 | 80 | /// Defined in [Zcash Protocol Spec § 5.4.5.3: Sapling Key Agreement][concretesaplingkeyagreement]. 81 | /// 82 | /// [concretesaplingkeyagreement]: https://zips.z.cash/protocol/protocol.pdf#concretesaplingkeyagreement 83 | pub(crate) fn ka_sapling_derive_public( 84 | sk: &jubjub::Scalar, 85 | b: &jubjub::ExtendedPoint, 86 | ) -> jubjub::ExtendedPoint { 87 | ka_sapling_derive_public_prepared(&PreparedScalar::new(sk), &PreparedBase::new(*b)) 88 | } 89 | 90 | /// Defined in [Zcash Protocol Spec § 5.4.5.3: Sapling Key Agreement][concretesaplingkeyagreement]. 91 | /// 92 | /// [concretesaplingkeyagreement]: https://zips.z.cash/protocol/protocol.pdf#concretesaplingkeyagreement 93 | pub(crate) fn ka_sapling_derive_public_prepared( 94 | sk: &PreparedScalar, 95 | b: &PreparedBase, 96 | ) -> jubjub::ExtendedPoint { 97 | // [sk] b 98 | b * sk 99 | } 100 | 101 | /// This is defined implicitly by [Zcash Protocol Spec § 4.2.2: Sapling Key Components][saplingkeycomponents] 102 | /// which uses $KA^\mathsf{Sapling}.\mathsf{DerivePublic}$ to produce a diversified 103 | /// transmission key with type $KA^\mathsf{Sapling}.\mathsf{PublicPrimeSubgroup}$. 104 | /// 105 | /// [saplingkeycomponents]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents 106 | pub(crate) fn ka_sapling_derive_public_subgroup_prepared( 107 | sk: &PreparedScalar, 108 | b: &PreparedBaseSubgroup, 109 | ) -> jubjub::SubgroupPoint { 110 | // [sk] b 111 | b * sk 112 | } 113 | 114 | /// Defined in [Zcash Protocol Spec § 5.4.5.3: Sapling Key Agreement][concretesaplingkeyagreement]. 115 | /// 116 | /// [concretesaplingkeyagreement]: https://zips.z.cash/protocol/protocol.pdf#concretesaplingkeyagreement 117 | pub(crate) fn ka_sapling_agree( 118 | sk: &jubjub::Scalar, 119 | b: &jubjub::ExtendedPoint, 120 | ) -> jubjub::SubgroupPoint { 121 | ka_sapling_agree_prepared(&PreparedScalar::new(sk), &PreparedBase::new(*b)) 122 | } 123 | 124 | /// Defined in [Zcash Protocol Spec § 5.4.5.3: Sapling Key Agreement][concretesaplingkeyagreement]. 125 | /// 126 | /// [concretesaplingkeyagreement]: https://zips.z.cash/protocol/protocol.pdf#concretesaplingkeyagreement 127 | pub(crate) fn ka_sapling_agree_prepared( 128 | sk: &PreparedScalar, 129 | b: &PreparedBase, 130 | ) -> jubjub::SubgroupPoint { 131 | // [8 sk] b 132 | // ::clear_cofactor is implemented using 133 | // ExtendedPoint::mul_by_cofactor in the jubjub crate. 134 | 135 | (b * sk).clear_cofactor() 136 | } 137 | 138 | /// $WindowedPedersenCommit_r(s)$ 139 | /// 140 | /// Defined in [Zcash Protocol Spec § 5.4.8.2: Windowed Pedersen commitments][concretewindowedcommit]. 141 | /// 142 | /// [concretewindowedcommit]: https://zips.z.cash/protocol/protocol.pdf#concretewindowedcommit 143 | pub(crate) fn windowed_pedersen_commit( 144 | personalization: Personalization, 145 | s: I, 146 | r: jubjub::Scalar, 147 | ) -> jubjub::SubgroupPoint 148 | where 149 | I: IntoIterator, 150 | { 151 | pedersen_hash(personalization, s) + (NOTE_COMMITMENT_RANDOMNESS_GENERATOR * r) 152 | } 153 | 154 | /// Coordinate extractor for Jubjub. 155 | /// 156 | /// Defined in [Zcash Protocol Spec § 5.4.9.4: Coordinate Extractor for Jubjub][concreteextractorjubjub]. 157 | /// 158 | /// [concreteextractorjubjub]: https://zips.z.cash/protocol/protocol.pdf#concreteextractorjubjub 159 | pub(crate) fn extract_p(point: &jubjub::SubgroupPoint) -> bls12_381::Scalar { 160 | // The commitment is in the prime order subgroup, so mapping the 161 | // commitment to the u-coordinate is an injective encoding. 162 | Into::<&jubjub::ExtendedPoint>::into(point) 163 | .to_affine() 164 | .get_u() 165 | } 166 | -------------------------------------------------------------------------------- /src/test_vectors.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod note_encryption; 2 | pub(crate) mod signatures; 3 | -------------------------------------------------------------------------------- /src/tree.rs: -------------------------------------------------------------------------------- 1 | use bitvec::{order::Lsb0, view::AsBits}; 2 | use group::{ff::PrimeField, Curve}; 3 | use incrementalmerkletree::{Hashable, Level}; 4 | use lazy_static::lazy_static; 5 | use subtle::CtOption; 6 | 7 | use alloc::vec::Vec; 8 | use core::fmt; 9 | 10 | use super::{ 11 | note::ExtractedNoteCommitment, 12 | pedersen_hash::{pedersen_hash, Personalization}, 13 | }; 14 | 15 | pub const NOTE_COMMITMENT_TREE_DEPTH: u8 = 32; 16 | pub type CommitmentTree = 17 | incrementalmerkletree::frontier::CommitmentTree; 18 | pub type IncrementalWitness = 19 | incrementalmerkletree::witness::IncrementalWitness; 20 | pub type MerklePath = incrementalmerkletree::MerklePath; 21 | 22 | lazy_static! { 23 | static ref UNCOMMITTED_SAPLING: bls12_381::Scalar = bls12_381::Scalar::one(); 24 | static ref EMPTY_ROOTS: Vec = empty_roots(); 25 | } 26 | 27 | fn empty_roots() -> Vec { 28 | let mut v = vec![Node::empty_leaf()]; 29 | for d in 0..NOTE_COMMITMENT_TREE_DEPTH { 30 | let next = Node::combine(d.into(), &v[usize::from(d)], &v[usize::from(d)]); 31 | v.push(next); 32 | } 33 | v 34 | } 35 | 36 | /// Compute a parent node in the Sapling commitment tree given its two children. 37 | pub fn merkle_hash(depth: usize, lhs: &[u8; 32], rhs: &[u8; 32]) -> [u8; 32] { 38 | merkle_hash_field(depth, lhs, rhs).to_repr() 39 | } 40 | 41 | fn merkle_hash_field(depth: usize, lhs: &[u8; 32], rhs: &[u8; 32]) -> jubjub::Base { 42 | let lhs = { 43 | let mut tmp = [false; 256]; 44 | for (a, b) in tmp.iter_mut().zip(lhs.as_bits::()) { 45 | *a = *b; 46 | } 47 | tmp 48 | }; 49 | 50 | let rhs = { 51 | let mut tmp = [false; 256]; 52 | for (a, b) in tmp.iter_mut().zip(rhs.as_bits::()) { 53 | *a = *b; 54 | } 55 | tmp 56 | }; 57 | 58 | jubjub::ExtendedPoint::from(pedersen_hash( 59 | Personalization::MerkleTree(depth), 60 | lhs.iter() 61 | .copied() 62 | .take(bls12_381::Scalar::NUM_BITS as usize) 63 | .chain( 64 | rhs.iter() 65 | .copied() 66 | .take(bls12_381::Scalar::NUM_BITS as usize), 67 | ), 68 | )) 69 | .to_affine() 70 | .get_u() 71 | } 72 | 73 | /// The root of a Sapling commitment tree. 74 | #[derive(Eq, PartialEq, Clone, Copy, Debug)] 75 | pub struct Anchor(jubjub::Base); 76 | 77 | impl From for Anchor { 78 | fn from(anchor_field: jubjub::Base) -> Anchor { 79 | Anchor(anchor_field) 80 | } 81 | } 82 | 83 | impl From for Anchor { 84 | fn from(anchor: Node) -> Anchor { 85 | Anchor(anchor.0) 86 | } 87 | } 88 | 89 | impl Anchor { 90 | /// The anchor of the empty Sapling note commitment tree. 91 | /// 92 | /// This anchor does not correspond to any valid anchor for a spend, so it 93 | /// may only be used for coinbase bundles or in circumstances where Sapling 94 | /// functionality is not active. 95 | pub fn empty_tree() -> Anchor { 96 | Anchor(Node::empty_root(NOTE_COMMITMENT_TREE_DEPTH.into()).0) 97 | } 98 | 99 | pub(crate) fn inner(&self) -> jubjub::Base { 100 | self.0 101 | } 102 | 103 | /// Parses a Sapling anchor from a byte encoding. 104 | pub fn from_bytes(bytes: [u8; 32]) -> CtOption { 105 | jubjub::Base::from_repr(bytes).map(Self) 106 | } 107 | 108 | /// Returns the byte encoding of this anchor. 109 | pub fn to_bytes(self) -> [u8; 32] { 110 | self.0.to_repr() 111 | } 112 | } 113 | 114 | /// A node within the Sapling commitment tree. 115 | #[derive(Clone, Copy, PartialEq, Eq)] 116 | pub struct Node(jubjub::Base); 117 | 118 | impl fmt::Debug for Node { 119 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 120 | f.debug_struct("Node") 121 | .field("repr", &hex::encode(self.0.to_bytes())) 122 | .finish() 123 | } 124 | } 125 | 126 | impl Node { 127 | /// Creates a tree leaf from the given Sapling note commitment. 128 | pub fn from_cmu(value: &ExtractedNoteCommitment) -> Self { 129 | Node(value.inner()) 130 | } 131 | 132 | /// Constructs a new note commitment tree node from a [`bls12_381::Scalar`] 133 | pub fn from_scalar(cmu: bls12_381::Scalar) -> Self { 134 | Self(cmu) 135 | } 136 | 137 | /// Parses a tree leaf from the bytes of a Sapling note commitment. 138 | /// 139 | /// Returns `None` if the provided bytes represent a non-canonical encoding. 140 | pub fn from_bytes(bytes: [u8; 32]) -> CtOption { 141 | jubjub::Base::from_repr(bytes).map(Self) 142 | } 143 | 144 | /// Returns the canonical byte representation of this node. 145 | pub fn to_bytes(&self) -> [u8; 32] { 146 | self.0.to_repr() 147 | } 148 | 149 | /// Returns the wrapped value 150 | #[cfg(feature = "circuit")] 151 | pub(crate) fn inner(&self) -> &jubjub::Base { 152 | &self.0 153 | } 154 | } 155 | 156 | impl Hashable for Node { 157 | fn empty_leaf() -> Self { 158 | Node(*UNCOMMITTED_SAPLING) 159 | } 160 | 161 | fn combine(level: Level, lhs: &Self, rhs: &Self) -> Self { 162 | Node(merkle_hash_field( 163 | level.into(), 164 | &lhs.0.to_bytes(), 165 | &rhs.0.to_bytes(), 166 | )) 167 | } 168 | 169 | fn empty_root(level: Level) -> Self { 170 | EMPTY_ROOTS[::from(level)] 171 | } 172 | } 173 | 174 | impl From for bls12_381::Scalar { 175 | fn from(node: Node) -> Self { 176 | node.0 177 | } 178 | } 179 | 180 | #[cfg(any(test, feature = "test-dependencies"))] 181 | pub(super) mod testing { 182 | use ff::Field; 183 | use proptest::prelude::*; 184 | use rand::distributions::{Distribution, Standard}; 185 | 186 | use super::Node; 187 | use crate::note::testing::arb_cmu; 188 | 189 | prop_compose! { 190 | pub fn arb_node()(cmu in arb_cmu()) -> Node { 191 | Node::from_cmu(&cmu) 192 | } 193 | } 194 | 195 | impl Node { 196 | /// Return a random fake `MerkleHashOrchard`. 197 | pub fn random(rng: &mut impl RngCore) -> Self { 198 | Standard.sample(rng) 199 | } 200 | } 201 | 202 | impl Distribution for Standard { 203 | fn sample(&self, rng: &mut R) -> Node { 204 | Node::from_scalar(bls12_381::Scalar::random(rng)) 205 | } 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use blake2b_simd::Params; 2 | use ff::Field; 3 | use rand_core::{CryptoRng, RngCore}; 4 | 5 | use super::{note_encryption::Zip212Enforcement, Rseed}; 6 | 7 | pub fn hash_to_scalar(persona: &[u8], a: &[u8], b: &[u8]) -> jubjub::Fr { 8 | let mut hasher = Params::new().hash_length(64).personal(persona).to_state(); 9 | hasher.update(a); 10 | hasher.update(b); 11 | let ret = hasher.finalize(); 12 | jubjub::Fr::from_bytes_wide(ret.as_array()) 13 | } 14 | 15 | pub fn generate_random_rseed( 16 | zip212_enforcement: Zip212Enforcement, 17 | rng: &mut R, 18 | ) -> Rseed { 19 | generate_random_rseed_internal(zip212_enforcement, rng) 20 | } 21 | 22 | pub(crate) fn generate_random_rseed_internal( 23 | zip212_enforcement: Zip212Enforcement, 24 | rng: &mut R, 25 | ) -> Rseed { 26 | match zip212_enforcement { 27 | Zip212Enforcement::Off => Rseed::BeforeZip212(jubjub::Fr::random(rng)), 28 | Zip212Enforcement::GracePeriod | Zip212Enforcement::On => { 29 | let mut buffer = [0u8; 32]; 30 | rng.fill_bytes(&mut buffer); 31 | Rseed::AfterZip212(buffer) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/value.rs: -------------------------------------------------------------------------------- 1 | //! Monetary values within the Sapling shielded pool. 2 | //! 3 | //! Values are represented in three places within the Sapling protocol: 4 | //! - [`NoteValue`], the value of an individual note. It is an unsigned 64-bit integer 5 | //! (with maximum value [`MAX_NOTE_VALUE`]), and is serialized in a note plaintext. 6 | //! - [`ValueSum`], the sum of note values within a Sapling [`Bundle`]. It is represented 7 | //! as an `i128` and places an upper bound on the maximum number of notes within a 8 | //! single [`Bundle`]. 9 | //! - `valueBalanceSapling`, which is a signed 63-bit integer. This is represented 10 | //! by a user-defined type parameter on [`Bundle`], returned by 11 | //! [`Bundle::value_balance`] and [`Builder::value_balance`]. 12 | //! 13 | //! If your specific instantiation of the Sapling protocol requires a smaller bound on 14 | //! valid note values (for example, Zcash's `MAX_MONEY` fits into a 51-bit integer), you 15 | //! should enforce this in two ways: 16 | //! 17 | //! - Define your `valueBalanceSapling` type to enforce your valid value range. This can 18 | //! be checked in its `TryFrom` implementation. 19 | //! - Define your own "amount" type for note values, and convert it to `NoteValue` prior 20 | //! to calling [`Builder::add_output`]. 21 | //! 22 | //! Inside the circuit, note values are constrained to be unsigned 64-bit integers. 23 | //! 24 | //! # Caution! 25 | //! 26 | //! An `i64` is _not_ a signed 64-bit integer! The [Rust documentation] calls `i64` the 27 | //! 64-bit signed integer type, which is true in the sense that its encoding in memory 28 | //! takes up 64 bits. Numerically, however, `i64` is a signed 63-bit integer. 29 | //! 30 | //! Fortunately, users of this crate should never need to construct [`ValueSum`] directly; 31 | //! you should only need to interact with [`NoteValue`] (which can be safely constructed 32 | //! from a `u64`) and `valueBalanceSapling` (which can be represented as an `i64`). 33 | //! 34 | //! [`Bundle`]: crate::Bundle 35 | //! [`Bundle::value_balance`]: crate::Bundle::value_balance 36 | //! [`Builder::value_balance`]: crate::builder::Builder::value_balance 37 | //! [`Builder::add_output`]: crate::builder::Builder::add_output 38 | //! [Rust documentation]: https://doc.rust-lang.org/stable/std/primitive.i64.html 39 | 40 | use bitvec::{array::BitArray, order::Lsb0}; 41 | use ff::{Field, PrimeField}; 42 | use group::GroupEncoding; 43 | use rand::RngCore; 44 | use subtle::CtOption; 45 | 46 | use super::constants::{VALUE_COMMITMENT_RANDOMNESS_GENERATOR, VALUE_COMMITMENT_VALUE_GENERATOR}; 47 | 48 | mod sums; 49 | pub use sums::{CommitmentSum, OverflowError, TrapdoorSum, ValueSum}; 50 | 51 | /// Maximum note value. 52 | pub const MAX_NOTE_VALUE: u64 = u64::MAX; 53 | 54 | /// The non-negative value of an individual Sapling note. 55 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] 56 | pub struct NoteValue(u64); 57 | 58 | impl NoteValue { 59 | /// The zero note value. 60 | pub const ZERO: NoteValue = NoteValue(0); 61 | 62 | /// Returns the raw underlying value. 63 | pub fn inner(&self) -> u64 { 64 | self.0 65 | } 66 | 67 | /// Creates a note value from its raw numeric value. 68 | /// 69 | /// This only enforces that the value is an unsigned 64-bit integer. Callers should 70 | /// enforce any additional constraints on the value's valid range themselves. 71 | pub fn from_raw(value: u64) -> Self { 72 | NoteValue(value) 73 | } 74 | 75 | pub(crate) fn from_bytes(bytes: [u8; 8]) -> Self { 76 | NoteValue(u64::from_le_bytes(bytes)) 77 | } 78 | 79 | pub(crate) fn to_le_bits(self) -> BitArray<[u8; 8], Lsb0> { 80 | BitArray::<_, Lsb0>::new(self.0.to_le_bytes()) 81 | } 82 | } 83 | 84 | /// The blinding factor for a [`ValueCommitment`]. 85 | #[derive(Clone, Debug)] 86 | pub struct ValueCommitTrapdoor(jubjub::Scalar); 87 | 88 | impl ValueCommitTrapdoor { 89 | /// Generates a new value commitment trapdoor. 90 | /// 91 | /// This is public for access by `zcash_proofs`. 92 | pub fn random(rng: impl RngCore) -> Self { 93 | ValueCommitTrapdoor(jubjub::Scalar::random(rng)) 94 | } 95 | 96 | /// Constructs `ValueCommitTrapdoor` from the byte representation of a scalar. 97 | /// 98 | /// Returns a `None` [`CtOption`] if `bytes` is not a canonical representation of a 99 | /// Jubjub scalar. 100 | /// 101 | /// This is a low-level API, requiring a detailed understanding of the 102 | /// [use of value commitment trapdoors][saplingbalance] in the Zcash protocol 103 | /// to use correctly and securely. It is intended to be used in combination 104 | /// with [`ValueCommitment::derive`]. 105 | /// 106 | /// [saplingbalance]: https://zips.z.cash/protocol/protocol.pdf#saplingbalance 107 | pub fn from_bytes(bytes: [u8; 32]) -> CtOption { 108 | jubjub::Scalar::from_repr(bytes).map(ValueCommitTrapdoor) 109 | } 110 | 111 | /// Returns the inner Jubjub scalar representing this trapdoor. 112 | /// 113 | /// This is public for access by `zcash_proofs`. 114 | pub fn inner(&self) -> jubjub::Scalar { 115 | self.0 116 | } 117 | } 118 | 119 | /// A commitment to a [`ValueSum`]. 120 | /// 121 | /// # Consensus rules 122 | /// 123 | /// The Zcash Protocol Spec requires Sapling Spend Descriptions and Output Descriptions to 124 | /// not contain a small order `ValueCommitment`. However, the `ValueCommitment` type as 125 | /// specified (and implemented here) may contain a small order point. In practice, it will 126 | /// not occur: 127 | /// - [`ValueCommitment::derive`] will only produce a small order point if both the given 128 | /// [`NoteValue`] and [`ValueCommitTrapdoor`] are zero. However, the only constructor 129 | /// available for `ValueCommitTrapdoor` is [`ValueCommitTrapdoor::random`], which will 130 | /// produce zero with negligible probability (assuming a non-broken PRNG). 131 | /// - [`ValueCommitment::from_bytes_not_small_order`] enforces this by definition, and is 132 | /// the only constructor that can be used with data received over the network. 133 | #[derive(Clone, Debug)] 134 | pub struct ValueCommitment(jubjub::ExtendedPoint); 135 | 136 | impl ValueCommitment { 137 | /// Derives a `ValueCommitment` by $\mathsf{ValueCommit^{Sapling}}$. 138 | /// 139 | /// Defined in [Zcash Protocol Spec § 5.4.8.3: Homomorphic Pedersen commitments (Sapling and Orchard)][concretehomomorphiccommit]. 140 | /// 141 | /// [concretehomomorphiccommit]: https://zips.z.cash/protocol/protocol.pdf#concretehomomorphiccommit 142 | pub fn derive(value: NoteValue, rcv: ValueCommitTrapdoor) -> Self { 143 | let cv = (VALUE_COMMITMENT_VALUE_GENERATOR * jubjub::Scalar::from(value.0)) 144 | + (VALUE_COMMITMENT_RANDOMNESS_GENERATOR * rcv.0); 145 | 146 | ValueCommitment(cv.into()) 147 | } 148 | 149 | /// Returns the inner Jubjub point representing this value commitment. 150 | /// 151 | /// This is public for access by `zcash_proofs`. 152 | pub fn as_inner(&self) -> &jubjub::ExtendedPoint { 153 | &self.0 154 | } 155 | 156 | /// Deserializes a value commitment from its byte representation. 157 | /// 158 | /// Returns `None` if `bytes` is an invalid representation of a Jubjub point, or the 159 | /// resulting point is of small order. 160 | /// 161 | /// This method can be used to enforce the "not small order" consensus rules defined 162 | /// in [Zcash Protocol Spec § 4.4: Spend Descriptions][spenddesc] and 163 | /// [§ 4.5: Output Descriptions][outputdesc]. 164 | /// 165 | /// [spenddesc]: https://zips.z.cash/protocol/protocol.pdf#spenddesc 166 | /// [outputdesc]: https://zips.z.cash/protocol/protocol.pdf#outputdesc 167 | pub fn from_bytes_not_small_order(bytes: &[u8; 32]) -> CtOption { 168 | jubjub::ExtendedPoint::from_bytes(bytes) 169 | .and_then(|cv| CtOption::new(ValueCommitment(cv), !cv.is_small_order())) 170 | } 171 | 172 | /// Serializes this value commitment to its canonical byte representation. 173 | pub fn to_bytes(&self) -> [u8; 32] { 174 | self.0.to_bytes() 175 | } 176 | } 177 | 178 | /// Generators for property testing. 179 | #[cfg(any(test, feature = "test-dependencies"))] 180 | #[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))] 181 | pub mod testing { 182 | use proptest::prelude::*; 183 | 184 | use super::{NoteValue, ValueCommitTrapdoor, MAX_NOTE_VALUE}; 185 | 186 | prop_compose! { 187 | /// Generate an arbitrary value in the range of valid nonnegative amounts. 188 | pub fn arb_note_value()(value in 0u64..MAX_NOTE_VALUE) -> NoteValue { 189 | NoteValue(value) 190 | } 191 | } 192 | 193 | prop_compose! { 194 | /// Generate an arbitrary value in the range of valid positive amounts less than a 195 | /// specified value. 196 | pub fn arb_note_value_bounded(max: u64)(value in 0u64..max) -> NoteValue { 197 | NoteValue(value) 198 | } 199 | } 200 | 201 | prop_compose! { 202 | /// Generate an arbitrary value in the range of valid positive amounts less than a 203 | /// specified value. 204 | pub fn arb_positive_note_value(max: u64)(value in 1u64..max) -> NoteValue { 205 | NoteValue(value) 206 | } 207 | } 208 | 209 | prop_compose! { 210 | /// Generate an arbitrary Jubjub scalar. 211 | fn arb_scalar()(bytes in prop::array::uniform32(0u8..)) -> jubjub::Scalar { 212 | // Instead of rejecting out-of-range bytes, let's reduce them. 213 | let mut buf = [0; 64]; 214 | buf[..32].copy_from_slice(&bytes); 215 | jubjub::Scalar::from_bytes_wide(&buf) 216 | } 217 | } 218 | 219 | prop_compose! { 220 | /// Generate an arbitrary ValueCommitTrapdoor 221 | pub fn arb_trapdoor()(rcv in arb_scalar()) -> ValueCommitTrapdoor { 222 | ValueCommitTrapdoor(rcv) 223 | } 224 | } 225 | } 226 | 227 | #[cfg(test)] 228 | mod tests { 229 | use proptest::prelude::*; 230 | 231 | use super::{ 232 | testing::{arb_note_value_bounded, arb_trapdoor}, 233 | CommitmentSum, OverflowError, TrapdoorSum, ValueCommitment, ValueSum, 234 | }; 235 | 236 | proptest! { 237 | #[test] 238 | fn bsk_consistent_with_bvk( 239 | values in (1usize..10).prop_flat_map(|n_values| prop::collection::vec( 240 | (arb_note_value_bounded((i64::MAX as u64) / (n_values as u64)), arb_trapdoor()), 241 | n_values, 242 | )) 243 | ) { 244 | let value_balance: i64 = values 245 | .iter() 246 | .map(|(value, _)| value) 247 | .sum::>() 248 | .expect("we generate values that won't overflow") 249 | .try_into() 250 | .unwrap(); 251 | 252 | let bsk = values 253 | .iter() 254 | .map(|(_, rcv)| rcv) 255 | .sum::() 256 | .into_bsk(); 257 | 258 | let bvk = values 259 | .into_iter() 260 | .map(|(value, rcv)| ValueCommitment::derive(value, rcv)) 261 | .sum::() 262 | .into_bvk(value_balance); 263 | 264 | assert_eq!(redjubjub::VerificationKey::from(&bsk), bvk); 265 | } 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /src/value/sums.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::{self, Debug}; 2 | use core::iter::Sum; 3 | use core::ops::{Add, AddAssign, Sub, SubAssign}; 4 | 5 | use group::GroupEncoding; 6 | use redjubjub::Binding; 7 | 8 | use super::{NoteValue, ValueCommitTrapdoor, ValueCommitment}; 9 | use crate::constants::VALUE_COMMITMENT_VALUE_GENERATOR; 10 | 11 | /// A value operation overflowed. 12 | #[derive(Debug)] 13 | pub struct OverflowError; 14 | 15 | impl fmt::Display for OverflowError { 16 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 17 | write!(f, "Sapling value operation overflowed") 18 | } 19 | } 20 | 21 | #[cfg(feature = "std")] 22 | impl std::error::Error for OverflowError {} 23 | 24 | /// A sum of Sapling note values. 25 | /// 26 | /// [Zcash Protocol Spec § 4.13: Balance and Binding Signature (Sapling)][saplingbalance] 27 | /// constrains the range of this type to between `[-(r_J - 1)/2..(r_J - 1)/2]` in the 28 | /// abstract protocol, and `[−38913406623490299131842..104805176454780817500623]` in the 29 | /// concrete Zcash protocol. We represent it as an `i128`, which has a range large enough 30 | /// to handle Zcash transactions while small enough to ensure the abstract protocol bounds 31 | /// are not breached. 32 | /// 33 | /// [saplingbalance]: https://zips.z.cash/protocol/protocol.pdf#saplingbalance 34 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] 35 | pub struct ValueSum(i128); 36 | 37 | impl ValueSum { 38 | /// Initializes a sum of `NoteValue`s to zero. 39 | pub fn zero() -> Self { 40 | ValueSum(0) 41 | } 42 | 43 | /// Instantiates a value sum from a raw encoding. 44 | /// 45 | /// Only intended for use with PCZTs. 46 | pub(crate) fn from_raw(value_sum: i128) -> Self { 47 | Self(value_sum) 48 | } 49 | 50 | /// Extracts the raw encoding of this value sum. 51 | /// 52 | /// Only intended for use with PCZTs. 53 | pub fn to_raw(self) -> i128 { 54 | self.0 55 | } 56 | } 57 | 58 | impl Add for ValueSum { 59 | type Output = Option; 60 | 61 | #[allow(clippy::suspicious_arithmetic_impl)] 62 | fn add(self, rhs: NoteValue) -> Self::Output { 63 | self.0.checked_add(rhs.0.into()).map(ValueSum) 64 | } 65 | } 66 | 67 | impl Sub for ValueSum { 68 | type Output = Option; 69 | 70 | #[allow(clippy::suspicious_arithmetic_impl)] 71 | fn sub(self, rhs: NoteValue) -> Self::Output { 72 | self.0.checked_sub(rhs.0.into()).map(ValueSum) 73 | } 74 | } 75 | 76 | impl<'a> Sum<&'a NoteValue> for Result { 77 | fn sum>(mut iter: I) -> Self { 78 | iter.try_fold(ValueSum(0), |acc, v| acc + *v) 79 | .ok_or(OverflowError) 80 | } 81 | } 82 | 83 | impl Sum for Result { 84 | fn sum>(mut iter: I) -> Self { 85 | iter.try_fold(ValueSum(0), |acc, v| acc + v) 86 | .ok_or(OverflowError) 87 | } 88 | } 89 | 90 | impl TryFrom for i64 { 91 | type Error = OverflowError; 92 | 93 | fn try_from(v: ValueSum) -> Result { 94 | i64::try_from(v.0).map_err(|_| OverflowError) 95 | } 96 | } 97 | 98 | /// A sum of Sapling value commitment blinding factors. 99 | #[derive(Clone, Copy, Debug)] 100 | pub struct TrapdoorSum(jubjub::Scalar); 101 | 102 | impl TrapdoorSum { 103 | /// Initializes a sum of `ValueCommitTrapdoor`s to zero. 104 | pub fn zero() -> Self { 105 | TrapdoorSum(jubjub::Scalar::zero()) 106 | } 107 | 108 | /// Transform this trapdoor sum into the corresponding RedJubjub private key. 109 | /// 110 | /// This is public for access by `zcash_proofs`. 111 | pub fn into_bsk(self) -> redjubjub::SigningKey { 112 | redjubjub::SigningKey::try_from(self.0.to_bytes()) 113 | .expect("valid scalars are valid signing keys") 114 | } 115 | } 116 | 117 | impl Add<&ValueCommitTrapdoor> for ValueCommitTrapdoor { 118 | type Output = TrapdoorSum; 119 | 120 | fn add(self, rhs: &Self) -> Self::Output { 121 | TrapdoorSum(self.0 + rhs.0) 122 | } 123 | } 124 | 125 | impl Add<&ValueCommitTrapdoor> for TrapdoorSum { 126 | type Output = TrapdoorSum; 127 | 128 | fn add(self, rhs: &ValueCommitTrapdoor) -> Self::Output { 129 | TrapdoorSum(self.0 + rhs.0) 130 | } 131 | } 132 | 133 | impl AddAssign<&ValueCommitTrapdoor> for TrapdoorSum { 134 | fn add_assign(&mut self, rhs: &ValueCommitTrapdoor) { 135 | self.0 += rhs.0; 136 | } 137 | } 138 | 139 | impl Sub<&ValueCommitTrapdoor> for ValueCommitTrapdoor { 140 | type Output = TrapdoorSum; 141 | 142 | fn sub(self, rhs: &Self) -> Self::Output { 143 | TrapdoorSum(self.0 - rhs.0) 144 | } 145 | } 146 | 147 | impl Sub for TrapdoorSum { 148 | type Output = TrapdoorSum; 149 | 150 | fn sub(self, rhs: Self) -> Self::Output { 151 | TrapdoorSum(self.0 - rhs.0) 152 | } 153 | } 154 | 155 | impl SubAssign<&ValueCommitTrapdoor> for TrapdoorSum { 156 | fn sub_assign(&mut self, rhs: &ValueCommitTrapdoor) { 157 | self.0 -= rhs.0; 158 | } 159 | } 160 | 161 | impl<'a> Sum<&'a ValueCommitTrapdoor> for TrapdoorSum { 162 | fn sum>(iter: I) -> Self { 163 | iter.fold(TrapdoorSum::zero(), |acc, cv| acc + cv) 164 | } 165 | } 166 | 167 | /// A sum of Sapling value commitments. 168 | #[derive(Clone, Copy, Debug)] 169 | pub struct CommitmentSum(jubjub::ExtendedPoint); 170 | 171 | impl CommitmentSum { 172 | /// Initializes a sum of `ValueCommitment`s to zero. 173 | pub fn zero() -> Self { 174 | CommitmentSum(jubjub::ExtendedPoint::identity()) 175 | } 176 | 177 | /// Transform this value commitment sum into the corresponding RedJubjub public key. 178 | /// 179 | /// This is public for access by `zcash_proofs`. 180 | pub fn into_bvk>(self, value_balance: V) -> redjubjub::VerificationKey { 181 | let value: i64 = value_balance.into(); 182 | 183 | // Compute the absolute value. 184 | let abs_value = match value.checked_abs() { 185 | Some(v) => u64::try_from(v).expect("v is non-negative"), 186 | None => 1u64 << 63, 187 | }; 188 | 189 | // Construct the field representation of the signed value. 190 | let value_balance = if value.is_negative() { 191 | -jubjub::Scalar::from(abs_value) 192 | } else { 193 | jubjub::Scalar::from(abs_value) 194 | }; 195 | 196 | // Subtract `value_balance` from the sum to get the final bvk. 197 | let bvk = self.0 - VALUE_COMMITMENT_VALUE_GENERATOR * value_balance; 198 | 199 | redjubjub::VerificationKey::try_from(bvk.to_bytes()) 200 | .expect("valid points are valid verification keys") 201 | } 202 | } 203 | 204 | impl Add<&ValueCommitment> for ValueCommitment { 205 | type Output = CommitmentSum; 206 | 207 | fn add(self, rhs: &Self) -> Self::Output { 208 | CommitmentSum(self.0 + rhs.0) 209 | } 210 | } 211 | 212 | impl Add<&ValueCommitment> for CommitmentSum { 213 | type Output = CommitmentSum; 214 | 215 | fn add(self, rhs: &ValueCommitment) -> Self::Output { 216 | CommitmentSum(self.0 + rhs.0) 217 | } 218 | } 219 | 220 | impl AddAssign<&ValueCommitment> for CommitmentSum { 221 | fn add_assign(&mut self, rhs: &ValueCommitment) { 222 | self.0 += rhs.0; 223 | } 224 | } 225 | 226 | impl Sub<&ValueCommitment> for ValueCommitment { 227 | type Output = CommitmentSum; 228 | 229 | fn sub(self, rhs: &Self) -> Self::Output { 230 | CommitmentSum(self.0 - rhs.0) 231 | } 232 | } 233 | 234 | impl SubAssign<&ValueCommitment> for CommitmentSum { 235 | fn sub_assign(&mut self, rhs: &ValueCommitment) { 236 | self.0 -= rhs.0; 237 | } 238 | } 239 | 240 | impl Sub for CommitmentSum { 241 | type Output = CommitmentSum; 242 | 243 | fn sub(self, rhs: Self) -> Self::Output { 244 | CommitmentSum(self.0 - rhs.0) 245 | } 246 | } 247 | 248 | impl Sum for CommitmentSum { 249 | fn sum>(iter: I) -> Self { 250 | iter.fold(CommitmentSum::zero(), |acc, cv| acc + &cv) 251 | } 252 | } 253 | 254 | impl<'a> Sum<&'a ValueCommitment> for CommitmentSum { 255 | fn sum>(iter: I) -> Self { 256 | iter.fold(CommitmentSum::zero(), |acc, cv| acc + cv) 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /src/verifier.rs: -------------------------------------------------------------------------------- 1 | use bellman::{gadgets::multipack, groth16::Proof}; 2 | use bls12_381::Bls12; 3 | use group::{ff::PrimeField, Curve}; 4 | use redjubjub::{Binding, SpendAuth}; 5 | 6 | use crate::{ 7 | note::ExtractedNoteCommitment, 8 | value::{CommitmentSum, ValueCommitment}, 9 | }; 10 | 11 | mod single; 12 | pub use single::SaplingVerificationContext; 13 | 14 | mod batch; 15 | pub use batch::BatchValidator; 16 | 17 | /// A context object for verifying the Sapling components of a Zcash transaction. 18 | struct SaplingVerificationContextInner { 19 | // (sum of the Spend value commitments) - (sum of the Output value commitments) 20 | cv_sum: CommitmentSum, 21 | } 22 | 23 | impl SaplingVerificationContextInner { 24 | /// Construct a new context to be used with a single transaction. 25 | fn new() -> Self { 26 | SaplingVerificationContextInner { 27 | cv_sum: CommitmentSum::zero(), 28 | } 29 | } 30 | 31 | /// Perform consensus checks on a Sapling SpendDescription, while 32 | /// accumulating its value commitment inside the context for later use. 33 | #[allow(clippy::too_many_arguments)] 34 | fn check_spend( 35 | &mut self, 36 | cv: &ValueCommitment, 37 | anchor: bls12_381::Scalar, 38 | nullifier: &[u8; 32], 39 | rk: &redjubjub::VerificationKey, 40 | zkproof: Proof, 41 | verifier_ctx: &mut C, 42 | spend_auth_sig_verifier: impl FnOnce(&mut C, &redjubjub::VerificationKey) -> bool, 43 | proof_verifier: impl FnOnce(&mut C, Proof, [bls12_381::Scalar; 7]) -> bool, 44 | ) -> bool { 45 | // The "cv is not small order" happens when a SpendDescription is deserialized. 46 | // This happens when transactions or blocks are received over the network, or when 47 | // mined blocks are introduced via the `submitblock` RPC method on full nodes. 48 | let rk_affine = jubjub::AffinePoint::from_bytes((*rk).into()).unwrap(); 49 | if rk_affine.is_small_order().into() { 50 | return false; 51 | } 52 | 53 | // Accumulate the value commitment in the context 54 | self.cv_sum += cv; 55 | 56 | // Grab the nullifier as a sequence of bytes 57 | let nullifier = &nullifier[..]; 58 | 59 | // Verify the spend_auth_sig 60 | if !spend_auth_sig_verifier(verifier_ctx, rk) { 61 | return false; 62 | } 63 | 64 | // Construct public input for circuit 65 | let mut public_input = [bls12_381::Scalar::zero(); 7]; 66 | { 67 | let affine = rk_affine; 68 | let (u, v) = (affine.get_u(), affine.get_v()); 69 | public_input[0] = u; 70 | public_input[1] = v; 71 | } 72 | { 73 | let affine = cv.as_inner().to_affine(); 74 | let (u, v) = (affine.get_u(), affine.get_v()); 75 | public_input[2] = u; 76 | public_input[3] = v; 77 | } 78 | public_input[4] = anchor; 79 | 80 | // Add the nullifier through multiscalar packing 81 | { 82 | let nullifier = multipack::bytes_to_bits_le(nullifier); 83 | let nullifier = multipack::compute_multipacking(&nullifier); 84 | 85 | assert_eq!(nullifier.len(), 2); 86 | 87 | public_input[5] = nullifier[0]; 88 | public_input[6] = nullifier[1]; 89 | } 90 | 91 | // Verify the proof 92 | proof_verifier(verifier_ctx, zkproof, public_input) 93 | } 94 | 95 | /// Perform consensus checks on a Sapling OutputDescription, while 96 | /// accumulating its value commitment inside the context for later use. 97 | fn check_output( 98 | &mut self, 99 | cv: &ValueCommitment, 100 | cmu: ExtractedNoteCommitment, 101 | epk: jubjub::ExtendedPoint, 102 | zkproof: Proof, 103 | proof_verifier: impl FnOnce(Proof, [bls12_381::Scalar; 5]) -> bool, 104 | ) -> bool { 105 | // The "cv is not small order" happens when an OutputDescription is deserialized. 106 | // This happens when transactions or blocks are received over the network, or when 107 | // mined blocks are introduced via the `submitblock` RPC method on full nodes. 108 | if epk.is_small_order().into() { 109 | return false; 110 | } 111 | 112 | // Accumulate the value commitment in the context 113 | self.cv_sum -= cv; 114 | 115 | // Construct public input for circuit 116 | let mut public_input = [bls12_381::Scalar::zero(); 5]; 117 | { 118 | let affine = cv.as_inner().to_affine(); 119 | let (u, v) = (affine.get_u(), affine.get_v()); 120 | public_input[0] = u; 121 | public_input[1] = v; 122 | } 123 | { 124 | let affine = epk.to_affine(); 125 | let (u, v) = (affine.get_u(), affine.get_v()); 126 | public_input[2] = u; 127 | public_input[3] = v; 128 | } 129 | public_input[4] = bls12_381::Scalar::from_repr(cmu.to_bytes()).unwrap(); 130 | 131 | // Verify the proof 132 | proof_verifier(zkproof, public_input) 133 | } 134 | 135 | /// Perform consensus checks on the valueBalance and bindingSig parts of a 136 | /// Sapling transaction. All SpendDescriptions and OutputDescriptions must 137 | /// have been checked before calling this function. 138 | fn final_check>( 139 | &self, 140 | value_balance: V, 141 | binding_sig_verifier: impl FnOnce(redjubjub::VerificationKey) -> bool, 142 | ) -> bool { 143 | // Compute the final bvk. 144 | let bvk = self.cv_sum.into_bvk(value_balance); 145 | 146 | // Verify the binding_sig 147 | binding_sig_verifier(bvk) 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/verifier/batch.rs: -------------------------------------------------------------------------------- 1 | use bellman::groth16; 2 | use bls12_381::Bls12; 3 | use group::GroupEncoding; 4 | use rand_core::{CryptoRng, RngCore}; 5 | 6 | use super::SaplingVerificationContextInner; 7 | use crate::{ 8 | bundle::{Authorized, Bundle}, 9 | circuit::{OutputVerifyingKey, SpendVerifyingKey}, 10 | }; 11 | 12 | /// Batch validation context for Sapling. 13 | /// 14 | /// This batch-validates Spend and Output proofs, and RedJubjub signatures. 15 | /// 16 | /// Signatures are verified assuming ZIP 216 is active. 17 | pub struct BatchValidator { 18 | bundles_added: bool, 19 | spend_proofs: groth16::batch::Verifier, 20 | output_proofs: groth16::batch::Verifier, 21 | signatures: redjubjub::batch::Verifier, 22 | } 23 | 24 | impl Default for BatchValidator { 25 | fn default() -> Self { 26 | Self::new() 27 | } 28 | } 29 | 30 | impl BatchValidator { 31 | /// Constructs a new batch validation context. 32 | pub fn new() -> Self { 33 | BatchValidator { 34 | bundles_added: false, 35 | spend_proofs: groth16::batch::Verifier::new(), 36 | output_proofs: groth16::batch::Verifier::new(), 37 | signatures: redjubjub::batch::Verifier::new(), 38 | } 39 | } 40 | 41 | /// Checks the bundle against Sapling-specific consensus rules, and adds its proof and 42 | /// signatures to the validator. 43 | /// 44 | /// Returns `false` if the bundle doesn't satisfy all of the consensus rules. This 45 | /// `BatchValidator` can continue to be used regardless, but some or all of the proofs 46 | /// and signatures from this bundle may have already been added to the batch even if 47 | /// it fails other consensus rules. 48 | pub fn check_bundle>( 49 | &mut self, 50 | bundle: Bundle, 51 | sighash: [u8; 32], 52 | ) -> bool { 53 | self.bundles_added = true; 54 | 55 | let mut ctx = SaplingVerificationContextInner::new(); 56 | 57 | for spend in bundle.shielded_spends() { 58 | // Deserialize the proof 59 | let zkproof = match groth16::Proof::read(&spend.zkproof()[..]) { 60 | Ok(p) => p, 61 | Err(_) => return false, 62 | }; 63 | 64 | // Check the Spend consensus rules, and batch its proof and spend 65 | // authorization signature. 66 | let consensus_rules_passed = ctx.check_spend( 67 | spend.cv(), 68 | *spend.anchor(), 69 | &spend.nullifier().0, 70 | spend.rk(), 71 | zkproof, 72 | self, 73 | |this, rk| { 74 | this.signatures 75 | .queue(((*rk).into(), *spend.spend_auth_sig(), &sighash)); 76 | true 77 | }, 78 | |this, proof, public_inputs| { 79 | this.spend_proofs.queue((proof, public_inputs.to_vec())); 80 | true 81 | }, 82 | ); 83 | if !consensus_rules_passed { 84 | return false; 85 | } 86 | } 87 | 88 | for output in bundle.shielded_outputs() { 89 | // Deserialize the ephemeral key 90 | let epk = match jubjub::ExtendedPoint::from_bytes(&output.ephemeral_key().0).into() { 91 | Some(p) => p, 92 | None => return false, 93 | }; 94 | 95 | // Deserialize the proof 96 | let zkproof = match groth16::Proof::read(&output.zkproof()[..]) { 97 | Ok(p) => p, 98 | Err(_) => return false, 99 | }; 100 | 101 | // Check the Output consensus rules, and batch its proof. 102 | let consensus_rules_passed = ctx.check_output( 103 | output.cv(), 104 | *output.cmu(), 105 | epk, 106 | zkproof, 107 | |proof, public_inputs| { 108 | self.output_proofs.queue((proof, public_inputs.to_vec())); 109 | true 110 | }, 111 | ); 112 | if !consensus_rules_passed { 113 | return false; 114 | } 115 | } 116 | 117 | // Check the whole-bundle consensus rules, and batch the binding signature. 118 | ctx.final_check(*bundle.value_balance(), |bvk| { 119 | self.signatures 120 | .queue((bvk.into(), bundle.authorization().binding_sig, &sighash)); 121 | true 122 | }) 123 | } 124 | 125 | /// Batch-validates the accumulated bundles. 126 | /// 127 | /// Returns `true` if every proof and signature in every bundle added to the batch 128 | /// validator is valid, or `false` if one or more are invalid. No attempt is made to 129 | /// figure out which of the accumulated bundles might be invalid; if that information 130 | /// is desired, construct separate [`BatchValidator`]s for sub-batches of the bundles. 131 | pub fn validate( 132 | self, 133 | spend_vk: &SpendVerifyingKey, 134 | output_vk: &OutputVerifyingKey, 135 | mut rng: R, 136 | ) -> bool { 137 | if !self.bundles_added { 138 | // An empty batch is always valid, but is not free to run; skip it. 139 | return true; 140 | } 141 | 142 | if let Err(e) = self.signatures.verify(&mut rng) { 143 | #[cfg(feature = "std")] 144 | tracing::debug!("Signature batch validation failed: {}", e); 145 | #[cfg(not(feature = "std"))] 146 | tracing::debug!("Signature batch validation failed: {:?}", e); 147 | return false; 148 | } 149 | 150 | #[cfg(feature = "multicore")] 151 | let verify_proofs = |batch: groth16::batch::Verifier, vk| batch.verify_multicore(vk); 152 | 153 | #[cfg(not(feature = "multicore"))] 154 | let mut verify_proofs = 155 | |batch: groth16::batch::Verifier, vk| batch.verify(&mut rng, vk); 156 | 157 | if verify_proofs(self.spend_proofs, &spend_vk.0).is_err() { 158 | tracing::debug!("Spend proof batch validation failed"); 159 | return false; 160 | } 161 | 162 | if verify_proofs(self.output_proofs, &output_vk.0).is_err() { 163 | tracing::debug!("Output proof batch validation failed"); 164 | return false; 165 | } 166 | 167 | true 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/verifier/single.rs: -------------------------------------------------------------------------------- 1 | use bellman::groth16::{verify_proof, Proof}; 2 | use bls12_381::Bls12; 3 | use redjubjub::{Binding, SpendAuth}; 4 | 5 | use super::SaplingVerificationContextInner; 6 | use crate::{ 7 | circuit::{PreparedOutputVerifyingKey, PreparedSpendVerifyingKey}, 8 | note::ExtractedNoteCommitment, 9 | value::ValueCommitment, 10 | }; 11 | 12 | /// A context object for verifying the Sapling components of a single Zcash transaction. 13 | pub struct SaplingVerificationContext { 14 | inner: SaplingVerificationContextInner, 15 | } 16 | 17 | impl SaplingVerificationContext { 18 | /// Construct a new context to be used with a single transaction. 19 | #[allow(clippy::new_without_default)] 20 | pub fn new() -> Self { 21 | SaplingVerificationContext { 22 | inner: SaplingVerificationContextInner::new(), 23 | } 24 | } 25 | 26 | /// Perform consensus checks on a Sapling SpendDescription, while 27 | /// accumulating its value commitment inside the context for later use. 28 | #[allow(clippy::too_many_arguments)] 29 | pub fn check_spend( 30 | &mut self, 31 | cv: &ValueCommitment, 32 | anchor: bls12_381::Scalar, 33 | nullifier: &[u8; 32], 34 | rk: redjubjub::VerificationKey, 35 | sighash_value: &[u8; 32], 36 | spend_auth_sig: redjubjub::Signature, 37 | zkproof: Proof, 38 | verifying_key: &PreparedSpendVerifyingKey, 39 | ) -> bool { 40 | self.inner.check_spend( 41 | cv, 42 | anchor, 43 | nullifier, 44 | &rk, 45 | zkproof, 46 | &mut (), 47 | |_, rk| rk.verify(sighash_value, &spend_auth_sig).is_ok(), 48 | |_, proof, public_inputs| { 49 | verify_proof(&verifying_key.0, &proof, &public_inputs[..]).is_ok() 50 | }, 51 | ) 52 | } 53 | 54 | /// Perform consensus checks on a Sapling OutputDescription, while 55 | /// accumulating its value commitment inside the context for later use. 56 | pub fn check_output( 57 | &mut self, 58 | cv: &ValueCommitment, 59 | cmu: ExtractedNoteCommitment, 60 | epk: jubjub::ExtendedPoint, 61 | zkproof: Proof, 62 | verifying_key: &PreparedOutputVerifyingKey, 63 | ) -> bool { 64 | self.inner 65 | .check_output(cv, cmu, epk, zkproof, |proof, public_inputs| { 66 | verify_proof(&verifying_key.0, &proof, &public_inputs[..]).is_ok() 67 | }) 68 | } 69 | 70 | /// Perform consensus checks on the valueBalance and bindingSig parts of a 71 | /// Sapling transaction. All SpendDescriptions and OutputDescriptions must 72 | /// have been checked before calling this function. 73 | pub fn final_check>( 74 | &self, 75 | value_balance: V, 76 | sighash_value: &[u8; 32], 77 | binding_sig: redjubjub::Signature, 78 | ) -> bool { 79 | self.inner.final_check(value_balance, |bvk| { 80 | bvk.verify(sighash_value, &binding_sig).is_ok() 81 | }) 82 | } 83 | } 84 | --------------------------------------------------------------------------------