From 324eaf01e07aff2930ad1eb3872a6e468bbfa2f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 02:21:58 +0000 Subject: [PATCH 01/30] Bump auguwu/clippy-action from 1.3.0 to 1.4.0 Bumps [auguwu/clippy-action](https://github.com/auguwu/clippy-action) from 1.3.0 to 1.4.0. - [Release notes](https://github.com/auguwu/clippy-action/releases) - [Commits](https://github.com/auguwu/clippy-action/compare/1.3.0...1.4.0) --- updated-dependencies: - dependency-name: auguwu/clippy-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6944b140..8c57b34a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,7 +63,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Run Clippy - uses: auguwu/clippy-action@1.3.0 + uses: auguwu/clippy-action@1.4.0 with: token: ${{ secrets.GITHUB_TOKEN }} working-directory: ${{ inputs.target }} From 1aa51971dadc9459293278a8efb96565990ddaa2 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Mon, 12 Aug 2024 15:23:27 -0600 Subject: [PATCH 02/30] Release sapling-crypto version 0.2.0 This is a maintenance release to upgrade the `incrementalmerkletree` dependency to version 0.6. --- CHANGELOG.md | 5 +++++ Cargo.lock | 8 +++++--- Cargo.toml | 6 +++--- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71d33b03..715d846d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this library adheres to Rust's notion of ## [Unreleased] +## [0.2.0] - 2024-08-12 + +### Changed +- Updated to `incrementalmerkletree` version `0.6`. + ## [0.1.3] - 2024-03-25 ### Added diff --git a/Cargo.lock b/Cargo.lock index 49b66c0f..edcc4538 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -625,12 +625,14 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "incrementalmerkletree" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "361c467824d4d9d4f284be4b2608800839419dccc4d4608f28345237fe354623" +checksum = "75346da3bd8e3d8891d02508245ed2df34447ca6637e343829f8d08986e9cde2" dependencies = [ "either", "proptest", + "rand", + "rand_core", ] [[package]] @@ -1271,7 +1273,7 @@ dependencies = [ [[package]] name = "sapling-crypto" -version = "0.1.3" +version = "0.2.0" dependencies = [ "aes", "bellman", diff --git a/Cargo.toml b/Cargo.toml index 0732624f..1627bfbe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sapling-crypto" -version = "0.1.3" +version = "0.2.0" authors = [ "Sean Bowe ", "Jack Grigg ", @@ -50,7 +50,7 @@ tracing = "0.1" # Note Commitment Trees bitvec = "1" -incrementalmerkletree = { version = "0.5", features = ["legacy-api"] } +incrementalmerkletree = { version = "0.6", features = ["legacy-api"] } # Note encryption zcash_note_encryption = { version = "0.4", features = ["pre-zip-212"] } @@ -72,7 +72,7 @@ zip32 = "0.1" [dev-dependencies] chacha20poly1305 = "0.10" criterion = "0.4" -incrementalmerkletree = { version = "0.5", features = ["legacy-api", "test-dependencies"] } +incrementalmerkletree = { version = "0.6", features = ["legacy-api", "test-dependencies"] } proptest = "1" rand_xorshift = "0.3" From b1ad3694ee13a2fc5d291ad04721a6252da0993c Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 26 Sep 2024 15:24:47 -0600 Subject: [PATCH 03/30] Update to incrementalmerkletree version 0.7 --- CHANGELOG.md | 3 +++ Cargo.lock | 4 ++-- Cargo.toml | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 715d846d..43444d0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this library adheres to Rust's notion of ## [Unreleased] +### Changed +- Updated to `incrementalmerkletree` version `0.7`. + ## [0.2.0] - 2024-08-12 ### Changed diff --git a/Cargo.lock b/Cargo.lock index edcc4538..5ad07f87 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -625,9 +625,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "incrementalmerkletree" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75346da3bd8e3d8891d02508245ed2df34447ca6637e343829f8d08986e9cde2" +checksum = "d45063fbc4b0a37837f6bfe0445f269d13d730ad0aa3b5a7f74aa7bf27a0f4df" dependencies = [ "either", "proptest", diff --git a/Cargo.toml b/Cargo.toml index 1627bfbe..a8c28c3b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,7 +50,7 @@ tracing = "0.1" # Note Commitment Trees bitvec = "1" -incrementalmerkletree = { version = "0.6", features = ["legacy-api"] } +incrementalmerkletree = { version = "0.7", features = ["legacy-api"] } # Note encryption zcash_note_encryption = { version = "0.4", features = ["pre-zip-212"] } @@ -72,7 +72,7 @@ zip32 = "0.1" [dev-dependencies] chacha20poly1305 = "0.10" criterion = "0.4" -incrementalmerkletree = { version = "0.6", features = ["legacy-api", "test-dependencies"] } +incrementalmerkletree = { version = "0.7", features = ["legacy-api", "test-dependencies"] } proptest = "1" rand_xorshift = "0.3" From 2f6a08212cbd151dc55c48d586cf5636e45632c8 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Wed, 2 Oct 2024 11:18:18 -0600 Subject: [PATCH 04/30] Release sapling-crypto version 0.3.0 --- CHANGELOG.md | 2 ++ Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43444d0b..44538b3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this library adheres to Rust's notion of ## [Unreleased] +## [0.3.0] - 2024-10-02 + ### Changed - Updated to `incrementalmerkletree` version `0.7`. diff --git a/Cargo.lock b/Cargo.lock index 5ad07f87..b5ab48dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1273,7 +1273,7 @@ dependencies = [ [[package]] name = "sapling-crypto" -version = "0.2.0" +version = "0.3.0" dependencies = [ "aes", "bellman", diff --git a/Cargo.toml b/Cargo.toml index a8c28c3b..b68f8d26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sapling-crypto" -version = "0.2.0" +version = "0.3.0" authors = [ "Sean Bowe ", "Jack Grigg ", From a8fb276252894cf5d0e97e289f5509cc89bad095 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sat, 12 Oct 2024 19:42:50 +0000 Subject: [PATCH 05/30] Fix `OutputProver::prepare_circuit` API to be usable outside the crate The public APIs that produced `esk` all used the `EphemeralSecretKey` type, but that could only be converted to a `jubjub::Scalar` inside the crate. We now use the type-safe wrapper consistently. --- CHANGELOG.md | 5 +++++ src/builder.rs | 2 +- src/prover.rs | 12 +++++++----- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44538b3a..f55bbc2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this library adheres to Rust's notion of ## [Unreleased] +### Fixed +- `sapling_crypto::prover::OutputProver::prepare_circuit` now takes `esk` as an + `sapling_crypto::keys::EphemeralSecretKey`, matching the existing public APIs + that expose it. + ## [0.3.0] - 2024-10-02 ### Changed diff --git a/src/builder.rs b/src/builder.rs index 80dcf814..2b32c50b 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -369,7 +369,7 @@ impl PreparedOutputInfo { // Prepare the circuit that will be used to construct the proof. let zkproof = Pr::prepare_circuit( - encryptor.esk().0, + encryptor.esk(), self.note.recipient(), self.note.rcm(), self.note.value(), diff --git a/src/prover.rs b/src/prover.rs index d9b874c4..f5e2ad7f 100644 --- a/src/prover.rs +++ b/src/prover.rs @@ -7,6 +7,7 @@ use rand_core::RngCore; use crate::{ bundle::GrothProofBytes, circuit::{self, GROTH_PROOF_SIZE}, + keys::EphemeralSecretKey, value::{NoteValue, ValueCommitTrapdoor}, MerklePath, }; @@ -56,7 +57,7 @@ pub trait OutputProver { /// /// Returns `None` if `diversifier` is not a valid Sapling diversifier. fn prepare_circuit( - esk: jubjub::Fr, + esk: &EphemeralSecretKey, payment_address: PaymentAddress, rcm: jubjub::Fr, value: NoteValue, @@ -136,7 +137,7 @@ impl OutputProver for OutputParameters { type Proof = Proof; fn prepare_circuit( - esk: jubjub::Fr, + esk: &EphemeralSecretKey, payment_address: PaymentAddress, rcm: jubjub::Fr, value: NoteValue, @@ -153,7 +154,7 @@ impl OutputProver for OutputParameters { value_commitment_opening: Some(value_commitment_opening), payment_address: Some(payment_address), commitment_randomness: Some(rcm), - esk: Some(esk), + esk: Some(esk.0), } } @@ -179,6 +180,7 @@ pub mod mock { use crate::{ bundle::GrothProofBytes, circuit::{self, ValueCommitmentOpening, GROTH_PROOF_SIZE}, + keys::EphemeralSecretKey, value::{NoteValue, ValueCommitTrapdoor}, Diversifier, MerklePath, PaymentAddress, ProofGenerationKey, Rseed, }; @@ -235,7 +237,7 @@ pub mod mock { type Proof = GrothProofBytes; fn prepare_circuit( - esk: jubjub::Fr, + esk: &EphemeralSecretKey, payment_address: PaymentAddress, rcm: jubjub::Fr, value: NoteValue, @@ -248,7 +250,7 @@ pub mod mock { }), payment_address: Some(payment_address), commitment_randomness: Some(rcm), - esk: Some(esk), + esk: Some(esk.0), } } From 6527e792e428c9e47e87344e297e0a178bd95bd9 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sat, 12 Oct 2024 01:17:18 +0000 Subject: [PATCH 06/30] Broaden `Authorization`s from `SpendDescriptionV5::into_spend_description` This enables the method to be used with bundles that have spend auth signatures and proofs, but not yet binding signatures. --- CHANGELOG.md | 5 +++++ src/bundle.rs | 7 +++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f55bbc2d..95bd5e20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,11 @@ and this library adheres to Rust's notion of `sapling_crypto::keys::EphemeralSecretKey`, matching the existing public APIs that expose it. +### Changed +- `sapling_crypto::bundle::SpendDescriptionV5::into_spend_description` now + supports any `Authorization` for which the `SpendDescription` itself is fully + authorized. + ## [0.3.0] - 2024-10-02 ### Changed diff --git a/src/bundle.rs b/src/bundle.rs index c8fedbbc..7ade87ef 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -298,12 +298,15 @@ impl SpendDescriptionV5 { Self { cv, nullifier, rk } } - pub fn into_spend_description( + pub fn into_spend_description( self, anchor: bls12_381::Scalar, zkproof: GrothProofBytes, spend_auth_sig: redjubjub::Signature, - ) -> SpendDescription { + ) -> SpendDescription + where + A: Authorization>, + { SpendDescription { cv: self.cv, anchor, From 7696219bf3740f6d979b2b7362e6c11154004010 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 28 Nov 2024 09:20:24 +0000 Subject: [PATCH 07/30] Implement PCZT support --- CHANGELOG.md | 11 ++ Cargo.lock | 39 ++++- Cargo.toml | 3 + src/builder.rs | 305 +++++++++++++++++++++++++++++++-------- src/bundle.rs | 10 ++ src/keys.rs | 4 +- src/lib.rs | 1 + src/pczt.rs | 294 +++++++++++++++++++++++++++++++++++++ src/pczt/io_finalizer.rs | 91 ++++++++++++ src/pczt/parse.rs | 294 +++++++++++++++++++++++++++++++++++++ src/pczt/prover.rs | 105 ++++++++++++++ src/pczt/signer.rs | 38 +++++ src/pczt/tx_extractor.rs | 165 +++++++++++++++++++++ src/tree.rs | 4 + src/value/sums.rs | 14 ++ 15 files changed, 1310 insertions(+), 68 deletions(-) create mode 100644 src/pczt.rs create mode 100644 src/pczt/io_finalizer.rs create mode 100644 src/pczt/parse.rs create mode 100644 src/pczt/prover.rs create mode 100644 src/pczt/signer.rs create mode 100644 src/pczt/tx_extractor.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 95bd5e20..c999a9ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,12 +7,23 @@ and this library adheres to Rust's notion of ## [Unreleased] +### Added +- Support for Partially-Created Zcash Transactions: + - `sapling_crypto::builder::Builder::build_for_pczt` + - `sapling_crypto::pczt` module. +- `sapling_crypto::bundle::EffectsOnly` +- `sapling_crypto::keys`: + - `SpendAuthorizingKey::to_bytes` + - `SpendValidatingKey::to_bytes` +- `sapling_crypto::value::ValueSum::to_raw` + ### Fixed - `sapling_crypto::prover::OutputProver::prepare_circuit` now takes `esk` as an `sapling_crypto::keys::EphemeralSecretKey`, matching the existing public APIs that expose it. ### Changed +- `sapling_crypto::builder::Error` has a new variant `PcztRequiresZip212`. - `sapling_crypto::bundle::SpendDescriptionV5::into_spend_description` now supports any `Authorization` for which the `SpendDescription` itself is fully authorized. diff --git a/Cargo.lock b/Cargo.lock index b5ab48dc..2f9358b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -566,6 +566,18 @@ dependencies = [ "wasi", ] +[[package]] +name = "getset" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f636605b743120a8d32ed92fc27b6cde1a769f8f936c065151eb66f88ded513c" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "gimli" version = "0.28.1" @@ -1029,6 +1041,28 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.70" @@ -1287,6 +1321,7 @@ dependencies = [ "document-features", "ff", "fpe", + "getset", "group", "hex", "incrementalmerkletree", @@ -1369,9 +1404,9 @@ checksum = "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb" [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "symbolic-common" diff --git a/Cargo.toml b/Cargo.toml index b68f8d26..b74cbcf1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,9 @@ jubjub = "0.10" redjubjub = "0.7" zcash_spec = "0.1" +# Boilerplate +getset = "0.1" + # Circuits bellman = { version = "0.14", default-features = false, features = ["groth16"] } diff --git a/src/builder.rs b/src/builder.rs index 2b32c50b..5b3bc59b 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1,20 +1,22 @@ //! Types and functions for building Sapling transaction components. use core::fmt; -use std::{iter, marker::PhantomData}; +use std::{collections::BTreeMap, iter, marker::PhantomData}; use group::ff::Field; use incrementalmerkletree::Position; use rand::{seq::SliceRandom, RngCore}; use rand_core::CryptoRng; use redjubjub::{Binding, SpendAuth}; +use zcash_note_encryption::EphemeralKeyBytes; use crate::{ bundle::{ Authorization, Authorized, Bundle, GrothProofBytes, OutputDescription, SpendDescription, }, circuit, - keys::{OutgoingViewingKey, SpendAuthorizingKey, SpendValidatingKey}, + keys::{EphemeralSecretKey, OutgoingViewingKey, SpendAuthorizingKey, SpendValidatingKey}, + note::ExtractedNoteCommitment, note_encryption::{sapling_note_encryption, Zip212Enforcement}, prover::{OutputProver, SpendProver}, util::generate_random_rseed_internal, @@ -22,8 +24,8 @@ use crate::{ CommitmentSum, NoteValue, TrapdoorSum, ValueCommitTrapdoor, ValueCommitment, ValueSum, }, zip32::ExtendedSpendingKey, - Anchor, Diversifier, MerklePath, Node, Note, PaymentAddress, ProofGenerationKey, SaplingIvk, - NOTE_COMMITMENT_TREE_DEPTH, + Anchor, Diversifier, MerklePath, Node, Note, Nullifier, PaymentAddress, ProofGenerationKey, + SaplingIvk, NOTE_COMMITMENT_TREE_DEPTH, }; /// If there are any shielded inputs, always have at least two shielded outputs, padding @@ -121,6 +123,8 @@ pub enum Error { InvalidExternalSignature, /// A bundle could not be built because required signatures were missing. MissingSignatures, + /// [`Builder::build_for_pczt`] requires [`Zip212Enforcement::On`]. + PcztRequiresZip212, SpendProof, /// The bundle being constructed violated the construction rules for the requested bundle type. BundleTypeNotSatisfiable, @@ -138,6 +142,9 @@ impl fmt::Display for Error { Error::InvalidAmount => write!(f, "Invalid amount"), Error::InvalidExternalSignature => write!(f, "External signature was invalid"), Error::MissingSignatures => write!(f, "Required signatures were missing during build"), + Error::PcztRequiresZip212 => { + write!(f, "PCZTs require that ZIP 212 is enforced for outputs") + } Error::SpendProof => write!(f, "Failed to create Sapling spend proof"), Error::BundleTypeNotSatisfiable => { f.write_str("Bundle structure did not conform to requested bundle type.") @@ -226,15 +233,18 @@ struct PreparedSpendInfo { } impl PreparedSpendInfo { - fn build( - self, + fn build_inner( + &self, mut rng: R, - ) -> Result>, Error> { + ) -> ( + ValueCommitment, + Nullifier, + redjubjub::VerificationKey, + jubjub::Scalar, + ) { // Construct the value commitment. let alpha = jubjub::Fr::random(&mut rng); let cv = ValueCommitment::derive(self.note.value(), self.rcv.clone()); - let node = Node::from_cmu(&self.note.cmu()); - let anchor = *self.merkle_path.root(node).inner(); let ak = self.proof_generation_key.ak.clone(); @@ -247,6 +257,21 @@ impl PreparedSpendInfo { .expect("Sapling note commitment tree position must fit into a u64"), ); + (cv, nullifier, rk, alpha) + } + + fn build( + self, + rng: R, + ) -> Result>, Error> { + let (cv, nullifier, rk, alpha) = self.build_inner(rng); + + // Construct the value commitment. + let node = Node::from_cmu(&self.note.cmu()); + let anchor = *self.merkle_path.root(node).inner(); + + let ak = self.proof_generation_key.ak.clone(); + let zkproof = Pr::prepare_circuit( self.proof_generation_key, *self.note.recipient().diversifier(), @@ -271,6 +296,28 @@ impl PreparedSpendInfo { }, )) } + + fn into_pczt(self, rng: R) -> crate::pczt::Spend { + let (cv, nullifier, rk, alpha) = self.build_inner(rng); + + crate::pczt::Spend { + cv, + nullifier, + rk, + zkproof: None, + spend_auth_sig: None, + recipient: Some(self.note.recipient()), + value: Some(self.note.value()), + rseed: Some(*self.note.rseed()), + rcv: Some(self.rcv), + proof_generation_key: Some(self.proof_generation_key), + witness: Some(self.merkle_path), + alpha: Some(alpha), + zip32_derivation: None, + dummy_ask: self.dummy_ask, + proprietary: BTreeMap::new(), + } + } } /// A struct containing the information required in order to construct a @@ -358,23 +405,24 @@ struct PreparedOutputInfo { } impl PreparedOutputInfo { - fn build( - self, + fn build_inner( + &self, + zkproof: impl FnOnce(&EphemeralSecretKey) -> P, rng: &mut R, - ) -> OutputDescription { + ) -> ( + ValueCommitment, + ExtractedNoteCommitment, + EphemeralKeyBytes, + [u8; 580], + [u8; 80], + P, + ) { let encryptor = sapling_note_encryption::(self.ovk, self.note.clone(), self.memo, rng); // Construct the value commitment. let cv = ValueCommitment::derive(self.note.value(), self.rcv.clone()); - // Prepare the circuit that will be used to construct the proof. - let zkproof = Pr::prepare_circuit( - encryptor.esk(), - self.note.recipient(), - self.note.rcm(), - self.note.value(), - self.rcv, - ); + let zkproof = zkproof(encryptor.esk()); let cmu = self.note.cmu(); @@ -383,7 +431,7 @@ impl PreparedOutputInfo { let epk = encryptor.epk(); - OutputDescription::from_parts( + ( cv, cmu, epk.to_bytes(), @@ -392,6 +440,62 @@ impl PreparedOutputInfo { zkproof, ) } + + fn build( + self, + rng: &mut R, + ) -> OutputDescription { + let (cv, cmu, ephemeral_key, enc_ciphertext, out_ciphertext, zkproof) = self.build_inner( + |esk| { + Pr::prepare_circuit( + esk, + self.note.recipient(), + self.note.rcm(), + self.note.value(), + self.rcv.clone(), + ) + }, + rng, + ); + + OutputDescription::from_parts( + cv, + cmu, + ephemeral_key, + enc_ciphertext, + out_ciphertext, + zkproof, + ) + } + + fn into_pczt(self, rng: &mut R) -> crate::pczt::Output { + let (cv, cmu, ephemeral_key, enc_ciphertext, out_ciphertext, _) = + self.build_inner(|_| (), rng); + + let rseed = match self.note.rseed() { + crate::Rseed::BeforeZip212(_) => { + panic!("Builder::build_for_pczt should prevent pre-ZIP 212 outputs") + } + crate::Rseed::AfterZip212(rseed) => Some(*rseed), + }; + + crate::pczt::Output { + cv, + cmu, + ephemeral_key, + enc_ciphertext, + out_ciphertext, + zkproof: None, + recipient: Some(self.note.recipient()), + value: Some(self.note.value()), + rseed, + rcv: Some(self.rcv), + // TODO: Save this? + ock: None, + zip32_derivation: None, + proprietary: BTreeMap::new(), + } + } } /// Metadata about a transaction created by a [`Builder`]. @@ -549,18 +653,132 @@ impl Builder { self.outputs, ) } + + /// Builds a bundle containing the given spent notes and outputs along with their + /// metadata, for inclusion in a PCZT. + pub fn build_for_pczt( + self, + rng: impl RngCore, + ) -> Result<(crate::pczt::Bundle, SaplingMetadata), Error> { + match self.zip212_enforcement { + Zip212Enforcement::Off | Zip212Enforcement::GracePeriod => { + Err(Error::PcztRequiresZip212) + } + Zip212Enforcement::On => build_bundle::<_, (), (), _>( + rng, + self.bundle_type, + Zip212Enforcement::On, + self.anchor, + self.spends, + self.outputs, + |spend_infos, output_infos, value_sum, tx_metadata, mut rng| { + // Create the PCZT Spends and Outputs. + let spends = spend_infos + .into_iter() + .map(|a| a.into_pczt(&mut rng)) + .collect::>(); + let outputs = output_infos + .into_iter() + .map(|a| a.into_pczt(&mut rng)) + .collect::>(); + + Ok(( + crate::pczt::Bundle { + spends, + outputs, + value_sum, + anchor: self.anchor, + bsk: None, + }, + tx_metadata, + )) + }, + ), + } + } } /// Constructs a new Sapling transaction bundle of the given type from the specified set of spends /// and outputs. pub fn bundle>( - mut rng: R, + rng: R, bundle_type: BundleType, zip212_enforcement: Zip212Enforcement, anchor: Anchor, spends: Vec, outputs: Vec, ) -> Result, SaplingMetadata)>, Error> { + build_bundle::<_, SP, OP, _>( + rng, + bundle_type, + zip212_enforcement, + anchor, + spends, + outputs, + |spend_infos, output_infos, value_balance, tx_metadata, mut rng| { + let value_balance_i64 = + i64::try_from(value_balance).map_err(|_| Error::InvalidAmount)?; + + // Compute the transaction binding signing key. + let bsk = { + let spends: TrapdoorSum = spend_infos.iter().map(|spend| &spend.rcv).sum(); + let outputs: TrapdoorSum = output_infos.iter().map(|output| &output.rcv).sum(); + (spends - outputs).into_bsk() + }; + + // Create the unauthorized Spend and Output descriptions. + let shielded_spends = spend_infos + .into_iter() + .map(|a| a.build::(&mut rng)) + .collect::, _>>()?; + let shielded_outputs = output_infos + .into_iter() + .map(|a| a.build::(&mut rng)) + .collect::>(); + + // Verify that bsk and bvk are consistent. + let bvk = { + let spends = shielded_spends + .iter() + .map(|spend| spend.cv()) + .sum::(); + let outputs = shielded_outputs + .iter() + .map(|output| output.cv()) + .sum::(); + (spends - outputs).into_bvk(value_balance_i64) + }; + assert_eq!(redjubjub::VerificationKey::from(&bsk), bvk); + + Ok(Bundle::from_parts( + shielded_spends, + shielded_outputs, + V::try_from(value_balance_i64).map_err(|_| Error::InvalidAmount)?, + InProgress { + sigs: Unsigned { bsk }, + _proof_state: PhantomData::default(), + }, + ) + .map(|b| (b, tx_metadata))) + }, + ) +} + +fn build_bundle( + mut rng: R, + bundle_type: BundleType, + zip212_enforcement: Zip212Enforcement, + anchor: Anchor, + spends: Vec, + outputs: Vec, + finisher: impl FnOnce( + Vec, + Vec, + ValueSum, + SaplingMetadata, + R, + ) -> Result, +) -> Result { match bundle_type { BundleType::Transactional { .. } => { for spend in &spends { @@ -640,13 +858,6 @@ pub fn bundle>( }) .collect::>(); - // Compute the transaction binding signing key. - let bsk = { - let spends: TrapdoorSum = spend_infos.iter().map(|spend| &spend.rcv).sum(); - let outputs: TrapdoorSum = output_infos.iter().map(|output| &output.rcv).sum(); - (spends - outputs).into_bsk() - }; - // Compute the Sapling value balance of the bundle for comparison to `bvk` and `bsk` let input_total = spend_infos .iter() @@ -658,42 +869,8 @@ pub fn bundle>( .try_fold(input_total, |balance, output| { (balance - output.note.value()).ok_or(Error::InvalidAmount) })?; - let value_balance_i64 = i64::try_from(value_balance).map_err(|_| Error::InvalidAmount)?; - // Create the unauthorized Spend and Output descriptions. - let shielded_spends = spend_infos - .into_iter() - .map(|a| a.build::(&mut rng)) - .collect::, _>>()?; - let shielded_outputs = output_infos - .into_iter() - .map(|a| a.build::(&mut rng)) - .collect::>(); - - // Verify that bsk and bvk are consistent. - let bvk = { - let spends = shielded_spends - .iter() - .map(|spend| spend.cv()) - .sum::(); - let outputs = shielded_outputs - .iter() - .map(|output| output.cv()) - .sum::(); - (spends - outputs).into_bvk(value_balance_i64) - }; - assert_eq!(redjubjub::VerificationKey::from(&bsk), bvk); - - Ok(Bundle::from_parts( - shielded_spends, - shielded_outputs, - V::try_from(value_balance_i64).map_err(|_| Error::InvalidAmount)?, - InProgress { - sigs: Unsigned { bsk }, - _proof_state: PhantomData::default(), - }, - ) - .map(|b| (b, tx_metadata))) + finisher(spend_infos, output_infos, value_balance, tx_metadata, rng) } /// Type alias for an in-progress bundle that has no proofs or signatures. diff --git a/src/bundle.rs b/src/bundle.rs index 7ade87ef..b015f133 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -25,6 +25,16 @@ pub trait Authorization: Debug { type AuthSig: Clone + Debug; } +/// Marker type for a bundle that contains no authorizing data. +#[derive(Debug)] +pub struct EffectsOnly; + +impl Authorization for EffectsOnly { + type SpendProof = (); + type OutputProof = (); + type AuthSig = (); +} + /// Authorizing data for a bundle of Sapling spends and outputs, ready to be committed to /// the ledger. #[derive(Debug, Copy, Clone)] diff --git a/src/keys.rs b/src/keys.rs index 06348c74..b636785d 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -108,7 +108,7 @@ impl SpendAuthorizingKey { } /// Converts this spend authorizing key to its serialized form. - pub(crate) fn to_bytes(&self) -> [u8; 32] { + pub fn to_bytes(&self) -> [u8; 32] { <[u8; 32]>::from(self.0) } @@ -193,7 +193,7 @@ impl SpendValidatingKey { /// Converts this spend validating key to its serialized form, /// `LEBS2OSP_256(repr_J(ak))`. - pub(crate) fn to_bytes(&self) -> [u8; 32] { + pub fn to_bytes(&self) -> [u8; 32] { <[u8; 32]>::from(self.0) } diff --git a/src/lib.rs b/src/lib.rs index 5ffc49b7..654b767a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,6 +25,7 @@ pub mod group_hash; pub mod keys; pub mod note; pub mod note_encryption; +pub mod pczt; pub mod pedersen_hash; pub mod prover; mod spec; diff --git a/src/pczt.rs b/src/pczt.rs new file mode 100644 index 00000000..5dd27b45 --- /dev/null +++ b/src/pczt.rs @@ -0,0 +1,294 @@ +//! PCZT support for Sapling. + +use core::fmt; +use std::collections::BTreeMap; + +use getset::Getters; +use redjubjub::{Binding, SpendAuth}; +use zcash_note_encryption::{ + EphemeralKeyBytes, OutgoingCipherKey, ENC_CIPHERTEXT_SIZE, OUT_CIPHERTEXT_SIZE, +}; +use zip32::ChildIndex; + +use crate::{ + bundle::GrothProofBytes, + keys::SpendAuthorizingKey, + note::ExtractedNoteCommitment, + value::{NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum}, + Anchor, MerklePath, Nullifier, PaymentAddress, ProofGenerationKey, Rseed, +}; + +mod parse; +pub use parse::ParseError; + +mod io_finalizer; +pub use io_finalizer::IoFinalizerError; + +mod prover; +pub use prover::ProverError; + +mod signer; +pub use signer::SignerError; + +mod tx_extractor; +pub use tx_extractor::{TxExtractorError, Unbound}; + +/// PCZT fields that are specific to producing the transaction's Sapling bundle (if any). +/// +/// This struct is for representing Sapling in a partially-created transaction. If you +/// have a fully-created transaction, use [the regular `Bundle` struct]. +/// +/// [the regular `Bundle` struct]: crate::Bundle +#[derive(Debug, Getters)] +#[getset(get = "pub")] +pub struct Bundle { + /// The Sapling spends in this bundle. + /// + /// Entries are added by the Constructor, and modified by an Updater, IO Finalizer, + /// Prover, Signer, Combiner, or Spend Finalizer. + pub(crate) spends: Vec, + + /// The Sapling outputs in this bundle. + /// + /// Entries are added by the Constructor, and modified by an Updater, IO Finalizer, + /// Prover, Signer, Combiner, or Spend Finalizer. + pub(crate) outputs: Vec, + + /// The net value of Sapling `spends` minus `outputs`. + /// + /// This is initialized by the Creator, and updated by the Constructor as spends or + /// outputs are added to the PCZT. It enables per-spend and per-output values to be + /// redacted from the PCZT after they are no longer necessary. + pub(crate) value_sum: ValueSum, + + /// The Sapling anchor for this transaction. + /// + /// Set by the Creator. + pub(crate) anchor: Anchor, + + /// The Sapling binding signature signing key. + /// + /// - This is `None` until it is set by the IO Finalizer. + /// - The Transaction Extractor uses this to produce the binding signature. + pub(crate) bsk: Option>, +} + +impl Bundle { + /// Returns a mutable reference to the spends in this bundle. + /// + /// This is used by Signers to apply signatures with [`Spend::sign`]. + pub fn spends_mut(&mut self) -> &mut [Spend] { + &mut self.spends + } +} + +/// Information about a Sapling spend within a transaction. +/// +/// This struct is for representing Sapling spends in a partially-created transaction. If +/// you have a fully-created transaction, use [the regular `SpendDescription` struct]. +/// +/// [the regular `SpendDescription` struct]: crate::bundle::SpendDescription +#[derive(Debug, Getters)] +#[getset(get = "pub")] +pub struct Spend { + /// A commitment to the value consumed by this spend. + pub(crate) cv: ValueCommitment, + + /// The nullifier of the note being spent. + pub(crate) nullifier: Nullifier, + + /// The randomized verification key for the note being spent. + pub(crate) rk: redjubjub::VerificationKey, + + /// The Spend proof. + /// + /// This is set by the Prover. + pub(crate) zkproof: Option, + + /// The spend authorization signature. + /// + /// This is set by the Signer. + pub(crate) spend_auth_sig: Option>, + + /// The address that received the note being spent. + /// + /// - This is set by the Constructor (or Updater?). + /// - This is required by the Prover. + pub(crate) recipient: Option, + + /// The value of the input being spent. + /// + /// This may be used by Signers to verify that the value matches `cv`, and to confirm + /// the values and change involved in the transaction. + /// + /// This exposes the input value to all participants. For Signers who don't need this + /// information, or after signatures have been applied, this can be redacted. + pub(crate) value: Option, + + /// The seed randomness for the note being spent. + /// + /// - This is set by the Constructor. + /// - This is required by the Prover. + pub(crate) rseed: Option, + + /// The value commitment randomness. + /// + /// - This is set by the Constructor. + /// - The IO Finalizer compresses it into the `bsk`. + /// - This is required by the Prover. + /// - This may be used by Signers to verify that the value correctly matches `cv`. + /// + /// This opens `cv` for all participants. For Signers who don't need this information, + /// or after proofs / signatures have been applied, this can be redacted. + pub(crate) rcv: Option, + + /// The proof generation key `(ak, nsk)` corresponding to the recipient that received + /// the note being spent. + /// + /// - This is set by the Updater. + /// - This is required by the Prover. + pub(crate) proof_generation_key: Option, + + /// A witness from the note to the bundle's anchor. + /// + /// - This is set by the Updater. + /// - This is required by the Prover. + pub(crate) witness: Option, + + /// The spend authorization randomizer. + /// + /// - This is chosen by the Constructor. + /// - This is required by the Signer for creating `spend_auth_sig`, and may be used to + /// validate `rk`. + /// - After`zkproof` / `spend_auth_sig` has been set, this can be redacted. + pub(crate) alpha: Option, + + /// The ZIP 32 derivation path at which the spending key can be found for the note + /// being spent. + pub(crate) zip32_derivation: Option, + + /// The spend authorizing key for this spent note, if it is a dummy note. + /// + /// - This is chosen by the Constructor. + /// - This is required by the IO Finalizer, and is cleared by it once used. + /// - Signers MUST reject PCZTs that contain `dummy_ask` values. + pub(crate) dummy_ask: Option, + + /// Proprietary fields related to the note being spent. + pub(crate) proprietary: BTreeMap>, +} + +/// Information about a Sapling output within a transaction. +/// +/// This struct is for representing Sapling outputs in a partially-created transaction. If +/// you have a fully-created transaction, use [the regular `OutputDescription` struct]. +/// +/// [the regular `OutputDescription` struct]: crate::bundle::OutputDescription +#[derive(Getters)] +#[getset(get = "pub")] +pub struct Output { + /// A commitment to the value created by this output. + pub(crate) cv: ValueCommitment, + + /// A commitment to the new note being created. + pub(crate) cmu: ExtractedNoteCommitment, + + /// The ephemeral key used to encrypt the note plaintext. + pub(crate) ephemeral_key: EphemeralKeyBytes, + + /// The encrypted note plaintext for the output. + /// + /// Once we have memo bundles, we will be able to set memos independently of Outputs. + /// For now, the Constructor sets both at the same time. + pub(crate) enc_ciphertext: [u8; ENC_CIPHERTEXT_SIZE], + + /// The encrypted output plaintext for the output. + pub(crate) out_ciphertext: [u8; OUT_CIPHERTEXT_SIZE], + + /// The Output proof. + /// + /// This is set by the Prover. + pub(crate) zkproof: Option, + + /// The address that will receive the output. + /// + /// - This is set by the Constructor. + /// - This is required by the Prover. + /// - The Signer can use `recipient` and `rseed` (if present) to verify that + /// `enc_ciphertext` is correctly encrypted (and contains a note plaintext matching + /// the public commitments), and to confirm the value of the memo. + pub(crate) recipient: Option, + + /// The value of the output. + /// + /// This may be used by Signers to verify that the value matches `cv`, and to confirm + /// the values and change involved in the transaction. + /// + /// This exposes the output value to all participants. For Signers who don't need this + /// information, or after signatures have been applied, this can be redacted. + pub(crate) value: Option, + + /// The seed randomness for the output. + /// + /// - This is set by the Constructor. + /// - This is required by the Prover. + /// - The Signer can use `recipient` and `rseed` (if present) to verify that + /// `enc_ciphertext` is correctly encrypted (and contains a note plaintext matching + /// the public commitments), and to confirm the value of the memo. + pub(crate) rseed: Option<[u8; 32]>, + + /// The value commitment randomness. + /// + /// - This is set by the Constructor. + /// - The IO Finalizer compresses it into the bsk. + /// - This is required by the Prover. + /// - This may be used by Signers to verify that the value correctly matches `cv`. + /// + /// This opens `cv` for all participants. For Signers who don't need this information, + /// or after proofs / signatures have been applied, this can be redacted. + pub(crate) rcv: Option, + + /// The `ock` value used to encrypt `out_ciphertext`. + /// + /// This enables Signers to verify that `out_ciphertext` is correctly encrypted. + /// + /// This may be `None` if the Constructor added the output using an OVK policy of + /// "None", to make the output unrecoverable from the chain by the sender. + pub(crate) ock: Option, + + /// The ZIP 32 derivation path at which the spending key can be found for the output. + pub(crate) zip32_derivation: Option, + + /// Proprietary fields related to the note being created. + pub(crate) proprietary: BTreeMap>, +} + +impl fmt::Debug for Output { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Output") + .field("cv", &self.cv) + .field("cmu", &self.cmu) + .field("ephemeral_key", &self.ephemeral_key) + .field("enc_ciphertext", &self.enc_ciphertext) + .field("out_ciphertext", &self.out_ciphertext) + .field("zkproof", &self.zkproof) + .field("recipient", &self.recipient) + .field("value", &self.value) + .field("rseed", &self.rseed) + .field("rcv", &self.rcv) + .field("zip32_derivation", &self.zip32_derivation) + .field("proprietary", &self.proprietary) + .finish_non_exhaustive() + } +} + +/// The ZIP 32 derivation path at which a key can be found. +#[derive(Debug, Getters, PartialEq, Eq)] +#[getset(get = "pub")] +pub struct Zip32Derivation { + /// The [ZIP 32 seed fingerprint](https://zips.z.cash/zip-0032#seed-fingerprints). + seed_fingerprint: [u8; 32], + + /// The sequence of indices corresponding to the shielded HD path. + derivation_path: Vec, +} diff --git a/src/pczt/io_finalizer.rs b/src/pczt/io_finalizer.rs new file mode 100644 index 00000000..723a4aa4 --- /dev/null +++ b/src/pczt/io_finalizer.rs @@ -0,0 +1,91 @@ +use rand::{CryptoRng, RngCore}; + +use crate::value::{CommitmentSum, TrapdoorSum}; + +use super::SignerError; + +impl super::Bundle { + /// Finalizes the IO for this bundle. + pub fn finalize_io( + &mut self, + sighash: [u8; 32], + mut rng: R, + ) -> Result<(), IoFinalizerError> { + // Compute the transaction binding signing key. + let bsk = { + let spend_rcvs = self + .spends + .iter() + .map(|spend| { + spend + .rcv + .as_ref() + .ok_or(IoFinalizerError::MissingValueCommitTrapdoor) + }) + .collect::, _>>()?; + + let output_rcvs = self + .outputs + .iter() + .map(|output| { + output + .rcv + .as_ref() + .ok_or(IoFinalizerError::MissingValueCommitTrapdoor) + }) + .collect::, _>>()?; + + let spends: TrapdoorSum = spend_rcvs.into_iter().sum(); + let outputs: TrapdoorSum = output_rcvs.into_iter().sum(); + (spends - outputs).into_bsk() + }; + + // Verify that bsk and bvk are consistent. + let bvk = { + let spends = self + .spends + .iter() + .map(|spend| spend.cv()) + .sum::(); + let outputs = self + .outputs + .iter() + .map(|output| output.cv()) + .sum::(); + (spends - outputs).into_bvk( + i64::try_from(self.value_sum).map_err(|_| IoFinalizerError::InvalidValueSum)?, + ) + }; + if redjubjub::VerificationKey::from(&bsk) != bvk { + return Err(IoFinalizerError::ValueCommitMismatch); + } + self.bsk = Some(bsk); + + // Add signatures to dummy spends. + for spend in self.spends.iter_mut() { + // The `Option::take` ensures we don't have any spend authorizing keys in the + // PCZT after the IO Finalizer has run. + if let Some(ask) = spend.dummy_ask.take() { + spend + .sign(sighash, &ask, &mut rng) + .map_err(IoFinalizerError::DummySignature)?; + } + } + + Ok(()) + } +} + +/// Errors that can occur while finalizing the I/O for a PCZT bundle. +#[derive(Debug)] +pub enum IoFinalizerError { + /// An error occurred while signing a dummy spend. + DummySignature(SignerError), + /// The `value_sum` is too large for the `value_balance` field. + InvalidValueSum, + /// The IO Finalizer role requires all `rcv` fields to be set. + MissingValueCommitTrapdoor, + /// The `cv_net`, `rcv`, and `value_sum` values within the Orchard bundle are + /// inconsistent. + ValueCommitMismatch, +} diff --git a/src/pczt/parse.rs b/src/pczt/parse.rs new file mode 100644 index 00000000..ec79051a --- /dev/null +++ b/src/pczt/parse.rs @@ -0,0 +1,294 @@ +use std::collections::BTreeMap; + +use ff::PrimeField; +use zcash_note_encryption::{EphemeralKeyBytes, OutgoingCipherKey}; +use zip32::ChildIndex; + +use super::{Bundle, Output, Spend, Zip32Derivation}; +use crate::{ + bundle::GrothProofBytes, + keys::{SpendAuthorizingKey, SpendValidatingKey}, + note::ExtractedNoteCommitment, + value::{NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum}, + Anchor, MerklePath, Node, Nullifier, PaymentAddress, ProofGenerationKey, Rseed, +}; + +impl Bundle { + /// Parses a PCZT bundle from its component parts. + pub fn parse( + spends: Vec, + outputs: Vec, + value_sum: i128, + anchor: [u8; 32], + bsk: Option<[u8; 32]>, + ) -> Result { + let value_sum = ValueSum::from_raw(value_sum); + + let anchor = Anchor::from_bytes(anchor) + .into_option() + .ok_or(ParseError::InvalidAnchor)?; + + let bsk = bsk + .map(redjubjub::SigningKey::try_from) + .transpose() + .map_err(|_| ParseError::InvalidBindingSignatureSigningKey)?; + + Ok(Self { + spends, + outputs, + value_sum, + anchor, + bsk, + }) + } +} + +impl Spend { + /// Parses a PCZT spend from its component parts. + #[allow(clippy::too_many_arguments)] + pub fn parse( + cv: [u8; 32], + nullifier: [u8; 32], + rk: [u8; 32], + zkproof: Option, + spend_auth_sig: Option<[u8; 64]>, + recipient: Option<[u8; 43]>, + value: Option, + rcm: Option<[u8; 32]>, + rseed: Option<[u8; 32]>, + rcv: Option<[u8; 32]>, + proof_generation_key: Option<([u8; 32], [u8; 32])>, + witness: Option<(u32, [[u8; 32]; 32])>, + alpha: Option<[u8; 32]>, + zip32_derivation: Option, + dummy_ask: Option<[u8; 32]>, + proprietary: BTreeMap>, + ) -> Result { + let cv = ValueCommitment::from_bytes_not_small_order(&cv) + .into_option() + .ok_or(ParseError::InvalidValueCommitment)?; + + let nullifier = Nullifier(nullifier); + + let rk = redjubjub::VerificationKey::try_from(rk) + .map_err(|_| ParseError::InvalidRandomizedKey)?; + + let spend_auth_sig = spend_auth_sig.map(redjubjub::Signature::from); + + let recipient = recipient + .as_ref() + .map(|r| PaymentAddress::from_bytes(r).ok_or(ParseError::InvalidRecipient)) + .transpose()?; + + let value = value.map(NoteValue::from_raw); + + let rseed = match (rcm, rseed) { + (None, None) => Ok(None), + (Some(rcm), None) => jubjub::Scalar::from_repr(rcm) + .into_option() + .ok_or(ParseError::InvalidNoteCommitRandomness) + .map(Rseed::BeforeZip212) + .map(Some), + (None, Some(rseed)) => Ok(Some(Rseed::AfterZip212(rseed))), + (Some(_), Some(_)) => Err(ParseError::MixedNoteCommitRandomnessAndRseed), + }?; + + let rcv = rcv + .map(|rcv| { + ValueCommitTrapdoor::from_bytes(rcv) + .into_option() + .ok_or(ParseError::InvalidValueCommitTrapdoor) + }) + .transpose()?; + + let proof_generation_key = proof_generation_key + .map(|(ak, nsk)| { + Ok(ProofGenerationKey { + ak: SpendValidatingKey::from_bytes(&ak) + .ok_or(ParseError::InvalidProofGenerationKey)?, + nsk: jubjub::Scalar::from_repr(nsk) + .into_option() + .ok_or(ParseError::InvalidProofGenerationKey)?, + }) + }) + .transpose()?; + + let witness = witness + .map(|(position, auth_path_bytes)| { + let path_elems = auth_path_bytes + .into_iter() + .map(|hash| { + Node::from_bytes(hash) + .into_option() + .ok_or(ParseError::InvalidWitness) + }) + .collect::, _>>()?; + + MerklePath::from_parts(path_elems, u64::from(position).into()) + .map_err(|()| ParseError::InvalidWitness) + }) + .transpose()?; + + let alpha = alpha + .map(|alpha| { + jubjub::Scalar::from_repr(alpha) + .into_option() + .ok_or(ParseError::InvalidSpendAuthRandomizer) + }) + .transpose()?; + + let dummy_ask = dummy_ask + .map(|dummy_ask| { + SpendAuthorizingKey::from_bytes(&dummy_ask) + .ok_or(ParseError::InvalidDummySpendAuthorizingKey) + }) + .transpose()?; + + Ok(Self { + cv, + nullifier, + rk, + zkproof, + spend_auth_sig, + recipient, + value, + rseed, + rcv, + proof_generation_key, + witness, + alpha, + zip32_derivation, + dummy_ask, + proprietary, + }) + } +} + +impl Output { + /// Parses a PCZT output from its component parts. + #[allow(clippy::too_many_arguments)] + pub fn parse( + cv: [u8; 32], + cmu: [u8; 32], + ephemeral_key: [u8; 32], + enc_ciphertext: Vec, + out_ciphertext: Vec, + zkproof: Option, + recipient: Option<[u8; 43]>, + value: Option, + rseed: Option<[u8; 32]>, + rcv: Option<[u8; 32]>, + ock: Option<[u8; 32]>, + zip32_derivation: Option, + proprietary: BTreeMap>, + ) -> Result { + let cv = ValueCommitment::from_bytes_not_small_order(&cv) + .into_option() + .ok_or(ParseError::InvalidValueCommitment)?; + + let cmu = ExtractedNoteCommitment::from_bytes(&cmu) + .into_option() + .ok_or(ParseError::InvalidExtractedNoteCommitment)?; + + let ephemeral_key = EphemeralKeyBytes(ephemeral_key); + + let enc_ciphertext = enc_ciphertext + .as_slice() + .try_into() + .map_err(|_| ParseError::InvalidEncCiphertext)?; + + let out_ciphertext = out_ciphertext + .as_slice() + .try_into() + .map_err(|_| ParseError::InvalidOutCiphertext)?; + + let recipient = recipient + .as_ref() + .map(|r| PaymentAddress::from_bytes(r).ok_or(ParseError::InvalidRecipient)) + .transpose()?; + + let value = value.map(NoteValue::from_raw); + + let rcv = rcv + .map(|rcv| { + ValueCommitTrapdoor::from_bytes(rcv) + .into_option() + .ok_or(ParseError::InvalidValueCommitTrapdoor) + }) + .transpose()?; + + let ock = ock.map(OutgoingCipherKey); + + Ok(Self { + cv, + cmu, + ephemeral_key, + enc_ciphertext, + out_ciphertext, + zkproof, + recipient, + value, + rseed, + rcv, + ock, + zip32_derivation, + proprietary, + }) + } +} + +impl Zip32Derivation { + /// Parses a ZIP 32 derivation path from its component parts. + /// + /// Returns an error if any of the derivation path indices are non-hardened (which + /// this crate does not support, even though Sapling does). + pub fn parse( + seed_fingerprint: [u8; 32], + derivation_path: Vec, + ) -> Result { + Ok(Self { + seed_fingerprint, + derivation_path: derivation_path + .into_iter() + .map(|i| ChildIndex::from_index(i).ok_or(ParseError::InvalidZip32Derivation)) + .collect::>()?, + }) + } +} + +/// Errors that can occur while parsing a PCZT bundle. +#[derive(Debug)] +pub enum ParseError { + /// An invalid `anchor` was provided. + InvalidAnchor, + /// An invalid `bsk` was provided. + InvalidBindingSignatureSigningKey, + /// An invalid `dummy_ask` was provided. + InvalidDummySpendAuthorizingKey, + /// An invalid `enc_ciphertext` was provided. + InvalidEncCiphertext, + /// An invalid `cmu` was provided. + InvalidExtractedNoteCommitment, + /// An invalid `rcm` was provided. + InvalidNoteCommitRandomness, + /// An invalid `out_ciphertext` was provided. + InvalidOutCiphertext, + /// An invalid `proof_generation_key` was provided. + InvalidProofGenerationKey, + /// An invalid `rk` was provided. + InvalidRandomizedKey, + /// An invalid `recipient` was provided. + InvalidRecipient, + /// An invalid `alpha` was provided. + InvalidSpendAuthRandomizer, + /// An invalid `cv` was provided. + InvalidValueCommitment, + /// An invalid `rcv` was provided. + InvalidValueCommitTrapdoor, + /// An invalid `witness` was provided. + InvalidWitness, + /// An invalid `zip32_derivation` was provided. + InvalidZip32Derivation, + /// Both `rcm` and `rseed` were provided for a Spend. + MixedNoteCommitRandomnessAndRseed, +} diff --git a/src/pczt/prover.rs b/src/pczt/prover.rs new file mode 100644 index 00000000..7a9db7a7 --- /dev/null +++ b/src/pczt/prover.rs @@ -0,0 +1,105 @@ +use rand::{CryptoRng, RngCore}; + +use crate::{ + prover::{OutputProver, SpendProver}, + Note, Rseed, +}; + +impl super::Bundle { + /// Adds a proof to this PCZT bundle. + pub fn create_proofs( + &mut self, + spend_prover: &S, + output_prover: &O, + mut rng: R, + ) -> Result<(), ProverError> + where + S: SpendProver, + O: OutputProver, + { + for spend in &mut self.spends { + let proof_generation_key = spend + .proof_generation_key + .clone() + .ok_or(ProverError::MissingProofGenerationKey)?; + + let note = Note::from_parts( + spend.recipient.ok_or(ProverError::MissingRecipient)?, + spend.value.ok_or(ProverError::MissingValue)?, + spend.rseed.ok_or(ProverError::MissingRandomSeed)?, + ); + + let alpha = spend.alpha.ok_or(ProverError::MissingSpendAuthRandomizer)?; + + let rcv = spend + .rcv + .clone() + .ok_or(ProverError::MissingValueCommitTrapdoor)?; + + let merkle_path = spend.witness.clone().ok_or(ProverError::MissingWitness)?; + + let circuit = S::prepare_circuit( + proof_generation_key, + *note.recipient().diversifier(), + *note.rseed(), + note.value(), + alpha, + rcv, + self.anchor.inner(), + merkle_path, + ) + .ok_or(ProverError::InvalidDiversifier)?; + + let proof = spend_prover.create_proof(circuit, &mut rng); + spend.zkproof = Some(S::encode_proof(proof)); + } + + for output in &mut self.outputs { + let recipient = output.recipient.ok_or(ProverError::MissingRecipient)?; + let value = output.value.ok_or(ProverError::MissingValue)?; + + let note = Note::from_parts( + recipient, + value, + output + .rseed + .map(Rseed::AfterZip212) + .ok_or(ProverError::MissingRandomSeed)?, + ); + + let esk = note.generate_or_derive_esk(&mut rng); + let rcm = note.rcm(); + + let rcv = output + .rcv + .clone() + .ok_or(ProverError::MissingValueCommitTrapdoor)?; + + let circuit = O::prepare_circuit(&esk, recipient, rcm, value, rcv); + let proof = output_prover.create_proof(circuit, &mut rng); + output.zkproof = Some(O::encode_proof(proof)); + } + + Ok(()) + } +} + +/// Errors that can occur while creating Sapling proofs for a PCZT. +#[derive(Debug)] +pub enum ProverError { + InvalidDiversifier, + /// The Prover role requires all `proof_generation_key` fields to be set. + MissingProofGenerationKey, + /// The Prover role requires all `rseed` fields to be set. + MissingRandomSeed, + /// The Prover role requires all `recipient` fields to be set. + MissingRecipient, + /// The Prover role requires all `alpha` fields to be set. + MissingSpendAuthRandomizer, + /// The Prover role requires all `value` fields to be set. + MissingValue, + /// The Prover role requires all `rcv` fields to be set. + MissingValueCommitTrapdoor, + /// The Prover role requires all `witness` fields to be set. + MissingWitness, +} diff --git a/src/pczt/signer.rs b/src/pczt/signer.rs new file mode 100644 index 00000000..90776357 --- /dev/null +++ b/src/pczt/signer.rs @@ -0,0 +1,38 @@ +use rand::{CryptoRng, RngCore}; + +use crate::keys::SpendAuthorizingKey; + +impl super::Spend { + /// Signs the Sapling spend with the given spend authorizing key. + /// + /// It is the caller's responsibility to perform any semantic validity checks on the + /// PCZT (for example, comfirming that the change amounts are correct) before calling + /// this method. + pub fn sign( + &mut self, + sighash: [u8; 32], + ask: &SpendAuthorizingKey, + rng: R, + ) -> Result<(), SignerError> { + let alpha = self.alpha.ok_or(SignerError::MissingSpendAuthRandomizer)?; + + let rsk = ask.randomize(&alpha); + let rk = redjubjub::VerificationKey::from(&rsk); + + if self.rk == rk { + self.spend_auth_sig = Some(rsk.sign(rng, &sighash)); + Ok(()) + } else { + Err(SignerError::WrongSpendAuthorizingKey) + } + } +} + +/// Errors that can occur while signing an Orchard action in a PCZT. +#[derive(Debug)] +pub enum SignerError { + /// The Signer role requires `alpha` to be set. + MissingSpendAuthRandomizer, + /// The provided `ask` does not own the action's spent note. + WrongSpendAuthorizingKey, +} diff --git a/src/pczt/tx_extractor.rs b/src/pczt/tx_extractor.rs new file mode 100644 index 00000000..9a6cf667 --- /dev/null +++ b/src/pczt/tx_extractor.rs @@ -0,0 +1,165 @@ +use rand::{CryptoRng, RngCore}; + +use crate::{ + bundle::{ + Authorization, Authorized, EffectsOnly, GrothProofBytes, OutputDescription, + SpendDescription, + }, + Bundle, +}; + +use super::{Output, Spend}; + +impl super::Bundle { + /// Extracts the effects of this PCZT bundle as a [regular `Bundle`]. + /// + /// This is used by the Signer role to produce the transaction sighash. + /// + /// [regular `Bundle`]: crate::Bundle + pub fn extract_effects>( + &self, + ) -> Result>, TxExtractorError> { + self.to_tx_data(|_| Ok(()), |_| Ok(()), |_| Ok(()), |_| Ok(EffectsOnly)) + } + + /// Extracts a fully authorized [regular `Bundle`] from this PCZT bundle. + /// + /// This is used by the Transaction Extractor role to produce the final transaction. + /// + /// [regular `Bundle`]: crate::Bundle + pub fn extract>( + self, + ) -> Result>, TxExtractorError> { + self.to_tx_data( + |spend| spend.zkproof.ok_or(TxExtractorError::MissingProof), + |spend| { + spend + .spend_auth_sig + .ok_or(TxExtractorError::MissingSpendAuthSig) + }, + |output| output.zkproof.ok_or(TxExtractorError::MissingProof), + |bundle| { + Ok(Unbound { + bsk: bundle + .bsk + .ok_or(TxExtractorError::MissingBindingSignatureSigningKey)?, + }) + }, + ) + } + + fn to_tx_data( + &self, + spend_proof: F, + spend_auth: G, + output_proof: H, + bundle_auth: I, + ) -> Result>, E> + where + A: Authorization, + E: From, + F: Fn(&Spend) -> Result<::SpendProof, E>, + G: Fn(&Spend) -> Result<::AuthSig, E>, + H: Fn(&Output) -> Result<::OutputProof, E>, + I: FnOnce(&Self) -> Result, + V: TryFrom, + { + let spends = self + .spends + .iter() + .map(|spend| { + Ok(SpendDescription::from_parts( + spend.cv.clone(), + self.anchor.inner(), + spend.nullifier, + spend.rk, + spend_proof(spend)?, + spend_auth(spend)?, + )) + }) + .collect::>()?; + + let outputs = self + .outputs + .iter() + .map(|output| { + Ok(OutputDescription::from_parts( + output.cv.clone(), + output.cmu, + output.ephemeral_key.clone(), + output.enc_ciphertext, + output.out_ciphertext, + output_proof(output)?, + )) + }) + .collect::>()?; + + let value_balance = i64::try_from(self.value_sum) + .ok() + .and_then(|v| v.try_into().ok()) + .ok_or(TxExtractorError::ValueSumOutOfRange)?; + + let authorization = bundle_auth(self)?; + + Ok(Bundle::from_parts( + spends, + outputs, + value_balance, + authorization, + )) + } +} + +/// Errors that can occur while extracting a regular Sapling bundle from a PCZT bundle. +#[derive(Debug)] +pub enum TxExtractorError { + /// The Transaction Extractor role requires `bsk` to be set. + MissingBindingSignatureSigningKey, + /// The Transaction Extractor role requires all `zkproof` fields to be set. + MissingProof, + /// The Transaction Extractor role requires all `spend_auth_sig` fields to be set. + MissingSpendAuthSig, + /// The value sum does not fit into a `valueBalance`. + ValueSumOutOfRange, +} + +/// Authorizing data for a bundle of actions that is just missing a binding signature. +#[derive(Debug)] +pub struct Unbound { + bsk: redjubjub::SigningKey, +} + +impl Authorization for Unbound { + type SpendProof = GrothProofBytes; + type OutputProof = GrothProofBytes; + type AuthSig = redjubjub::Signature; +} + +impl crate::Bundle { + /// Verifies the given sighash with every `spend_auth_sig`, and then binds the bundle. + /// + /// Returns `None` if the given sighash does not validate against every `spend_auth_sig`. + pub fn apply_binding_signature( + self, + sighash: [u8; 32], + rng: R, + ) -> Option> { + if self + .shielded_spends() + .iter() + .all(|spend| spend.rk().verify(&sighash, spend.spend_auth_sig()).is_ok()) + { + Some(self.map_authorization( + &mut (), + |_, p| p, + |_, p| p, + |_, s| s, + |_, Unbound { bsk }| Authorized { + binding_sig: bsk.sign(rng, &sighash), + }, + )) + } else { + None + } + } +} diff --git a/src/tree.rs b/src/tree.rs index 1efb9d54..ea339b4b 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -93,6 +93,10 @@ impl Anchor { Anchor(Node::empty_root(NOTE_COMMITMENT_TREE_DEPTH.into()).0) } + pub(crate) fn inner(&self) -> jubjub::Base { + self.0 + } + /// Parses a Sapling anchor from a byte encoding. pub fn from_bytes(bytes: [u8; 32]) -> CtOption { jubjub::Base::from_repr(bytes).map(Self) diff --git a/src/value/sums.rs b/src/value/sums.rs index b598e0e5..05436067 100644 --- a/src/value/sums.rs +++ b/src/value/sums.rs @@ -38,6 +38,20 @@ impl ValueSum { pub fn zero() -> Self { ValueSum(0) } + + /// Instantiates a value sum from a raw encoding. + /// + /// Only intended for use with PCZTs. + pub(crate) fn from_raw(value_sum: i128) -> Self { + Self(value_sum) + } + + /// Extracts the raw encoding of this value sum. + /// + /// Only intended for use with PCZTs. + pub fn to_raw(self) -> i128 { + self.0 + } } impl Add for ValueSum { From f228f52542749ea89f4a7cffbc0682ed9ea4b8d1 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 5 Dec 2024 15:12:25 +0000 Subject: [PATCH 08/30] Add the PCZT Updater role --- src/pczt.rs | 3 ++ src/pczt/updater.rs | 102 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 src/pczt/updater.rs diff --git a/src/pczt.rs b/src/pczt.rs index 5dd27b45..080ababc 100644 --- a/src/pczt.rs +++ b/src/pczt.rs @@ -24,6 +24,9 @@ pub use parse::ParseError; mod io_finalizer; pub use io_finalizer::IoFinalizerError; +mod updater; +pub use updater::{OutputUpdater, SpendUpdater, Updater, UpdaterError}; + mod prover; pub use prover::ProverError; diff --git a/src/pczt/updater.rs b/src/pczt/updater.rs new file mode 100644 index 00000000..7ed8435c --- /dev/null +++ b/src/pczt/updater.rs @@ -0,0 +1,102 @@ +use crate::ProofGenerationKey; + +use super::{Bundle, Output, Spend, Zip32Derivation}; + +impl Bundle { + /// Updates the bundle with information provided in the given closure. + pub fn update_with(&mut self, f: F) -> Result<(), UpdaterError> + where + F: FnOnce(Updater<'_>) -> Result<(), UpdaterError>, + { + f(Updater(self)) + } +} + +/// An updater for a Sapling PCZT bundle. +pub struct Updater<'a>(&'a mut Bundle); + +impl<'a> Updater<'a> { + /// Provides read access to the bundle being updated. + pub fn bundle(&self) -> &Bundle { + &self.0 + } + + /// Updates the spend at the given index with information provided in the given + /// closure. + pub fn update_spend_with(&mut self, index: usize, f: F) -> Result<(), UpdaterError> + where + F: FnOnce(SpendUpdater<'_>) -> Result<(), UpdaterError>, + { + f(SpendUpdater( + self.0 + .spends + .get_mut(index) + .ok_or(UpdaterError::InvalidIndex)?, + )) + } + + /// Updates the output at the given index with information provided in the given + /// closure. + pub fn update_output_with(&mut self, index: usize, f: F) -> Result<(), UpdaterError> + where + F: FnOnce(OutputUpdater<'_>) -> Result<(), UpdaterError>, + { + f(OutputUpdater( + self.0 + .outputs + .get_mut(index) + .ok_or(UpdaterError::InvalidIndex)?, + )) + } +} + +/// An updater for a Sapling PCZT spend. +pub struct SpendUpdater<'a>(&'a mut Spend); + +impl<'a> SpendUpdater<'a> { + /// Sets the proof generation key for this spend. + /// + /// Returns an error if the proof generation key does not match the spend. + pub fn set_proof_generation_key( + &mut self, + proof_generation_key: ProofGenerationKey, + ) -> Result<(), UpdaterError> { + // TODO: Verify that the proof generation key matches the spend, if possible. + self.0.proof_generation_key = Some(proof_generation_key); + Ok(()) + } + + /// Sets the ZIP 32 derivation path for the spent note's signing key. + pub fn set_zip32_derivation(&mut self, derivation: Zip32Derivation) { + self.0.zip32_derivation = Some(derivation); + } + + /// Stores the given proprietary value at the given key. + pub fn set_proprietary(&mut self, key: String, value: Vec) { + self.0.proprietary.insert(key, value); + } +} + +/// An updater for a Sapling PCZT output. +pub struct OutputUpdater<'a>(&'a mut Output); + +impl<'a> OutputUpdater<'a> { + /// Sets the ZIP 32 derivation path for the new note's signing key. + pub fn set_zip32_derivation(&mut self, derivation: Zip32Derivation) { + self.0.zip32_derivation = Some(derivation); + } + + /// Stores the given proprietary value at the given key. + pub fn set_proprietary(&mut self, key: String, value: Vec) { + self.0.proprietary.insert(key, value); + } +} + +/// Errors that can occur while updating a Sapling bundle in a PCZT. +#[derive(Debug)] +pub enum UpdaterError { + /// An out-of-bounds index was provided when looking up a spend or output. + InvalidIndex, + /// The provided `proof_generation_key` does not match the spend. + WrongProofGenerationKey, +} From 833eb2ec02d4445e2fe977794b801ac1a5ef9139 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 5 Dec 2024 14:50:51 +0000 Subject: [PATCH 09/30] Modify builder to take spending keys as late as possible --- CHANGELOG.md | 11 +++++- src/builder.rs | 97 +++++++++++++++++++++++++++++++++++--------------- 2 files changed, 79 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c999a9ae..4a6e53c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,16 @@ and this library adheres to Rust's notion of that expose it. ### Changed -- `sapling_crypto::builder::Error` has a new variant `PcztRequiresZip212`. +- `sapling_crypto::builder`: + - `SpendInfo::new` now takes a `FullViewingKey` instead of a + `ProofGenerationKey`. + - `Builder::add_spend` now takes a `FullViewingKey` instead of an + `&ExtendedSpendingKey`. + - `Builder::build` and `bundle` now take an `&[ExtendedSpendingKey]` argument. + - `Error` has new variants: + - `MissingSpendingKey` + - `PcztRequiresZip212` + - `WrongSpendingKey` - `sapling_crypto::bundle::SpendDescriptionV5::into_spend_description` now supports any `Authorization` for which the `SpendDescription` itself is fully authorized. diff --git a/src/builder.rs b/src/builder.rs index 5b3bc59b..7c525318 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -15,7 +15,10 @@ use crate::{ Authorization, Authorized, Bundle, GrothProofBytes, OutputDescription, SpendDescription, }, circuit, - keys::{EphemeralSecretKey, OutgoingViewingKey, SpendAuthorizingKey, SpendValidatingKey}, + keys::{ + EphemeralSecretKey, ExpandedSpendingKey, FullViewingKey, OutgoingViewingKey, + SpendAuthorizingKey, SpendValidatingKey, + }, note::ExtractedNoteCommitment, note_encryption::{sapling_note_encryption, Zip212Enforcement}, prover::{OutputProver, SpendProver}, @@ -123,11 +126,15 @@ pub enum Error { InvalidExternalSignature, /// A bundle could not be built because required signatures were missing. MissingSignatures, + /// A required spending key was not provided. + MissingSpendingKey, /// [`Builder::build_for_pczt`] requires [`Zip212Enforcement::On`]. PcztRequiresZip212, SpendProof, /// The bundle being constructed violated the construction rules for the requested bundle type. BundleTypeNotSatisfiable, + /// The wrong spending key was provided. + WrongSpendingKey, } impl fmt::Display for Error { @@ -142,6 +149,7 @@ impl fmt::Display for Error { Error::InvalidAmount => write!(f, "Invalid amount"), Error::InvalidExternalSignature => write!(f, "External signature was invalid"), Error::MissingSignatures => write!(f, "Required signatures were missing during build"), + Error::MissingSpendingKey => write!(f, "A required spending key was not provided"), Error::PcztRequiresZip212 => { write!(f, "PCZTs require that ZIP 212 is enforced for outputs") } @@ -149,6 +157,7 @@ impl fmt::Display for Error { Error::BundleTypeNotSatisfiable => { f.write_str("Bundle structure did not conform to requested bundle type.") } + Error::WrongSpendingKey => write!(f, "The wrong spending key was provided"), } } } @@ -156,24 +165,20 @@ impl fmt::Display for Error { /// A struct containing the information necessary to add a spend to a bundle. #[derive(Debug, Clone)] pub struct SpendInfo { - proof_generation_key: ProofGenerationKey, + fvk: FullViewingKey, note: Note, merkle_path: MerklePath, - dummy_ask: Option, + dummy_expsk: Option, } impl SpendInfo { /// Constructs a [`SpendInfo`] from its constituent parts. - pub fn new( - proof_generation_key: ProofGenerationKey, - note: Note, - merkle_path: MerklePath, - ) -> Self { + pub fn new(fvk: FullViewingKey, note: Note, merkle_path: MerklePath) -> Self { Self { - proof_generation_key, + fvk, note, merkle_path, - dummy_ask: None, + dummy_expsk: None, } } @@ -196,10 +201,13 @@ impl SpendInfo { .expect("The path length corresponds to the length of the generated vector."); SpendInfo { - proof_generation_key: sk.proof_generation_key(), + fvk: FullViewingKey { + vk: sk.proof_generation_key().to_viewing_key(), + ovk: sk.ovk, + }, note, merkle_path, - dummy_ask: Some(sk.ask), + dummy_expsk: Some(sk), } } @@ -214,22 +222,22 @@ impl SpendInfo { fn prepare(self, rng: R) -> PreparedSpendInfo { PreparedSpendInfo { - proof_generation_key: self.proof_generation_key, + fvk: self.fvk, note: self.note, merkle_path: self.merkle_path, rcv: ValueCommitTrapdoor::random(rng), - dummy_ask: self.dummy_ask, + dummy_expsk: self.dummy_expsk, } } } #[derive(Debug, Clone)] struct PreparedSpendInfo { - proof_generation_key: ProofGenerationKey, + fvk: FullViewingKey, note: Note, merkle_path: MerklePath, rcv: ValueCommitTrapdoor, - dummy_ask: Option, + dummy_expsk: Option, } impl PreparedSpendInfo { @@ -246,13 +254,13 @@ impl PreparedSpendInfo { let alpha = jubjub::Fr::random(&mut rng); let cv = ValueCommitment::derive(self.note.value(), self.rcv.clone()); - let ak = self.proof_generation_key.ak.clone(); + let ak = self.fvk.vk.ak.clone(); // This is the result of the re-randomization, we compute it for the caller let rk = ak.randomize(&alpha); let nullifier = self.note.nf( - &self.proof_generation_key.to_viewing_key().nk, + &self.fvk.vk.nk, u64::try_from(self.merkle_path.position()) .expect("Sapling note commitment tree position must fit into a u64"), ); @@ -262,18 +270,30 @@ impl PreparedSpendInfo { fn build( self, + proof_generation_key: Option, rng: R, ) -> Result>, Error> { + let proof_generation_key = match (proof_generation_key, self.dummy_expsk.as_ref()) { + (Some(proof_generation_key), None) => Ok(proof_generation_key), + (None, Some(expsk)) => Ok(expsk.proof_generation_key()), + (Some(_), Some(_)) => Err(Error::WrongSpendingKey), + (None, None) => Err(Error::MissingSpendingKey), + }?; + let expected_vk = proof_generation_key.to_viewing_key(); + if (&expected_vk.ak, &expected_vk.nk) != (&self.fvk.vk.ak, &self.fvk.vk.nk) { + return Err(Error::WrongSpendingKey); + } + let (cv, nullifier, rk, alpha) = self.build_inner(rng); // Construct the value commitment. let node = Node::from_cmu(&self.note.cmu()); let anchor = *self.merkle_path.root(node).inner(); - let ak = self.proof_generation_key.ak.clone(); + let ak = self.fvk.vk.ak.clone(); let zkproof = Pr::prepare_circuit( - self.proof_generation_key, + proof_generation_key, *self.note.recipient().diversifier(), *self.note.rseed(), self.note.value(), @@ -291,7 +311,7 @@ impl PreparedSpendInfo { rk, zkproof, SigningMetadata { - dummy_ask: self.dummy_ask, + dummy_ask: self.dummy_expsk.map(|expsk| expsk.ask), parts: SigningParts { ak, alpha }, }, )) @@ -310,11 +330,11 @@ impl PreparedSpendInfo { value: Some(self.note.value()), rseed: Some(*self.note.rseed()), rcv: Some(self.rcv), - proof_generation_key: Some(self.proof_generation_key), + proof_generation_key: None, witness: Some(self.merkle_path), alpha: Some(alpha), zip32_derivation: None, - dummy_ask: self.dummy_ask, + dummy_ask: self.dummy_expsk.map(|expsk| expsk.ask), proprietary: BTreeMap::new(), } } @@ -595,11 +615,11 @@ impl Builder { /// paths for previous Sapling notes. pub fn add_spend( &mut self, - extsk: &ExtendedSpendingKey, + fvk: FullViewingKey, note: Note, merkle_path: MerklePath, ) -> Result<(), Error> { - let spend = SpendInfo::new(extsk.expsk.proof_generation_key(), note, merkle_path); + let spend = SpendInfo::new(fvk, note, merkle_path); // Consistency check: all anchors must equal the first one match self.bundle_type { @@ -642,6 +662,7 @@ impl Builder { /// Constructs the Sapling bundle from the builder's accumulated state. pub fn build>( self, + extsks: &[ExtendedSpendingKey], rng: R, ) -> Result, SaplingMetadata)>, Error> { bundle::( @@ -651,6 +672,7 @@ impl Builder { self.anchor, self.spends, self.outputs, + extsks, ) } @@ -707,6 +729,7 @@ pub fn bundle>( anchor: Anchor, spends: Vec, outputs: Vec, + extsks: &[ExtendedSpendingKey], ) -> Result, SaplingMetadata)>, Error> { build_bundle::<_, SP, OP, _>( rng, @@ -729,7 +752,21 @@ pub fn bundle>( // Create the unauthorized Spend and Output descriptions. let shielded_spends = spend_infos .into_iter() - .map(|a| a.build::(&mut rng)) + .map(|a| { + // Look up the proof generation key for non-dummy spends. + let proof_generation_key = a + .dummy_expsk + .is_none() + .then(|| { + extsks.iter().find_map(|extsk| { + let dfvk = extsk.to_diversifiable_full_viewing_key(); + (dfvk.fvk().to_bytes() == a.fvk.to_bytes()) + .then(|| extsk.expsk.proof_generation_key()) + }) + }) + .flatten(); + a.build::(proof_generation_key, &mut rng) + }) .collect::, _>>()?; let shielded_outputs = output_infos .into_iter() @@ -1288,6 +1325,7 @@ pub(crate) mod testing { }) .prop_map( move |(extsk, spendable_notes, commitment_trees, rng_seed, fake_sighash_bytes)| { + let dfvk = extsk.to_diversifiable_full_viewing_key(); let anchor = spendable_notes .first() .zip(commitment_trees.first()) @@ -1302,11 +1340,14 @@ pub(crate) mod testing { .into_iter() .zip(commitment_trees.into_iter()) { - builder.add_spend(&extsk, note, path).unwrap(); + builder.add_spend(dfvk.fvk().clone(), note, path).unwrap(); } let (bundle, _) = builder - .build::(&mut rng) + .build::( + &[extsk.clone()], + &mut rng, + ) .unwrap() .unwrap(); From 42a1de5a20007050fd3169b5da1cc28962dcb258 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 5 Dec 2024 09:09:21 -0700 Subject: [PATCH 10/30] Add `DiversifiableFullViewingKey::to_internal_fvk`. This is needed in order to permit spending of internal notes after the previous change to `Builder::add_spend`. --- CHANGELOG.md | 1 + src/zip32.rs | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a6e53c5..d3d7af64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this library adheres to Rust's notion of - `SpendAuthorizingKey::to_bytes` - `SpendValidatingKey::to_bytes` - `sapling_crypto::value::ValueSum::to_raw` +- `sapling_crypto::zip32::DiversifiableFullViewingKey::to_internal_fvk` ### Fixed - `sapling_crypto::prover::OutputProver::prepare_circuit` now takes `esk` as an diff --git a/src/zip32.rs b/src/zip32.rs index c2b7ea63..ea744f42 100644 --- a/src/zip32.rs +++ b/src/zip32.rs @@ -705,6 +705,11 @@ impl DiversifiableFullViewingKey { &self.fvk } + /// Returns the internal [`FullViewingKey`] component of this diversifiable full viewing key. + pub fn to_internal_fvk(&self) -> FullViewingKey { + self.derive_internal().fvk + } + /// Derives a nullifier-deriving key for the provided scope. /// /// This API is provided so that nullifiers for change notes can be correctly computed. From 29cff9683cdf2f0c522ff3224081dfb4fbc80248 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sat, 7 Dec 2024 11:00:45 +0000 Subject: [PATCH 11/30] pczt: Store proof generation key for dummy spends --- src/builder.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/builder.rs b/src/builder.rs index 7c525318..91a6cd42 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -330,7 +330,10 @@ impl PreparedSpendInfo { value: Some(self.note.value()), rseed: Some(*self.note.rseed()), rcv: Some(self.rcv), - proof_generation_key: None, + proof_generation_key: self + .dummy_expsk + .as_ref() + .map(|expsk| expsk.proof_generation_key()), witness: Some(self.merkle_path), alpha: Some(alpha), zip32_derivation: None, From 96d0e6944e052019ca4b0a284c456d30d9bfdb79 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sat, 7 Dec 2024 12:05:49 +0000 Subject: [PATCH 12/30] Fix clippy lint --- src/pczt/updater.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pczt/updater.rs b/src/pczt/updater.rs index 7ed8435c..2f059986 100644 --- a/src/pczt/updater.rs +++ b/src/pczt/updater.rs @@ -18,7 +18,7 @@ pub struct Updater<'a>(&'a mut Bundle); impl<'a> Updater<'a> { /// Provides read access to the bundle being updated. pub fn bundle(&self) -> &Bundle { - &self.0 + self.0 } /// Updates the spend at the given index with information provided in the given From 231f81911628499a8877be57e66e60c09e55bdea Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 13 Dec 2024 00:13:20 +0000 Subject: [PATCH 13/30] Add methods for validating aspects of PCZT bundles --- src/pczt.rs | 3 + src/pczt/verify.rs | 190 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 193 insertions(+) create mode 100644 src/pczt/verify.rs diff --git a/src/pczt.rs b/src/pczt.rs index 080ababc..2bbeafcc 100644 --- a/src/pczt.rs +++ b/src/pczt.rs @@ -21,6 +21,9 @@ use crate::{ mod parse; pub use parse::ParseError; +mod verify; +pub use verify::VerifyError; + mod io_finalizer; pub use io_finalizer::IoFinalizerError; diff --git a/src/pczt/verify.rs b/src/pczt/verify.rs new file mode 100644 index 00000000..2e5078fe --- /dev/null +++ b/src/pczt/verify.rs @@ -0,0 +1,190 @@ +use crate::{keys::FullViewingKey, value::ValueCommitment, Note, ViewingKey}; + +impl super::Spend { + /// Verifies that the `cv` field is consistent with the note fields. + /// + /// Requires that the following optional fields are set: + /// - `value` + /// - `rcv` + pub fn verify_cv(&self) -> Result<(), VerifyError> { + let value = self.value.ok_or(VerifyError::MissingValue)?; + let rcv = self + .rcv + .clone() + .ok_or(VerifyError::MissingValueCommitTrapdoor)?; + + let cv_net = ValueCommitment::derive(value, rcv); + if cv_net.to_bytes() == self.cv.to_bytes() { + Ok(()) + } else { + Err(VerifyError::InvalidValueCommitment) + } + } + + /// Returns the [`ViewingKey`] to use when validating this note. + /// + /// Handles dummy notes when the `value` field is set. + fn vk_for_validation( + &self, + expected_fvk: Option<&FullViewingKey>, + ) -> Result { + let vk = self + .proof_generation_key + .as_ref() + .map(|proof_generation_key| proof_generation_key.to_viewing_key()); + + match (expected_fvk, vk, self.value.as_ref()) { + // Dummy notes use random keys, which must be provided. + (_, Some(vk), Some(value)) if value.inner() == 0 => Ok(vk), + (_, None, Some(value)) if value.inner() == 0 => { + Err(VerifyError::MissingProofGenerationKey) + } + // If the `proof_generation_key` field has been pruned, assume the caller + // provided the correct FVK. + (Some(expected_fvk), None, _) => Ok(expected_fvk.vk.clone()), + // This is not a dummy note; if the FVK field is present, it must match. + (Some(expected_fvk), Some(vk), _) + if vk.ak == expected_fvk.vk.ak && vk.nk == expected_fvk.vk.nk => + { + Ok(vk) + } + (Some(_), Some(_), _) => Err(VerifyError::MismatchedFullViewingKey), + (None, Some(vk), _) => Ok(vk), + (None, None, _) => Err(VerifyError::MissingProofGenerationKey), + } + } + + /// Verifies that the `nullifier` field is consistent with the note fields. + /// + /// Requires that the following optional fields are set: + /// - `recipient` + /// - `value` + /// - `rseed` + /// - `witness` + /// + /// The provided [`FullViewingKey`] is ignored if the spent note is a dummy note. + /// Otherwise, it will be checked against the `proof_generation_key` field (if set). + pub fn verify_nullifier( + &self, + expected_fvk: Option<&FullViewingKey>, + ) -> Result<(), VerifyError> { + let vk = self.vk_for_validation(expected_fvk)?; + + let note = Note::from_parts( + self.recipient.ok_or(VerifyError::MissingRecipient)?, + self.value.ok_or(VerifyError::MissingValue)?, + self.rseed.ok_or(VerifyError::MissingRandomSeed)?, + ); + + // We need both the note and the VK to verify the nullifier; we have everything + // needed to also verify that the correct VK was provided (the nullifier check + // itself only constrains `nk` within the VK). + if vk.to_payment_address(*note.recipient().diversifier()) != Some(note.recipient()) { + return Err(VerifyError::WrongFvkForNote); + } + + let merkle_path = self.witness().as_ref().ok_or(VerifyError::MissingWitness)?; + + if note.nf(&vk.nk, merkle_path.position().into()) == self.nullifier { + Ok(()) + } else { + Err(VerifyError::InvalidNullifier) + } + } + + /// Verifies that the `rk` field is consistent with the given FVK. + /// + /// Requires that the following optional fields are set: + /// - `alpha` + /// + /// The provided [`FullViewingKey`] is ignored if the spent note is a dummy note + /// (which can only be determined if the `value` field is set). Otherwise, it will be + /// checked against the `proof_generation_key` field (if set). + pub fn verify_rk(&self, expected_fvk: Option<&FullViewingKey>) -> Result<(), VerifyError> { + let vk = self.vk_for_validation(expected_fvk)?; + + let alpha = self + .alpha + .as_ref() + .ok_or(VerifyError::MissingSpendAuthRandomizer)?; + + if vk.ak.randomize(alpha) == self.rk { + Ok(()) + } else { + Err(VerifyError::InvalidRandomizedVerificationKey) + } + } +} + +impl super::Output { + /// Verifies that the `cv` field is consistent with the note fields. + /// + /// Requires that the following optional fields are set: + /// - `value` + /// - `rcv` + pub fn verify_cv(&self) -> Result<(), VerifyError> { + let value = self.value.ok_or(VerifyError::MissingValue)?; + let rcv = self + .rcv + .clone() + .ok_or(VerifyError::MissingValueCommitTrapdoor)?; + + let cv_net = ValueCommitment::derive(value, rcv); + if cv_net.to_bytes() == self.cv.to_bytes() { + Ok(()) + } else { + Err(VerifyError::InvalidValueCommitment) + } + } + + /// Verifies that the `cmu` field is consistent with the note fields. + /// + /// Requires that the following optional fields are set: + /// - `recipient` + /// - `value` + /// - `rseed` + pub fn verify_note_commitment(&self) -> Result<(), VerifyError> { + let note = Note::from_parts( + self.recipient.ok_or(VerifyError::MissingRecipient)?, + self.value.ok_or(VerifyError::MissingValue)?, + crate::Rseed::AfterZip212(self.rseed.ok_or(VerifyError::MissingRandomSeed)?), + ); + + if note.cmu() == self.cmu { + Ok(()) + } else { + Err(VerifyError::InvalidExtractedNoteCommitment) + } + } +} + +/// Errors that can occur while verifying a PCZT bundle. +#[derive(Debug)] +pub enum VerifyError { + /// The output note's components do not produce the expected `cmx`. + InvalidExtractedNoteCommitment, + /// The spent note's components do not produce the expected `nullifier`. + InvalidNullifier, + /// The Spend's FVK and `alpha` do not produce the expected `rk`. + InvalidRandomizedVerificationKey, + /// The action's `cv_net` does not match the provided note values and `rcv`. + InvalidValueCommitment, + /// The spend or output's `fvk` field does not match the provided FVK. + MismatchedFullViewingKey, + /// Dummy notes must have their `proof_generation_key` field set in order to be verified. + MissingProofGenerationKey, + /// `nullifier` verification requires `rseed` to be set. + MissingRandomSeed, + /// `nullifier` verification requires `recipient` to be set. + MissingRecipient, + /// `rk` verification requires `alpha` to be set. + MissingSpendAuthRandomizer, + /// Verification requires all `value` fields to be set. + MissingValue, + /// `cv_net` verification requires `rcv` to be set. + MissingValueCommitTrapdoor, + /// `nullifier` verification requires `witness` to be set. + MissingWitness, + /// The provided `fvk` does not own the spent note. + WrongFvkForNote, +} From 94ad2b356e9b495eae90a93e8b0c27d06924e79e Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 13 Dec 2024 07:50:04 +0000 Subject: [PATCH 14/30] Adjust cases in `pczt::Spend::vk_for_validation` Co-authored-by: Daira-Emma Hopwood --- src/pczt/verify.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/pczt/verify.rs b/src/pczt/verify.rs index 2e5078fe..704d00ad 100644 --- a/src/pczt/verify.rs +++ b/src/pczt/verify.rs @@ -34,21 +34,15 @@ impl super::Spend { .map(|proof_generation_key| proof_generation_key.to_viewing_key()); match (expected_fvk, vk, self.value.as_ref()) { - // Dummy notes use random keys, which must be provided. - (_, Some(vk), Some(value)) if value.inner() == 0 => Ok(vk), - (_, None, Some(value)) if value.inner() == 0 => { - Err(VerifyError::MissingProofGenerationKey) - } - // If the `proof_generation_key` field has been pruned, assume the caller - // provided the correct FVK. - (Some(expected_fvk), None, _) => Ok(expected_fvk.vk.clone()), - // This is not a dummy note; if the FVK field is present, it must match. (Some(expected_fvk), Some(vk), _) if vk.ak == expected_fvk.vk.ak && vk.nk == expected_fvk.vk.nk => { Ok(vk) } + // `expected_fvk` is ignored if the spent note is a dummy note. + (Some(_), Some(vk), Some(value)) if value.inner() == 0 => Ok(vk), (Some(_), Some(_), _) => Err(VerifyError::MismatchedFullViewingKey), + (Some(expected_fvk), None, _) => Ok(expected_fvk.vk.clone()), (None, Some(vk), _) => Ok(vk), (None, None, _) => Err(VerifyError::MissingProofGenerationKey), } From e47d57f5c9c46f05740328f8ef9601f6d697cf34 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 13 Dec 2024 18:21:52 +0000 Subject: [PATCH 15/30] pczt: Add output field for storing the user-facing address This is necessary in order for Signers to display the address encoding that a user is expecting to confirm. Co-authored-by: Kris Nuttycombe --- src/builder.rs | 1 + src/pczt.rs | 8 ++++++++ src/pczt/parse.rs | 2 ++ src/pczt/updater.rs | 5 +++++ 4 files changed, 16 insertions(+) diff --git a/src/builder.rs b/src/builder.rs index 91a6cd42..6665563f 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -516,6 +516,7 @@ impl PreparedOutputInfo { // TODO: Save this? ock: None, zip32_derivation: None, + user_address: None, proprietary: BTreeMap::new(), } } diff --git a/src/pczt.rs b/src/pczt.rs index 2bbeafcc..d478b445 100644 --- a/src/pczt.rs +++ b/src/pczt.rs @@ -265,6 +265,13 @@ pub struct Output { /// The ZIP 32 derivation path at which the spending key can be found for the output. pub(crate) zip32_derivation: Option, + /// The user-facing address to which this output is being sent, if any. + /// + /// - This is set by an Updater. + /// - Signers must parse this address (if present) and confirm that it contains + /// `recipient` (either directly, or e.g. as a receiver within a Unified Address). + pub(crate) user_address: Option, + /// Proprietary fields related to the note being created. pub(crate) proprietary: BTreeMap>, } @@ -283,6 +290,7 @@ impl fmt::Debug for Output { .field("rseed", &self.rseed) .field("rcv", &self.rcv) .field("zip32_derivation", &self.zip32_derivation) + .field("user_address", &self.user_address) .field("proprietary", &self.proprietary) .finish_non_exhaustive() } diff --git a/src/pczt/parse.rs b/src/pczt/parse.rs index ec79051a..4ac77db9 100644 --- a/src/pczt/parse.rs +++ b/src/pczt/parse.rs @@ -180,6 +180,7 @@ impl Output { rcv: Option<[u8; 32]>, ock: Option<[u8; 32]>, zip32_derivation: Option, + user_address: Option, proprietary: BTreeMap>, ) -> Result { let cv = ValueCommitment::from_bytes_not_small_order(&cv) @@ -232,6 +233,7 @@ impl Output { rcv, ock, zip32_derivation, + user_address, proprietary, }) } diff --git a/src/pczt/updater.rs b/src/pczt/updater.rs index 2f059986..71c7d7a1 100644 --- a/src/pczt/updater.rs +++ b/src/pczt/updater.rs @@ -86,6 +86,11 @@ impl<'a> OutputUpdater<'a> { self.0.zip32_derivation = Some(derivation); } + /// Sets the user-facing address that the new note is being sent to. + pub fn set_user_address(&mut self, user_address: String) { + self.0.user_address = Some(user_address); + } + /// Stores the given proprietary value at the given key. pub fn set_proprietary(&mut self, key: String, value: Vec) { self.0.proprietary.insert(key, value); From 2394484ae359cdca1725f54e4a6a49f00eb1fd9a Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Mon, 16 Dec 2024 20:19:02 -0700 Subject: [PATCH 16/30] Release sapling-crypto version 0.4.0 --- CHANGELOG.md | 2 ++ Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3d7af64..b0808f7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this library adheres to Rust's notion of ## [Unreleased] +## [0.4.0] - 2024-12-16 + ### Added - Support for Partially-Created Zcash Transactions: - `sapling_crypto::builder::Builder::build_for_pczt` diff --git a/Cargo.lock b/Cargo.lock index 2f9358b0..2225ab87 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1307,7 +1307,7 @@ dependencies = [ [[package]] name = "sapling-crypto" -version = "0.3.0" +version = "0.4.0" dependencies = [ "aes", "bellman", diff --git a/Cargo.toml b/Cargo.toml index b74cbcf1..fa48cb33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sapling-crypto" -version = "0.3.0" +version = "0.4.0" authors = [ "Sean Bowe ", "Jack Grigg ", From 0d39b4444f46f6d86a6bbf4d3f4b2b003b474e15 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Tue, 17 Dec 2024 17:39:27 +0000 Subject: [PATCH 17/30] pczt: Document how `expected_fvk` is used by `Spend::verify_nullifier` --- src/pczt/verify.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pczt/verify.rs b/src/pczt/verify.rs index 704d00ad..2aae492c 100644 --- a/src/pczt/verify.rs +++ b/src/pczt/verify.rs @@ -56,8 +56,12 @@ impl super::Spend { /// - `rseed` /// - `witness` /// + /// In addition, at least one of the `proof_generation_key` field or `expected_fvk` + /// must be provided. + /// /// The provided [`FullViewingKey`] is ignored if the spent note is a dummy note. - /// Otherwise, it will be checked against the `proof_generation_key` field (if set). + /// Otherwise, it will be checked against the `proof_generation_key` field (if both + /// are set). pub fn verify_nullifier( &self, expected_fvk: Option<&FullViewingKey>, From 29ec9ada6016ac2177c6e304348c7162c3ddbfe3 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Tue, 17 Dec 2024 17:40:18 +0000 Subject: [PATCH 18/30] pczt: Add `Zip32Derivation::extract_account_index` --- CHANGELOG.md | 3 +++ src/pczt.rs | 29 +++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0808f7d..53186af4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this library adheres to Rust's notion of ## [Unreleased] +### Added +- `sapling_crypto::pczt::Zip32Derivation::extract_account_index` + ## [0.4.0] - 2024-12-16 ### Added diff --git a/src/pczt.rs b/src/pczt.rs index d478b445..0a6ca0cc 100644 --- a/src/pczt.rs +++ b/src/pczt.rs @@ -306,3 +306,32 @@ pub struct Zip32Derivation { /// The sequence of indices corresponding to the shielded HD path. derivation_path: Vec, } + +impl Zip32Derivation { + /// Extracts the ZIP 32 account index from this derivation path. + /// + /// Returns `None` if the seed fingerprints don't match, or if this is a non-standard + /// derivation path. + pub fn extract_account_index( + &self, + seed_fp: &zip32::fingerprint::SeedFingerprint, + expected_coin_type: zip32::ChildIndex, + ) -> Option { + if self.seed_fingerprint == seed_fp.to_bytes() { + match &self.derivation_path[..] { + [purpose, coin_type, account_index] + if purpose == &zip32::ChildIndex::hardened(32) + && coin_type == &expected_coin_type => + { + Some( + zip32::AccountId::try_from(account_index.index() - (1 << 31)) + .expect("zip32::ChildIndex only supports hardened"), + ) + } + _ => None, + } + } else { + None + } + } +} From e8b04770f9da1a1ce98b3dd453ffb85c4d338040 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Wed, 18 Dec 2024 15:04:55 -0700 Subject: [PATCH 19/30] Implement `no_std` support via a default-enabled `std` feature flag. Add thumbv7em-none-eabihf as a no-default-features build target. --- .github/workflows/ci.yml | 28 +++++++++++- CHANGELOG.md | 7 +++ Cargo.lock | 38 +++++++--------- Cargo.toml | 68 +++++++++++++++++++---------- README.md | 9 +++- rust-toolchain.toml | 2 +- src/builder.rs | 43 +++++++++++++----- src/bundle.rs | 15 ++++--- src/circuit.rs | 13 +++--- src/circuit/constants.rs | 1 + src/circuit/ecc.rs | 4 +- src/circuit/pedersen_hash.rs | 1 + src/constants.rs | 4 ++ src/keys.rs | 7 ++- src/lib.rs | 15 ++++++- src/note/nullifier.rs | 5 ++- src/note_encryption.rs | 9 ++-- src/pczt.rs | 8 +++- src/pczt/io_finalizer.rs | 1 + src/pczt/parse.rs | 4 +- src/pczt/updater.rs | 3 ++ src/pedersen_hash.rs | 17 +++++--- src/pedersen_hash/test_vectors.rs | 1 + src/prover.rs | 6 ++- src/test_vectors/note_encryption.rs | 2 + src/test_vectors/signatures.rs | 2 + src/tree.rs | 22 ++++++---- src/value/sums.rs | 1 + src/verifier/batch.rs | 3 ++ src/zip32.rs | 57 +++++++++++++----------- 30 files changed, 261 insertions(+), 135 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8c57b34a..6ebcf063 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,12 +41,36 @@ jobs: matrix: target: - wasm32-wasi + - thumbv7em-none-eabihf steps: - uses: actions/checkout@v4 + with: + path: crate_root + # We use a synthetic crate to ensure no dev-dependencies are enabled, which can + # be incompatible with some of these targets. + - name: Create synthetic crate for testing + run: cargo init --lib ci-build + - name: Copy Rust version into synthetic crate + run: cp crate_root/rust-toolchain.toml ci-build/ + - name: Copy patch directives into synthetic crate + run: | + echo "[patch.crates-io]" >> ./ci-build/Cargo.toml + cat ./crate_root/Cargo.toml | sed "0,/.\+\(patch.crates.\+\)/d" >> ./ci-build/Cargo.toml + - name: Add no_std pragma to lib.rs + run: | + echo "#![no_std]" > ./ci-build/src/lib.rs + - name: Add sapling-crypto as a dependency of the synthetic crate + working-directory: ./ci-build + run: cargo add --no-default-features --path ../crate_root + - name: Add lazy_static with the spin_no_std feature + working-directory: ./ci-build + run: cargo add lazy_static --features "spin_no_std" - name: Add target + working-directory: ./ci-build run: rustup target add ${{ matrix.target }} - - name: Build crate - run: cargo build --no-default-features --verbose --target ${{ matrix.target }} + - name: Build for target + working-directory: ./ci-build + run: cargo build --verbose --target ${{ matrix.target }} bitrot: name: Bitrot check diff --git a/CHANGELOG.md b/CHANGELOG.md index 53186af4..cc86cd76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,13 @@ and this library adheres to Rust's notion of ### Added - `sapling_crypto::pczt::Zip32Derivation::extract_account_index` +- `no_std` compatibility has been introduced by means of a default-enabled + `std` feature flag. +- A default-enabled `circuit` is now provided to enable downstream users to + avoid the need to depend upon the `bellman` crate. + +### Changed +- MSRV is now 1.66 ## [0.4.0] - 2024-12-16 diff --git a/Cargo.lock b/Cargo.lock index 2225ab87..b85b6470 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -342,6 +342,15 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21a53c0a4d288377e7415b53dcfc3c04da5cdc2cc95c8d5ac178b58f0b861ad6" +[[package]] +name = "core2" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "239fa3ae9b63c2dc74bd3fa852d4792b8b305ae64eeede946265b6af62f1fff3" +dependencies = [ + "memchr", +] + [[package]] name = "cpp_demangle" version = "0.4.3" @@ -815,9 +824,9 @@ dependencies = [ [[package]] name = "memuse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2145869435ace5ea6ea3d35f59be559317ec9a0d04e1812d5f185a87b6d36f1a" +checksum = "3d97bbf43eb4f088f8ca469930cde17fa036207c9a5e02ccc5107c4e8b17c964" [[package]] name = "miniz_oxide" @@ -1202,12 +1211,10 @@ dependencies = [ [[package]] name = "redjubjub" version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a60db2c3bc9c6fd1e8631fee75abc008841d27144be744951d6b9b75f9b569c" +source = "git+https://github.com/nuttycom/redjubjub?rev=e413019904400f4caa3550df7c4040befadfbb14#e413019904400f4caa3550df7c4040befadfbb14" dependencies = [ "rand_core", "reddsa", - "serde", "thiserror", "zeroize", ] @@ -1315,8 +1322,8 @@ dependencies = [ "blake2b_simd", "blake2s_simd", "bls12_381", - "byteorder", "chacha20poly1305", + "core2", "criterion", "document-features", "ff", @@ -1504,29 +1511,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "pin-project-lite", - "tracing-attributes", "tracing-core", ] -[[package]] -name = "tracing-attributes" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "tracing-core" version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" -dependencies = [ - "once_cell", -] [[package]] name = "typenum" @@ -1893,9 +1885,9 @@ dependencies = [ [[package]] name = "zip32" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d724a63be4dfb50b7f3617e542984e22e4b4a5b8ca5de91f55613152885e6b22" +checksum = "4226d0aee9c9407c27064dfeec9d7b281c917de3374e1e5a2e2cfad9e09de19e" dependencies = [ "blake2b_simd", "memuse", diff --git a/Cargo.toml b/Cargo.toml index fa48cb33..f92ccd86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ authors = [ "Kris Nuttycombe ", ] edition = "2021" -rust-version = "1.65" +rust-version = "1.66" description = "Cryptographic library for Zcash Sapling" homepage = "https://github.com/zcash/sapling-crypto" repository = "https://github.com/zcash/sapling-crypto" @@ -18,48 +18,50 @@ features = ["test-dependencies"] rustdoc-args = ["--cfg", "docsrs"] [dependencies] -ff = "0.13" -group = { version = "0.13", features = ["wnaf-memuse"] } +ff = { version = "0.13", default-features = false } +group = "0.13" -bls12_381 = "0.8" -jubjub = "0.10" -redjubjub = "0.7" +bls12_381 = { version = "0.8", default-features = false, features = ["alloc"] } +jubjub = { version = "0.10", default-features = false, features = ["alloc"] } +redjubjub = { version = "0.7", default-features = false } zcash_spec = "0.1" # Boilerplate getset = "0.1" +# No-std support +core2 = { version = "0.3", default-features = false, features = ["alloc"] } + # Circuits -bellman = { version = "0.14", default-features = false, features = ["groth16"] } +bellman = { version = "0.14", features = ["groth16"], optional = true } # CSPRNG -rand = "0.8" -rand_core = "0.6" +rand = { version = "0.8", default-features = false } +rand_core = { version = "0.6", default-features = false } # Digests -blake2b_simd = "1" -blake2s_simd = "1" +blake2b_simd = { version = "1", default-features = false } +blake2s_simd = { version = "1", default-features = false } # Documentation -document-features = "0.2" +document-features = { version = "0.2", optional = true } # Encodings -byteorder = "1" -hex = "0.4" +hex = { version = "0.4", default-features = false, features = ["alloc"] } # Logging and metrics -memuse = "0.2.1" -tracing = "0.1" +memuse = { version = "0.2.2", default-features = false } +tracing = { version = "0.1", default-features = false } # Note Commitment Trees -bitvec = "1" -incrementalmerkletree = { version = "0.7", features = ["legacy-api"] } +bitvec = { version = "1", default-features = false } +incrementalmerkletree = { version = "0.7", default-features = false, features = ["legacy-api"] } # Note encryption zcash_note_encryption = { version = "0.4", features = ["pre-zip-212"] } # Secret management -subtle = "2.2.3" +subtle = { version = "2.2.3", default-features = false } # Static constants lazy_static = "1" @@ -69,8 +71,9 @@ proptest = { version = "1", optional = true } # ZIP 32 aes = "0.8" -fpe = "0.6" -zip32 = "0.1" +fpe = { version = "0.6", default-features = false, features = ["alloc"] } +zip32 = { version = "0.1.1", default-features = false } + [dev-dependencies] chacha20poly1305 = "0.10" @@ -83,10 +86,26 @@ rand_xorshift = "0.3" pprof = { version = "0.11", features = ["criterion", "flamegraph"] } # MSRV 1.56 [features] -default = ["multicore"] +default = ["multicore", "std"] +std = [ + "core2/std", + "document-features", + "group/wnaf-memuse", + "redjubjub/std", + "circuit", +] + +## Enables creation of Sapling proofs +circuit = [ + "bellman", + "bls12_381/bits", + "bls12_381/groups", + "bls12_381/pairings", + "jubjub/bits", +] ## Enables multithreading support for creating proofs. -multicore = ["bellman/multicore"] +multicore = ["circuit", "bellman/multicore"] ### A temporary feature flag that exposes granular APIs needed by `zcashd`. These APIs ### should not be relied upon and will be removed in a future release. @@ -105,3 +124,6 @@ harness = false [[bench]] name = "pedersen_hash" harness = false + +[patch.crates-io] +redjubjub = { git = "https://github.com/nuttycom/redjubjub", rev = "e413019904400f4caa3550df7c4040befadfbb14" } diff --git a/README.md b/README.md index 6becc043..4f9c0df8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,13 @@ # sapling-crypto -This repository contains a (work-in-progress) implementation of Zcash's "Sapling" cryptography. +This repository contains an implementation of Zcash's "Sapling" cryptography. + +## `no_std` compatibility + +Downstream users of this crate must enable the `spin_no_std` feature of the +`lazy_static` crate in order to take advantage of `no_std` builds; this is due +to the fact that `--no-default-features` builds of `lazy_static` still rely on +`std`. ## License diff --git a/rust-toolchain.toml b/rust-toolchain.toml index ce699381..4a496752 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.65.0" +channel = "1.66.0" components = ["clippy", "rustfmt"] diff --git a/src/builder.rs b/src/builder.rs index 6665563f..4b08d6e1 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1,7 +1,8 @@ //! Types and functions for building Sapling transaction components. -use core::fmt; -use std::{collections::BTreeMap, iter, marker::PhantomData}; +use alloc::collections::BTreeMap; +use alloc::vec::Vec; +use core::{fmt, iter, marker::PhantomData}; use group::ff::Field; use incrementalmerkletree::Position; @@ -11,24 +12,27 @@ use redjubjub::{Binding, SpendAuth}; use zcash_note_encryption::EphemeralKeyBytes; use crate::{ - bundle::{ - Authorization, Authorized, Bundle, GrothProofBytes, OutputDescription, SpendDescription, - }, - circuit, + bundle::{Authorization, Authorized, Bundle, GrothProofBytes}, keys::{ EphemeralSecretKey, ExpandedSpendingKey, FullViewingKey, OutgoingViewingKey, SpendAuthorizingKey, SpendValidatingKey, }, note::ExtractedNoteCommitment, note_encryption::{sapling_note_encryption, Zip212Enforcement}, - prover::{OutputProver, SpendProver}, util::generate_random_rseed_internal, - value::{ - CommitmentSum, NoteValue, TrapdoorSum, ValueCommitTrapdoor, ValueCommitment, ValueSum, - }, + value::{NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum}, + Anchor, Diversifier, MerklePath, Node, Note, Nullifier, PaymentAddress, SaplingIvk, + NOTE_COMMITMENT_TREE_DEPTH, +}; + +#[cfg(feature = "circuit")] +use crate::{ + bundle::{OutputDescription, SpendDescription}, + circuit, + prover::{OutputProver, SpendProver}, + value::{CommitmentSum, TrapdoorSum}, zip32::ExtendedSpendingKey, - Anchor, Diversifier, MerklePath, Node, Note, Nullifier, PaymentAddress, ProofGenerationKey, - SaplingIvk, NOTE_COMMITMENT_TREE_DEPTH, + ProofGenerationKey, }; /// If there are any shielded inputs, always have at least two shielded outputs, padding @@ -268,6 +272,7 @@ impl PreparedSpendInfo { (cv, nullifier, rk, alpha) } + #[cfg(feature = "circuit")] fn build( self, proof_generation_key: Option, @@ -464,6 +469,7 @@ impl PreparedOutputInfo { ) } + #[cfg(feature = "circuit")] fn build( self, rng: &mut R, @@ -664,6 +670,7 @@ impl Builder { } /// Constructs the Sapling bundle from the builder's accumulated state. + #[cfg(feature = "circuit")] pub fn build>( self, extsks: &[ExtendedSpendingKey], @@ -726,6 +733,7 @@ impl Builder { /// Constructs a new Sapling transaction bundle of the given type from the specified set of spends /// and outputs. +#[cfg(feature = "circuit")] pub fn bundle>( rng: R, bundle_type: BundleType, @@ -917,6 +925,7 @@ fn build_bundle( /// Type alias for an in-progress bundle that has no proofs or signatures. /// /// This is returned by [`Builder::build`]. +#[cfg(feature = "circuit")] pub type UnauthorizedBundle = Bundle, V>; /// Marker trait representing bundle proofs in the process of being created. @@ -951,9 +960,11 @@ impl Authorization for InProgress< /// /// The [`SpendDescription`]s and [`OutputDescription`]s within the bundle contain the /// private data needed to create proofs. +#[cfg(feature = "circuit")] #[derive(Clone, Copy, Debug)] pub struct Unproven; +#[cfg(feature = "circuit")] impl InProgressProofs for Unproven { type SpendProof = circuit::Spend; type OutputProof = circuit::Output; @@ -969,16 +980,19 @@ impl InProgressProofs for Proven { } /// Reports on the progress made towards creating proofs for a bundle. +#[cfg(feature = "circuit")] pub trait ProverProgress { /// Updates the progress instance with the number of steps completed and the total /// number of steps. fn update(&mut self, cur: u32, end: u32); } +#[cfg(feature = "circuit")] impl ProverProgress for () { fn update(&mut self, _: u32, _: u32) {} } +#[cfg(feature = "circuit")] impl> ProverProgress for std::sync::mpsc::Sender { fn update(&mut self, cur: u32, end: u32) { // If the send fails, we should ignore the error, not crash. @@ -986,12 +1000,14 @@ impl> ProverProgress for std::sync::mpsc::Sender { } } +#[cfg(feature = "circuit")] impl ProverProgress for &mut U { fn update(&mut self, cur: u32, end: u32) { (*self).update(cur, end); } } +#[cfg(feature = "circuit")] struct CreateProofs<'a, SP: SpendProver, OP: OutputProver, R: RngCore, U: ProverProgress> { spend_prover: &'a SP, output_prover: &'a OP, @@ -1001,6 +1017,7 @@ struct CreateProofs<'a, SP: SpendProver, OP: OutputProver, R: RngCore, U: Prover progress: u32, } +#[cfg(feature = "circuit")] impl<'a, SP: SpendProver, OP: OutputProver, R: RngCore, U: ProverProgress> CreateProofs<'a, SP, OP, R, U> { @@ -1041,6 +1058,7 @@ impl<'a, SP: SpendProver, OP: OutputProver, R: RngCore, U: ProverProgress> OP::encode_proof(proof) } + #[cfg(feature = "circuit")] fn map_authorization( &mut self, a: InProgress, @@ -1052,6 +1070,7 @@ impl<'a, SP: SpendProver, OP: OutputProver, R: RngCore, U: ProverProgress> } } +#[cfg(feature = "circuit")] impl Bundle, V> { /// Creates the proofs for this bundle. pub fn create_proofs( diff --git a/src/bundle.rs b/src/bundle.rs index b015f133..f81353c3 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -1,3 +1,4 @@ +use alloc::vec::Vec; use core::fmt::Debug; use memuse::DynamicUsage; @@ -9,7 +10,7 @@ use zcash_note_encryption::{ }; use crate::{ - circuit::GROTH_PROOF_SIZE, + constants::GROTH_PROOF_SIZE, note::ExtractedNoteCommitment, note_encryption::{CompactOutputDescription, SaplingDomain}, value::ValueCommitment, @@ -220,8 +221,8 @@ pub struct SpendDescription { spend_auth_sig: A::AuthSig, } -impl std::fmt::Debug for SpendDescription { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { +impl core::fmt::Debug for SpendDescription { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { write!( f, "SpendDescription(cv = {:?}, anchor = {:?}, nullifier = {:?}, rk = {:?}, spend_auth_sig = {:?})", @@ -431,8 +432,8 @@ impl ShieldedOutput for OutputDescription } } -impl std::fmt::Debug for OutputDescription { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { +impl core::fmt::Debug for OutputDescription { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { write!( f, "OutputDescription(cv = {:?}, cmu = {:?}, ephemeral_key = {:?})", @@ -498,7 +499,7 @@ impl From> for CompactOutputDescription { #[cfg(any(test, feature = "test-dependencies"))] #[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))] pub mod testing { - use std::fmt; + use core::fmt; use ff::Field; use group::{Group, GroupEncoding}; @@ -507,7 +508,7 @@ pub mod testing { use rand::{rngs::StdRng, SeedableRng}; use crate::{ - circuit::GROTH_PROOF_SIZE, + constants::GROTH_PROOF_SIZE, note::testing::arb_cmu, value::{ testing::{arb_note_value_bounded, arb_trapdoor}, diff --git a/src/circuit.rs b/src/circuit.rs index 611dc887..2a67f319 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -1,15 +1,14 @@ //! The Sapling circuits. +use alloc::vec::Vec; use core::fmt; -use std::io; +use core2::io; use group::{ff::PrimeField, Curve}; use bellman::{groth16, Circuit, ConstraintSystem, SynthesisError}; use bls12_381::Bls12; -use super::{value::NoteValue, PaymentAddress, ProofGenerationKey}; - use bellman::gadgets::blake2s; use bellman::gadgets::boolean; use bellman::gadgets::multipack; @@ -21,6 +20,7 @@ use self::constants::{ PROOF_GENERATION_KEY_GENERATOR, SPENDING_KEY_GENERATOR, VALUE_COMMITMENT_RANDOMNESS_GENERATOR, VALUE_COMMITMENT_VALUE_GENERATOR, }; +use crate::{value::NoteValue, PaymentAddress, ProofGenerationKey}; #[cfg(test)] use group::ff::PrimeFieldBits; @@ -29,9 +29,6 @@ mod constants; mod ecc; mod pedersen_hash; -// π_A + π_B + π_C -pub(crate) const GROTH_PROOF_SIZE: usize = 48 + 96 + 48; - /// The opening (value and randomness) of a Sapling value commitment. #[derive(Clone)] pub struct ValueCommitmentOpening { @@ -703,7 +700,7 @@ fn test_input_circuit_with_bls12_381() { let mut rhs = uncle; if b { - ::std::mem::swap(&mut lhs, &mut rhs); + ::core::mem::swap(&mut lhs, &mut rhs); } let lhs = lhs.to_le_bits(); @@ -886,7 +883,7 @@ fn test_input_circuit_with_bls12_381_external_test_vectors() { let mut rhs = uncle; if b { - ::std::mem::swap(&mut lhs, &mut rhs); + ::core::mem::swap(&mut lhs, &mut rhs); } let lhs = lhs.to_le_bits(); diff --git a/src/circuit/constants.rs b/src/circuit/constants.rs index 692c688f..17ae9593 100644 --- a/src/circuit/constants.rs +++ b/src/circuit/constants.rs @@ -2,6 +2,7 @@ use crate::constants::{PEDERSEN_HASH_CHUNKS_PER_GENERATOR, PEDERSEN_HASH_GENERATORS}; +use alloc::vec::Vec; use bls12_381::Scalar; use group::{ff::Field, Curve, Group}; use jubjub::ExtendedPoint; diff --git a/src/circuit/ecc.rs b/src/circuit/ecc.rs index 6bffd876..ebe73314 100644 --- a/src/circuit/ecc.rs +++ b/src/circuit/ecc.rs @@ -1,6 +1,7 @@ //! Gadgets implementing Jubjub elliptic curve operations. -use std::ops::{AddAssign, MulAssign, Neg, SubAssign}; +use alloc::vec::Vec; +use core::ops::{AddAssign, MulAssign, Neg, SubAssign}; use bellman::{ConstraintSystem, SynthesisError}; @@ -620,6 +621,7 @@ impl MontgomeryPoint { #[cfg(test)] mod test { + use alloc::vec::Vec; use bellman::ConstraintSystem; use group::{ ff::{Field, PrimeField, PrimeFieldBits}, diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs index 94b90140..29718a8a 100644 --- a/src/circuit/pedersen_hash.rs +++ b/src/circuit/pedersen_hash.rs @@ -3,6 +3,7 @@ use super::ecc::{EdwardsPoint, MontgomeryPoint}; pub use crate::pedersen_hash::Personalization; +use alloc::vec::Vec; use bellman::gadgets::boolean::Boolean; use bellman::gadgets::lookup::*; use bellman::{ConstraintSystem, SynthesisError}; diff --git a/src/constants.rs b/src/constants.rs index bb730a97..0bfdf4db 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,5 +1,6 @@ //! Various constants used by the Sapling protocol. +use alloc::vec::Vec; use ff::PrimeField; use group::Group; use jubjub::SubgroupPoint; @@ -38,6 +39,9 @@ pub const VALUE_COMMITMENT_GENERATOR_PERSONALIZATION: &[u8; 8] = b"Zcash_cv"; /// BLAKE2s Personalization for the nullifier position generator (for computing rho) pub const NULLIFIER_POSITION_IN_TREE_GENERATOR_PERSONALIZATION: &[u8; 8] = b"Zcash_J_"; +// π_A + π_B + π_C +pub(crate) const GROTH_PROOF_SIZE: usize = 48 + 96 + 48; + /// The prover will demonstrate knowledge of discrete log with respect to this base when /// they are constructing a proof, in order to authorize proof construction. pub const PROOF_GENERATION_KEY_GENERATOR: SubgroupPoint = SubgroupPoint::from_raw_unchecked( diff --git a/src/keys.rs b/src/keys.rs index b636785d..9e583b09 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -4,8 +4,9 @@ //! //! [section 4.2.2]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents -use std::fmt; -use std::io::{self, Read, Write}; +use alloc::vec::Vec; +use core::fmt; +use core2::io::{self, Read, Write}; use super::{ address::PaymentAddress, @@ -444,6 +445,7 @@ impl SaplingIvk { #[derive(Clone, Debug)] pub struct PreparedIncomingViewingKey(PreparedScalar); +#[cfg(feature = "std")] impl memuse::DynamicUsage for PreparedIncomingViewingKey { fn dynamic_usage(&self) -> usize { self.0.dynamic_usage() @@ -690,6 +692,7 @@ pub mod testing { #[cfg(test)] mod tests { + use alloc::string::ToString; use group::{Group, GroupEncoding}; use super::{FullViewingKey, SpendAuthorizingKey, SpendValidatingKey}; diff --git a/src/lib.rs b/src/lib.rs index 654b767a..8cc57207 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,17 +8,26 @@ //! opposed to e.g. an Orchard payment address, which is also shielded). //! //! ## Feature flags -#![doc = document_features::document_features!()] +#![cfg_attr(feature = "std", doc = document_features::document_features!())] //! +#![no_std] #![cfg_attr(docsrs, feature(doc_cfg))] // Catch documentation errors caused by code changes. #![deny(rustdoc::broken_intra_doc_links)] #![deny(unsafe_code)] +#[macro_use] +extern crate alloc; + +#[cfg(feature = "std")] +extern crate std; + mod address; pub mod builder; pub mod bundle; + +#[cfg(feature = "circuit")] pub mod circuit; pub mod constants; pub mod group_hash; @@ -27,11 +36,13 @@ pub mod note; pub mod note_encryption; pub mod pczt; pub mod pedersen_hash; +#[cfg(feature = "circuit")] pub mod prover; mod spec; mod tree; pub mod util; pub mod value; +#[cfg(feature = "circuit")] mod verifier; pub mod zip32; @@ -43,6 +54,8 @@ pub use tree::{ merkle_hash, Anchor, CommitmentTree, IncrementalWitness, MerklePath, Node, NOTE_COMMITMENT_TREE_DEPTH, }; + +#[cfg(feature = "circuit")] pub use verifier::{BatchValidator, SaplingVerificationContext}; #[cfg(any(test, feature = "test-dependencies"))] diff --git a/src/note/nullifier.rs b/src/note/nullifier.rs index 8176f40d..58fd01b8 100644 --- a/src/note/nullifier.rs +++ b/src/note/nullifier.rs @@ -1,5 +1,6 @@ -use std::array::TryFromSliceError; -use std::fmt; +use alloc::fmt; +use alloc::vec::Vec; +use core::array::TryFromSliceError; use subtle::{Choice, ConstantTimeEq}; diff --git a/src/note_encryption.rs b/src/note_encryption.rs index 48f03afa..756e04ec 100644 --- a/src/note_encryption.rs +++ b/src/note_encryption.rs @@ -2,8 +2,8 @@ //! //! NB: the example code is only covering the post-Canopy case. +use alloc::vec::Vec; use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams}; -use byteorder::{LittleEndian, WriteBytesExt}; use ff::PrimeField; use memuse::DynamicUsage; use rand_core::RngCore; @@ -193,9 +193,7 @@ impl Domain for SaplingDomain { Rseed::AfterZip212(_) => 2, }; input[1..12].copy_from_slice(¬e.recipient().diversifier().0); - (&mut input[12..20]) - .write_u64::(note.value().inner()) - .unwrap(); + input[12..20].copy_from_slice(¬e.value().inner().to_le_bytes()); match note.rseed() { Rseed::BeforeZip212(rcm) => { @@ -462,6 +460,7 @@ pub fn try_sapling_output_recovery( #[cfg(test)] mod tests { + use alloc::vec::Vec; use chacha20poly1305::{ aead::{AeadInPlace, KeyInit}, ChaCha20Poly1305, @@ -486,7 +485,7 @@ mod tests { use crate::{ bundle::{GrothProofBytes, OutputDescription}, - circuit::GROTH_PROOF_SIZE, + constants::GROTH_PROOF_SIZE, keys::{DiversifiedTransmissionKey, EphemeralSecretKey, OutgoingViewingKey}, note::ExtractedNoteCommitment, note_encryption::PreparedIncomingViewingKey, diff --git a/src/pczt.rs b/src/pczt.rs index 0a6ca0cc..c0badbb4 100644 --- a/src/pczt.rs +++ b/src/pczt.rs @@ -1,7 +1,9 @@ //! PCZT support for Sapling. +use alloc::collections::BTreeMap; +use alloc::string::String; +use alloc::vec::Vec; use core::fmt; -use std::collections::BTreeMap; use getset::Getters; use redjubjub::{Binding, SpendAuth}; @@ -30,7 +32,9 @@ pub use io_finalizer::IoFinalizerError; mod updater; pub use updater::{OutputUpdater, SpendUpdater, Updater, UpdaterError}; +#[cfg(feature = "circuit")] mod prover; +#[cfg(feature = "circuit")] pub use prover::ProverError; mod signer; @@ -277,7 +281,7 @@ pub struct Output { } impl fmt::Debug for Output { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("Output") .field("cv", &self.cv) .field("cmu", &self.cmu) diff --git a/src/pczt/io_finalizer.rs b/src/pczt/io_finalizer.rs index 723a4aa4..81642296 100644 --- a/src/pczt/io_finalizer.rs +++ b/src/pczt/io_finalizer.rs @@ -1,3 +1,4 @@ +use alloc::vec::Vec; use rand::{CryptoRng, RngCore}; use crate::value::{CommitmentSum, TrapdoorSum}; diff --git a/src/pczt/parse.rs b/src/pczt/parse.rs index 4ac77db9..dc11af4d 100644 --- a/src/pczt/parse.rs +++ b/src/pczt/parse.rs @@ -1,4 +1,6 @@ -use std::collections::BTreeMap; +use alloc::collections::BTreeMap; +use alloc::string::String; +use alloc::vec::Vec; use ff::PrimeField; use zcash_note_encryption::{EphemeralKeyBytes, OutgoingCipherKey}; diff --git a/src/pczt/updater.rs b/src/pczt/updater.rs index 71c7d7a1..357c3ecc 100644 --- a/src/pczt/updater.rs +++ b/src/pczt/updater.rs @@ -1,3 +1,6 @@ +use alloc::string::String; +use alloc::vec::Vec; + use crate::ProofGenerationKey; use super::{Bundle, Output, Spend, Zip32Derivation}; diff --git a/src/pedersen_hash.rs b/src/pedersen_hash.rs index 20cc40a5..43d4a102 100644 --- a/src/pedersen_hash.rs +++ b/src/pedersen_hash.rs @@ -3,14 +3,12 @@ #[cfg(test)] pub(crate) mod test_vectors; -use byteorder::{ByteOrder, LittleEndian}; +use alloc::vec::Vec; +use core::ops::{AddAssign, Neg}; use ff::PrimeField; use group::Group; -use std::ops::{AddAssign, Neg}; -use super::constants::{ - PEDERSEN_HASH_CHUNKS_PER_GENERATOR, PEDERSEN_HASH_EXP_TABLE, PEDERSEN_HASH_EXP_WINDOW_SIZE, -}; +use super::constants::{PEDERSEN_HASH_CHUNKS_PER_GENERATOR, PEDERSEN_HASH_EXP_WINDOW_SIZE}; #[derive(Copy, Clone)] pub enum Personalization { @@ -41,7 +39,7 @@ where .chain(bits.into_iter()); let mut result = jubjub::SubgroupPoint::identity(); - let mut generators = PEDERSEN_HASH_EXP_TABLE.iter(); + let mut generators = crate::constants::PEDERSEN_HASH_EXP_TABLE.iter(); loop { let mut acc = jubjub::Fr::zero(); @@ -94,7 +92,11 @@ where let acc = acc.to_repr(); let num_limbs: usize = acc.as_ref().len() / 8; let mut limbs = vec![0u64; num_limbs + 1]; - LittleEndian::read_u64_into(acc.as_ref(), &mut limbs[..num_limbs]); + for (src, dst) in acc.chunks_exact(8).zip(limbs[..num_limbs].iter_mut()) { + let mut limb_bytes = [0u8; 8]; + limb_bytes.copy_from_slice(src); + *dst = u64::from_le_bytes(limb_bytes); + } let mut tmp = jubjub::SubgroupPoint::identity(); @@ -124,6 +126,7 @@ where #[cfg(test)] pub mod test { + use alloc::string::ToString; use group::Curve; use super::*; diff --git a/src/pedersen_hash/test_vectors.rs b/src/pedersen_hash/test_vectors.rs index 4d051afc..84cd8d84 100644 --- a/src/pedersen_hash/test_vectors.rs +++ b/src/pedersen_hash/test_vectors.rs @@ -1,6 +1,7 @@ //! Test vectors from https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/sapling_pedersen.py use super::{test::TestVector, Personalization}; +use alloc::vec::Vec; pub fn get_vectors<'a>() -> Vec> { vec![ diff --git a/src/prover.rs b/src/prover.rs index f5e2ad7f..cfcbcb28 100644 --- a/src/prover.rs +++ b/src/prover.rs @@ -6,7 +6,8 @@ use rand_core::RngCore; use crate::{ bundle::GrothProofBytes, - circuit::{self, GROTH_PROOF_SIZE}, + circuit, + constants::GROTH_PROOF_SIZE, keys::EphemeralSecretKey, value::{NoteValue, ValueCommitTrapdoor}, MerklePath, @@ -179,7 +180,8 @@ pub mod mock { use super::{OutputProver, SpendProver}; use crate::{ bundle::GrothProofBytes, - circuit::{self, ValueCommitmentOpening, GROTH_PROOF_SIZE}, + circuit::{self, ValueCommitmentOpening}, + constants::GROTH_PROOF_SIZE, keys::EphemeralSecretKey, value::{NoteValue, ValueCommitTrapdoor}, Diversifier, MerklePath, PaymentAddress, ProofGenerationKey, Rseed, diff --git a/src/test_vectors/note_encryption.rs b/src/test_vectors/note_encryption.rs index 09209f29..9f7073c6 100644 --- a/src/test_vectors/note_encryption.rs +++ b/src/test_vectors/note_encryption.rs @@ -1,3 +1,5 @@ +use alloc::vec::Vec; + pub(crate) struct TestVector { pub ovk: [u8; 32], pub ivk: [u8; 32], diff --git a/src/test_vectors/signatures.rs b/src/test_vectors/signatures.rs index 7ce341cf..bec89fbd 100644 --- a/src/test_vectors/signatures.rs +++ b/src/test_vectors/signatures.rs @@ -1,3 +1,5 @@ +use alloc::vec::Vec; + pub(crate) struct TestVector { pub(crate) sk: [u8; 32], pub(crate) vk: [u8; 32], diff --git a/src/tree.rs b/src/tree.rs index ea339b4b..11c3906d 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -4,7 +4,8 @@ use incrementalmerkletree::{Hashable, Level}; use lazy_static::lazy_static; use subtle::CtOption; -use std::fmt; +use alloc::vec::Vec; +use core::fmt; use super::{ note::ExtractedNoteCommitment, @@ -20,14 +21,16 @@ pub type MerklePath = incrementalmerkletree::MerklePath = { - let mut v = vec![Node::empty_leaf()]; - for d in 0..NOTE_COMMITMENT_TREE_DEPTH { - let next = Node::combine(d.into(), &v[usize::from(d)], &v[usize::from(d)]); - v.push(next); - } - v - }; + static ref EMPTY_ROOTS: Vec = empty_roots(); +} + +fn empty_roots() -> Vec { + let mut v = vec![Node::empty_leaf()]; + for d in 0..NOTE_COMMITMENT_TREE_DEPTH { + let next = Node::combine(d.into(), &v[usize::from(d)], &v[usize::from(d)]); + v.push(next); + } + v } /// Compute a parent node in the Sapling commitment tree given its two children. @@ -144,6 +147,7 @@ impl Node { } /// Returns the wrapped value + #[cfg(feature = "circuit")] pub(crate) fn inner(&self) -> &jubjub::Base { &self.0 } diff --git a/src/value/sums.rs b/src/value/sums.rs index 05436067..12235441 100644 --- a/src/value/sums.rs +++ b/src/value/sums.rs @@ -18,6 +18,7 @@ impl fmt::Display for OverflowError { } } +#[cfg(feature = "std")] impl std::error::Error for OverflowError {} /// A sum of Sapling note values. diff --git a/src/verifier/batch.rs b/src/verifier/batch.rs index 0eb196e0..d6306202 100644 --- a/src/verifier/batch.rs +++ b/src/verifier/batch.rs @@ -140,7 +140,10 @@ impl BatchValidator { } if let Err(e) = self.signatures.verify(&mut rng) { + #[cfg(feature = "std")] tracing::debug!("Signature batch validation failed: {}", e); + #[cfg(not(feature = "std"))] + tracing::debug!("Signature batch validation failed: {:?}", e); return false; } diff --git a/src/zip32.rs b/src/zip32.rs index ea744f42..bf04deb4 100644 --- a/src/zip32.rs +++ b/src/zip32.rs @@ -6,14 +6,13 @@ use aes::Aes256; use blake2b_simd::Params as Blake2bParams; -use byteorder::{ByteOrder, LittleEndian, ReadBytesExt, WriteBytesExt}; use fpe::ff1::{BinaryNumeralString, FF1}; use subtle::CtOption; use zcash_spec::PrfExpand; use zip32::{ChainCode, ChildIndex, DiversifierIndex, Scope}; -use std::io::{self, Read, Write}; -use std::ops::AddAssign; +use core::ops::AddAssign; +use core2::io::{self, Read, Write}; use super::{Diversifier, NullifierDerivingKey, PaymentAddress, ViewingKey}; use crate::note_encryption::PreparedIncomingViewingKey; @@ -269,7 +268,7 @@ pub struct ExtendedSpendingKey { dk: DiversifierKey, } -impl std::cmp::PartialEq for ExtendedSpendingKey { +impl core::cmp::PartialEq for ExtendedSpendingKey { fn eq(&self, rhs: &ExtendedSpendingKey) -> bool { self.depth == rhs.depth && self.parent_fvk_tag == rhs.parent_fvk_tag @@ -282,8 +281,8 @@ impl std::cmp::PartialEq for ExtendedSpendingKey { } } -impl std::fmt::Debug for ExtendedSpendingKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { +impl core::fmt::Debug for ExtendedSpendingKey { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { write!( f, "ExtendedSpendingKey(d = {}, tag_p = {:?}, i = {:?})", @@ -354,17 +353,20 @@ impl ExtendedSpendingKey { /// Reads and decodes the encoded form of the extended spending key as defined in /// [ZIP 32](https://zips.z.cash/zip-0032) from the provided reader. pub fn read(mut reader: R) -> io::Result { - let depth = reader.read_u8()?; + let mut depth = [0; 1]; + reader.read_exact(&mut depth)?; + let depth = depth[0]; let mut tag = [0; 4]; reader.read_exact(&mut tag)?; - let child_index = reader.read_u32::().and_then(|i| { - KeyIndex::new(depth, i).ok_or_else(|| { - io::Error::new( - io::ErrorKind::Unsupported, + let mut child_index_bytes = [0; 4]; + reader.read_exact(&mut child_index_bytes)?; + let child_index = + KeyIndex::new(depth, u32::from_le_bytes(child_index_bytes)).ok_or_else(|| { + core2::io::Error::new( + core2::io::ErrorKind::InvalidData, "Unsupported child index in encoding", ) - }) - })?; + })?; let mut c = [0; 32]; reader.read_exact(&mut c)?; let expsk = ExpandedSpendingKey::read(&mut reader)?; @@ -420,7 +422,7 @@ impl ExtendedSpendingKey { let fvk = FullViewingKey::from_expanded_spending_key(&self.expsk); let tmp = { let mut le_i = [0; 4]; - LittleEndian::write_u32(&mut le_i, i.index()); + le_i.copy_from_slice(&i.index().to_le_bytes()); PrfExpand::SAPLING_ZIP32_CHILD_HARDENED.with( self.chain_code.as_bytes(), &self.expsk.to_bytes(), @@ -529,7 +531,7 @@ pub struct ExtendedFullViewingKey { pub(crate) dk: DiversifierKey, } -impl std::cmp::PartialEq for ExtendedFullViewingKey { +impl core::cmp::PartialEq for ExtendedFullViewingKey { fn eq(&self, rhs: &ExtendedFullViewingKey) -> bool { self.depth == rhs.depth && self.parent_fvk_tag == rhs.parent_fvk_tag @@ -542,8 +544,8 @@ impl std::cmp::PartialEq for ExtendedFullViewingKey { } } -impl std::fmt::Debug for ExtendedFullViewingKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { +impl core::fmt::Debug for ExtendedFullViewingKey { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { write!( f, "ExtendedFullViewingKey(d = {}, tag_p = {:?}, i = {:?})", @@ -554,17 +556,20 @@ impl std::fmt::Debug for ExtendedFullViewingKey { impl ExtendedFullViewingKey { pub fn read(mut reader: R) -> io::Result { - let depth = reader.read_u8()?; + let mut depth = [0; 1]; + reader.read_exact(&mut depth)?; + let depth = depth[0]; let mut tag = [0; 4]; reader.read_exact(&mut tag)?; - let child_index = reader.read_u32::().and_then(|i| { - KeyIndex::new(depth, i).ok_or_else(|| { - io::Error::new( - io::ErrorKind::Unsupported, + let mut child_index_bytes = [0; 4]; + reader.read_exact(&mut child_index_bytes)?; + let child_index = + KeyIndex::new(depth, u32::from_le_bytes(child_index_bytes)).ok_or_else(|| { + core2::io::Error::new( + core2::io::ErrorKind::InvalidData, "Unsupported child index in encoding", ) - }) - })?; + })?; let mut c = [0; 32]; reader.read_exact(&mut c)?; let fvk = FullViewingKey::read(&mut reader)?; @@ -582,9 +587,9 @@ impl ExtendedFullViewingKey { } pub fn write(&self, mut writer: W) -> io::Result<()> { - writer.write_u8(self.depth)?; + writer.write_all(&[self.depth])?; writer.write_all(&self.parent_fvk_tag.0)?; - writer.write_u32::(self.child_index.index())?; + writer.write_all(&self.child_index.index().to_le_bytes())?; writer.write_all(self.chain_code.as_bytes())?; writer.write_all(&self.fvk.to_bytes())?; writer.write_all(&self.dk.0)?; From f1e3d3bdd0bc61962d23423194ff35df85c2cc6b Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 19 Dec 2024 10:03:07 -0700 Subject: [PATCH 20/30] Apply suggestions from code review Co-authored-by: Jack Grigg --- Cargo.toml | 12 ++++++------ src/builder.rs | 12 +++++++----- src/circuit.rs | 5 ++++- src/keys.rs | 4 ++-- src/lib.rs | 2 +- src/pedersen_hash.rs | 4 +--- src/zip32.rs | 3 +-- 7 files changed, 22 insertions(+), 20 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f92ccd86..f191ae5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ getset = "0.1" core2 = { version = "0.3", default-features = false, features = ["alloc"] } # Circuits -bellman = { version = "0.14", features = ["groth16"], optional = true } +bellman = { version = "0.14", default-features = false, features = ["groth16"], optional = true } # CSPRNG rand = { version = "0.8", default-features = false } @@ -86,26 +86,26 @@ rand_xorshift = "0.3" pprof = { version = "0.11", features = ["criterion", "flamegraph"] } # MSRV 1.56 [features] -default = ["multicore", "std"] +default = ["multicore", "circuit"] std = [ "core2/std", - "document-features", + "dep:document-features", "group/wnaf-memuse", "redjubjub/std", - "circuit", ] ## Enables creation of Sapling proofs circuit = [ - "bellman", + "dep:bellman", "bls12_381/bits", "bls12_381/groups", "bls12_381/pairings", "jubjub/bits", + "std" ] ## Enables multithreading support for creating proofs. -multicore = ["circuit", "bellman/multicore"] +multicore = ["bellman?/multicore"] ### A temporary feature flag that exposes granular APIs needed by `zcashd`. These APIs ### should not be relied upon and will be removed in a future release. diff --git a/src/builder.rs b/src/builder.rs index 4b08d6e1..dd6e181a 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -992,7 +992,7 @@ impl ProverProgress for () { fn update(&mut self, _: u32, _: u32) {} } -#[cfg(feature = "circuit")] +#[cfg(all(feature = "circuit", feature = "std"))] impl> ProverProgress for std::sync::mpsc::Sender { fn update(&mut self, cur: u32, end: u32) { // If the send fails, we should ignore the error, not crash. @@ -1058,7 +1058,6 @@ impl<'a, SP: SpendProver, OP: OutputProver, R: RngCore, U: ProverProgress> OP::encode_proof(proof) } - #[cfg(feature = "circuit")] fn map_authorization( &mut self, a: InProgress, @@ -1301,9 +1300,9 @@ impl Bundle, V> { } } -#[cfg(any(test, feature = "test-dependencies"))] +#[cfg(all(feature = "circuit", any(test, feature = "test-dependencies")))] pub(crate) mod testing { - use std::fmt; + use core::fmt; use proptest::collection::vec; use proptest::prelude::*; @@ -1312,7 +1311,6 @@ pub(crate) mod testing { use crate::{ bundle::{Authorized, Bundle}, note_encryption::Zip212Enforcement, - prover::mock::{MockOutputProver, MockSpendProver}, testing::{arb_node, arb_note}, value::testing::arb_positive_note_value, zip32::testing::arb_extended_spending_key, @@ -1324,7 +1322,11 @@ pub(crate) mod testing { use super::{Builder, BundleType}; + #[cfg(feature = "circuit")] + use crate::prover::mock::{MockOutputProver, MockSpendProver}; + #[allow(dead_code)] + #[cfg(feature = "circuit")] fn arb_bundle>( max_money: u64, zip212_enforcement: Zip212Enforcement, diff --git a/src/circuit.rs b/src/circuit.rs index 2a67f319..ff0a7dcc 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -562,7 +562,10 @@ impl SpendParameters { /// Only set `verify_point_encodings` to false if you are verifying the parameters in /// another way (such as checking the hash of the parameters file on disk). pub fn read(reader: R, verify_point_encodings: bool) -> io::Result { - groth16::Parameters::::read(reader, verify_point_encodings).map(Self) + Ok(Self(groth16::Parameters::::read( + reader, + verify_point_encodings, + )?)) } /// Returns the verifying key for the Sapling Spend circuit. diff --git a/src/keys.rs b/src/keys.rs index 9e583b09..4c357f83 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -27,7 +27,7 @@ use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; use zcash_note_encryption::EphemeralKeyBytes; use zcash_spec::PrfExpand; -#[cfg(test)] +#[cfg(all(feature = "circuit", test))] use rand_core::RngCore; /// Errors that can occur in the decoding of Sapling spending keys. @@ -154,7 +154,7 @@ impl Eq for SpendValidatingKey {} impl SpendValidatingKey { /// For circuit tests only. - #[cfg(test)] + #[cfg(all(feature = "circuit", test))] pub(crate) fn fake_random(mut rng: R) -> Self { loop { if let Some(k) = Self::from_bytes(&jubjub::SubgroupPoint::random(&mut rng).to_bytes()) { diff --git a/src/lib.rs b/src/lib.rs index 8cc57207..65fbaa62 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,7 @@ //! shielded payment address; we implicitly mean it is an Sapling payment address (as //! opposed to e.g. an Orchard payment address, which is also shielded). //! -//! ## Feature flags +#![cfg_attr(feature = "std", doc = "## Feature flags")] #![cfg_attr(feature = "std", doc = document_features::document_features!())] //! diff --git a/src/pedersen_hash.rs b/src/pedersen_hash.rs index 43d4a102..0e2f41a9 100644 --- a/src/pedersen_hash.rs +++ b/src/pedersen_hash.rs @@ -93,9 +93,7 @@ where let num_limbs: usize = acc.as_ref().len() / 8; let mut limbs = vec![0u64; num_limbs + 1]; for (src, dst) in acc.chunks_exact(8).zip(limbs[..num_limbs].iter_mut()) { - let mut limb_bytes = [0u8; 8]; - limb_bytes.copy_from_slice(src); - *dst = u64::from_le_bytes(limb_bytes); + *dst = u64::from_le_bytes(src.try_into().expect("correct length")); } let mut tmp = jubjub::SubgroupPoint::identity(); diff --git a/src/zip32.rs b/src/zip32.rs index bf04deb4..66ae8f91 100644 --- a/src/zip32.rs +++ b/src/zip32.rs @@ -421,8 +421,7 @@ impl ExtendedSpendingKey { pub fn derive_child(&self, i: ChildIndex) -> Self { let fvk = FullViewingKey::from_expanded_spending_key(&self.expsk); let tmp = { - let mut le_i = [0; 4]; - le_i.copy_from_slice(&i.index().to_le_bytes()); + let le_i = i.index().to_le_bytes(); PrfExpand::SAPLING_ZIP32_CHILD_HARDENED.with( self.chain_code.as_bytes(), &self.expsk.to_bytes(), From 469b78674147cf56d5b830014f8f5a20e36ed49f Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Tue, 28 Jan 2025 15:01:12 -0700 Subject: [PATCH 21/30] Update patched `redjubjub` dependency. --- Cargo.lock | 2 +- Cargo.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b85b6470..c097b175 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1211,7 +1211,7 @@ dependencies = [ [[package]] name = "redjubjub" version = "0.7.0" -source = "git+https://github.com/nuttycom/redjubjub?rev=e413019904400f4caa3550df7c4040befadfbb14#e413019904400f4caa3550df7c4040befadfbb14" +source = "git+https://github.com/ZcashFoundation/redjubjub?rev=eae848c5c14d9c795d000dd9f4c4762d1aee7ee1#eae848c5c14d9c795d000dd9f4c4762d1aee7ee1" dependencies = [ "rand_core", "reddsa", diff --git a/Cargo.toml b/Cargo.toml index f191ae5c..fc5c17fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,7 +91,7 @@ std = [ "core2/std", "dep:document-features", "group/wnaf-memuse", - "redjubjub/std", + "redjubjub/std" ] ## Enables creation of Sapling proofs @@ -126,4 +126,4 @@ name = "pedersen_hash" harness = false [patch.crates-io] -redjubjub = { git = "https://github.com/nuttycom/redjubjub", rev = "e413019904400f4caa3550df7c4040befadfbb14" } +redjubjub = { git = "https://github.com/ZcashFoundation/redjubjub", rev = "eae848c5c14d9c795d000dd9f4c4762d1aee7ee1" } From 53566aef8d77e709533d1b4d4d7f65f3ca0ee9cf Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Tue, 28 Jan 2025 13:56:27 -0700 Subject: [PATCH 22/30] Re-expose the `ProverProgress` type outside of the `circuit` feature. --- src/builder.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index dd6e181a..a2bd7565 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -980,19 +980,17 @@ impl InProgressProofs for Proven { } /// Reports on the progress made towards creating proofs for a bundle. -#[cfg(feature = "circuit")] pub trait ProverProgress { /// Updates the progress instance with the number of steps completed and the total /// number of steps. fn update(&mut self, cur: u32, end: u32); } -#[cfg(feature = "circuit")] impl ProverProgress for () { fn update(&mut self, _: u32, _: u32) {} } -#[cfg(all(feature = "circuit", feature = "std"))] +#[cfg(feature = "std")] impl> ProverProgress for std::sync::mpsc::Sender { fn update(&mut self, cur: u32, end: u32) { // If the send fails, we should ignore the error, not crash. @@ -1000,7 +998,6 @@ impl> ProverProgress for std::sync::mpsc::Sender { } } -#[cfg(feature = "circuit")] impl ProverProgress for &mut U { fn update(&mut self, cur: u32, end: u32) { (*self).update(cur, end); From befbf95646d2a9387cdd7340721a5fe66c1473a5 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Tue, 28 Jan 2025 17:45:22 -0700 Subject: [PATCH 23/30] Bump MSRV to 1.70 --- CHANGELOG.md | 2 +- Cargo.toml | 2 +- rust-toolchain.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc86cd76..0379b9e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ and this library adheres to Rust's notion of avoid the need to depend upon the `bellman` crate. ### Changed -- MSRV is now 1.66 +- MSRV is now 1.70 ## [0.4.0] - 2024-12-16 diff --git a/Cargo.toml b/Cargo.toml index fc5c17fd..2e5e6443 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ authors = [ "Kris Nuttycombe ", ] edition = "2021" -rust-version = "1.66" +rust-version = "1.70" description = "Cryptographic library for Zcash Sapling" homepage = "https://github.com/zcash/sapling-crypto" repository = "https://github.com/zcash/sapling-crypto" diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 4a496752..5299106e 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.66.0" +channel = "1.70.0" components = ["clippy", "rustfmt"] From a3cb90a2d4b5690f2afd0969153997de30458e25 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Tue, 28 Jan 2025 17:53:21 -0700 Subject: [PATCH 24/30] Remove unused type parameterization (fix Clippy lint) --- src/builder.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index a2bd7565..b7e847ef 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -697,7 +697,7 @@ impl Builder { Zip212Enforcement::Off | Zip212Enforcement::GracePeriod => { Err(Error::PcztRequiresZip212) } - Zip212Enforcement::On => build_bundle::<_, (), (), _>( + Zip212Enforcement::On => build_bundle( rng, self.bundle_type, Zip212Enforcement::On, @@ -743,7 +743,7 @@ pub fn bundle>( outputs: Vec, extsks: &[ExtendedSpendingKey], ) -> Result, SaplingMetadata)>, Error> { - build_bundle::<_, SP, OP, _>( + build_bundle( rng, bundle_type, zip212_enforcement, @@ -813,7 +813,7 @@ pub fn bundle>( ) } -fn build_bundle( +fn build_bundle( mut rng: R, bundle_type: BundleType, zip212_enforcement: Zip212Enforcement, From d78c8775c8b13e7fc47ae5bd2761b928c6a81479 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Tue, 28 Jan 2025 12:50:02 -0700 Subject: [PATCH 25/30] Update to `incrementalmerkletree 0.8.1` --- CHANGELOG.md | 1 + Cargo.lock | 4 ++-- Cargo.toml | 4 ++-- src/builder.rs | 8 ++++++-- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0379b9e6..ddb5a2ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this library adheres to Rust's notion of ### Changed - MSRV is now 1.70 +- Updated to `incrementalmerkletree 0.8.1` ## [0.4.0] - 2024-12-16 diff --git a/Cargo.lock b/Cargo.lock index c097b175..b9bb248e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -646,9 +646,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "incrementalmerkletree" -version = "0.7.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d45063fbc4b0a37837f6bfe0445f269d13d730ad0aa3b5a7f74aa7bf27a0f4df" +checksum = "53227276b450ef58e4899e656ab4e9b4188aab0efc465f761bdbe56403ede61a" dependencies = [ "either", "proptest", diff --git a/Cargo.toml b/Cargo.toml index 2e5e6443..ac15c9e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,7 +55,7 @@ tracing = { version = "0.1", default-features = false } # Note Commitment Trees bitvec = { version = "1", default-features = false } -incrementalmerkletree = { version = "0.7", default-features = false, features = ["legacy-api"] } +incrementalmerkletree = { version = "0.8.1", default-features = false, features = ["legacy-api"] } # Note encryption zcash_note_encryption = { version = "0.4", features = ["pre-zip-212"] } @@ -78,7 +78,7 @@ zip32 = { version = "0.1.1", default-features = false } [dev-dependencies] chacha20poly1305 = "0.10" criterion = "0.4" -incrementalmerkletree = { version = "0.7", features = ["legacy-api", "test-dependencies"] } +incrementalmerkletree = { version = "0.8.1", features = ["legacy-api", "test-dependencies"] } proptest = "1" rand_xorshift = "0.3" diff --git a/src/builder.rs b/src/builder.rs index b7e847ef..b03fdf1e 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1337,8 +1337,12 @@ pub(crate) mod testing { n_notes, ), vec( - arb_commitment_tree::<_, _, 32>(n_notes, arb_node()) - .prop_map(|t| IncrementalWitness::from_tree(t).path().unwrap()), + arb_commitment_tree::<_, _, 32>(n_notes, arb_node()).prop_map(|t| { + IncrementalWitness::from_tree(t) + .expect("valid encoding of an incremental witness") + .path() + .unwrap() + }), n_notes, ), prop::array::uniform32(any::()), From 793e0900cd60dd707f8ad322222a0498b0330b1d Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Wed, 29 Jan 2025 18:29:59 -0700 Subject: [PATCH 26/30] Fix clippy beta lints --- src/address.rs | 2 +- src/builder.rs | 14 ++++++-------- src/circuit.rs | 4 ++-- src/circuit/ecc.rs | 2 +- src/constants.rs | 2 +- src/note_encryption.rs | 4 ++-- src/pczt/updater.rs | 6 +++--- src/pedersen_hash.rs | 5 +---- src/prover.rs | 2 +- src/value/sums.rs | 10 ++++++---- 10 files changed, 24 insertions(+), 27 deletions(-) diff --git a/src/address.rs b/src/address.rs index fa71c2d6..04d8f6a4 100644 --- a/src/address.rs +++ b/src/address.rs @@ -10,7 +10,7 @@ use super::{ /// /// - `diversifier` is guaranteed to be valid for Sapling (only 50% of diversifiers are). /// - `pk_d` is guaranteed to be prime-order (i.e. in the prime-order subgroup of Jubjub, -/// and not the identity). +/// and not the identity). #[derive(Clone, Copy, Debug)] pub struct PaymentAddress { pk_d: DiversifiedTransmissionKey, diff --git a/src/builder.rs b/src/builder.rs index b03fdf1e..0a617177 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -263,11 +263,9 @@ impl PreparedSpendInfo { // This is the result of the re-randomization, we compute it for the caller let rk = ak.randomize(&alpha); - let nullifier = self.note.nf( - &self.fvk.vk.nk, - u64::try_from(self.merkle_path.position()) - .expect("Sapling note commitment tree position must fit into a u64"), - ); + let nullifier = self + .note + .nf(&self.fvk.vk.nk, u64::from(self.merkle_path.position())); (cv, nullifier, rk, alpha) } @@ -805,7 +803,7 @@ pub fn bundle>( V::try_from(value_balance_i64).map_err(|_| Error::InvalidAmount)?, InProgress { sigs: Unsigned { bsk }, - _proof_state: PhantomData::default(), + _proof_state: PhantomData, }, ) .map(|b| (b, tx_metadata))) @@ -1061,7 +1059,7 @@ impl<'a, SP: SpendProver, OP: OutputProver, R: RngCore, U: ProverProgress> ) -> InProgress { InProgress { sigs: a.sigs, - _proof_state: PhantomData::default(), + _proof_state: PhantomData, } } } @@ -1189,7 +1187,7 @@ impl Bundle, V> { binding_signature: auth.sigs.bsk.sign(rng, &sighash), sighash, }, - _proof_state: PhantomData::default(), + _proof_state: PhantomData, }, ) } diff --git a/src/circuit.rs b/src/circuit.rs index ff0a7dcc..d2f083e1 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -799,7 +799,7 @@ fn test_input_circuit_with_bls12_381_external_test_vectors() { let tree_depth = 32; - let expected_commitment_us = vec![ + let expected_commitment_us = [ "43821661663052659750276289184181083197337192946256245809816728673021647664276", "7220807656052227578299730541645543434083158611414003423211850718229633594616", "13239753550660714843257636471668037031928211668773449453628093339627668081697", @@ -812,7 +812,7 @@ fn test_input_circuit_with_bls12_381_external_test_vectors() { "18269767207277008186871145355531741929166733260352590789136389380124992250945", ]; - let expected_commitment_vs = vec![ + let expected_commitment_vs = [ "27630722367128086497290371604583225252915685718989450292520883698391703910", "23310648738313092772044712773481584369462075017189681529702825235349449805260", "25709635353183537915646348052945798827495141780341329896098121888376871589480", diff --git a/src/circuit/ecc.rs b/src/circuit/ecc.rs index ebe73314..ef67b629 100644 --- a/src/circuit/ecc.rs +++ b/src/circuit/ecc.rs @@ -38,7 +38,7 @@ where for (i, (chunk, window)) in by.chunks(3).zip(base.iter()).enumerate() { let chunk_a = chunk - .get(0) + .first() .cloned() .unwrap_or_else(|| Boolean::constant(false)); let chunk_b = chunk diff --git a/src/constants.rs b/src/constants.rs index 0bfdf4db..94dd55ff 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -291,7 +291,7 @@ mod tests { let gh = group_hash(&tag, personalization); // We don't want to overflow and start reusing generators - assert!(tag[i] != u8::max_value()); + assert!(tag[i] != u8::MAX); tag[i] += 1; if let Some(gh) = gh { diff --git a/src/note_encryption.rs b/src/note_encryption.rs index 756e04ec..597349d4 100644 --- a/src/note_encryption.rs +++ b/src/note_encryption.rs @@ -294,7 +294,7 @@ impl BatchDomain for SaplingDomain { let (shared_secrets, ephemeral_keys): (Vec<_>, Vec<_>) = items.unzip(); SharedSecret::batch_to_affine(shared_secrets) - .zip(ephemeral_keys.into_iter()) + .zip(ephemeral_keys) .map(|(secret, ephemeral_key)| { secret.map(|dhsecret| SharedSecret::kdf_sapling_inner(dhsecret, ephemeral_key)) }) @@ -307,7 +307,7 @@ impl BatchDomain for SaplingDomain { let ephemeral_keys: Vec<_> = ephemeral_keys.collect(); let epks = jubjub::AffinePoint::batch_from_bytes(ephemeral_keys.iter().map(|b| b.0)); epks.into_iter() - .zip(ephemeral_keys.into_iter()) + .zip(ephemeral_keys) .map(|(epk, ephemeral_key)| { ( Option::from(epk) diff --git a/src/pczt/updater.rs b/src/pczt/updater.rs index 357c3ecc..4dd025c6 100644 --- a/src/pczt/updater.rs +++ b/src/pczt/updater.rs @@ -18,7 +18,7 @@ impl Bundle { /// An updater for a Sapling PCZT bundle. pub struct Updater<'a>(&'a mut Bundle); -impl<'a> Updater<'a> { +impl Updater<'_> { /// Provides read access to the bundle being updated. pub fn bundle(&self) -> &Bundle { self.0 @@ -56,7 +56,7 @@ impl<'a> Updater<'a> { /// An updater for a Sapling PCZT spend. pub struct SpendUpdater<'a>(&'a mut Spend); -impl<'a> SpendUpdater<'a> { +impl SpendUpdater<'_> { /// Sets the proof generation key for this spend. /// /// Returns an error if the proof generation key does not match the spend. @@ -83,7 +83,7 @@ impl<'a> SpendUpdater<'a> { /// An updater for a Sapling PCZT output. pub struct OutputUpdater<'a>(&'a mut Output); -impl<'a> OutputUpdater<'a> { +impl OutputUpdater<'_> { /// Sets the ZIP 32 derivation path for the new note's signing key. pub fn set_zip32_derivation(&mut self, derivation: Zip32Derivation) { self.0.zip32_derivation = Some(derivation); diff --git a/src/pedersen_hash.rs b/src/pedersen_hash.rs index 0e2f41a9..f2193481 100644 --- a/src/pedersen_hash.rs +++ b/src/pedersen_hash.rs @@ -33,10 +33,7 @@ pub fn pedersen_hash(personalization: Personalization, bits: I) -> jubjub::Su where I: IntoIterator, { - let mut bits = personalization - .get_bits() - .into_iter() - .chain(bits.into_iter()); + let mut bits = personalization.get_bits().into_iter().chain(bits); let mut result = jubjub::SubgroupPoint::identity(); let mut generators = crate::constants::PEDERSEN_HASH_EXP_TABLE.iter(); diff --git a/src/prover.rs b/src/prover.rs index cfcbcb28..38c34b09 100644 --- a/src/prover.rs +++ b/src/prover.rs @@ -115,7 +115,7 @@ impl SpendProver for SpendParameters { .path_elems() .iter() .enumerate() - .map(|(i, node)| Some(((*node).into(), pos >> i & 0x1 == 1))) + .map(|(i, node)| Some(((*node).into(), (pos >> i) & 0x1 == 1))) .collect(), anchor: Some(anchor), }) diff --git a/src/value/sums.rs b/src/value/sums.rs index 12235441..5edb3b5f 100644 --- a/src/value/sums.rs +++ b/src/value/sums.rs @@ -74,14 +74,16 @@ impl Sub for ValueSum { } impl<'a> Sum<&'a NoteValue> for Result { - fn sum>(iter: I) -> Self { - iter.fold(Ok(ValueSum(0)), |acc, v| (acc? + *v).ok_or(OverflowError)) + fn sum>(mut iter: I) -> Self { + iter.try_fold(ValueSum(0), |acc, v| acc + *v) + .ok_or(OverflowError) } } impl Sum for Result { - fn sum>(iter: I) -> Self { - iter.fold(Ok(ValueSum(0)), |acc, v| (acc? + v).ok_or(OverflowError)) + fn sum>(mut iter: I) -> Self { + iter.try_fold(ValueSum(0), |acc, v| acc + v) + .ok_or(OverflowError) } } From 3b6c60ae0fdbf76597ce6e8bae9b20654ba3b770 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Wed, 19 Feb 2025 14:05:30 -0700 Subject: [PATCH 27/30] Upgrade to redjubjub 0.8.0 --- CHANGELOG.md | 2 +- Cargo.lock | 5 +++-- Cargo.toml | 5 +---- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ddb5a2ba..03352cf4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ and this library adheres to Rust's notion of ### Changed - MSRV is now 1.70 -- Updated to `incrementalmerkletree 0.8.1` +- Updated to `incrementalmerkletree 0.8.1, redjubjub 0.8` ## [0.4.0] - 2024-12-16 diff --git a/Cargo.lock b/Cargo.lock index b9bb248e..78e244ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1210,8 +1210,9 @@ dependencies = [ [[package]] name = "redjubjub" -version = "0.7.0" -source = "git+https://github.com/ZcashFoundation/redjubjub?rev=eae848c5c14d9c795d000dd9f4c4762d1aee7ee1#eae848c5c14d9c795d000dd9f4c4762d1aee7ee1" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b0ac1bc6bb3696d2c6f52cff8fba57238b81da8c0214ee6cd146eb8fde364e" dependencies = [ "rand_core", "reddsa", diff --git a/Cargo.toml b/Cargo.toml index ac15c9e3..1d7cbbbb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ group = "0.13" bls12_381 = { version = "0.8", default-features = false, features = ["alloc"] } jubjub = { version = "0.10", default-features = false, features = ["alloc"] } -redjubjub = { version = "0.7", default-features = false } +redjubjub = { version = "0.8", default-features = false } zcash_spec = "0.1" # Boilerplate @@ -124,6 +124,3 @@ harness = false [[bench]] name = "pedersen_hash" harness = false - -[patch.crates-io] -redjubjub = { git = "https://github.com/ZcashFoundation/redjubjub", rev = "eae848c5c14d9c795d000dd9f4c4762d1aee7ee1" } From 5c1462ef3c11057bffd27d0843b0f7ae755d10cb Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 20 Feb 2025 16:40:20 -0700 Subject: [PATCH 28/30] Update to zcash_spec 0.2 and zip32 0.2 releases. --- CHANGELOG.md | 3 ++- Cargo.lock | 9 +++++---- Cargo.toml | 4 ++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03352cf4..0ffba778 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,8 @@ and this library adheres to Rust's notion of ### Changed - MSRV is now 1.70 -- Updated to `incrementalmerkletree 0.8.1, redjubjub 0.8` +- Updated to `incrementalmerkletree 0.8.1`, `redjubjub 0.8`, `zcash_spec 0.2`, + `zip32 0.2` ## [0.4.0] - 2024-12-16 diff --git a/Cargo.lock b/Cargo.lock index 78e244ed..c9279bda 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1837,9 +1837,9 @@ dependencies = [ [[package]] name = "zcash_spec" -version = "0.1.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7a3bf58b673cb3dacd8ae09ba345998923a197ab0da70d6239d8e8838949e9b" +checksum = "ded3f58b93486aa79b85acba1001f5298f27a46489859934954d262533ee2915" dependencies = [ "blake2b_simd", ] @@ -1886,11 +1886,12 @@ dependencies = [ [[package]] name = "zip32" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4226d0aee9c9407c27064dfeec9d7b281c917de3374e1e5a2e2cfad9e09de19e" +checksum = "13ff9ea444cdbce820211f91e6aa3d3a56bde7202d3c0961b7c38f793abf5637" dependencies = [ "blake2b_simd", "memuse", "subtle", + "zcash_spec", ] diff --git a/Cargo.toml b/Cargo.toml index 1d7cbbbb..098e4dd3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ group = "0.13" bls12_381 = { version = "0.8", default-features = false, features = ["alloc"] } jubjub = { version = "0.10", default-features = false, features = ["alloc"] } redjubjub = { version = "0.8", default-features = false } -zcash_spec = "0.1" +zcash_spec = "0.2" # Boilerplate getset = "0.1" @@ -72,7 +72,7 @@ proptest = { version = "1", optional = true } # ZIP 32 aes = "0.8" fpe = { version = "0.6", default-features = false, features = ["alloc"] } -zip32 = { version = "0.1.1", default-features = false } +zip32 = { version = "0.2", default-features = false } [dev-dependencies] From 7979a71bd4a2457e253fca326c021ffc92ece08b Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Wed, 13 Dec 2023 11:34:44 -0700 Subject: [PATCH 29/30] Remove the network-specific 0xf6 convention for empty memos --- CHANGELOG.md | 2 ++ src/builder.rs | 12 ++++-------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ffba778..67c0e367 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ and this library adheres to Rust's notion of - MSRV is now 1.70 - Updated to `incrementalmerkletree 0.8.1`, `redjubjub 0.8`, `zcash_spec 0.2`, `zip32 0.2` +- `sapling_crypto::builder::SaplingBuilder::add_output` now takes `[u8; 512]` + for its `memo` argument instead of an optional value. ## [0.4.0] - 2024-12-16 diff --git a/src/builder.rs b/src/builder.rs index 0a617177..ac11e8a1 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -363,17 +363,13 @@ impl OutputInfo { ovk: Option, to: PaymentAddress, value: NoteValue, - memo: Option<[u8; 512]>, + memo: [u8; 512], ) -> Self { Self { ovk, to, value, - memo: memo.unwrap_or_else(|| { - let mut memo = [0; 512]; - memo[0] = 0xf6; - memo - }), + memo, } } @@ -401,7 +397,7 @@ impl OutputInfo { } }; - Self::new(None, dummy_to, NoteValue::ZERO, None) + Self::new(None, dummy_to, NoteValue::ZERO, [0u8; 512]) } fn prepare( @@ -655,7 +651,7 @@ impl Builder { ovk: Option, to: PaymentAddress, value: NoteValue, - memo: Option<[u8; 512]>, + memo: [u8; 512], ) -> Result<(), Error> { let output = OutputInfo::new(ovk, to, value, memo); From 4ac190db9954d3e5388d7a08f8de6f57a69a2e78 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 20 Feb 2025 18:11:24 -0700 Subject: [PATCH 30/30] Release sapling-crypto version 0.5.0 --- CHANGELOG.md | 14 ++++++++------ Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67c0e367..7e640932 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,18 +7,20 @@ and this library adheres to Rust's notion of ## [Unreleased] +## [0.5.0] - 2025-02-20 + ### Added - `sapling_crypto::pczt::Zip32Derivation::extract_account_index` - `no_std` compatibility has been introduced by means of a default-enabled `std` feature flag. -- A default-enabled `circuit` is now provided to enable downstream users to - avoid the need to depend upon the `bellman` crate. +- A default-enabled `circuit` feature is now provided to enable downstream + users to avoid the need to depend upon the `bellman` crate. ### Changed - MSRV is now 1.70 - Updated to `incrementalmerkletree 0.8.1`, `redjubjub 0.8`, `zcash_spec 0.2`, `zip32 0.2` -- `sapling_crypto::builder::SaplingBuilder::add_output` now takes `[u8; 512]` +- `sapling_crypto::builder::SaplingBuilder::add_output` now takes `[u8; 512]` for its `memo` argument instead of an optional value. ## [0.4.0] - 2024-12-16 @@ -178,7 +180,7 @@ The entries below are relative to the `zcash_primitives::sapling` module as of - `Builder::new` now takes a `Zip212Enforcement` argument instead of a `P: zcash_primitives::consensus::Parameters` argument and a target height. It also now takes as an argument the Sapling anchor to be used for all - spends in the bundle. + spends in the bundle. - `Builder::add_spend` now takes `extsk` by reference. Also, it no longer takes a `diversifier` argument as the diversifier may be obtained from the note. All calls to `add_spend` are now required to use an anchor @@ -201,8 +203,8 @@ The entries below are relative to the `zcash_primitives::sapling` module as of - `Bundle` now has a second generic parameter `V`. - `Bundle::value_balance` now returns `&V` instead of `&zcash_primitives::transaction::components::Amount`. - - `Bundle::map_authorization` now takes a context argument and explicit - functions for each mappable field, rather than a `MapAuth` value, in + - `Bundle::map_authorization` now takes a context argument and explicit + functions for each mappable field, rather than a `MapAuth` value, in order to simplify handling of context values. - `Authorized::binding_sig` now has type `redjubjub::Signature`. - `Authorized::AuthSig` now has type `redjubjub::Signature`. diff --git a/Cargo.lock b/Cargo.lock index c9279bda..6cdaea95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1315,7 +1315,7 @@ dependencies = [ [[package]] name = "sapling-crypto" -version = "0.4.0" +version = "0.5.0" dependencies = [ "aes", "bellman", diff --git a/Cargo.toml b/Cargo.toml index 098e4dd3..868d3162 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sapling-crypto" -version = "0.4.0" +version = "0.5.0" authors = [ "Sean Bowe ", "Jack Grigg ",