diff --git a/src/builder.rs b/src/builder.rs index bf9d5ee7..28fb8f23 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -19,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, @@ -1124,7 +1125,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: VerBindingSig, sighash: [u8; 32], } @@ -1152,11 +1153,11 @@ pub enum MaybeSigned { /// The information needed to sign this [`SpendDescription`]. SigningParts(SigningParts), /// The signature for this [`SpendDescription`]. - Signature(redjubjub::Signature), + Signature(VerSpendAuthSig), } impl MaybeSigned { - fn finalize(self) -> Result, Error> { + fn finalize(self) -> Result { match self { Self::Signature(sig) => Ok(sig), _ => Err(Error::MissingSignatures), @@ -1179,13 +1180,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(VerSpendAuthSig::new( + SaplingSighashVersion::V0, + ask.randomize(&parts.alpha).sign(rng, &sighash), + )), }, |rng, auth: InProgress| InProgress { sigs: PartiallyAuthorized { - binding_signature: auth.sigs.bsk.sign(rng, &sighash), + binding_signature: VerBindingSig::new( + SaplingSighashVersion::V0, + auth.sigs.bsk.sign(rng, &sighash), + ), sighash, }, _proof_state: PhantomData, @@ -1227,7 +1232,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(VerSpendAuthSig::new( + SaplingSighashVersion::V0, + ask.randomize(&parts.alpha).sign(rng, &sighash), + )) } s => s, }, @@ -1240,14 +1248,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: &[redjubjub::Signature], - ) -> Result { + pub fn append_signatures(self, signatures: &[VerSpendAuthSig]) -> Result { signatures.iter().try_fold(self, Self::append_signature) } - fn append_signature(self, signature: &redjubjub::Signature) -> 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( @@ -1257,9 +1262,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.sig()).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..21cf3636 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,7 @@ use crate::{ note_encryption::{ CompactOutputDescription, SaplingDomain, COMPACT_NOTE_SIZE, ENC_CIPHERTEXT_SIZE, }, + sapling_sighash_versioning::{VerBindingSig, VerSpendAuthSig}, value::ValueCommitment, Nullifier, }; @@ -40,16 +41,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: VerBindingSig, } impl Authorization for Authorized { type SpendProof = GrothProofBytes; type OutputProof = GrothProofBytes; - type AuthSig = redjubjub::Signature; + type AuthSig = VerSpendAuthSig; } #[derive(Debug, Clone)] @@ -123,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 @@ -163,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::>()?, @@ -220,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 ) } } @@ -241,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, @@ -249,7 +250,7 @@ impl SpendDescription { nullifier, rk, zkproof, - spend_auth_sig, + spend_auth, } } @@ -279,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 } } @@ -315,10 +316,10 @@ impl SpendDescriptionV5 { self, anchor: bls12_381::Scalar, zkproof: GrothProofBytes, - spend_auth_sig: redjubjub::Signature, + spend_auth: VerSpendAuthSig, ) -> SpendDescription where - A: Authorization>, + A: Authorization, { SpendDescription { cv: self.cv, @@ -326,7 +327,7 @@ impl SpendDescriptionV5 { nullifier: self.nullifier, rk: self.rk, zkproof, - spend_auth_sig, + spend_auth, } } } @@ -522,6 +523,7 @@ pub mod testing { use crate::{ 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, @@ -568,7 +570,7 @@ pub mod testing { nullifier, rk, zkproof, - spend_auth_sig: sk1.sign(&mut rng, &fake_sighash_bytes), + spend_auth: VerSpendAuthSig::new(SaplingSighashVersion::V0, sk1.sign(&mut rng, &fake_sighash_bytes)), } } } @@ -625,7 +627,10 @@ pub mod testing { shielded_outputs, value_balance, authorization: Authorized { - binding_sig: bsk.sign(&mut rng, &fake_bvk_bytes), + binding_sig: VerBindingSig::new( + 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 1f11a96f..cd5b470b 100644 --- a/src/pczt.rs +++ b/src/pczt.rs @@ -14,6 +14,7 @@ use crate::{ bundle::GrothProofBytes, keys::SpendAuthorizingKey, note::ExtractedNoteCommitment, + sapling_sighash_versioning::VerSpendAuthSig, 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..1994a599 100644 --- a/src/pczt/parse.rs +++ b/src/pczt/parse.rs @@ -11,6 +11,7 @@ use crate::{ bundle::GrothProofBytes, keys::{SpendAuthorizingKey, SpendValidatingKey}, note::ExtractedNoteCommitment, + sapling_sighash_versioning::{SaplingSighashVersion, VerSpendAuthSig}, value::{NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum}, Anchor, MerklePath, Node, Nullifier, PaymentAddress, ProofGenerationKey, Rseed, }; @@ -45,6 +46,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)] @@ -53,7 +63,7 @@ impl Spend { nullifier: [u8; 32], rk: [u8; 32], zkproof: Option, - spend_auth_sig: Option<[u8; 64]>, + spend_auth_sig: Option<(u8, [u8; 64])>, recipient: Option<[u8; 43]>, value: Option, rcm: Option<[u8; 32]>, @@ -75,7 +85,15 @@ 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 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() @@ -283,6 +301,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 90776357..bda1fa7d 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, + sapling_sighash_versioning::{SaplingSighashVersion, VerSpendAuthSig}, +}; 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(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 9a6cf667..e01e53cc 100644 --- a/src/pczt/tx_extractor.rs +++ b/src/pczt/tx_extractor.rs @@ -5,6 +5,7 @@ use crate::{ Authorization, Authorized, EffectsOnly, GrothProofBytes, OutputDescription, SpendDescription, }, + sapling_sighash_versioning::{SaplingSighashVersion, VerBindingSig, VerSpendAuthSig}, Bundle, }; @@ -35,6 +36,7 @@ impl super::Bundle { |spend| { spend .spend_auth_sig + .clone() .ok_or(TxExtractorError::MissingSpendAuthSig) }, |output| output.zkproof.ok_or(TxExtractorError::MissingProof), @@ -132,7 +134,7 @@ pub struct Unbound { impl Authorization for Unbound { type SpendProof = GrothProofBytes; type OutputProof = GrothProofBytes; - type AuthSig = redjubjub::Signature; + type AuthSig = VerSpendAuthSig; } impl crate::Bundle { @@ -144,18 +146,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()) + .is_ok() + }) { Some(self.map_authorization( &mut (), |_, p| p, |_, p| p, |_, s| s, |_, Unbound { bsk }| Authorized { - binding_sig: 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..9ac80eab --- /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 a `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); + } +} diff --git a/src/verifier/batch.rs b/src/verifier/batch.rs index d6306202..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(), &sighash)); + .queue(((*rk).into(), *spend.spend_auth().sig(), &sighash)); true }, |this, proof, public_inputs| { @@ -116,8 +116,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.sig(), + &sighash, + )); true }) }