diff --git a/benches/circuit.rs b/benches/circuit.rs index 579a34675..6e3708f94 100644 --- a/benches/circuit.rs +++ b/benches/circuit.rs @@ -31,10 +31,7 @@ fn criterion_benchmark(c: &mut Criterion) { let pk = ProvingKey::build::(); let create_bundle = |num_recipients| { - let mut builder = Builder::new( - BundleType::DEFAULT_VANILLA, - Anchor::from_bytes([0; 32]).unwrap(), - ); + let mut builder = Builder::new(BundleType::DEFAULT, Anchor::from_bytes([0; 32]).unwrap()); for _ in 0..num_recipients { builder .add_output( diff --git a/benches/note_decryption.rs b/benches/note_decryption.rs index 8f2ce8373..f6247afca 100644 --- a/benches/note_decryption.rs +++ b/benches/note_decryption.rs @@ -50,10 +50,7 @@ fn bench_note_decryption(c: &mut Criterion) { .collect(); let bundle = { - let mut builder = Builder::new( - BundleType::DEFAULT_VANILLA, - Anchor::from_bytes([0; 32]).unwrap(), - ); + let mut builder = Builder::new(BundleType::DEFAULT, Anchor::from_bytes([0; 32]).unwrap()); // The builder pads to two actions, and shuffles their order. Add two recipients // so the first action is always decryptable. builder diff --git a/src/action.rs b/src/action.rs index e48eae5b8..5125a0b3b 100644 --- a/src/action.rs +++ b/src/action.rs @@ -13,7 +13,7 @@ use crate::{ /// This both creates a note (adding a commitment to the global ledger), and consumes /// some note created prior to this action (adding a nullifier to the global ledger). #[derive(Debug, Clone)] -pub struct Action { +pub struct Action { /// The nullifier of the note being spent. nf: Nullifier, /// The randomized verification key for the note being spent. @@ -21,20 +21,20 @@ pub struct Action { /// A commitment to the new note being created. cmx: ExtractedNoteCommitment, /// The transmitted note ciphertext. - encrypted_note: TransmittedNoteCiphertext

, + encrypted_note: TransmittedNoteCiphertext, /// A commitment to the net value created or consumed by this action. cv_net: ValueCommitment, /// The authorization for this action. authorization: A, } -impl Action { +impl Action { /// Constructs an `Action` from its constituent parts. pub fn from_parts( nf: Nullifier, rk: redpallas::VerificationKey, cmx: ExtractedNoteCommitment, - encrypted_note: TransmittedNoteCiphertext

, + encrypted_note: TransmittedNoteCiphertext, cv_net: ValueCommitment, authorization: A, ) -> Self { @@ -64,7 +64,7 @@ impl Action { } /// Returns the encrypted note ciphertext. - pub fn encrypted_note(&self) -> &TransmittedNoteCiphertext

{ + pub fn encrypted_note(&self) -> &TransmittedNoteCiphertext { &self.encrypted_note } @@ -84,7 +84,7 @@ impl Action { } /// Transitions this action from one authorization state to another. - pub fn map(self, step: impl FnOnce(A) -> U) -> Action { + pub fn map(self, step: impl FnOnce(A) -> U) -> Action { Action { nf: self.nf, rk: self.rk, @@ -96,7 +96,7 @@ impl Action { } /// Transitions this action from one authorization state to another. - pub fn try_map(self, step: impl FnOnce(A) -> Result) -> Result, E> { + pub fn try_map(self, step: impl FnOnce(A) -> Result) -> Result, E> { Ok(Action { nf: self.nf, rk: self.rk, @@ -108,7 +108,7 @@ impl Action { } } -impl DynamicUsage for Action { +impl DynamicUsage for Action { #[inline(always)] fn dynamic_usage(&self) -> usize { 0 @@ -150,20 +150,20 @@ pub(crate) mod testing { /// `ActionArb` adapts `arb_...` functions for both Vanilla and ZSA Orchard protocol flavors /// in property-based testing, addressing proptest crate limitations. #[derive(Debug)] - pub struct ActionArb { - phantom: core::marker::PhantomData

, + pub struct ActionArb { + phantom: core::marker::PhantomData, } - impl ActionArb

{ + impl ActionArb { fn encrypt_note( note: Note, memo: Vec, cmx: &ExtractedNoteCommitment, cv_net: &ValueCommitment, rng: &mut R, - ) -> TransmittedNoteCiphertext

{ + ) -> TransmittedNoteCiphertext { let encryptor = - NoteEncryption::>::new(None, note, memo.try_into().unwrap()); + NoteEncryption::>::new(None, note, memo.try_into().unwrap()); TransmittedNoteCiphertext { epk_bytes: encryptor.epk().to_bytes().0, @@ -181,7 +181,7 @@ pub(crate) mod testing { asset in arb_asset_base(), rng_seed in prop::array::uniform32(prop::num::u8::ANY), memo in prop::collection::vec(prop::num::u8::ANY, 512), - ) -> Action<(), P> { + ) -> Action<(), Pr> { let cmx = ExtractedNoteCommitment::from(note.commitment()); let cv_net = ValueCommitment::derive( spend_value - output_value, @@ -213,7 +213,7 @@ pub(crate) mod testing { fake_sighash in prop::array::uniform32(prop::num::u8::ANY), asset in arb_asset_base(), memo in prop::collection::vec(prop::num::u8::ANY, 512), - ) -> Action { + ) -> Action { let cmx = ExtractedNoteCommitment::from(note.commitment()); let cv_net = ValueCommitment::derive( spend_value - output_value, @@ -231,7 +231,7 @@ pub(crate) mod testing { cmx, encrypted_note, cv_net, - authorization: VerSpendAuthSig::new(P::default_sighash_version(), sk.sign(rng, &fake_sighash)), + authorization: VerSpendAuthSig::new(Pr::default_sighash_version(), sk.sign(rng, &fake_sighash)), } } } diff --git a/src/builder.rs b/src/builder.rs index 850534350..6f2ca22b2 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -61,9 +61,9 @@ pub enum BundleType { impl BundleType { /// The default bundle type has all flags enabled, ZSA disabled, and does not require a bundle - /// to be produced. - pub const DEFAULT_VANILLA: BundleType = BundleType::Transactional { - flags: Flags::ENABLED_WITHOUT_ZSA, + /// to be produced if no spends or outputs have been added to the bundle. + pub const DEFAULT: BundleType = BundleType::Transactional { + flags: Flags::ENABLED, bundle_required: false, }; @@ -123,7 +123,7 @@ impl BundleType { pub fn flags(&self) -> Flags { match self { BundleType::Transactional { flags, .. } => *flags, - BundleType::Coinbase => Flags::SPENDS_DISABLED_WITHOUT_ZSA, + BundleType::Coinbase => Flags::SPENDS_DISABLED, } } } @@ -417,18 +417,18 @@ impl OutputInfo { /// Defined in [Zcash Protocol Spec § 4.7.3: Sending Notes (Orchard)][orchardsend]. /// /// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend - fn build( + fn build( &self, cv_net: &ValueCommitment, nf_old: Nullifier, mut rng: impl RngCore, - ) -> (Note, ExtractedNoteCommitment, TransmittedNoteCiphertext

) { + ) -> (Note, ExtractedNoteCommitment, TransmittedNoteCiphertext) { let rho = Rho::from_nf_old(nf_old); let note = Note::new(self.recipient, self.value, self.asset, rho, &mut rng); let cm_new = note.commitment(); let cmx = cm_new.into(); - let encryptor = NoteEncryption::>::new(self.ovk.clone(), note, self.memo); + let encryptor = NoteEncryption::>::new(self.ovk.clone(), note, self.memo); let encrypted_note = TransmittedNoteCiphertext { epk_bytes: encryptor.epk().to_bytes().0, @@ -474,7 +474,18 @@ struct ActionInfo { } impl ActionInfo { + /// Creates an `ActionInfo` with a random `rcv`. + /// + /// # Panics + /// + /// Panics if the asset types of the spent and output notes do not match. fn new(spend: SpendInfo, output: OutputInfo, rng: impl RngCore) -> Self { + assert_eq!( + spend.note.asset(), + output.asset, + "spend and recipient note types must be equal" + ); + ActionInfo { spend, output, @@ -483,6 +494,7 @@ impl ActionInfo { } /// Returns the value sum for this action. + /// /// Split notes do not contribute to the value sum. fn value_sum(&self) -> ValueSum { let spent_value = if self.spend.split_flag { @@ -499,21 +511,11 @@ impl ActionInfo { /// Defined in [Zcash Protocol Spec § 4.7.3: Sending Notes (Orchard)][orchardsend]. /// /// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend - /// - /// # Panics - /// - /// Panics if the asset types of the spent and output notes do not match. #[cfg(feature = "circuit")] fn build( self, mut rng: impl RngCore, ) -> (Action, Witnesses) { - assert_eq!( - self.spend.note.asset(), - self.output.asset, - "spend and recipient note types must be equal" - ); - let v_net = self.value_sum(); let cv_net = ValueCommitment::derive(v_net, self.rcv, self.output.asset); @@ -537,11 +539,6 @@ impl ActionInfo { } fn build_for_pczt(self, mut rng: impl RngCore) -> crate::pczt::Action { - assert_eq!( - self.spend.note.asset(), - self.output.asset, - "spend and recipient note types must be equal" - ); let v_net = self.value_sum(); let cv_net = ValueCommitment::derive(v_net, self.rcv, self.spend.note.asset()); @@ -619,6 +616,7 @@ pub type UnauthorizedBundleWithMetadata = (UnauthorizedBundle, Bun /// A builder for constructing an Orchard [`Bundle`] by specifying notes to spend, outputs to /// receive, and assets to burn. +/// /// This builder provides a structured way to incrementally assemble the components of a bundle. #[derive(Debug)] pub struct Builder { @@ -696,7 +694,7 @@ impl Builder { Ok(()) } - /// Add an instruction to burn a given amount of a specific asset. + /// Adds an instruction to burn a given amount of a specific asset. pub fn add_burn(&mut self, asset: AssetBase, value: NoteValue) -> Result<(), BuildError> { use alloc::collections::btree_map::Entry; @@ -1004,8 +1002,9 @@ fn build_bundle( .take(num_asset_pre_actions) .collect::>(); - // Shuffle the spends and outputs, so that the position does not reveal any - // information about the content. + // Shuffle the spends and outputs, so that learning the position of a + // specific spent note or output note doesn't reveal anything on its own + // about the meaning of that note in the transaction context. indexed_spends.shuffle(&mut rng); indexed_outputs.shuffle(&mut rng); @@ -1025,8 +1024,8 @@ fn build_bundle( .take(MIN_ACTIONS.saturating_sub(indexed_spends_outputs.len())), ); - // Shuffle the spends and outputs, so that the position does not reveal any information - // about the content. + // We shuffled the spends and outputs within each `AssetBase` above; now we + // shuffle the actions to achieve a similar property across `AssetBase`s. indexed_spends_outputs.shuffle(&mut rng); let mut bundle_meta = BundleMetadata::new(num_requested_spends, num_requested_outputs); @@ -1058,18 +1057,7 @@ fn build_bundle( .try_fold(ValueSum::zero(), |acc, action| acc + action.value_sum()) .ok_or(OverflowError)?; - let burn_vec = burn - .into_iter() - .map(|(asset, value)| { - Ok(( - asset, - NoteValue::from_raw( - u64::try_from(i128::from(value)) - .map_err(|_| BuildError::ValueSum(OverflowError))?, - ), - )) - }) - .collect::, BuildError>>()?; + let burn_vec = burn.into_iter().collect(); finisher( pre_actions, @@ -1222,7 +1210,7 @@ impl MaybeSigned { } } -impl Bundle, V, P> { +impl Bundle, V, Pr> { /// Loads the sighash into this bundle, preparing it for signing. /// /// This API ensures that all signatures are created over the same sighash. @@ -1230,7 +1218,7 @@ impl Bundle Bundle, V, P> { + ) -> Bundle, V, Pr> { self.map_authorization( &mut rng, |rng, _, SigningMetadata { dummy_ask, parts }| { @@ -1238,7 +1226,7 @@ impl Bundle Bundle Bundle Bundle, V, P> { +impl Bundle, V, Pr> { /// Applies signatures to this bundle, in order to authorize it. /// /// This is a helper method that wraps [`Bundle::prepare`], [`Bundle::sign`], and @@ -1270,7 +1258,7 @@ impl Bundle, V, P> { mut rng: R, sighash: [u8; 32], signing_keys: &[SpendAuthorizingKey], - ) -> Result, BuildError> { + ) -> Result, BuildError> { signing_keys .iter() .fold(self.prepare(&mut rng, sighash), |partial, ask| { @@ -1280,9 +1268,7 @@ impl Bundle, V, P> { } } -impl - Bundle, V, P> -{ +impl Bundle, V, Pr> { /// Signs this bundle with the given [`SpendAuthorizingKey`]. /// /// This will apply versioned signatures for all notes controlled by this spending key. @@ -1293,7 +1279,7 @@ impl |rng, partial, maybe| match maybe { MaybeSigned::SigningMetadata(parts) if parts.ak == expected_ak => { MaybeSigned::Signature(VerSpendAuthSig::new( - P::default_sighash_version(), + Pr::default_sighash_version(), ask.randomize(&parts.alpha).sign(rng, &partial.sigs.sighash), )) } @@ -1302,6 +1288,7 @@ impl |_, partial| partial, ) } + /// Appends externally computed versioned signatures. /// /// Each versioned signature will be applied to the one input for which it is valid. An error @@ -1343,11 +1330,11 @@ impl } } -impl Bundle, V, P> { +impl Bundle, V, Pr> { /// Finalizes this bundle, enabling it to be included in a transaction. /// /// Returns an error if any signatures are missing. - pub fn finalize(self) -> Result, BuildError> { + pub fn finalize(self) -> Result, BuildError> { self.try_map_authorization( &mut (), |_, _, maybe| maybe.finalize(), @@ -1475,10 +1462,10 @@ pub mod testing { } /// `BuilderArb` adapts `arb_...` functions for both Vanilla and ZSA Orchard protocol variations - /// in property-based testing, addressing proptest crate limitations. + /// in property-based testing, addressing proptest crate limitations. #[derive(Debug)] - pub struct BuilderArb { - phantom: core::marker::PhantomData

, + pub struct BuilderArb { + phantom: core::marker::PhantomData, } impl BuilderArb { @@ -1575,7 +1562,7 @@ mod tests { let recipient = fvk.address_at(0u32, Scope::External); let mut builder = Builder::new( - BundleType::DEFAULT_VANILLA, + BundleType::DEFAULT, EMPTY_ROOTS[MERKLE_DEPTH_ORCHARD].into(), ); diff --git a/src/bundle.rs b/src/bundle.rs index edc19f3e5..14ff81d88 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -1,8 +1,8 @@ //! Structs related to bundles of Orchard actions. -pub mod burn_validation; use alloc::vec::Vec; +pub mod burn_validation; pub mod commitments; #[cfg(feature = "circuit")] @@ -38,7 +38,7 @@ use crate::{ use crate::circuit::{Instance, VerifyingKey}; #[cfg(feature = "circuit")] -impl Action { +impl Action { /// Prepares the public instance for this action, for creating and verifying the /// bundle proof. pub fn to_instance(&self, flags: Flags, anchor: Anchor) -> Instance { @@ -70,10 +70,10 @@ pub struct Flags { /// guaranteed to be dummy notes. If `true`, the created notes may be either real or /// dummy notes. outputs_enabled: bool, - /// Flag denoting whether ZSA transaction is enabled. + /// Flag denoting whether ZSA functionality is enabled in the transaction. /// - /// If `false`, all notes within [`Action`]s in the transaction's [`Bundle`] are - /// guaranteed to be notes with native asset. + /// If `false`, all notes within [`Action`]s in the transaction's [`Bundle`] are + /// guaranteed to be notes with native asset. If `true`, `Action`s may use any asset. zsa_enabled: bool, } @@ -97,7 +97,7 @@ impl Flags { } /// The flag set with both spends and outputs enabled and ZSA disabled. - pub const ENABLED_WITHOUT_ZSA: Flags = Flags { + pub const ENABLED: Flags = Flags { spends_enabled: true, outputs_enabled: true, zsa_enabled: false, @@ -111,7 +111,7 @@ impl Flags { }; /// The flag set with spends and ZSA disabled. - pub const SPENDS_DISABLED_WITHOUT_ZSA: Flags = Flags { + pub const SPENDS_DISABLED: Flags = Flags { spends_enabled: false, outputs_enabled: true, zsa_enabled: false, @@ -149,10 +149,10 @@ impl Flags { self.outputs_enabled } - /// Flag denoting whether ZSA transaction is enabled. + /// Flag denoting whether ZSA functionality is enabled in the transaction. /// - /// If `false`, all notes within [`Action`]s in the transaction's [`Bundle`] are - /// guaranteed to be notes with native asset. + /// If `false`, all notes within [`Action`]s in the transaction's [`Bundle`] are + /// guaranteed to be notes with native asset. If `true`, `Action`s may use any asset. pub fn zsa_enabled(&self) -> bool { self.zsa_enabled } @@ -203,9 +203,9 @@ pub trait Authorization: fmt::Debug { /// A bundle of actions to be applied to the ledger. #[derive(Clone)] -pub struct Bundle { +pub struct Bundle { /// The list of actions that make up this bundle. - actions: NonEmpty>, + actions: NonEmpty>, /// Orchard-specific transaction-level flags for this bundle. flags: Flags, /// The net value moved out of the Orchard shielded pool. @@ -225,11 +225,11 @@ pub struct Bundle { authorization: A, } -impl fmt::Debug for Bundle { +impl fmt::Debug for Bundle { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { /// Helper struct for debug-printing actions without exposing `NonEmpty`. - struct Actions<'a, A, P: OrchardPrimitives>(&'a NonEmpty>); - impl fmt::Debug for Actions<'_, A, P> { + struct Actions<'a, A, Pr: OrchardPrimitives>(&'a NonEmpty>); + impl fmt::Debug for Actions<'_, A, Pr> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_list().entries(self.0.iter()).finish() } @@ -245,10 +245,10 @@ impl fmt::Debug for Bundl } } -impl Bundle { +impl Bundle { /// Constructs a `Bundle` from its constituent parts. pub fn from_parts( - actions: NonEmpty>, + actions: NonEmpty>, flags: Flags, value_balance: V, burn: Vec<(AssetBase, NoteValue)>, @@ -268,7 +268,7 @@ impl Bundle { } /// Returns the list of actions that make up this bundle. - pub fn actions(&self) -> &NonEmpty> { + pub fn actions(&self) -> &NonEmpty> { &self.actions } @@ -311,7 +311,7 @@ impl Bundle { pub fn try_map_value_balance Result>( self, f: F, - ) -> Result, E> { + ) -> Result, E> { Ok(Bundle { actions: self.actions, flags: self.flags, @@ -329,7 +329,7 @@ impl Bundle { context: &mut R, mut spend_auth: impl FnMut(&mut R, &A, A::SpendAuth) -> U::SpendAuth, step: impl FnOnce(&mut R, A) -> U, - ) -> Bundle { + ) -> Bundle { let authorization = self.authorization; Bundle { actions: self @@ -350,7 +350,7 @@ impl Bundle { context: &mut R, mut spend_auth: impl FnMut(&mut R, &A, A::SpendAuth) -> Result, step: impl FnOnce(&mut R, A) -> Result, - ) -> Result, E> { + ) -> Result, E> { let authorization = self.authorization; let new_actions = self .actions @@ -465,21 +465,25 @@ impl Bundle { } } -pub(crate) fn derive_bvk, P: OrchardPrimitives>( - actions: &NonEmpty>, +pub(crate) fn derive_bvk, Pr: OrchardPrimitives>( + actions: &NonEmpty>, value_balance: V, burn: &[(AssetBase, NoteValue)], ) -> redpallas::VerificationKey { - let cv_nets: Vec<_> = actions.into_iter().map(|a| a.cv_net().clone()).collect(); - derive_bvk_raw(&cv_nets, ValueSum::from_raw(value_balance.into()), burn) + derive_bvk_raw( + actions.into_iter().map(|a| a.cv_net()), + ValueSum::from_raw(value_balance.into()), + burn, + ) } -pub(crate) fn derive_bvk_raw( - cv_nets: &[ValueCommitment], +pub(crate) fn derive_bvk_raw<'a>( + cv_nets: impl IntoIterator, value_balance: ValueSum, burn: &[(AssetBase, NoteValue)], ) -> redpallas::VerificationKey { - (cv_nets.iter().sum::() + // https://p.z.cash/TCR:bad-txns-orchard-binding-signature-invalid?partial + (cv_nets.into_iter().sum::() - ValueCommitment::derive( value_balance, ValueCommitTrapdoor::zero(), @@ -494,7 +498,7 @@ pub(crate) fn derive_bvk_raw( .into_bvk() } -impl, P: OrchardPrimitives> Bundle { +impl, Pr: OrchardPrimitives> Bundle { /// Computes a commitment to the effects of this bundle, suitable for inclusion within /// a transaction ID. pub fn commitment(&self) -> BundleCommitment { @@ -549,7 +553,7 @@ impl Authorized { } } -impl Bundle { +impl Bundle { /// Computes a commitment to the authorizing data within for this bundle. /// /// This together with `Bundle::commitment` bind the entire bundle. @@ -573,7 +577,7 @@ impl Bundle { } #[cfg(feature = "std")] -impl DynamicUsage for Bundle { +impl DynamicUsage for Bundle { fn dynamic_usage(&self) -> usize { self.actions.tail.dynamic_usage() + self.value_balance.dynamic_usage() @@ -651,16 +655,16 @@ pub mod testing { /// `BundleArb` adapts `arb_...` functions for both Vanilla and ZSA Orchard protocol variations /// in property-based testing, addressing proptest crate limitations. #[derive(Debug)] - pub struct BundleArb { - phantom: std::marker::PhantomData

, + pub struct BundleArb { + phantom: std::marker::PhantomData, } - impl BundleArb

{ + impl BundleArb { /// Generate an unauthorized action having spend and output values less than MAX_NOTE_VALUE / n_actions. pub fn arb_unauthorized_action_n( n_actions: usize, flags: Flags, - ) -> impl Strategy)> { + ) -> impl Strategy)> { let spend_value_gen = if flags.spends_enabled { Strategy::boxed(arb_note_value_bounded(MAX_NOTE_VALUE / n_actions as u64)) } else { @@ -685,7 +689,7 @@ pub mod testing { pub fn arb_action_n( n_actions: usize, flags: Flags, - ) -> impl Strategy)> { + ) -> impl Strategy)> { let spend_value_gen = if flags.spends_enabled { Strategy::boxed(arb_note_value_bounded(MAX_NOTE_VALUE / n_actions as u64)) } else { @@ -746,7 +750,7 @@ pub mod testing { anchor in Self::arb_base().prop_map(Anchor::from), flags in Just(flags), burn in vec(Self::arb_asset_to_burn(), 1usize..10) - ) -> Bundle { + ) -> Bundle { let (balances, actions): (Vec, Vec>) = acts.into_iter().unzip(); Bundle::from_parts( @@ -777,7 +781,7 @@ pub mod testing { fake_sighash in prop::array::uniform32(prop::num::u8::ANY), flags in Just(flags), burn in vec(Self::arb_asset_to_burn(), 1usize..10) - ) -> Bundle { + ) -> Bundle { let (balances, actions): (Vec, Vec, >) = acts.into_iter().unzip(); let rng = StdRng::from_seed(rng_seed); @@ -789,7 +793,7 @@ pub mod testing { anchor, Authorized { proof: Proof::new(fake_proof), - binding_signature: VerBindingSig::new(P::default_sighash_version(), sk.sign(rng, &fake_sighash)), + binding_signature: VerBindingSig::new(Pr::default_sighash_version(), sk.sign(rng, &fake_sighash)), }, ) } diff --git a/src/bundle/batch.rs b/src/bundle/batch.rs index 481dadd17..6d679787b 100644 --- a/src/bundle/batch.rs +++ b/src/bundle/batch.rs @@ -40,9 +40,9 @@ impl BatchValidator { } /// Adds the proof and RedPallas signatures from the given bundle to the validator. - pub fn add_bundle, P: OrchardPrimitives>( + pub fn add_bundle, Pr: OrchardPrimitives>( &mut self, - bundle: &Bundle, + bundle: &Bundle, sighash: [u8; 32], ) { for action in bundle.actions().iter() { diff --git a/src/bundle/commitments.rs b/src/bundle/commitments.rs index 2a11a308b..4e369fbb7 100644 --- a/src/bundle/commitments.rs +++ b/src/bundle/commitments.rs @@ -46,10 +46,14 @@ pub(crate) fn hasher(personal: &[u8; 16]) -> State { /// /// [zip244]: https://zips.z.cash/zip-0244 /// [zip246]: https://zips.z.cash/zip-0246 -pub(crate) fn hash_bundle_txid_data, P: OrchardPrimitives>( - bundle: &Bundle, +pub(crate) fn hash_bundle_txid_data< + A: Authorization, + V: Copy + Into, + Pr: OrchardPrimitives, +>( + bundle: &Bundle, ) -> Blake2bHash { - P::hash_bundle_txid_data(bundle) + Pr::hash_bundle_txid_data(bundle) } /// Construct the commitment for the absent bundle as defined in @@ -73,11 +77,11 @@ pub fn hash_bundle_txid_empty() -> Blake2bHash { /// /// [zip244]: https://zips.z.cash/zip-0244 /// [zip246]: https://zips.z.cash/zip-0246 -pub(crate) fn hash_bundle_auth_data( - bundle: &Bundle, +pub(crate) fn hash_bundle_auth_data( + bundle: &Bundle, sighash_version_map: &BTreeMap>, ) -> Blake2bHash { - P::hash_bundle_auth_data(bundle, sighash_version_map) + Pr::hash_bundle_auth_data(bundle, sighash_version_map) } /// Construct the `orchard_auth_digest` commitment for an absent bundle as defined in @@ -236,7 +240,7 @@ mod tests { /// to ensure consistency. #[test] fn test_hash_bundle_txid_data_for_orchard_vanilla() { - let bundle = generate_bundle::(BundleType::DEFAULT_VANILLA); + let bundle = generate_bundle::(BundleType::DEFAULT); let sighash = hash_bundle_txid_data(&bundle); assert_eq!( sighash.to_hex().as_str(), @@ -272,7 +276,7 @@ mod tests { /// reference value to ensure consistency. #[test] fn test_hash_bundle_auth_data_for_orchard_vanilla() { - let bundle = generate_auth_bundle::(BundleType::DEFAULT_VANILLA); + let bundle = generate_auth_bundle::(BundleType::DEFAULT); let orchard_auth_digest = hash_bundle_auth_data(&bundle, &BTreeMap::new()); assert_eq!( orchard_auth_digest.to_hex().as_str(), diff --git a/src/note.rs b/src/note.rs index 67808b81f..8f4e144d9 100644 --- a/src/note.rs +++ b/src/note.rs @@ -401,17 +401,17 @@ pub(crate) fn rho_for_issuance_note( /// An encrypted note. #[derive(Clone)] -pub struct TransmittedNoteCiphertext { +pub struct TransmittedNoteCiphertext { /// The serialization of the ephemeral public key pub epk_bytes: [u8; 32], /// The encrypted note ciphertext - pub enc_ciphertext: P::NoteCiphertextBytes, + pub enc_ciphertext: Pr::NoteCiphertextBytes, /// An encrypted value that allows the holder of the outgoing cipher /// key for the note to recover the note plaintext. pub out_ciphertext: [u8; 80], } -impl fmt::Debug for TransmittedNoteCiphertext

{ +impl fmt::Debug for TransmittedNoteCiphertext { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("TransmittedNoteCiphertext") .field("epk_bytes", &self.epk_bytes) diff --git a/src/pczt.rs b/src/pczt.rs index 8a924cf70..2f2b0a5bb 100644 --- a/src/pczt.rs +++ b/src/pczt.rs @@ -363,7 +363,7 @@ mod tests { // Run the Creator and Constructor roles. let mut builder = Builder::new( - BundleType::DEFAULT_VANILLA, + BundleType::DEFAULT, EMPTY_ROOTS[MERKLE_DEPTH_ORCHARD].into(), ); builder @@ -450,7 +450,7 @@ mod tests { }; // Run the Creator and Constructor roles. - let mut builder = Builder::new(BundleType::DEFAULT_VANILLA, anchor); + let mut builder = Builder::new(BundleType::DEFAULT, anchor); builder .add_spend(fvk.clone(), note, merkle_path.into()) .unwrap(); diff --git a/src/primitives/compact_action.rs b/src/primitives/compact_action.rs index c0cdd742d..ffe5f170e 100644 --- a/src/primitives/compact_action.rs +++ b/src/primitives/compact_action.rs @@ -1,7 +1,5 @@ //! Defines actions for Orchard shielded outputs and compact action for light clients. -// Review hint: this file is largely derived from src/note_encryption.rs - use core::fmt; use zcash_note_encryption::{note_bytes::NoteBytes, EphemeralKeyBytes, ShieldedOutput}; @@ -13,7 +11,7 @@ use crate::{ use super::{orchard_domain::OrchardDomain, orchard_primitives::OrchardPrimitives}; -impl ShieldedOutput> for Action { +impl ShieldedOutput> for Action { fn ephemeral_key(&self) -> EphemeralKeyBytes { EphemeralKeyBytes(self.encrypted_note().epk_bytes) } @@ -26,38 +24,38 @@ impl ShieldedOutput> for Action self.cmx().to_bytes() } - fn enc_ciphertext(&self) -> Option<&P::NoteCiphertextBytes> { + fn enc_ciphertext(&self) -> Option<&Pr::NoteCiphertextBytes> { Some(&self.encrypted_note().enc_ciphertext) } - fn enc_ciphertext_compact(&self) -> P::CompactNoteCiphertextBytes { - P::CompactNoteCiphertextBytes::from_slice( - &self.encrypted_note().enc_ciphertext.as_ref()[..P::COMPACT_NOTE_SIZE], + fn enc_ciphertext_compact(&self) -> Pr::CompactNoteCiphertextBytes { + Pr::CompactNoteCiphertextBytes::from_slice( + &self.encrypted_note().enc_ciphertext.as_ref()[..Pr::COMPACT_NOTE_SIZE], ) - .unwrap() + .expect("Pr::CompactNoteCiphertextBytes should have size Pr::COMPACT_NOTE_SIZE") } } /// A compact Action for light clients. #[derive(Clone)] -pub struct CompactAction { +pub struct CompactAction { nullifier: Nullifier, cmx: ExtractedNoteCommitment, ephemeral_key: EphemeralKeyBytes, - enc_ciphertext: P::CompactNoteCiphertextBytes, + enc_ciphertext: Pr::CompactNoteCiphertextBytes, } -impl fmt::Debug for CompactAction

{ +impl fmt::Debug for CompactAction { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "CompactAction") } } -impl From<&Action> for CompactAction

+impl From<&Action> for CompactAction where - Action: ShieldedOutput>, + Action: ShieldedOutput>, { - fn from(action: &Action) -> Self { + fn from(action: &Action) -> Self { CompactAction { nullifier: *action.nullifier(), cmx: *action.cmx(), @@ -67,7 +65,7 @@ where } } -impl ShieldedOutput> for CompactAction

{ +impl ShieldedOutput> for CompactAction { fn ephemeral_key(&self) -> EphemeralKeyBytes { EphemeralKeyBytes(self.ephemeral_key.0) } @@ -80,22 +78,22 @@ impl ShieldedOutput> for CompactAction

self.cmx.to_bytes() } - fn enc_ciphertext(&self) -> Option<&P::NoteCiphertextBytes> { + fn enc_ciphertext(&self) -> Option<&Pr::NoteCiphertextBytes> { None } - fn enc_ciphertext_compact(&self) -> P::CompactNoteCiphertextBytes { - P::CompactNoteCiphertextBytes::from_slice(self.enc_ciphertext.as_ref()).unwrap() + fn enc_ciphertext_compact(&self) -> Pr::CompactNoteCiphertextBytes { + self.enc_ciphertext } } -impl CompactAction

{ +impl CompactAction { /// Create a CompactAction from its constituent parts pub fn from_parts( nullifier: Nullifier, cmx: ExtractedNoteCommitment, ephemeral_key: EphemeralKeyBytes, - enc_ciphertext: P::CompactNoteCiphertextBytes, + enc_ciphertext: Pr::CompactNoteCiphertextBytes, ) -> Self { Self { nullifier, @@ -141,13 +139,13 @@ pub mod testing { /// Creates a fake `CompactAction` paying the given recipient the specified value. /// /// Returns the `CompactAction` and the new note. - pub fn fake_compact_action( + pub fn fake_compact_action( rng: &mut R, nf_old: Nullifier, recipient: Address, value: NoteValue, ovk: Option, - ) -> (CompactAction

, Note) { + ) -> (CompactAction, Note) { let rho = Rho::from_nf_old(nf_old); let rseed = { loop { @@ -160,9 +158,9 @@ pub mod testing { } }; let note = Note::from_parts(recipient, value, AssetBase::native(), rho, rseed).unwrap(); - let encryptor = NoteEncryption::>::new(ovk, note, [0u8; MEMO_SIZE]); + let encryptor = NoteEncryption::>::new(ovk, note, [0u8; MEMO_SIZE]); let cmx = ExtractedNoteCommitment::from(note.commitment()); - let ephemeral_key = OrchardDomain::

::epk_bytes(encryptor.epk()); + let ephemeral_key = OrchardDomain::::epk_bytes(encryptor.epk()); let enc_ciphertext = encryptor.encrypt_note_plaintext(); ( @@ -170,8 +168,8 @@ pub mod testing { nullifier: nf_old, cmx, ephemeral_key, - enc_ciphertext: P::CompactNoteCiphertextBytes::from_slice( - &enc_ciphertext.as_ref()[..P::COMPACT_NOTE_SIZE], + enc_ciphertext: Pr::CompactNoteCiphertextBytes::from_slice( + &enc_ciphertext.as_ref()[..Pr::COMPACT_NOTE_SIZE], ) .unwrap(), }, diff --git a/src/primitives/orchard_domain.rs b/src/primitives/orchard_domain.rs index da49de040..e9cfeb38e 100644 --- a/src/primitives/orchard_domain.rs +++ b/src/primitives/orchard_domain.rs @@ -1,7 +1,5 @@ //! Orchard-specific note encryption domain. -// Review hint: this file is largely derived from src/note_encryption.rs - use crate::{ action::Action, note::Rho, primitives::compact_action::CompactAction, primitives::orchard_primitives::OrchardPrimitives, @@ -9,23 +7,40 @@ use crate::{ /// Orchard-specific note encryption logic. #[derive(Debug, Clone)] -pub struct OrchardDomain { +pub struct OrchardDomain { /// A parameter needed to generate the nullifier. pub rho: Rho, - phantom: core::marker::PhantomData

, + phantom: core::marker::PhantomData, +} + +impl memuse::DynamicUsage for OrchardDomain { + fn dynamic_usage(&self) -> usize { + self.rho.dynamic_usage() + } + fn dynamic_usage_bounds(&self) -> (usize, Option) { + self.rho.dynamic_usage_bounds() + } } -impl OrchardDomain

{ +impl OrchardDomain { /// Constructs a domain that can be used to trial-decrypt this action's output note. - pub fn for_action(act: &Action) -> Self { + pub fn for_action(act: &Action) -> Self { Self { rho: act.rho(), phantom: Default::default(), } } + /// Constructs a domain that can be used to trial-decrypt a PCZT action's output note. + pub fn for_pczt_action(act: &crate::pczt::Action) -> Self { + Self { + rho: Rho::from_nf_old(act.spend().nullifier), + phantom: Default::default(), + } + } + /// Constructs a domain that can be used to trial-decrypt this compact action's output note. - pub fn for_compact_action(act: &CompactAction

) -> Self { + pub fn for_compact_action(act: &CompactAction) -> Self { Self { rho: act.rho(), phantom: Default::default(), @@ -34,7 +49,7 @@ impl OrchardDomain

{ /// Constructs a domain from a rho. #[cfg(test)] - pub fn for_rho(rho: Rho) -> Self { + pub(crate) fn for_rho(rho: Rho) -> Self { Self { rho, phantom: Default::default(), diff --git a/src/primitives/zcash_note_encryption_domain.rs b/src/primitives/zcash_note_encryption_domain.rs index aecd43a31..a53b8a0c5 100644 --- a/src/primitives/zcash_note_encryption_domain.rs +++ b/src/primitives/zcash_note_encryption_domain.rs @@ -36,8 +36,7 @@ const NOTE_VALUE_OFFSET: usize = NOTE_DIVERSIFIER_OFFSET + NOTE_DIVERSIFIER_SIZE const NOTE_RSEED_OFFSET: usize = NOTE_VALUE_OFFSET + NOTE_VALUE_SIZE; /// The size of a Vanilla compact note. -pub(super) const COMPACT_NOTE_SIZE_VANILLA: usize = - NOTE_VERSION_SIZE + NOTE_DIVERSIFIER_SIZE + NOTE_VALUE_SIZE + NOTE_RSEED_SIZE; +pub(super) const COMPACT_NOTE_SIZE_VANILLA: usize = NOTE_RSEED_OFFSET + NOTE_RSEED_SIZE; /// The size of the encoding of a ZSA asset. const ZSA_ASSET_SIZE: usize = 32; @@ -93,9 +92,9 @@ pub(super) fn parse_note_version(plaintext: &[u8]) -> Option { /// Parses the note plaintext (excluding the memo) and extracts the note and address if valid. /// Domain-specific requirements: /// - If the note version is 3, the `plaintext` must contain a valid encoding of a ZSA asset type. -pub(super) fn parse_note_plaintext_without_memo( +pub(super) fn parse_note_plaintext_without_memo( rho: Rho, - plaintext: &P::CompactNotePlaintextBytes, + plaintext: &Pr::CompactNotePlaintextBytes, get_validated_pk_d: F, ) -> Option<(Note, Address)> where @@ -125,7 +124,7 @@ where let pk_d = get_validated_pk_d(&diversifier)?; let recipient = Address::from_parts(diversifier, pk_d); - let asset = P::extract_asset(plaintext)?; + let asset = Pr::extract_asset(plaintext)?; let note = Option::from(Note::from_parts(recipient, value, asset, rho, rseed))?; Some((note, recipient)) @@ -147,7 +146,7 @@ pub(super) fn build_base_note_plaintext_bytes( np } -impl Domain for OrchardDomain

{ +impl Domain for OrchardDomain { type EphemeralSecretKey = EphemeralSecretKey; type EphemeralPublicKey = EphemeralPublicKey; type PreparedEphemeralPublicKey = PreparedEphemeralPublicKey; @@ -163,10 +162,10 @@ impl Domain for OrchardDomain

{ type ExtractedCommitmentBytes = [u8; 32]; type Memo = Memo; - type NotePlaintextBytes = P::NotePlaintextBytes; - type NoteCiphertextBytes = P::NoteCiphertextBytes; - type CompactNotePlaintextBytes = P::CompactNotePlaintextBytes; - type CompactNoteCiphertextBytes = P::CompactNoteCiphertextBytes; + type NotePlaintextBytes = Pr::NotePlaintextBytes; + type NoteCiphertextBytes = Pr::NoteCiphertextBytes; + type CompactNotePlaintextBytes = Pr::CompactNotePlaintextBytes; + type CompactNoteCiphertextBytes = Pr::CompactNoteCiphertextBytes; fn derive_esk(note: &Self::Note) -> Option { Some(note.esk()) @@ -205,8 +204,8 @@ impl Domain for OrchardDomain

{ secret.kdf_orchard(ephemeral_key) } - fn note_plaintext_bytes(note: &Self::Note, memo: &Self::Memo) -> P::NotePlaintextBytes { - P::build_note_plaintext_bytes(note, memo) + fn note_plaintext_bytes(note: &Self::Note, memo: &Self::Memo) -> Pr::NotePlaintextBytes { + Pr::build_note_plaintext_bytes(note, memo) } fn derive_ock( @@ -243,9 +242,9 @@ impl Domain for OrchardDomain

{ fn parse_note_plaintext_without_memo_ivk( &self, ivk: &Self::IncomingViewingKey, - plaintext: &P::CompactNotePlaintextBytes, + plaintext: &Pr::CompactNotePlaintextBytes, ) -> Option<(Self::Note, Self::Recipient)> { - parse_note_plaintext_without_memo::(self.rho, plaintext, |diversifier| { + parse_note_plaintext_without_memo::(self.rho, plaintext, |diversifier| { Some(DiversifiedTransmissionKey::derive(ivk, diversifier)) }) } @@ -253,16 +252,16 @@ impl Domain for OrchardDomain

{ fn parse_note_plaintext_without_memo_ovk( &self, pk_d: &Self::DiversifiedTransmissionKey, - plaintext: &P::CompactNotePlaintextBytes, + plaintext: &Pr::CompactNotePlaintextBytes, ) -> Option<(Self::Note, Self::Recipient)> { - parse_note_plaintext_without_memo::(self.rho, plaintext, |_| Some(*pk_d)) + parse_note_plaintext_without_memo::(self.rho, plaintext, |_| Some(*pk_d)) } fn split_plaintext_at_memo( &self, - plaintext: &P::NotePlaintextBytes, + plaintext: &Pr::NotePlaintextBytes, ) -> Option<(Self::CompactNotePlaintextBytes, Self::Memo)> { - let (compact, memo) = plaintext.as_ref().split_at(P::COMPACT_NOTE_SIZE); + let (compact, memo) = plaintext.as_ref().split_at(Pr::COMPACT_NOTE_SIZE); Some(( Self::CompactNotePlaintextBytes::from_slice(compact)?, memo.try_into().ok()?, @@ -279,7 +278,7 @@ impl Domain for OrchardDomain

{ } } -impl BatchDomain for OrchardDomain

{ +impl BatchDomain for OrchardDomain { fn batch_kdf<'a>( items: impl Iterator, &'a EphemeralKeyBytes)>, ) -> Vec> { diff --git a/src/test_vectors/zip32.rs b/src/test_vectors/zip32.rs index c388858f4..755294dc9 100644 --- a/src/test_vectors/zip32.rs +++ b/src/test_vectors/zip32.rs @@ -1,4 +1,4 @@ -// From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/orchard_zip32.py +//! Test vectors for Orchard ZIP 32 key derivation. pub(crate) struct TestVector { pub(crate) sk: [u8; 32], @@ -7,6 +7,7 @@ pub(crate) struct TestVector { pub(crate) fp: [u8; 32], } +// From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/orchard_zip32.py pub(crate) const TEST_VECTORS: &[TestVector] = &[ TestVector { sk: [ diff --git a/tests/builder.rs b/tests/builder.rs index 3cf55faa8..c9e2f853e 100644 --- a/tests/builder.rs +++ b/tests/builder.rs @@ -16,8 +16,8 @@ use rand::SeedableRng; use shardtree::{store::memory::MemoryShardStore, ShardTree}; use zcash_note_encryption::try_note_decryption; -pub fn verify_bundle( - bundle: &Bundle, +pub fn verify_bundle( + bundle: &Bundle, vk: &VerifyingKey, verify_proof: bool, ) { @@ -29,7 +29,7 @@ pub fn verify_bundle( for action in bundle.actions() { assert_eq!( action.authorization().version(), - &P::default_sighash_version() + &Pr::default_sighash_version() ); assert_eq!( action.rk().verify(&sighash, action.authorization().sig()), @@ -73,8 +73,8 @@ trait BundleOrchardFlavor: OrchardFlavor { } impl BundleOrchardFlavor for OrchardVanilla { - const DEFAULT_BUNDLE_TYPE: BundleType = BundleType::DEFAULT_VANILLA; - const SPENDS_DISABLED_FLAGS: Flags = Flags::SPENDS_DISABLED_WITHOUT_ZSA; + const DEFAULT_BUNDLE_TYPE: BundleType = BundleType::DEFAULT; + const SPENDS_DISABLED_FLAGS: Flags = Flags::SPENDS_DISABLED; } impl BundleOrchardFlavor for OrchardZSA { @@ -154,9 +154,9 @@ fn bundle_chain() -> ([u8; 32], [u8; 32]) { let mut builder = Builder::new( BundleType::Transactional { - // Intentionally testing with SPENDS_DISABLED_WITHOUT_ZSA as SPENDS_DISABLED_WITH_ZSA is already + // Intentionally testing with SPENDS_DISABLED as SPENDS_DISABLED_WITH_ZSA is already // tested above (for OrchardZSA case). Both should work. - flags: Flags::SPENDS_DISABLED_WITHOUT_ZSA, + flags: Flags::SPENDS_DISABLED, bundle_required: false, }, anchor,