From 375b20e5d6dc25608715b366936dfc6a2afb81f2 Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Sun, 17 Aug 2025 15:08:35 +0200 Subject: [PATCH 1/9] Add sighash version --- src/builder.rs | 37 +++++++++++------ src/bundle.rs | 23 +++++++---- src/lib.rs | 1 + src/pczt.rs | 3 +- src/pczt/parse.rs | 5 +-- src/pczt/signer.rs | 10 ++++- src/pczt/tx_extractor.rs | 22 ++++++---- src/signature_with_sighash_info.rs | 64 ++++++++++++++++++++++++++++++ src/verifier/batch.rs | 14 +++++-- 9 files changed, 143 insertions(+), 36 deletions(-) create mode 100644 src/signature_with_sighash_info.rs diff --git a/src/builder.rs b/src/builder.rs index bf9d5ee7..73cc9456 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -19,6 +19,9 @@ use crate::{ }, note::ExtractedNoteCommitment, note_encryption::{sapling_note_encryption, Zip212Enforcement}, + signature_with_sighash_info::{ + BindingSignatureWithSighashInfo, SpendAuthSignatureWithSighashInfo, SAPLING_SIG_V0, + }, util::generate_random_rseed_internal, value::{NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum}, Anchor, Diversifier, MerklePath, Node, Note, Nullifier, PaymentAddress, SaplingIvk, @@ -1124,7 +1127,7 @@ pub struct SigningParts { /// Marker for a partially-authorized bundle, in the process of being signed. #[derive(Clone, Debug)] pub struct PartiallyAuthorized { - binding_signature: redjubjub::Signature, + binding_signature: BindingSignatureWithSighashInfo, sighash: [u8; 32], } @@ -1152,11 +1155,11 @@ pub enum MaybeSigned { /// The information needed to sign this [`SpendDescription`]. SigningParts(SigningParts), /// The signature for this [`SpendDescription`]. - Signature(redjubjub::Signature), + Signature(SpendAuthSignatureWithSighashInfo), } impl MaybeSigned { - fn finalize(self) -> Result, Error> { + fn finalize(self) -> Result { match self { Self::Signature(sig) => Ok(sig), _ => Err(Error::MissingSignatures), @@ -1179,13 +1182,17 @@ impl Bundle, V> { |_, proof| proof, |rng, SigningMetadata { dummy_ask, parts }| match dummy_ask { None => MaybeSigned::SigningParts(parts), - Some(ask) => { - MaybeSigned::Signature(ask.randomize(&parts.alpha).sign(rng, &sighash)) - } + Some(ask) => MaybeSigned::Signature(SpendAuthSignatureWithSighashInfo::new( + SAPLING_SIG_V0, + ask.randomize(&parts.alpha).sign(rng, &sighash), + )), }, |rng, auth: InProgress| InProgress { sigs: PartiallyAuthorized { - binding_signature: auth.sigs.bsk.sign(rng, &sighash), + binding_signature: BindingSignatureWithSighashInfo::new( + SAPLING_SIG_V0, + auth.sigs.bsk.sign(rng, &sighash), + ), sighash, }, _proof_state: PhantomData, @@ -1227,7 +1234,10 @@ impl Bundle, V> { |_, proof| proof, |rng, maybe| match maybe { MaybeSigned::SigningParts(parts) if parts.ak == expected_ak => { - MaybeSigned::Signature(ask.randomize(&parts.alpha).sign(rng, &sighash)) + MaybeSigned::Signature(SpendAuthSignatureWithSighashInfo::new( + SAPLING_SIG_V0, + ask.randomize(&parts.alpha).sign(rng, &sighash), + )) } s => s, }, @@ -1242,12 +1252,15 @@ impl Bundle, V> { /// for more than one input. pub fn append_signatures( self, - signatures: &[redjubjub::Signature], + signatures: &[SpendAuthSignatureWithSighashInfo], ) -> Result { signatures.iter().try_fold(self, Self::append_signature) } - fn append_signature(self, signature: &redjubjub::Signature) -> Result { + fn append_signature( + self, + signature: &SpendAuthSignatureWithSighashInfo, + ) -> Result { let sighash = self.authorization().sigs.sighash; let mut signature_valid_for = 0usize; let bundle = self.map_authorization( @@ -1257,9 +1270,9 @@ impl Bundle, V> { |ctx, maybe| match maybe { MaybeSigned::SigningParts(parts) => { let rk = parts.ak.randomize(&parts.alpha); - if rk.verify(&sighash, signature).is_ok() { + if rk.verify(&sighash, signature.signature()).is_ok() { **ctx += 1; - MaybeSigned::Signature(*signature) + MaybeSigned::Signature(signature.clone()) } else { // Signature isn't for this input. MaybeSigned::SigningParts(parts) diff --git a/src/bundle.rs b/src/bundle.rs index d7ba7e0f..3f2008af 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -3,7 +3,7 @@ use core::fmt::Debug; use memuse::DynamicUsage; -use redjubjub::{Binding, SpendAuth}; +use redjubjub::SpendAuth; use zcash_note_encryption::{ note_bytes::NoteBytesData, EphemeralKeyBytes, ShieldedOutput, OUT_CIPHERTEXT_SIZE, @@ -15,6 +15,9 @@ use crate::{ note_encryption::{ CompactOutputDescription, SaplingDomain, COMPACT_NOTE_SIZE, ENC_CIPHERTEXT_SIZE, }, + signature_with_sighash_info::{ + BindingSignatureWithSighashInfo, SpendAuthSignatureWithSighashInfo, + }, value::ValueCommitment, Nullifier, }; @@ -40,16 +43,16 @@ impl Authorization for EffectsOnly { /// Authorizing data for a bundle of Sapling spends and outputs, ready to be committed to /// the ledger. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Clone)] pub struct Authorized { // TODO: Make this private. - pub binding_sig: redjubjub::Signature, + pub binding_sig: BindingSignatureWithSighashInfo, } impl Authorization for Authorized { type SpendProof = GrothProofBytes; type OutputProof = GrothProofBytes; - type AuthSig = redjubjub::Signature; + type AuthSig = SpendAuthSignatureWithSighashInfo; } #[derive(Debug, Clone)] @@ -315,7 +318,7 @@ impl SpendDescriptionV5 { self, anchor: bls12_381::Scalar, zkproof: GrothProofBytes, - spend_auth_sig: redjubjub::Signature, + spend_auth_sig: redjubjub::Signature, // TODO ) -> SpendDescription where A: Authorization>, @@ -522,6 +525,9 @@ pub mod testing { use crate::{ constants::GROTH_PROOF_SIZE, note::testing::arb_cmu, + signature_with_sighash_info::{ + BindingSignatureWithSighashInfo, SpendAuthSignatureWithSighashInfo, SAPLING_SIG_V0, + }, value::{ testing::{arb_note_value_bounded, arb_trapdoor}, ValueCommitment, MAX_NOTE_VALUE, @@ -568,7 +574,7 @@ pub mod testing { nullifier, rk, zkproof, - spend_auth_sig: sk1.sign(&mut rng, &fake_sighash_bytes), + spend_auth_sig: SpendAuthSignatureWithSighashInfo::new(SAPLING_SIG_V0, sk1.sign(&mut rng, &fake_sighash_bytes)), } } } @@ -625,7 +631,10 @@ pub mod testing { shielded_outputs, value_balance, authorization: Authorized { - binding_sig: bsk.sign(&mut rng, &fake_bvk_bytes), + binding_sig: BindingSignatureWithSighashInfo::new( + SAPLING_SIG_V0, + bsk.sign(&mut rng, &fake_bvk_bytes), + ), }, }) } diff --git a/src/lib.rs b/src/lib.rs index 65fbaa62..820e5870 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,6 +38,7 @@ pub mod pczt; pub mod pedersen_hash; #[cfg(feature = "circuit")] pub mod prover; +mod signature_with_sighash_info; mod spec; mod tree; pub mod util; diff --git a/src/pczt.rs b/src/pczt.rs index 1f11a96f..5be600c3 100644 --- a/src/pczt.rs +++ b/src/pczt.rs @@ -14,6 +14,7 @@ use crate::{ bundle::GrothProofBytes, keys::SpendAuthorizingKey, note::ExtractedNoteCommitment, + signature_with_sighash_info::SpendAuthSignatureWithSighashInfo, value::{NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum}, Anchor, MerklePath, Nullifier, PaymentAddress, ProofGenerationKey, Rseed, }; @@ -117,7 +118,7 @@ pub struct Spend { /// The spend authorization signature. /// /// This is set by the Signer. - pub(crate) spend_auth_sig: Option>, + pub(crate) spend_auth_sig: Option, /// The address that received the note being spent. /// diff --git a/src/pczt/parse.rs b/src/pczt/parse.rs index dc11af4d..34d10990 100644 --- a/src/pczt/parse.rs +++ b/src/pczt/parse.rs @@ -11,6 +11,7 @@ use crate::{ bundle::GrothProofBytes, keys::{SpendAuthorizingKey, SpendValidatingKey}, note::ExtractedNoteCommitment, + signature_with_sighash_info::SpendAuthSignatureWithSighashInfo, value::{NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum}, Anchor, MerklePath, Node, Nullifier, PaymentAddress, ProofGenerationKey, Rseed, }; @@ -53,7 +54,7 @@ impl Spend { nullifier: [u8; 32], rk: [u8; 32], zkproof: Option, - spend_auth_sig: Option<[u8; 64]>, + spend_auth_sig: Option, recipient: Option<[u8; 43]>, value: Option, rcm: Option<[u8; 32]>, @@ -75,8 +76,6 @@ impl Spend { 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)) diff --git a/src/pczt/signer.rs b/src/pczt/signer.rs index 90776357..ea7e1d62 100644 --- a/src/pczt/signer.rs +++ b/src/pczt/signer.rs @@ -1,6 +1,9 @@ use rand::{CryptoRng, RngCore}; -use crate::keys::SpendAuthorizingKey; +use crate::{ + keys::SpendAuthorizingKey, + signature_with_sighash_info::{SpendAuthSignatureWithSighashInfo, SAPLING_SIG_V0}, +}; impl super::Spend { /// Signs the Sapling spend with the given spend authorizing key. @@ -20,7 +23,10 @@ impl super::Spend { let rk = redjubjub::VerificationKey::from(&rsk); if self.rk == rk { - self.spend_auth_sig = Some(rsk.sign(rng, &sighash)); + self.spend_auth_sig = Some(SpendAuthSignatureWithSighashInfo::new( + SAPLING_SIG_V0, + rsk.sign(rng, &sighash), + )); Ok(()) } else { Err(SignerError::WrongSpendAuthorizingKey) diff --git a/src/pczt/tx_extractor.rs b/src/pczt/tx_extractor.rs index 9a6cf667..f6faed75 100644 --- a/src/pczt/tx_extractor.rs +++ b/src/pczt/tx_extractor.rs @@ -5,6 +5,9 @@ use crate::{ Authorization, Authorized, EffectsOnly, GrothProofBytes, OutputDescription, SpendDescription, }, + signature_with_sighash_info::{ + BindingSignatureWithSighashInfo, SpendAuthSignatureWithSighashInfo, SAPLING_SIG_V0, + }, Bundle, }; @@ -35,6 +38,7 @@ impl super::Bundle { |spend| { spend .spend_auth_sig + .clone() .ok_or(TxExtractorError::MissingSpendAuthSig) }, |output| output.zkproof.ok_or(TxExtractorError::MissingProof), @@ -132,7 +136,7 @@ pub struct Unbound { impl Authorization for Unbound { type SpendProof = GrothProofBytes; type OutputProof = GrothProofBytes; - type AuthSig = redjubjub::Signature; + type AuthSig = SpendAuthSignatureWithSighashInfo; } impl crate::Bundle { @@ -144,18 +148,22 @@ impl crate::Bundle { sighash: [u8; 32], rng: R, ) -> Option> { - if self - .shielded_spends() - .iter() - .all(|spend| spend.rk().verify(&sighash, spend.spend_auth_sig()).is_ok()) - { + if self.shielded_spends().iter().all(|spend| { + spend + .rk() + .verify(&sighash, spend.spend_auth_sig().signature()) + .is_ok() + }) { Some(self.map_authorization( &mut (), |_, p| p, |_, p| p, |_, s| s, |_, Unbound { bsk }| Authorized { - binding_sig: bsk.sign(rng, &sighash), + binding_sig: BindingSignatureWithSighashInfo::new( + SAPLING_SIG_V0, + bsk.sign(rng, &sighash), + ), }, )) } else { diff --git a/src/signature_with_sighash_info.rs b/src/signature_with_sighash_info.rs new file mode 100644 index 00000000..951021ff --- /dev/null +++ b/src/signature_with_sighash_info.rs @@ -0,0 +1,64 @@ +//! Defines `SighashInfo` and signatures with `SighashInfo`. + +use alloc::vec::Vec; + +use redjubjub::{Binding, SigType, SpendAuth}; + +/// The sighash version and associated information +#[derive(Debug, Clone)] +pub(crate) struct SighashInfo { + version: u8, + associated_information: Vec, +} + +/// The sighash version and associated information for Sapling binding/authorizing signatures. +pub(crate) const SAPLING_SIG_V0: SighashInfo = SighashInfo { + version: 0x00, + associated_information: vec![], +}; + +#[derive(Debug, Clone)] +pub struct SignatureWithSighashInfo { + info: SighashInfo, + signature: redjubjub::Signature, +} + +impl SignatureWithSighashInfo { + pub(crate) fn new(info: SighashInfo, signature: redjubjub::Signature) -> Self { + Self { info, signature } + } + + pub fn to_bytes(&self) -> Vec { + let mut result = Vec::with_capacity(1 + self.info.associated_information.len() + 64); + result.push(self.info.version); + result.extend_from_slice(&self.info.associated_information); + result.extend_from_slice(&<[u8; 64]>::from(self.signature)); + result + } + + /// Returns the signature. + pub fn signature(&self) -> &redjubjub::Signature { + &self.signature + } +} + +/// Binding signature containing the sighash information and the signature itself. +pub type BindingSignatureWithSighashInfo = SignatureWithSighashInfo; + +/// Authorizing signature containing the sighash information and the signature itself. +pub type SpendAuthSignatureWithSighashInfo = SignatureWithSighashInfo; + +#[cfg(test)] +mod tests { + use super::{BindingSignatureWithSighashInfo, SAPLING_SIG_V0}; + use redjubjub::Binding; + + #[test] + fn signature_with_sighash_info() { + let binding_signature_with_info = BindingSignatureWithSighashInfo::new( + SAPLING_SIG_V0, + redjubjub::Signature::::from([0u8; 64]), + ); + assert_eq!(binding_signature_with_info.to_bytes(), [0u8; 65]); + } +} diff --git a/src/verifier/batch.rs b/src/verifier/batch.rs index d6306202..ff1e0c8d 100644 --- a/src/verifier/batch.rs +++ b/src/verifier/batch.rs @@ -71,8 +71,11 @@ impl BatchValidator { zkproof, self, |this, rk| { - this.signatures - .queue(((*rk).into(), *spend.spend_auth_sig(), &sighash)); + this.signatures.queue(( + (*rk).into(), + *spend.spend_auth_sig().signature(), + &sighash, + )); true }, |this, proof, public_inputs| { @@ -116,8 +119,11 @@ impl BatchValidator { // Check the whole-bundle consensus rules, and batch the binding signature. ctx.final_check(*bundle.value_balance(), |bvk| { - self.signatures - .queue((bvk.into(), bundle.authorization().binding_sig, &sighash)); + self.signatures.queue(( + bvk.into(), + *bundle.authorization().binding_sig.signature(), + &sighash, + )); true }) } From 5cf58e86bbff2d8b36d44f33e8fa2be1b3207392 Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Sun, 17 Aug 2025 15:46:37 +0200 Subject: [PATCH 2/9] Add sighash_info_size function --- src/signature_with_sighash_info.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/signature_with_sighash_info.rs b/src/signature_with_sighash_info.rs index 951021ff..6af1bf41 100644 --- a/src/signature_with_sighash_info.rs +++ b/src/signature_with_sighash_info.rs @@ -28,6 +28,7 @@ impl SignatureWithSighashInfo { Self { info, signature } } + /// Returns the `SignatureWithSighashInfo` in bytes. pub fn to_bytes(&self) -> Vec { let mut result = Vec::with_capacity(1 + self.info.associated_information.len() + 64); result.push(self.info.version); @@ -36,6 +37,11 @@ impl SignatureWithSighashInfo { result } + /// Returns the `SighashIngo` size in bytes. + pub fn sighash_info_size(&self) -> usize { + 1 + self.info.associated_information.len() + } + /// Returns the signature. pub fn signature(&self) -> &redjubjub::Signature { &self.signature From 5acd533377bdce8ec6691a3d13ca7fcd99348dd9 Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Mon, 18 Aug 2025 15:59:52 +0200 Subject: [PATCH 3/9] Update visibility --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 820e5870..b161786c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,7 +38,7 @@ pub mod pczt; pub mod pedersen_hash; #[cfg(feature = "circuit")] pub mod prover; -mod signature_with_sighash_info; +pub mod signature_with_sighash_info; mod spec; mod tree; pub mod util; From 5d2a8dfab32ba0e3bf83a2bf8e9a5e3ebb21e2e7 Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Mon, 8 Sep 2025 11:50:52 +0200 Subject: [PATCH 4/9] Update with new version of zcash_spec repo --- Cargo.lock | 3 +- Cargo.toml | 21 ++++----- src/builder.rs | 40 ++++++++--------- src/bundle.rs | 19 ++++---- src/lib.rs | 1 - src/pczt.rs | 4 +- src/pczt/parse.rs | 4 +- src/pczt/signer.rs | 11 ++--- src/pczt/tx_extractor.rs | 14 +++--- src/signature_with_sighash_info.rs | 70 ------------------------------ src/verifier/batch.rs | 9 ++-- 11 files changed, 54 insertions(+), 142 deletions(-) delete mode 100644 src/signature_with_sighash_info.rs diff --git a/Cargo.lock b/Cargo.lock index 986cb22b..817e07c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1837,8 +1837,7 @@ dependencies = [ [[package]] name = "zcash_spec" version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded3f58b93486aa79b85acba1001f5298f27a46489859934954d262533ee2915" +source = "git+https://github.com/QED-it/zcash_spec?rev=842d697048a8960348adcde704c24438fc5b4544#842d697048a8960348adcde704c24438fc5b4544" dependencies = [ "blake2b_simd", ] diff --git a/Cargo.toml b/Cargo.toml index 573519fc..428844c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,20 +88,20 @@ pprof = { version = "0.11", features = ["criterion", "flamegraph"] } # MSRV 1.56 [features] default = ["multicore", "circuit"] std = [ - "core2/std", - "dep:document-features", - "group/wnaf-memuse", - "redjubjub/std" + "core2/std", + "dep:document-features", + "group/wnaf-memuse", + "redjubjub/std" ] ## Enables creation of Sapling proofs circuit = [ - "dep:bellman", - "bls12_381/bits", - "bls12_381/groups", - "bls12_381/pairings", - "jubjub/bits", - "std" + "dep:bellman", + "bls12_381/bits", + "bls12_381/groups", + "bls12_381/pairings", + "jubjub/bits", + "std" ] ## Enables multithreading support for creating proofs. @@ -127,3 +127,4 @@ harness = false [patch.crates-io] zcash_note_encryption = { version = "0.4.1", git = "https://github.com/zcash/zcash_note_encryption", branch = "main" } +zcash_spec = { git = "https://github.com/QED-it/zcash_spec", rev = "842d697048a8960348adcde704c24438fc5b4544" } \ No newline at end of file diff --git a/src/builder.rs b/src/builder.rs index 73cc9456..86b6487e 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -10,6 +10,7 @@ use rand::{seq::SliceRandom, RngCore}; use rand_core::CryptoRng; use redjubjub::{Binding, SpendAuth}; use zcash_note_encryption::EphemeralKeyBytes; +use zcash_spec::sighash_versioning::{VersionedSig, SIGHASH_V0}; use crate::{ bundle::{Authorization, Authorized, Bundle, GrothProofBytes}, @@ -19,9 +20,6 @@ use crate::{ }, note::ExtractedNoteCommitment, note_encryption::{sapling_note_encryption, Zip212Enforcement}, - signature_with_sighash_info::{ - BindingSignatureWithSighashInfo, SpendAuthSignatureWithSighashInfo, SAPLING_SIG_V0, - }, util::generate_random_rseed_internal, value::{NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum}, Anchor, Diversifier, MerklePath, Node, Note, Nullifier, PaymentAddress, SaplingIvk, @@ -1124,10 +1122,13 @@ pub struct SigningParts { alpha: jubjub::Scalar, } +/// A versioned binding signature. +pub type VerBindingSig = VersionedSig>; + /// Marker for a partially-authorized bundle, in the process of being signed. #[derive(Clone, Debug)] pub struct PartiallyAuthorized { - binding_signature: BindingSignatureWithSighashInfo, + binding_signature: VerBindingSig, sighash: [u8; 32], } @@ -1147,6 +1148,9 @@ impl InProgressSignatures for PartiallyAuthorized { type AuthSig = MaybeSigned; } +/// A versioned SpendAuth signature. +pub type VerSpendAuthSig = VersionedSig>; + /// A heisen[`Signature`] for a particular [`SpendDescription`]. /// /// [`Signature`]: redjubjub::Signature @@ -1155,11 +1159,11 @@ pub enum MaybeSigned { /// The information needed to sign this [`SpendDescription`]. SigningParts(SigningParts), /// The signature for this [`SpendDescription`]. - Signature(SpendAuthSignatureWithSighashInfo), + Signature(VerSpendAuthSig), } impl MaybeSigned { - fn finalize(self) -> Result { + fn finalize(self) -> Result { match self { Self::Signature(sig) => Ok(sig), _ => Err(Error::MissingSignatures), @@ -1182,15 +1186,15 @@ impl Bundle, V> { |_, proof| proof, |rng, SigningMetadata { dummy_ask, parts }| match dummy_ask { None => MaybeSigned::SigningParts(parts), - Some(ask) => MaybeSigned::Signature(SpendAuthSignatureWithSighashInfo::new( - SAPLING_SIG_V0, + Some(ask) => MaybeSigned::Signature(VerSpendAuthSig::new( + SIGHASH_V0, ask.randomize(&parts.alpha).sign(rng, &sighash), )), }, |rng, auth: InProgress| InProgress { sigs: PartiallyAuthorized { - binding_signature: BindingSignatureWithSighashInfo::new( - SAPLING_SIG_V0, + binding_signature: VerBindingSig::new( + SIGHASH_V0, auth.sigs.bsk.sign(rng, &sighash), ), sighash, @@ -1234,8 +1238,8 @@ impl Bundle, V> { |_, proof| proof, |rng, maybe| match maybe { MaybeSigned::SigningParts(parts) if parts.ak == expected_ak => { - MaybeSigned::Signature(SpendAuthSignatureWithSighashInfo::new( - SAPLING_SIG_V0, + MaybeSigned::Signature(VerSpendAuthSig::new( + SIGHASH_V0, ask.randomize(&parts.alpha).sign(rng, &sighash), )) } @@ -1250,17 +1254,11 @@ impl Bundle, V> { /// Each signature will be applied to the one input for which it is valid. An error /// will be returned if the signature is not valid for any inputs, or if it is valid /// for more than one input. - pub fn append_signatures( - self, - signatures: &[SpendAuthSignatureWithSighashInfo], - ) -> Result { + pub fn append_signatures(self, signatures: &[VerSpendAuthSig]) -> Result { signatures.iter().try_fold(self, Self::append_signature) } - fn append_signature( - self, - signature: &SpendAuthSignatureWithSighashInfo, - ) -> Result { + fn append_signature(self, signature: &VerSpendAuthSig) -> Result { let sighash = self.authorization().sigs.sighash; let mut signature_valid_for = 0usize; let bundle = self.map_authorization( @@ -1270,7 +1268,7 @@ impl Bundle, V> { |ctx, maybe| match maybe { MaybeSigned::SigningParts(parts) => { let rk = parts.ak.randomize(&parts.alpha); - if rk.verify(&sighash, signature.signature()).is_ok() { + if rk.verify(&sighash, signature.sig()).is_ok() { **ctx += 1; MaybeSigned::Signature(signature.clone()) } else { diff --git a/src/bundle.rs b/src/bundle.rs index 3f2008af..caaae902 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -10,14 +10,12 @@ use zcash_note_encryption::{ }; use crate::{ + builder::{VerBindingSig, VerSpendAuthSig}, constants::GROTH_PROOF_SIZE, note::ExtractedNoteCommitment, note_encryption::{ CompactOutputDescription, SaplingDomain, COMPACT_NOTE_SIZE, ENC_CIPHERTEXT_SIZE, }, - signature_with_sighash_info::{ - BindingSignatureWithSighashInfo, SpendAuthSignatureWithSighashInfo, - }, value::ValueCommitment, Nullifier, }; @@ -46,13 +44,13 @@ impl Authorization for EffectsOnly { #[derive(Debug, Clone)] pub struct Authorized { // TODO: Make this private. - pub binding_sig: BindingSignatureWithSighashInfo, + pub binding_sig: VerBindingSig, } impl Authorization for Authorized { type SpendProof = GrothProofBytes; type OutputProof = GrothProofBytes; - type AuthSig = SpendAuthSignatureWithSighashInfo; + type AuthSig = VerSpendAuthSig; } #[derive(Debug, Clone)] @@ -521,13 +519,12 @@ pub mod testing { use proptest::collection::vec; use proptest::prelude::*; use rand::{rngs::StdRng, SeedableRng}; + use zcash_spec::sighash_versioning::SIGHASH_V0; use crate::{ + builder::{VerBindingSig, VerSpendAuthSig}, constants::GROTH_PROOF_SIZE, note::testing::arb_cmu, - signature_with_sighash_info::{ - BindingSignatureWithSighashInfo, SpendAuthSignatureWithSighashInfo, SAPLING_SIG_V0, - }, value::{ testing::{arb_note_value_bounded, arb_trapdoor}, ValueCommitment, MAX_NOTE_VALUE, @@ -574,7 +571,7 @@ pub mod testing { nullifier, rk, zkproof, - spend_auth_sig: SpendAuthSignatureWithSighashInfo::new(SAPLING_SIG_V0, sk1.sign(&mut rng, &fake_sighash_bytes)), + spend_auth_sig: VerSpendAuthSig::new(SIGHASH_V0, sk1.sign(&mut rng, &fake_sighash_bytes)), } } } @@ -631,8 +628,8 @@ pub mod testing { shielded_outputs, value_balance, authorization: Authorized { - binding_sig: BindingSignatureWithSighashInfo::new( - SAPLING_SIG_V0, + binding_sig: VerBindingSig::new( + SIGHASH_V0, bsk.sign(&mut rng, &fake_bvk_bytes), ), }, diff --git a/src/lib.rs b/src/lib.rs index b161786c..65fbaa62 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,7 +38,6 @@ pub mod pczt; pub mod pedersen_hash; #[cfg(feature = "circuit")] pub mod prover; -pub mod signature_with_sighash_info; mod spec; mod tree; pub mod util; diff --git a/src/pczt.rs b/src/pczt.rs index 5be600c3..63510772 100644 --- a/src/pczt.rs +++ b/src/pczt.rs @@ -11,10 +11,10 @@ use zcash_note_encryption::{EphemeralKeyBytes, OutgoingCipherKey, OUT_CIPHERTEXT use zip32::ChildIndex; use crate::{ + builder::VerSpendAuthSig, bundle::GrothProofBytes, keys::SpendAuthorizingKey, note::ExtractedNoteCommitment, - signature_with_sighash_info::SpendAuthSignatureWithSighashInfo, value::{NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum}, Anchor, MerklePath, Nullifier, PaymentAddress, ProofGenerationKey, Rseed, }; @@ -118,7 +118,7 @@ pub struct Spend { /// The spend authorization signature. /// /// This is set by the Signer. - pub(crate) spend_auth_sig: Option, + pub(crate) spend_auth_sig: Option, /// The address that received the note being spent. /// diff --git a/src/pczt/parse.rs b/src/pczt/parse.rs index 34d10990..107db4d2 100644 --- a/src/pczt/parse.rs +++ b/src/pczt/parse.rs @@ -8,10 +8,10 @@ use zip32::ChildIndex; use super::{Bundle, Output, Spend, Zip32Derivation}; use crate::{ + builder::VerSpendAuthSig, bundle::GrothProofBytes, keys::{SpendAuthorizingKey, SpendValidatingKey}, note::ExtractedNoteCommitment, - signature_with_sighash_info::SpendAuthSignatureWithSighashInfo, value::{NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum}, Anchor, MerklePath, Node, Nullifier, PaymentAddress, ProofGenerationKey, Rseed, }; @@ -54,7 +54,7 @@ impl Spend { nullifier: [u8; 32], rk: [u8; 32], zkproof: Option, - spend_auth_sig: Option, + spend_auth_sig: Option, recipient: Option<[u8; 43]>, value: Option, rcm: Option<[u8; 32]>, diff --git a/src/pczt/signer.rs b/src/pczt/signer.rs index ea7e1d62..92884fd0 100644 --- a/src/pczt/signer.rs +++ b/src/pczt/signer.rs @@ -1,9 +1,7 @@ use rand::{CryptoRng, RngCore}; +use zcash_spec::sighash_versioning::SIGHASH_V0; -use crate::{ - keys::SpendAuthorizingKey, - signature_with_sighash_info::{SpendAuthSignatureWithSighashInfo, SAPLING_SIG_V0}, -}; +use crate::{builder::VerSpendAuthSig, keys::SpendAuthorizingKey}; impl super::Spend { /// Signs the Sapling spend with the given spend authorizing key. @@ -23,10 +21,7 @@ impl super::Spend { let rk = redjubjub::VerificationKey::from(&rsk); if self.rk == rk { - self.spend_auth_sig = Some(SpendAuthSignatureWithSighashInfo::new( - SAPLING_SIG_V0, - rsk.sign(rng, &sighash), - )); + self.spend_auth_sig = Some(VerSpendAuthSig::new(SIGHASH_V0, rsk.sign(rng, &sighash))); Ok(()) } else { Err(SignerError::WrongSpendAuthorizingKey) diff --git a/src/pczt/tx_extractor.rs b/src/pczt/tx_extractor.rs index f6faed75..26dd0f27 100644 --- a/src/pczt/tx_extractor.rs +++ b/src/pczt/tx_extractor.rs @@ -1,13 +1,12 @@ use rand::{CryptoRng, RngCore}; +use zcash_spec::sighash_versioning::SIGHASH_V0; use crate::{ + builder::{VerBindingSig, VerSpendAuthSig}, bundle::{ Authorization, Authorized, EffectsOnly, GrothProofBytes, OutputDescription, SpendDescription, }, - signature_with_sighash_info::{ - BindingSignatureWithSighashInfo, SpendAuthSignatureWithSighashInfo, SAPLING_SIG_V0, - }, Bundle, }; @@ -136,7 +135,7 @@ pub struct Unbound { impl Authorization for Unbound { type SpendProof = GrothProofBytes; type OutputProof = GrothProofBytes; - type AuthSig = SpendAuthSignatureWithSighashInfo; + type AuthSig = VerSpendAuthSig; } impl crate::Bundle { @@ -151,7 +150,7 @@ impl crate::Bundle { if self.shielded_spends().iter().all(|spend| { spend .rk() - .verify(&sighash, spend.spend_auth_sig().signature()) + .verify(&sighash, spend.spend_auth_sig().sig()) .is_ok() }) { Some(self.map_authorization( @@ -160,10 +159,7 @@ impl crate::Bundle { |_, p| p, |_, s| s, |_, Unbound { bsk }| Authorized { - binding_sig: BindingSignatureWithSighashInfo::new( - SAPLING_SIG_V0, - bsk.sign(rng, &sighash), - ), + binding_sig: VerBindingSig::new(SIGHASH_V0, bsk.sign(rng, &sighash)), }, )) } else { diff --git a/src/signature_with_sighash_info.rs b/src/signature_with_sighash_info.rs deleted file mode 100644 index 6af1bf41..00000000 --- a/src/signature_with_sighash_info.rs +++ /dev/null @@ -1,70 +0,0 @@ -//! Defines `SighashInfo` and signatures with `SighashInfo`. - -use alloc::vec::Vec; - -use redjubjub::{Binding, SigType, SpendAuth}; - -/// The sighash version and associated information -#[derive(Debug, Clone)] -pub(crate) struct SighashInfo { - version: u8, - associated_information: Vec, -} - -/// The sighash version and associated information for Sapling binding/authorizing signatures. -pub(crate) const SAPLING_SIG_V0: SighashInfo = SighashInfo { - version: 0x00, - associated_information: vec![], -}; - -#[derive(Debug, Clone)] -pub struct SignatureWithSighashInfo { - info: SighashInfo, - signature: redjubjub::Signature, -} - -impl SignatureWithSighashInfo { - pub(crate) fn new(info: SighashInfo, signature: redjubjub::Signature) -> Self { - Self { info, signature } - } - - /// Returns the `SignatureWithSighashInfo` in bytes. - pub fn to_bytes(&self) -> Vec { - let mut result = Vec::with_capacity(1 + self.info.associated_information.len() + 64); - result.push(self.info.version); - result.extend_from_slice(&self.info.associated_information); - result.extend_from_slice(&<[u8; 64]>::from(self.signature)); - result - } - - /// Returns the `SighashIngo` size in bytes. - pub fn sighash_info_size(&self) -> usize { - 1 + self.info.associated_information.len() - } - - /// Returns the signature. - pub fn signature(&self) -> &redjubjub::Signature { - &self.signature - } -} - -/// Binding signature containing the sighash information and the signature itself. -pub type BindingSignatureWithSighashInfo = SignatureWithSighashInfo; - -/// Authorizing signature containing the sighash information and the signature itself. -pub type SpendAuthSignatureWithSighashInfo = SignatureWithSighashInfo; - -#[cfg(test)] -mod tests { - use super::{BindingSignatureWithSighashInfo, SAPLING_SIG_V0}; - use redjubjub::Binding; - - #[test] - fn signature_with_sighash_info() { - let binding_signature_with_info = BindingSignatureWithSighashInfo::new( - SAPLING_SIG_V0, - redjubjub::Signature::::from([0u8; 64]), - ); - assert_eq!(binding_signature_with_info.to_bytes(), [0u8; 65]); - } -} diff --git a/src/verifier/batch.rs b/src/verifier/batch.rs index ff1e0c8d..2bdb4192 100644 --- a/src/verifier/batch.rs +++ b/src/verifier/batch.rs @@ -71,11 +71,8 @@ impl BatchValidator { zkproof, self, |this, rk| { - this.signatures.queue(( - (*rk).into(), - *spend.spend_auth_sig().signature(), - &sighash, - )); + this.signatures + .queue(((*rk).into(), *spend.spend_auth_sig().sig(), &sighash)); true }, |this, proof, public_inputs| { @@ -121,7 +118,7 @@ impl BatchValidator { ctx.final_check(*bundle.value_balance(), |bvk| { self.signatures.queue(( bvk.into(), - *bundle.authorization().binding_sig.signature(), + *bundle.authorization().binding_sig.sig(), &sighash, )); true From 369d3c078a224df96ddfc4acc79d16a537a12f10 Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Mon, 8 Sep 2025 12:00:39 +0200 Subject: [PATCH 5/9] Update Cargo.toml --- Cargo.toml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 428844c5..641f9d1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,20 +88,20 @@ pprof = { version = "0.11", features = ["criterion", "flamegraph"] } # MSRV 1.56 [features] default = ["multicore", "circuit"] std = [ - "core2/std", - "dep:document-features", - "group/wnaf-memuse", - "redjubjub/std" + "core2/std", + "dep:document-features", + "group/wnaf-memuse", + "redjubjub/std" ] ## Enables creation of Sapling proofs circuit = [ - "dep:bellman", - "bls12_381/bits", - "bls12_381/groups", - "bls12_381/pairings", - "jubjub/bits", - "std" + "dep:bellman", + "bls12_381/bits", + "bls12_381/groups", + "bls12_381/pairings", + "jubjub/bits", + "std" ] ## Enables multithreading support for creating proofs. @@ -127,4 +127,4 @@ harness = false [patch.crates-io] zcash_note_encryption = { version = "0.4.1", git = "https://github.com/zcash/zcash_note_encryption", branch = "main" } -zcash_spec = { git = "https://github.com/QED-it/zcash_spec", rev = "842d697048a8960348adcde704c24438fc5b4544" } \ No newline at end of file +zcash_spec = { git = "https://github.com/QED-it/zcash_spec", rev = "842d697048a8960348adcde704c24438fc5b4544" } From 527a70b3bcec698e7c71bfccf41e3e0517de867c Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Mon, 8 Sep 2025 12:06:35 +0200 Subject: [PATCH 6/9] Remove TODO comment --- src/bundle.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bundle.rs b/src/bundle.rs index caaae902..26fdce30 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -316,7 +316,7 @@ impl SpendDescriptionV5 { self, anchor: bls12_381::Scalar, zkproof: GrothProofBytes, - spend_auth_sig: redjubjub::Signature, // TODO + spend_auth_sig: redjubjub::Signature, ) -> SpendDescription where A: Authorization>, From bb26734790b1309fec77e4646bd90945733a9981 Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Mon, 29 Sep 2025 17:13:14 +0200 Subject: [PATCH 7/9] Create SaplingSighashVersion enum --- Cargo.lock | 3 +- Cargo.toml | 1 - src/builder.rs | 14 +++----- src/bundle.rs | 13 ++++--- src/lib.rs | 1 + src/pczt.rs | 2 +- src/pczt/parse.rs | 26 ++++++++++++-- src/pczt/signer.rs | 11 ++++-- src/pczt/tx_extractor.rs | 8 +++-- src/sapling_sighash_versioning.rs | 57 +++++++++++++++++++++++++++++++ 10 files changed, 108 insertions(+), 28 deletions(-) create mode 100644 src/sapling_sighash_versioning.rs diff --git a/Cargo.lock b/Cargo.lock index 817e07c0..986cb22b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1837,7 +1837,8 @@ dependencies = [ [[package]] name = "zcash_spec" version = "0.2.1" -source = "git+https://github.com/QED-it/zcash_spec?rev=842d697048a8960348adcde704c24438fc5b4544#842d697048a8960348adcde704c24438fc5b4544" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded3f58b93486aa79b85acba1001f5298f27a46489859934954d262533ee2915" dependencies = [ "blake2b_simd", ] diff --git a/Cargo.toml b/Cargo.toml index 641f9d1c..573519fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -127,4 +127,3 @@ harness = false [patch.crates-io] zcash_note_encryption = { version = "0.4.1", git = "https://github.com/zcash/zcash_note_encryption", branch = "main" } -zcash_spec = { git = "https://github.com/QED-it/zcash_spec", rev = "842d697048a8960348adcde704c24438fc5b4544" } diff --git a/src/builder.rs b/src/builder.rs index 86b6487e..28fb8f23 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -10,7 +10,6 @@ use rand::{seq::SliceRandom, RngCore}; use rand_core::CryptoRng; use redjubjub::{Binding, SpendAuth}; use zcash_note_encryption::EphemeralKeyBytes; -use zcash_spec::sighash_versioning::{VersionedSig, SIGHASH_V0}; use crate::{ bundle::{Authorization, Authorized, Bundle, GrothProofBytes}, @@ -20,6 +19,7 @@ use crate::{ }, note::ExtractedNoteCommitment, note_encryption::{sapling_note_encryption, Zip212Enforcement}, + sapling_sighash_versioning::{SaplingSighashVersion, VerBindingSig, VerSpendAuthSig}, util::generate_random_rseed_internal, value::{NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum}, Anchor, Diversifier, MerklePath, Node, Note, Nullifier, PaymentAddress, SaplingIvk, @@ -1122,9 +1122,6 @@ pub struct SigningParts { alpha: jubjub::Scalar, } -/// A versioned binding signature. -pub type VerBindingSig = VersionedSig>; - /// Marker for a partially-authorized bundle, in the process of being signed. #[derive(Clone, Debug)] pub struct PartiallyAuthorized { @@ -1148,9 +1145,6 @@ impl InProgressSignatures for PartiallyAuthorized { type AuthSig = MaybeSigned; } -/// A versioned SpendAuth signature. -pub type VerSpendAuthSig = VersionedSig>; - /// A heisen[`Signature`] for a particular [`SpendDescription`]. /// /// [`Signature`]: redjubjub::Signature @@ -1187,14 +1181,14 @@ impl Bundle, V> { |rng, SigningMetadata { dummy_ask, parts }| match dummy_ask { None => MaybeSigned::SigningParts(parts), Some(ask) => MaybeSigned::Signature(VerSpendAuthSig::new( - SIGHASH_V0, + SaplingSighashVersion::V0, ask.randomize(&parts.alpha).sign(rng, &sighash), )), }, |rng, auth: InProgress| InProgress { sigs: PartiallyAuthorized { binding_signature: VerBindingSig::new( - SIGHASH_V0, + SaplingSighashVersion::V0, auth.sigs.bsk.sign(rng, &sighash), ), sighash, @@ -1239,7 +1233,7 @@ impl Bundle, V> { |rng, maybe| match maybe { MaybeSigned::SigningParts(parts) if parts.ak == expected_ak => { MaybeSigned::Signature(VerSpendAuthSig::new( - SIGHASH_V0, + SaplingSighashVersion::V0, ask.randomize(&parts.alpha).sign(rng, &sighash), )) } diff --git a/src/bundle.rs b/src/bundle.rs index 26fdce30..737a247a 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -10,12 +10,12 @@ use zcash_note_encryption::{ }; use crate::{ - builder::{VerBindingSig, VerSpendAuthSig}, constants::GROTH_PROOF_SIZE, note::ExtractedNoteCommitment, note_encryption::{ CompactOutputDescription, SaplingDomain, COMPACT_NOTE_SIZE, ENC_CIPHERTEXT_SIZE, }, + sapling_sighash_versioning::{VerBindingSig, VerSpendAuthSig}, value::ValueCommitment, Nullifier, }; @@ -316,10 +316,10 @@ impl SpendDescriptionV5 { self, anchor: bls12_381::Scalar, zkproof: GrothProofBytes, - spend_auth_sig: redjubjub::Signature, + spend_auth_sig: VerSpendAuthSig, ) -> SpendDescription where - A: Authorization>, + A: Authorization, { SpendDescription { cv: self.cv, @@ -519,12 +519,11 @@ pub mod testing { use proptest::collection::vec; use proptest::prelude::*; use rand::{rngs::StdRng, SeedableRng}; - use zcash_spec::sighash_versioning::SIGHASH_V0; use crate::{ - builder::{VerBindingSig, VerSpendAuthSig}, constants::GROTH_PROOF_SIZE, note::testing::arb_cmu, + sapling_sighash_versioning::{SaplingSighashVersion, VerBindingSig, VerSpendAuthSig}, value::{ testing::{arb_note_value_bounded, arb_trapdoor}, ValueCommitment, MAX_NOTE_VALUE, @@ -571,7 +570,7 @@ pub mod testing { nullifier, rk, zkproof, - spend_auth_sig: VerSpendAuthSig::new(SIGHASH_V0, sk1.sign(&mut rng, &fake_sighash_bytes)), + spend_auth_sig: VerSpendAuthSig::new(SaplingSighashVersion::V0, sk1.sign(&mut rng, &fake_sighash_bytes)), } } } @@ -629,7 +628,7 @@ pub mod testing { value_balance, authorization: Authorized { binding_sig: VerBindingSig::new( - SIGHASH_V0, + SaplingSighashVersion::V0, bsk.sign(&mut rng, &fake_bvk_bytes), ), }, diff --git a/src/lib.rs b/src/lib.rs index 65fbaa62..de3645e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,6 +38,7 @@ pub mod pczt; pub mod pedersen_hash; #[cfg(feature = "circuit")] pub mod prover; +pub mod sapling_sighash_versioning; mod spec; mod tree; pub mod util; diff --git a/src/pczt.rs b/src/pczt.rs index 63510772..cd5b470b 100644 --- a/src/pczt.rs +++ b/src/pczt.rs @@ -11,10 +11,10 @@ use zcash_note_encryption::{EphemeralKeyBytes, OutgoingCipherKey, OUT_CIPHERTEXT use zip32::ChildIndex; use crate::{ - builder::VerSpendAuthSig, bundle::GrothProofBytes, keys::SpendAuthorizingKey, note::ExtractedNoteCommitment, + sapling_sighash_versioning::VerSpendAuthSig, value::{NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum}, Anchor, MerklePath, Nullifier, PaymentAddress, ProofGenerationKey, Rseed, }; diff --git a/src/pczt/parse.rs b/src/pczt/parse.rs index 107db4d2..ccee44ec 100644 --- a/src/pczt/parse.rs +++ b/src/pczt/parse.rs @@ -8,10 +8,11 @@ use zip32::ChildIndex; use super::{Bundle, Output, Spend, Zip32Derivation}; use crate::{ - builder::VerSpendAuthSig, bundle::GrothProofBytes, keys::{SpendAuthorizingKey, SpendValidatingKey}, note::ExtractedNoteCommitment, + sapling_sighash_versioning::SaplingSighashVersion, + sapling_sighash_versioning::VerSpendAuthSig, value::{NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum}, Anchor, MerklePath, Node, Nullifier, PaymentAddress, ProofGenerationKey, Rseed, }; @@ -46,6 +47,15 @@ impl Bundle { } } +/// Converts an unsigned 8-bit integer into an `Option`. +fn sapling_sighash_version_from_u8(n: u8) -> Option { + match n { + 0 => Some(SaplingSighashVersion::V0), + u8::MAX => Some(SaplingSighashVersion::NoVersion), + _ => None, + } +} + impl Spend { /// Parses a PCZT spend from its component parts. #[allow(clippy::too_many_arguments)] @@ -54,7 +64,7 @@ impl Spend { nullifier: [u8; 32], rk: [u8; 32], zkproof: Option, - spend_auth_sig: Option, + spend_auth_sig: Option<(u8, [u8; 64])>, recipient: Option<[u8; 43]>, value: Option, rcm: Option<[u8; 32]>, @@ -76,6 +86,16 @@ impl Spend { let rk = redjubjub::VerificationKey::try_from(rk) .map_err(|_| ParseError::InvalidRandomizedKey)?; + let spend_auth_sig = spend_auth_sig + .as_ref() + .map(|(version, sig)| { + let version = sapling_sighash_version_from_u8(*version) + .ok_or(ParseError::InvalidSighashVersion)?; + let sig = redjubjub::Signature::from(*sig); + Ok(VerSpendAuthSig::new(version, sig)) + }) + .transpose()?; + let recipient = recipient .as_ref() .map(|r| PaymentAddress::from_bytes(r).ok_or(ParseError::InvalidRecipient)) @@ -282,6 +302,8 @@ pub enum ParseError { InvalidRandomizedKey, /// An invalid `recipient` was provided. InvalidRecipient, + /// An invalid `SaplingSighashVersion` was provided. + InvalidSighashVersion, /// An invalid `alpha` was provided. InvalidSpendAuthRandomizer, /// An invalid `cv` was provided. diff --git a/src/pczt/signer.rs b/src/pczt/signer.rs index 92884fd0..bda1fa7d 100644 --- a/src/pczt/signer.rs +++ b/src/pczt/signer.rs @@ -1,7 +1,9 @@ use rand::{CryptoRng, RngCore}; -use zcash_spec::sighash_versioning::SIGHASH_V0; -use crate::{builder::VerSpendAuthSig, keys::SpendAuthorizingKey}; +use crate::{ + keys::SpendAuthorizingKey, + sapling_sighash_versioning::{SaplingSighashVersion, VerSpendAuthSig}, +}; impl super::Spend { /// Signs the Sapling spend with the given spend authorizing key. @@ -21,7 +23,10 @@ impl super::Spend { let rk = redjubjub::VerificationKey::from(&rsk); if self.rk == rk { - self.spend_auth_sig = Some(VerSpendAuthSig::new(SIGHASH_V0, rsk.sign(rng, &sighash))); + self.spend_auth_sig = Some(VerSpendAuthSig::new( + SaplingSighashVersion::V0, + rsk.sign(rng, &sighash), + )); Ok(()) } else { Err(SignerError::WrongSpendAuthorizingKey) diff --git a/src/pczt/tx_extractor.rs b/src/pczt/tx_extractor.rs index 26dd0f27..b8647c5f 100644 --- a/src/pczt/tx_extractor.rs +++ b/src/pczt/tx_extractor.rs @@ -1,12 +1,11 @@ use rand::{CryptoRng, RngCore}; -use zcash_spec::sighash_versioning::SIGHASH_V0; use crate::{ - builder::{VerBindingSig, VerSpendAuthSig}, bundle::{ Authorization, Authorized, EffectsOnly, GrothProofBytes, OutputDescription, SpendDescription, }, + sapling_sighash_versioning::{SaplingSighashVersion, VerBindingSig, VerSpendAuthSig}, Bundle, }; @@ -159,7 +158,10 @@ impl crate::Bundle { |_, p| p, |_, s| s, |_, Unbound { bsk }| Authorized { - binding_sig: VerBindingSig::new(SIGHASH_V0, bsk.sign(rng, &sighash)), + binding_sig: VerBindingSig::new( + SaplingSighashVersion::V0, + bsk.sign(rng, &sighash), + ), }, )) } else { diff --git a/src/sapling_sighash_versioning.rs b/src/sapling_sighash_versioning.rs new file mode 100644 index 00000000..a51d32cf --- /dev/null +++ b/src/sapling_sighash_versioning.rs @@ -0,0 +1,57 @@ +//! This module defines the versioning for Sapling signatures. + +use redjubjub::{Binding, SigType, Signature, SpendAuth}; + +/// The Sapling Sighash version. +/// Represented as a `u8` for compatibility with the PCZT encoding. +#[repr(u8)] +#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord)] +pub enum SaplingSighashVersion { + /// Version V0. + V0 = 0, + + /// No version (used for Sapling and TXv5 compatibility). + /// TXv5 does not require the sighash versioning bytes. + NoVersion = u8::MAX, +} + +/// The Sapling versioned signature. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct SaplingVersionedSig { + version: SaplingSighashVersion, + sig: Signature, +} + +impl SaplingVersionedSig { + /// Constructs an `SaplingVersionedSig` from its constituent parts. + pub fn new(version: SaplingSighashVersion, sig: Signature) -> Self { + Self { version, sig } + } + + /// Returns the version of the signature. + pub fn version(&self) -> &SaplingSighashVersion { + &self.version + } + + /// Returns the signature. + pub fn sig(&self) -> &Signature { + &self.sig + } +} + +/// A versioned Sapling SpendAuth signature. +pub type VerSpendAuthSig = SaplingVersionedSig; + +/// A versioned Sapling binding signature. +pub type VerBindingSig = SaplingVersionedSig; + +#[cfg(test)] +mod tests { + use super::SaplingSighashVersion; + #[test] + fn lock_sapling_sighash_version_encoding() { + // Ensure the encoding of SaplingSighashVersion is as expected. + assert_eq!(SaplingSighashVersion::V0 as u8, 0); + assert_eq!(SaplingSighashVersion::NoVersion as u8, u8::MAX); + } +} From 9fb73cafdc3278321eff020ac8cacc596302f473 Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Mon, 29 Sep 2025 17:23:09 +0200 Subject: [PATCH 8/9] Refactor --- src/pczt/parse.rs | 3 +-- src/sapling_sighash_versioning.rs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pczt/parse.rs b/src/pczt/parse.rs index ccee44ec..1994a599 100644 --- a/src/pczt/parse.rs +++ b/src/pczt/parse.rs @@ -11,8 +11,7 @@ use crate::{ bundle::GrothProofBytes, keys::{SpendAuthorizingKey, SpendValidatingKey}, note::ExtractedNoteCommitment, - sapling_sighash_versioning::SaplingSighashVersion, - sapling_sighash_versioning::VerSpendAuthSig, + sapling_sighash_versioning::{SaplingSighashVersion, VerSpendAuthSig}, value::{NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum}, Anchor, MerklePath, Node, Nullifier, PaymentAddress, ProofGenerationKey, Rseed, }; diff --git a/src/sapling_sighash_versioning.rs b/src/sapling_sighash_versioning.rs index a51d32cf..9ac80eab 100644 --- a/src/sapling_sighash_versioning.rs +++ b/src/sapling_sighash_versioning.rs @@ -23,7 +23,7 @@ pub struct SaplingVersionedSig { } impl SaplingVersionedSig { - /// Constructs an `SaplingVersionedSig` from its constituent parts. + /// Constructs a `SaplingVersionedSig` from its constituent parts. pub fn new(version: SaplingSighashVersion, sig: Signature) -> Self { Self { version, sig } } From 19f13b6679c3ce10121ab9178350fbd290f73581 Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Tue, 7 Oct 2025 09:12:10 +0200 Subject: [PATCH 9/9] Rename spend_auth_sig to spend_auth --- src/bundle.rs | 24 ++++++++++++------------ src/pczt/tx_extractor.rs | 2 +- src/verifier/batch.rs | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/bundle.rs b/src/bundle.rs index 737a247a..21cf3636 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -124,7 +124,7 @@ impl Bundle { nullifier: d.nullifier, rk: d.rk, zkproof: spend_proof(&mut context, d.zkproof), - spend_auth_sig: auth_sig(&mut context, d.spend_auth_sig), + spend_auth: auth_sig(&mut context, d.spend_auth), }) .collect(), shielded_outputs: self @@ -164,7 +164,7 @@ impl Bundle { nullifier: d.nullifier, rk: d.rk, zkproof: spend_proof(&mut context, d.zkproof)?, - spend_auth_sig: auth_sig(&mut context, d.spend_auth_sig)?, + spend_auth: auth_sig(&mut context, d.spend_auth)?, }) }) .collect::>()?, @@ -221,15 +221,15 @@ pub struct SpendDescription { nullifier: Nullifier, rk: redjubjub::VerificationKey, zkproof: A::SpendProof, - spend_auth_sig: A::AuthSig, + spend_auth: A::AuthSig, } 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 = {:?})", - self.cv, self.anchor, self.nullifier, self.rk, self.spend_auth_sig + "SpendDescription(cv = {:?}, anchor = {:?}, nullifier = {:?}, rk = {:?}, spend_auth = {:?})", + self.cv, self.anchor, self.nullifier, self.rk, self.spend_auth ) } } @@ -242,7 +242,7 @@ impl SpendDescription { nullifier: Nullifier, rk: redjubjub::VerificationKey, zkproof: A::SpendProof, - spend_auth_sig: A::AuthSig, + spend_auth: A::AuthSig, ) -> Self { Self { cv, @@ -250,7 +250,7 @@ impl SpendDescription { nullifier, rk, zkproof, - spend_auth_sig, + spend_auth, } } @@ -280,8 +280,8 @@ impl SpendDescription { } /// Returns the authorization signature for this spend. - pub fn spend_auth_sig(&self) -> &A::AuthSig { - &self.spend_auth_sig + pub fn spend_auth(&self) -> &A::AuthSig { + &self.spend_auth } } @@ -316,7 +316,7 @@ impl SpendDescriptionV5 { self, anchor: bls12_381::Scalar, zkproof: GrothProofBytes, - spend_auth_sig: VerSpendAuthSig, + spend_auth: VerSpendAuthSig, ) -> SpendDescription where A: Authorization, @@ -327,7 +327,7 @@ impl SpendDescriptionV5 { nullifier: self.nullifier, rk: self.rk, zkproof, - spend_auth_sig, + spend_auth, } } } @@ -570,7 +570,7 @@ pub mod testing { nullifier, rk, zkproof, - spend_auth_sig: VerSpendAuthSig::new(SaplingSighashVersion::V0, sk1.sign(&mut rng, &fake_sighash_bytes)), + spend_auth: VerSpendAuthSig::new(SaplingSighashVersion::V0, sk1.sign(&mut rng, &fake_sighash_bytes)), } } } diff --git a/src/pczt/tx_extractor.rs b/src/pczt/tx_extractor.rs index b8647c5f..e01e53cc 100644 --- a/src/pczt/tx_extractor.rs +++ b/src/pczt/tx_extractor.rs @@ -149,7 +149,7 @@ impl crate::Bundle { if self.shielded_spends().iter().all(|spend| { spend .rk() - .verify(&sighash, spend.spend_auth_sig().sig()) + .verify(&sighash, spend.spend_auth().sig()) .is_ok() }) { Some(self.map_authorization( diff --git a/src/verifier/batch.rs b/src/verifier/batch.rs index 2bdb4192..26547713 100644 --- a/src/verifier/batch.rs +++ b/src/verifier/batch.rs @@ -72,7 +72,7 @@ impl BatchValidator { self, |this, rk| { this.signatures - .queue(((*rk).into(), *spend.spend_auth_sig().sig(), &sighash)); + .queue(((*rk).into(), *spend.spend_auth().sig(), &sighash)); true }, |this, proof, public_inputs| {