From 2df66bf4fcf0bcbc1b55493da59b78236cd0cd64 Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Tue, 1 Oct 2024 16:59:09 +0200 Subject: [PATCH 01/48] Add split_notes HashMap into Builder --- src/builder.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/builder.rs b/src/builder.rs index bf49d2efd..b17be8b73 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -623,6 +623,7 @@ pub type UnauthorizedBundleWithMetadata = (UnauthorizedBundle, Bun pub struct Builder { spends: Vec, outputs: Vec, + split_notes: BTreeMap, burn: BTreeMap, bundle_type: BundleType, anchor: Anchor, @@ -634,6 +635,7 @@ impl Builder { Builder { spends: vec![], outputs: vec![], + split_notes: BTreeMap::new(), burn: BTreeMap::new(), bundle_type, anchor, @@ -695,6 +697,25 @@ impl Builder { Ok(()) } + /// Add a reference split note which could be used to create Actions. + pub fn add_split_note( + &mut self, + fvk: FullViewingKey, + note: Note, + merkle_path: MerklePath, + ) -> Result<(), SpendError> { + let spend = SpendInfo::new(fvk, note, merkle_path, false).ok_or(SpendError::FvkMismatch)?; + + // Consistency check: all anchors must be equal. + if !spend.has_matching_anchor(&self.anchor) { + return Err(SpendError::AnchorMismatch); + } + + self.split_notes.entry(note.asset()).or_insert(spend); + + Ok(()) + } + /// Add 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; @@ -770,6 +791,7 @@ impl Builder { self.bundle_type, self.spends, self.outputs, + self.split_notes, self.burn, ) } @@ -976,6 +998,7 @@ fn build_bundle( bundle_type: BundleType, spends: Vec, outputs: Vec, + split_notes: BTreeMap, burn: BTreeMap, finisher: impl FnOnce( Vec, // pre-actions @@ -1017,7 +1040,10 @@ fn build_bundle( |(asset, (spends, outputs))| { let num_asset_pre_actions = spends.len().max(outputs.len()); - let first_spend = spends.first().map(|(s, _)| s.clone()); + let mut first_spend = spends.first().map(|(s, _)| s.clone()); + if first_spend.is_none() { + first_spend = split_notes.get(&asset).cloned(); + } let mut indexed_spends = spends .into_iter() From 78d4e4617cd1bb58ca0dba6926cdfe1e2f7b1ea1 Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Thu, 3 Oct 2024 15:13:45 +0200 Subject: [PATCH 02/48] Add timelimit into Builder and Bundle --- benches/circuit.rs | 1 + benches/note_decryption.rs | 1 + src/builder.rs | 18 ++++++++++++++++-- src/bundle.rs | 3 +++ tests/builder.rs | 4 +++- tests/zsa.rs | 4 ++-- 6 files changed, 26 insertions(+), 5 deletions(-) diff --git a/benches/circuit.rs b/benches/circuit.rs index 579a34675..3b1343375 100644 --- a/benches/circuit.rs +++ b/benches/circuit.rs @@ -34,6 +34,7 @@ fn criterion_benchmark(c: &mut Criterion) { let mut builder = Builder::new( BundleType::DEFAULT_VANILLA, Anchor::from_bytes([0; 32]).unwrap(), + None, ); for _ in 0..num_recipients { builder diff --git a/benches/note_decryption.rs b/benches/note_decryption.rs index 8f2ce8373..e1a9a578c 100644 --- a/benches/note_decryption.rs +++ b/benches/note_decryption.rs @@ -53,6 +53,7 @@ fn bench_note_decryption(c: &mut Criterion) { let mut builder = Builder::new( BundleType::DEFAULT_VANILLA, Anchor::from_bytes([0; 32]).unwrap(), + None, ); // The builder pads to two actions, and shuffles their order. Add two recipients // so the first action is always decryptable. diff --git a/src/builder.rs b/src/builder.rs index b17be8b73..8be9477fc 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -159,6 +159,8 @@ pub enum BuildError { BurnDuplicateAsset, /// There is no available split note for this asset. NoSplitNoteAvailable, + /// Timelimit is set (thus it is an ActionGroup builder) but burn is not empty. + TimelimitSetAndBurnNotEmpty, } impl fmt::Display for BuildError { @@ -183,6 +185,9 @@ impl fmt::Display for BuildError { BurnZero => f.write_str("Burning is not possible for zero values"), BurnDuplicateAsset => f.write_str("Duplicate assets are not allowed when burning"), NoSplitNoteAvailable => f.write_str("No split note has been provided for this asset"), + TimelimitSetAndBurnNotEmpty => f.write_str( + "Timelimit is set (thus it is an ActionGroup builder) but burn is not empty", + ), } } } @@ -627,11 +632,13 @@ pub struct Builder { burn: BTreeMap, bundle_type: BundleType, anchor: Anchor, + // When timelimit is set, the Builder will build an ActionGroup (burn must be empty) + timelimit: Option, } impl Builder { /// Constructs a new empty builder for an Orchard bundle. - pub fn new(bundle_type: BundleType, anchor: Anchor) -> Self { + pub fn new(bundle_type: BundleType, anchor: Anchor, timelimit: Option) -> Self { Builder { spends: vec![], outputs: vec![], @@ -639,6 +646,7 @@ impl Builder { burn: BTreeMap::new(), bundle_type, anchor, + timelimit, } } @@ -788,6 +796,7 @@ impl Builder { bundle( rng, self.anchor, + self.timelimit, self.bundle_type, self.spends, self.outputs, @@ -933,6 +942,8 @@ fn pad_spend( /// /// The returned bundle will have no proof or signatures; these can be applied with /// [`Bundle::create_proof`] and [`Bundle::apply_signatures`] respectively. +#[allow(clippy::type_complexity)] +#[allow(clippy::too_many_arguments)] #[cfg(feature = "circuit")] pub fn bundle, FL: OrchardFlavor>( rng: impl RngCore, @@ -995,6 +1006,7 @@ pub fn bundle, FL: OrchardFlavor>( fn build_bundle( mut rng: R, anchor: Anchor, + timelimit: Option, bundle_type: BundleType, spends: Vec, outputs: Vec, @@ -1134,6 +1146,7 @@ fn build_bundle( flags, native_value_balance, burn_vec, + timelimit, bundle_meta, rng, ) @@ -1503,7 +1516,7 @@ pub mod testing { mut self, ) -> Bundle { let fvk = FullViewingKey::from(&self.sk); - let mut builder = Builder::new(BundleType::DEFAULT_ZSA, self.anchor); + let mut builder = Builder::new(BundleType::DEFAULT_ZSA, self.anchor, None); for (note, path) in self.notes.into_iter() { builder.add_spend(fvk.clone(), note, path).unwrap(); @@ -1635,6 +1648,7 @@ mod tests { let mut builder = Builder::new( BundleType::DEFAULT_VANILLA, EMPTY_ROOTS[MERKLE_DEPTH_ORCHARD].into(), + None, ); builder diff --git a/src/bundle.rs b/src/bundle.rs index edc19f3e5..1826d8de8 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -253,6 +253,7 @@ impl Bundle { value_balance: V, burn: Vec<(AssetBase, NoteValue)>, anchor: Anchor, + timelimit: Option, authorization: A, ) -> Self { Bundle { @@ -755,6 +756,7 @@ pub mod testing { balances.into_iter().sum::>().unwrap(), burn, anchor, + None, super::EffectsOnly, ) } @@ -787,6 +789,7 @@ pub mod testing { balances.into_iter().sum::>().unwrap(), burn, anchor, + None, Authorized { proof: Proof::new(fake_proof), binding_signature: VerBindingSig::new(P::default_sighash_version(), sk.sign(rng, &fake_sighash)), diff --git a/tests/builder.rs b/tests/builder.rs index 5d130d184..0b0dd6646 100644 --- a/tests/builder.rs +++ b/tests/builder.rs @@ -102,6 +102,7 @@ fn bundle_chain() -> ([u8; 32], [u8; 32]) { bundle_required: false, }, anchor, + None, ); let note_value = NoteValue::from_raw(5000); assert_eq!( @@ -160,6 +161,7 @@ fn bundle_chain() -> ([u8; 32], [u8; 32]) { bundle_required: false, }, anchor, + None, ); assert!(builder.add_spend(fvk.clone(), note, merkle_path).is_err()); @@ -169,7 +171,7 @@ fn bundle_chain() -> ([u8; 32], [u8; 32]) { let (shielded_bundle, orchard_digest_2): (Bundle<_, i64, FL>, [u8; 32]) = { let (merkle_path, anchor) = build_merkle_path(¬e); - let mut builder = Builder::new(FL::DEFAULT_BUNDLE_TYPE, anchor); + let mut builder = Builder::new(FL::DEFAULT_BUNDLE_TYPE, anchor, None); assert_eq!(builder.add_spend(fvk, note, merkle_path), Ok(())); assert_eq!( builder.add_output( diff --git a/tests/zsa.rs b/tests/zsa.rs index 66017e4b9..92c5221e0 100644 --- a/tests/zsa.rs +++ b/tests/zsa.rs @@ -203,7 +203,7 @@ fn create_native_note(keys: &Keychain) -> Note { // Use the empty tree. let anchor = MerkleHashOrchard::empty_root(32.into()).into(); - let mut builder = Builder::new(BundleType::Coinbase, anchor); + let mut builder = Builder::new(BundleType::Coinbase, anchor, None); assert_eq!( builder.add_output( None, @@ -259,7 +259,7 @@ fn build_and_verify_bundle( ) -> Result<(), String> { let rng = OsRng; let shielded_bundle: Bundle<_, i64, OrchardZSA> = { - let mut builder = Builder::new(BundleType::DEFAULT_ZSA, anchor); + let mut builder = Builder::new(BundleType::DEFAULT_ZSA, anchor, None); spends .iter() From 8771671e88caa723a5bb91b7e1cca7f2779d1c0d Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Thu, 3 Oct 2024 16:19:27 +0200 Subject: [PATCH 03/48] Timelimit is u32 and not u64 --- src/builder.rs | 6 +++--- src/bundle.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 8be9477fc..f0459f501 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -633,12 +633,12 @@ pub struct Builder { bundle_type: BundleType, anchor: Anchor, // When timelimit is set, the Builder will build an ActionGroup (burn must be empty) - timelimit: Option, + timelimit: Option, } impl Builder { /// Constructs a new empty builder for an Orchard bundle. - pub fn new(bundle_type: BundleType, anchor: Anchor, timelimit: Option) -> Self { + pub fn new(bundle_type: BundleType, anchor: Anchor, timelimit: Option) -> Self { Builder { spends: vec![], outputs: vec![], @@ -1006,7 +1006,7 @@ pub fn bundle, FL: OrchardFlavor>( fn build_bundle( mut rng: R, anchor: Anchor, - timelimit: Option, + timelimit: Option, bundle_type: BundleType, spends: Vec, outputs: Vec, diff --git a/src/bundle.rs b/src/bundle.rs index 1826d8de8..b39f06e59 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -253,7 +253,7 @@ impl Bundle { value_balance: V, burn: Vec<(AssetBase, NoteValue)>, anchor: Anchor, - timelimit: Option, + timelimit: Option, authorization: A, ) -> Self { Bundle { From 95930db38a327015da6db1a3139f2db6e012bdff Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Thu, 3 Oct 2024 16:20:06 +0200 Subject: [PATCH 04/48] Add sighash evaluation for action groups --- src/bundle.rs | 12 ++++++-- src/bundle/commitments.rs | 63 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/src/bundle.rs b/src/bundle.rs index b39f06e59..67037a0fc 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -23,7 +23,10 @@ use memuse::DynamicUsage; use crate::{ action::Action, address::Address, - bundle::commitments::{hash_bundle_auth_data, hash_bundle_txid_data}, + bundle::commitments::{ + hash_action_groups_txid_data, hash_bundle_auth_data, hash_bundle_txid_data, + }, + circuit::{Instance, Proof, VerifyingKey}, keys::{IncomingViewingKey, OutgoingViewingKey, PreparedIncomingViewingKey}, note::{AssetBase, Note}, orchard_sighash_versioning::{OrchardSighashVersion, VerBindingSig, VerSpendAuthSig}, @@ -499,7 +502,12 @@ impl, P: OrchardPrimitives> Bundle BundleCommitment { - BundleCommitment(hash_bundle_txid_data(self)) + match self.timelimit { + Some(_) => { + BundleCommitment(hash_action_groups_txid_data(vec![self], self.value_balance)) + } + None => BundleCommitment(hash_bundle_txid_data(self)), + } } /// Returns the transaction binding validating key for this bundle. diff --git a/src/bundle/commitments.rs b/src/bundle/commitments.rs index dc08357c5..6e912d20a 100644 --- a/src/bundle/commitments.rs +++ b/src/bundle/commitments.rs @@ -52,6 +52,69 @@ pub(crate) fn hash_bundle_txid_data, P: Or P::hash_bundle_txid_data(bundle) } +/// TODO update description +/// Write disjoint parts of each ActionGroup as 3 separate hashes: +/// * \[(nullifier, cmx, ephemeral_key, enc_ciphertext\[..52\])*\] personalized +/// with ZCASH_ORCHARD_ACTIONS_COMPACT_HASH_PERSONALIZATION +/// * \[enc_ciphertext\[52..564\]*\] (memo ciphertexts) personalized +/// with ZCASH_ORCHARD_ACTIONS_MEMOS_HASH_PERSONALIZATION +/// * \[(cv, rk, enc_ciphertext\[564..\], out_ciphertext)*\] personalized +/// with ZCASH_ORCHARD_ACTIONS_NONCOMPACT_HASH_PERSONALIZATION +/// as defined in [ZIP-244: Transaction Identifier Non-Malleability][zip244] +/// +/// Then, hash these together along with (flags, anchor_orchard, timelimit). +/// +/// The final hash is personalized with ZCASH_ORCHARD_HASH_PERSONALIZATION. +/// +/// [zip244]: https://zips.z.cash/zip-0244 +/// [zip226]: https://zips.z.cash/zip-0226 (for ZSA burn field hashing) +pub(crate) fn hash_action_groups_txid_data< + A: Authorization, + V: Copy + Into, + D: OrchardDomainCommon + OrchardHash, +>( + bundles: Vec<&Bundle>, + value_balance: V, +) -> Blake2bHash { + let mut h = hasher(ZCASH_ORCHARD_HASH_PERSONALIZATION); + + for bundle in bundles { + let mut agh = hasher(ZCASH_ORCHARD_ACTION_GROUP_HASH_PERSONALIZATION); + let mut ch = hasher(ZCASH_ORCHARD_ACTIONS_COMPACT_HASH_PERSONALIZATION); + let mut mh = hasher(ZCASH_ORCHARD_ACTIONS_MEMOS_HASH_PERSONALIZATION); + let mut nh = hasher(ZCASH_ORCHARD_ACTIONS_NONCOMPACT_HASH_PERSONALIZATION); + for action in bundle.actions().iter() { + ch.update(&action.nullifier().to_bytes()); + ch.update(&action.cmx().to_bytes()); + ch.update(&action.encrypted_note().epk_bytes); + ch.update(&action.encrypted_note().enc_ciphertext.as_ref()[..D::COMPACT_NOTE_SIZE]); + + mh.update( + &action.encrypted_note().enc_ciphertext.as_ref() + [D::COMPACT_NOTE_SIZE..D::COMPACT_NOTE_SIZE + MEMO_SIZE], + ); + + nh.update(&action.cv_net().to_bytes()); + nh.update(&<[u8; 32]>::from(action.rk())); + nh.update( + &action.encrypted_note().enc_ciphertext.as_ref() + [D::COMPACT_NOTE_SIZE + MEMO_SIZE..], + ); + nh.update(&action.encrypted_note().out_ciphertext); + } + agh.update(ch.finalize().as_bytes()); + agh.update(mh.finalize().as_bytes()); + agh.update(nh.finalize().as_bytes()); + agh.update(&[bundle.flags().to_byte()]); + agh.update(&bundle.anchor().to_bytes()); + agh.update(&bundle.timelimit().unwrap().to_le_bytes()); + h.update(agh.finalize().as_bytes()); + } + + h.update(&value_balance.into().to_le_bytes()); + h.finalize() +} + /// Construct the commitment for the absent bundle as defined in /// [ZIP-244: Transaction Identifier Non-Malleability][zip244] /// From 15f56f31309d8243eec48ef2a4879fb60f5930d0 Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Thu, 10 Oct 2024 16:11:40 +0200 Subject: [PATCH 05/48] Add ActionGroup tests --- tests/builder.rs | 18 ++++ tests/zsa.rs | 233 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 246 insertions(+), 5 deletions(-) diff --git a/tests/builder.rs b/tests/builder.rs index 0b0dd6646..bc6d4a0e8 100644 --- a/tests/builder.rs +++ b/tests/builder.rs @@ -42,6 +42,24 @@ pub fn verify_bundle( ); } +// Verify an action group +// - verify the proof +// - verify the signature on each action +// - do not verify the binding signature because for some asset, the value balance could be not zero +pub fn verify_action_group( + bundle: &Bundle, + vk: &VerifyingKey, + verify_proof: bool, +) { + if verify_proof { + assert!(matches!(bundle.verify_proof(vk), Ok(()))); + } + let sighash: [u8; 32] = bundle.commitment().into(); + for action in bundle.actions() { + assert_eq!(action.rk().verify(&sighash, action.authorization()), Ok(())); + } +} + pub fn build_merkle_path(note: &Note) -> (MerklePath, Anchor) { // Use the tree with a single leaf. let cmx: ExtractedNoteCommitment = note.commitment().into(); diff --git a/tests/zsa.rs b/tests/zsa.rs index 92c5221e0..00a6f4e22 100644 --- a/tests/zsa.rs +++ b/tests/zsa.rs @@ -1,8 +1,15 @@ mod builder; -use crate::builder::verify_bundle; -use incrementalmerkletree::{Hashable, Marking, Retention}; -use nonempty::NonEmpty; +use crate::builder::{verify_action_group, verify_bundle}; +use bridgetree::BridgeTree; +use incrementalmerkletree::Hashable; +use orchard::bundle::Authorized; +use orchard::issuance::{verify_issue_bundle, IssueBundle, IssueInfo, Signed, Unauthorized}; +use orchard::keys::{IssuanceAuthorizingKey, IssuanceValidatingKey}; +use orchard::note::{AssetBase, ExtractedNoteCommitment}; + +use orchard::bundle::burn_validation::BurnError::NativeAsset; +use orchard::tree::{MerkleHashOrchard, MerklePath}; use orchard::{ builder::{Builder, BundleType}, bundle::Authorized, @@ -143,12 +150,45 @@ fn build_merkle_paths(notes: Vec<&Note>) -> (Vec, Anchor) { (merkle_paths, root.into()) } +fn build_merkle_paths(notes: Vec<&Note>) -> (Vec, Anchor) { + let mut tree = BridgeTree::::new(100); + + let mut commitments = vec![]; + let mut positions = vec![]; + + // Add leaves + for note in notes { + let cmx: ExtractedNoteCommitment = note.commitment().into(); + commitments.push(cmx); + let leaf = MerkleHashOrchard::from_cmx(&cmx); + tree.append(leaf); + positions.push(tree.mark().unwrap()); + } + + let root = tree.root(0).unwrap(); + let anchor = root.into(); + + // Calculate paths + let mut merkle_paths = vec![]; + for (position, commitment) in positions.iter().zip(commitments.iter()) { + let auth_path = tree.witness(*position, 0).unwrap(); + let merkle_path = MerklePath::from_parts( + u64::from(*position).try_into().unwrap(), + auth_path[..].try_into().unwrap(), + ); + merkle_paths.push(merkle_path.clone()); + assert_eq!(anchor, merkle_path.root(*commitment)); + } + + (merkle_paths, anchor) +} + + fn issue_zsa_notes( asset_descr: &[u8], keys: &Keychain, first_nullifier: &Nullifier, -) -> (Note, Note, Note) { - let mut rng = OsRng; +) -> (Note, Note, Note) { let mut rng = OsRng; // Create an issuance bundle let asset_desc_hash = compute_asset_desc_hash(&NonEmpty::from_slice(asset_descr).unwrap()); let (mut awaiting_nullifier_bundle, _) = IssueBundle::new( @@ -293,6 +333,46 @@ fn build_and_verify_bundle( Ok(()) } +fn build_and_verify_action_group( + spends: Vec<&TestSpendInfo>, + outputs: Vec, + split_notes: Vec<&TestSpendInfo>, + anchor: Anchor, + timelimit: u32, + expected_num_actions: usize, + keys: &Keychain, +) -> Result, String> { + let rng = OsRng; + let shielded_bundle: Bundle<_, i64, OrchardZSA> = { + let mut builder = Builder::new(BundleType::DEFAULT_ZSA, anchor, Some(timelimit)); + + spends + .iter() + .try_for_each(|spend| { + builder.add_spend(keys.fvk().clone(), spend.note, spend.merkle_path().clone()) + }) + .map_err(|err| err.to_string())?; + outputs + .iter() + .try_for_each(|output| { + builder.add_output(None, keys.recipient, output.value, output.asset, None) + }) + .map_err(|err| err.to_string())?; + split_notes + .iter() + .try_for_each(|spend| { + builder.add_split_note(keys.fvk().clone(), spend.note, spend.merkle_path().clone()) + }) + .map_err(|err| err.to_string())?; + build_and_sign_bundle(builder, rng, keys.pk(), keys.sk()) + }; + + verify_action_group(&shielded_bundle, &keys.vk, true); + assert_eq!(shielded_bundle.actions().len(), expected_num_actions); + assert!(verify_unique_spent_nullifiers(&shielded_bundle)); + Ok(shielded_bundle) +} + fn verify_unique_spent_nullifiers(bundle: &Bundle) -> bool { let mut seen = HashSet::new(); bundle @@ -619,3 +699,146 @@ fn zsa_issue_and_transfer() { Err(error) => assert_eq!(error, "Burning is not possible for zero values"), } } + +/// Create several swap orders and combine them to create a SwapBundle +#[test] +fn swap_order_and_swap_bundle() { + // --------------------------- Setup ----------------------------------------- + // Create notes for user1 + let keys1 = prepare_keys(); + + let asset_descr1 = "zsa_asset1"; + let (asset1_note1, asset1_note2) = issue_zsa_notes(asset_descr1, &keys1); + + let user1_native_note1 = create_native_note(&keys1); + let user1_native_note2 = create_native_note(&keys1); + + // Create notes for user2 + let keys2 = prepare_keys(); + + let asset_descr2 = "zsa_asset2"; + let (asset2_note1, asset2_note2) = issue_zsa_notes(asset_descr2, &keys2); + + let user2_native_note1 = create_native_note(&keys2); + let user2_native_note2 = create_native_note(&keys2); + + // Create Merkle tree with all notes + let (merkle_paths, anchor) = build_merkle_paths(vec![ + &asset1_note1, + &asset1_note2, + &user1_native_note1, + &user1_native_note2, + &asset2_note1, + &asset2_note2, + &user2_native_note1, + &user2_native_note2, + ]); + + assert_eq!(merkle_paths.len(), 8); + let merkle_path_asset1_note1 = merkle_paths[0].clone(); + let merkle_path_asset1_note2 = merkle_paths[1].clone(); + let merkle_path_user1_native_note1 = merkle_paths[2].clone(); + let merkle_path_user1_native_note2 = merkle_paths[3].clone(); + let merkle_path_asset2_note1 = merkle_paths[4].clone(); + let merkle_path_asset2_note2 = merkle_paths[5].clone(); + let merkle_path_user2_native_note1 = merkle_paths[6].clone(); + let merkle_path_user2_native_note2 = merkle_paths[7].clone(); + + // Create TestSpendInfo + let asset1_spend1 = TestSpendInfo { + note: asset1_note1, + merkle_path: merkle_path_asset1_note1, + }; + let asset1_spend2 = TestSpendInfo { + note: asset1_note2, + merkle_path: merkle_path_asset1_note2, + }; + let user1_native_note1_spend = TestSpendInfo { + note: user1_native_note1, + merkle_path: merkle_path_user1_native_note1, + }; + let user1_native_note2_spend = TestSpendInfo { + note: user1_native_note2, + merkle_path: merkle_path_user1_native_note2, + }; + let asset2_spend1 = TestSpendInfo { + note: asset2_note1, + merkle_path: merkle_path_asset2_note1, + }; + let asset2_spend2 = TestSpendInfo { + note: asset2_note2, + merkle_path: merkle_path_asset2_note2, + }; + let user2_native_note1_spend = TestSpendInfo { + note: user2_native_note1, + merkle_path: merkle_path_user2_native_note1, + }; + let user2_native_note2_spend = TestSpendInfo { + note: user2_native_note2, + merkle_path: merkle_path_user2_native_note2, + }; + + // --------------------------- Tests ----------------------------------------- + + // 1. Create and verify ActionGroup for user1 + let action_group1 = build_and_verify_action_group( + vec![ + &asset1_spend1, + &asset1_spend2, + &user1_native_note1_spend, + &user1_native_note2_spend, + ], + vec![ + TestOutputInfo { + value: NoteValue::from_raw(10), + asset: asset1_note1.asset(), + }, + TestOutputInfo { + value: NoteValue::from_raw(5), + asset: asset2_note1.asset(), + }, + TestOutputInfo { + value: NoteValue::from_raw(95), + asset: AssetBase::native(), + }, + ], + vec![&asset2_spend1], + anchor, + 0, + 5, + &keys1, + ) + .unwrap(); + + // 2. Create and verify ActionGroup for user2 + let action_group2 = build_and_verify_action_group( + vec![ + &asset2_spend1, + &asset2_spend2, + &user2_native_note1_spend, + &user2_native_note2_spend, + ], + vec![ + TestOutputInfo { + value: NoteValue::from_raw(10), + asset: asset2_note1.asset(), + }, + TestOutputInfo { + value: NoteValue::from_raw(5), + asset: asset1_note1.asset(), + }, + TestOutputInfo { + value: NoteValue::from_raw(95), + asset: AssetBase::native(), + }, + ], + vec![&asset1_spend1], + anchor, + 0, + 5, + &keys2, + ) + .unwrap(); + + // 3. Create a SwapBundle from the two previous ActionGroups +} From 5965687cc7f555545017537a51964cf5b10470f4 Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Thu, 10 Oct 2024 16:43:29 +0200 Subject: [PATCH 06/48] Add matcher action group for fees in tests --- tests/zsa.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/tests/zsa.rs b/tests/zsa.rs index 00a6f4e22..a9970ad2c 100644 --- a/tests/zsa.rs +++ b/tests/zsa.rs @@ -722,6 +722,9 @@ fn swap_order_and_swap_bundle() { let user2_native_note1 = create_native_note(&keys2); let user2_native_note2 = create_native_note(&keys2); + // Create matcher keys + let matcher_keys = prepare_keys(); + // Create Merkle tree with all notes let (merkle_paths, anchor) = build_merkle_paths(vec![ &asset1_note1, @@ -840,5 +843,20 @@ fn swap_order_and_swap_bundle() { ) .unwrap(); - // 3. Create a SwapBundle from the two previous ActionGroups + // 3. Matcher fees action group + let action_group_matcher = build_and_verify_action_group( + vec![], + vec![TestOutputInfo { + value: NoteValue::from_raw(10), + asset: AssetBase::native(), + }], + vec![], + anchor, + 0, + 2, + &matcher_keys, + ) + .unwrap(); + + // 4. Create a SwapBundle from the three previous ActionGroups } From 69556e7143483e1ba3742983decff0c58338d430 Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Fri, 11 Oct 2024 13:35:19 +0200 Subject: [PATCH 07/48] Add SwapBundle struct --- src/bundle.rs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/bundle.rs b/src/bundle.rs index 67037a0fc..e0aa23118 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -29,9 +29,10 @@ use crate::{ circuit::{Instance, Proof, VerifyingKey}, keys::{IncomingViewingKey, OutgoingViewingKey, PreparedIncomingViewingKey}, note::{AssetBase, Note}, - orchard_sighash_versioning::{OrchardSighashVersion, VerBindingSig, VerSpendAuthSig}, - primitives::redpallas::{self, Binding}, - primitives::{OrchardDomain, OrchardPrimitives}, + note_encryption::{OrchardDomain, OrchardDomainCommon}, + orchard_flavor::OrchardFlavor, + orchard_flavor::OrchardZSA, + primitives::redpallas::{self, Binding, SpendAuth}, tree::Anchor, value::{NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum}, Proof, @@ -469,6 +470,21 @@ impl Bundle { } } +/// A swap bundle to be applied to the ledger. +#[derive(Clone, Debug)] +pub struct SwapBundle { + /// The list of action groups that make up this swap bundle. + action_groups: Vec>, + /// Orchard-specific transaction-level flags for this swap. + flags: Flags, + /// The net value moved out of this swap. + /// + /// This is the sum of Orchard spends minus the sum of Orchard outputs. + value_balance: V, + /// The binding signature for this swap. + binding_signature: redpallas::Signature, +} + pub(crate) fn derive_bvk, P: OrchardPrimitives>( actions: &NonEmpty>, value_balance: V, From e92b55699b234edd15d53e23ca0205283efdc4b4 Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Mon, 14 Oct 2024 21:18:24 +0200 Subject: [PATCH 08/48] Add SwapBundle functionalities --- src/builder.rs | 103 +++++++++++++++++++++++++++++++++++++++++++- src/bundle.rs | 109 +++++++++++++++++++++++++++++++++++++++++++++++ src/value.rs | 10 +++++ tests/builder.rs | 27 +++++++++++- tests/zsa.rs | 32 +++++++++++--- 5 files changed, 272 insertions(+), 9 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index f0459f501..6309bd934 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -11,6 +11,8 @@ use rand::{prelude::SliceRandom, CryptoRng, RngCore}; use zcash_note_encryption::NoteEncryption; +use crate::builder::BuildError::{BurnNative, BurnZero}; +use crate::bundle::ActionGroupAuthorized; use crate::{ address::Address, builder::BuildError::{BurnNative, BurnZero}, @@ -1273,6 +1275,17 @@ impl InProgressSignatures for PartiallyAuthorized { type SpendAuth = MaybeSigned; } +/// Marker for a partially-authorized bundle, in the process of being signed. +#[derive(Debug)] +pub struct ActionGroupPartiallyAuthorized { + bsk: redpallas::SigningKey, + sighash: [u8; 32], +} + +impl InProgressSignatures for ActionGroupPartiallyAuthorized { + type SpendAuth = MaybeSigned; +} + /// A heisen[`Signature`] for a particular [`Action`]. /// /// [`Signature`]: VerSpendAuthSig @@ -1331,6 +1344,35 @@ impl Bundle Bundle, V, D> { + /// Loads the sighash into this bundle, preparing it for signing. + /// + /// This API ensures that all signatures are created over the same sighash. + pub fn prepare_for_action_group( + self, + mut rng: R, + sighash: [u8; 32], + ) -> Bundle, V, D> { + self.map_authorization( + &mut rng, + |rng, _, SigningMetadata { dummy_ask, parts }| { + // We can create signatures for dummy spends immediately. + dummy_ask + .map(|ask| ask.randomize(&parts.alpha).sign(rng, &sighash)) + .map(MaybeSigned::Signature) + .unwrap_or(MaybeSigned::SigningMetadata(parts)) + }, + |_rng, auth| InProgress { + proof: auth.proof, + sigs: ActionGroupPartiallyAuthorized { + bsk: auth.sigs.bsk, + sighash, + }, + }, + ) + } +} + impl Bundle, V, P> { /// Applies signatures to this bundle, in order to authorize it. /// @@ -1351,8 +1393,49 @@ impl Bundle, V, P> { } } +impl Bundle, V, D> { + /// Applies signatures to this action group, in order to authorize it. + pub fn apply_signatures_for_action_group( + self, + mut rng: R, + sighash: [u8; 32], + signing_keys: &[SpendAuthorizingKey], + ) -> Result, BuildError> { + signing_keys + .iter() + .fold( + self.prepare_for_action_group(&mut rng, sighash), + |partial, ask| partial.sign(&mut rng, ask), + ) + .finalize() + } +} + +impl + Bundle, V, D> +{ + /// Signs this action group with the given [`SpendAuthorizingKey`]. + /// + /// This will apply signatures for all notes controlled by this spending key. + pub fn sign(self, mut rng: R, ask: &SpendAuthorizingKey) -> Self { + let expected_ak = ask.into(); + self.map_authorization( + &mut rng, + |rng, partial, maybe| match maybe { + MaybeSigned::SigningMetadata(parts) if parts.ak == expected_ak => { + MaybeSigned::Signature( + ask.randomize(&parts.alpha).sign(rng, &partial.sigs.sighash), + ) + } + s => s, + }, + |_, partial| partial, + ) + } +} + impl - Bundle, V, P> +Bundle, V, P> { /// Signs this bundle with the given [`SpendAuthorizingKey`]. /// @@ -1432,6 +1515,24 @@ impl Bundle, V, } } +impl Bundle, V, D> { + /// 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> { + self.try_map_authorization( + &mut (), + |_, _, maybe| maybe.finalize(), + |_, partial| { + Ok(ActionGroupAuthorized::from_parts( + partial.proof, + partial.sigs.bsk, + )) + }, + ) + } +} + /// A trait that provides a minimized view of an Orchard input suitable for use in /// fee and change calculation. pub trait InputView { diff --git a/src/bundle.rs b/src/bundle.rs index e0aa23118..2fb58b986 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -14,6 +14,7 @@ use core::fmt; use alloc::collections::BTreeMap; use blake2b_simd::Hash as Blake2bHash; +use k256::elliptic_curve::rand_core::{CryptoRng, RngCore}; use nonempty::NonEmpty; use zcash_note_encryption::{try_note_decryption, try_output_recovery_with_ovk}; @@ -543,6 +544,82 @@ impl Authorization for EffectsOnly { type SpendAuth = (); } +/// A swap bundle to be applied to the ledger. +#[derive(Clone, Debug)] +pub struct SwapBundle { + /// The list of action groups that make up this swap bundle. + action_groups: Vec>, + /// The net value moved out of this swap. + /// + /// This is the sum of Orchard spends minus the sum of Orchard outputs. + value_balance: V, + /// The binding signature for this swap. + binding_signature: redpallas::Signature, +} + +impl + std::iter::Sum> SwapBundle { + /// Constructs a `Bundle` from its constituent parts. + pub fn new( + rng: R, + action_groups: Vec>, + ) -> Self { + let value_balance = action_groups.iter().map(|a| *a.value_balance()).sum(); + let bsk = action_groups + .iter() + .map(|a| ValueCommitTrapdoor::from_bsk(a.authorization().bsk)) + .sum::() + .into_bsk(); + let sighash = BundleCommitment(hash_action_groups_txid_data( + action_groups.iter().collect(), + value_balance, + )) + .into(); + let binding_signature = bsk.sign(rng, &sighash); + SwapBundle { + action_groups, + value_balance, + binding_signature, + } + } +} + +impl SwapBundle { + /// Returns the list of action groups that make up this swapbundle. + pub fn action_groups(&self) -> &Vec> { + &self.action_groups + } + + /// Returns the binding signature of this swap bundle. + pub fn binding_signature(&self) -> &redpallas::Signature { + &self.binding_signature + } +} + +impl> SwapBundle { + /// Computes a commitment to the effects of this swap bundle, suitable for inclusion + /// within a transaction ID. + pub fn commitment(&self) -> BundleCommitment { + BundleCommitment(hash_action_groups_txid_data( + self.action_groups.iter().collect(), + self.value_balance, + )) + } + + /// Returns the transaction binding validating key for this swap bundle. + pub fn binding_validating_key(&self) -> redpallas::VerificationKey { + let actions = self + .action_groups + .iter() + .flat_map(|ag| ag.actions()) + .collect::>(); + derive_bvk( + actions, + self.value_balance, + std::iter::empty::<(AssetBase, NoteValue)>(), + ) + } +} + /// Authorizing data for a bundle of actions, ready to be committed to the ledger. #[derive(Debug, Clone)] pub struct Authorized { @@ -588,6 +665,38 @@ impl Bundle { BundleAuthorizingCommitment(hash_bundle_auth_data(self, sighash_version_map)) } + /// Verifies the proof for this bundle. + pub fn verify_proof(&self, vk: &VerifyingKey) -> Result<(), halo2_proofs::plonk::Error> { + self.authorization() + .proof() + .verify(vk, &self.to_instances()) + } +} + +/// Authorizing data for an action group, ready to be sent to the matcher. +#[derive(Debug, Clone)] +pub struct ActionGroupAuthorized { + proof: Proof, + bsk: redpallas::SigningKey, +} + +impl Authorization for ActionGroupAuthorized { + type SpendAuth = redpallas::Signature; +} + +impl ActionGroupAuthorized { + /// Constructs the authorizing data for a bundle of actions from its constituent parts. + pub fn from_parts(proof: Proof, bsk: redpallas::SigningKey) -> Self { + ActionGroupAuthorized { proof, bsk } + } + + /// Return the proof component of the authorizing data. + pub fn proof(&self) -> &Proof { + &self.proof + } +} + +impl Bundle { /// Verifies the proof for this bundle. #[cfg(feature = "circuit")] pub fn verify_proof(&self, vk: &VerifyingKey) -> Result<(), halo2_proofs::plonk::Error> { diff --git a/src/value.rs b/src/value.rs index 54c377af0..367dff625 100644 --- a/src/value.rs +++ b/src/value.rs @@ -328,6 +328,12 @@ impl Add<&ValueCommitTrapdoor> for ValueCommitTrapdoor { } } +impl Sum for ValueCommitTrapdoor { + fn sum>(iter: I) -> Self { + iter.fold(ValueCommitTrapdoor::zero(), |acc, cv| acc + &cv) + } +} + impl<'a> Sum<&'a ValueCommitTrapdoor> for ValueCommitTrapdoor { fn sum>(iter: I) -> Self { iter.fold(ValueCommitTrapdoor::zero(), |acc, cv| acc + cv) @@ -349,6 +355,10 @@ impl ValueCommitTrapdoor { // TODO: impl From for redpallas::SigningKey. self.0.to_repr().try_into().unwrap() } + + pub(crate) fn from_bsk(bsk: redpallas::SigningKey) -> Self { + ValueCommitTrapdoor(pallas::Scalar::from_repr(bsk.into()).unwrap()) + } } /// A commitment to a [`ValueSum`]. diff --git a/tests/builder.rs b/tests/builder.rs index bc6d4a0e8..b1009d9de 100644 --- a/tests/builder.rs +++ b/tests/builder.rs @@ -1,7 +1,7 @@ use incrementalmerkletree::{Hashable, Marking, Retention}; use orchard::{ builder::{Builder, BundleType}, - bundle::{Authorized, Flags}, + bundle::{ActionGroupAuthorized, Authorized, Flags, SwapBundle}, circuit::{ProvingKey, VerifyingKey}, keys::{FullViewingKey, PreparedIncomingViewingKey, Scope, SpendAuthorizingKey, SpendingKey}, note::{AssetBase, ExtractedNoteCommitment}, @@ -42,12 +42,35 @@ pub fn verify_bundle( ); } +pub fn verify_swap_bundle(swap_bundle: &SwapBundle, vks: Vec<&VerifyingKey>) { + assert_eq!(vks.len(), swap_bundle.action_groups().len()); + for (action_group, vk) in swap_bundle.action_groups().iter().zip(vks.iter()) { + assert!(matches!(action_group.verify_proof(vk), Ok(()))); + let action_group_sighash: [u8; 32] = action_group.commitment().into(); + for action in action_group.actions() { + assert_eq!( + action + .rk() + .verify(&action_group_sighash, action.authorization()), + Ok(()) + ); + } + } + + let sighash = swap_bundle.commitment().into(); + let bvk = swap_bundle.binding_validating_key(); + assert_eq!( + bvk.verify(&sighash, swap_bundle.binding_signature()), + Ok(()) + ); +} + // Verify an action group // - verify the proof // - verify the signature on each action // - do not verify the binding signature because for some asset, the value balance could be not zero pub fn verify_action_group( - bundle: &Bundle, + bundle: &Bundle, vk: &VerifyingKey, verify_proof: bool, ) { diff --git a/tests/zsa.rs b/tests/zsa.rs index a9970ad2c..7eab8d455 100644 --- a/tests/zsa.rs +++ b/tests/zsa.rs @@ -1,14 +1,13 @@ mod builder; -use crate::builder::{verify_action_group, verify_bundle}; +use crate::builder::{verify_action_group, verify_bundle, verify_swap_bundle}; use bridgetree::BridgeTree; use incrementalmerkletree::Hashable; -use orchard::bundle::Authorized; +use orchard::bundle::{ActionGroupAuthorized, Authorized, SwapBundle}; use orchard::issuance::{verify_issue_bundle, IssueBundle, IssueInfo, Signed, Unauthorized}; use orchard::keys::{IssuanceAuthorizingKey, IssuanceValidatingKey}; use orchard::note::{AssetBase, ExtractedNoteCommitment}; -use orchard::bundle::burn_validation::BurnError::NativeAsset; use orchard::tree::{MerkleHashOrchard, MerklePath}; use orchard::{ builder::{Builder, BundleType}, @@ -104,12 +103,27 @@ fn build_and_sign_bundle( .unwrap() } +fn build_and_sign_action_group( + builder: Builder, + mut rng: OsRng, + pk: &ProvingKey, + sk: &SpendingKey, +) -> Bundle { + let unauthorized = builder.build(&mut rng).unwrap().unwrap().0; + let sighash = unauthorized.commitment().into(); + let proven = unauthorized.create_proof(pk, &mut rng).unwrap(); + proven + .apply_signatures_for_action_group(rng, sighash, &[SpendAuthorizingKey::from(sk)]) + .unwrap() +} + fn build_merkle_paths(notes: Vec<&Note>) -> (Vec, Anchor) { let mut tree: ShardTree, 32, 16> = ShardTree::new(MemoryShardStore::empty(), 100); let max_index = (notes.len() as u32) - 1; + let mut commitments = vec![]; let mut positions = vec![]; @@ -341,7 +355,7 @@ fn build_and_verify_action_group( timelimit: u32, expected_num_actions: usize, keys: &Keychain, -) -> Result, String> { +) -> Result, String> { let rng = OsRng; let shielded_bundle: Bundle<_, i64, OrchardZSA> = { let mut builder = Builder::new(BundleType::DEFAULT_ZSA, anchor, Some(timelimit)); @@ -364,12 +378,13 @@ fn build_and_verify_action_group( builder.add_split_note(keys.fvk().clone(), spend.note, spend.merkle_path().clone()) }) .map_err(|err| err.to_string())?; - build_and_sign_bundle(builder, rng, keys.pk(), keys.sk()) + build_and_sign_action_group(builder, rng, keys.pk(), keys.sk()) }; verify_action_group(&shielded_bundle, &keys.vk, true); assert_eq!(shielded_bundle.actions().len(), expected_num_actions); - assert!(verify_unique_spent_nullifiers(&shielded_bundle)); + // TODO + // assert!(verify_unique_spent_nullifiers(&shielded_bundle)); Ok(shielded_bundle) } @@ -859,4 +874,9 @@ fn swap_order_and_swap_bundle() { .unwrap(); // 4. Create a SwapBundle from the three previous ActionGroups + let swap_bundle = SwapBundle::new( + OsRng, + vec![action_group1, action_group2, action_group_matcher], + ); + verify_swap_bundle(&swap_bundle, vec![&keys1.vk, &keys2.vk, &matcher_keys.vk]); } From 24033977735150680ce8c5999c9d4fda78d7e3cc Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Tue, 15 Oct 2024 09:13:05 +0200 Subject: [PATCH 09/48] Fix errors --- src/bundle.rs | 2 +- tests/builder.rs | 23 +++++++---------------- tests/zsa.rs | 19 +++++++++++++------ 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/bundle.rs b/src/bundle.rs index 2fb58b986..7a5402326 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -569,7 +569,7 @@ impl + std::iter::Sum> SwapBundle { .map(|a| ValueCommitTrapdoor::from_bsk(a.authorization().bsk)) .sum::() .into_bsk(); - let sighash = BundleCommitment(hash_action_groups_txid_data( + let sighash: [u8; 32] = BundleCommitment(hash_action_groups_txid_data( action_groups.iter().collect(), value_balance, )) diff --git a/tests/builder.rs b/tests/builder.rs index b1009d9de..b8824d5ad 100644 --- a/tests/builder.rs +++ b/tests/builder.rs @@ -42,22 +42,16 @@ pub fn verify_bundle( ); } +// Verify a swap bundle +// - verify each action group (its proof and for each action, the spend authorization signature) +// - verify the binding signature pub fn verify_swap_bundle(swap_bundle: &SwapBundle, vks: Vec<&VerifyingKey>) { assert_eq!(vks.len(), swap_bundle.action_groups().len()); for (action_group, vk) in swap_bundle.action_groups().iter().zip(vks.iter()) { - assert!(matches!(action_group.verify_proof(vk), Ok(()))); - let action_group_sighash: [u8; 32] = action_group.commitment().into(); - for action in action_group.actions() { - assert_eq!( - action - .rk() - .verify(&action_group_sighash, action.authorization()), - Ok(()) - ); - } + verify_action_group(action_group, vk); } - let sighash = swap_bundle.commitment().into(); + let sighash: [u8; 32] = swap_bundle.commitment().into(); let bvk = swap_bundle.binding_validating_key(); assert_eq!( bvk.verify(&sighash, swap_bundle.binding_signature()), @@ -68,15 +62,12 @@ pub fn verify_swap_bundle(swap_bundle: &SwapBundle, vks: Vec<&VerifyingKey> // Verify an action group // - verify the proof // - verify the signature on each action -// - do not verify the binding signature because for some asset, the value balance could be not zero pub fn verify_action_group( bundle: &Bundle, vk: &VerifyingKey, - verify_proof: bool, ) { - if verify_proof { - assert!(matches!(bundle.verify_proof(vk), Ok(()))); - } + assert!(matches!(bundle.verify_proof(vk), Ok(()))); + let sighash: [u8; 32] = bundle.commitment().into(); for action in bundle.actions() { assert_eq!(action.rk().verify(&sighash, action.authorization()), Ok(())); diff --git a/tests/zsa.rs b/tests/zsa.rs index 7eab8d455..adf175af9 100644 --- a/tests/zsa.rs +++ b/tests/zsa.rs @@ -381,7 +381,7 @@ fn build_and_verify_action_group( build_and_sign_action_group(builder, rng, keys.pk(), keys.sk()) }; - verify_action_group(&shielded_bundle, &keys.vk, true); + verify_action_group(&shielded_bundle, &keys.vk); assert_eq!(shielded_bundle.actions().len(), expected_num_actions); // TODO // assert!(verify_unique_spent_nullifiers(&shielded_bundle)); @@ -796,8 +796,15 @@ fn swap_order_and_swap_bundle() { merkle_path: merkle_path_user2_native_note2, }; - // --------------------------- Tests ----------------------------------------- + // --------------------------- Swap description-------------------------------- + // User1: + // - spends 10 asset1 + // - receives 20 asset2 + // User2: + // - spends 20 asset2 + // - receives 10 asset1 + // --------------------------- Tests ----------------------------------------- // 1. Create and verify ActionGroup for user1 let action_group1 = build_and_verify_action_group( vec![ @@ -808,11 +815,11 @@ fn swap_order_and_swap_bundle() { ], vec![ TestOutputInfo { - value: NoteValue::from_raw(10), + value: NoteValue::from_raw(32), asset: asset1_note1.asset(), }, TestOutputInfo { - value: NoteValue::from_raw(5), + value: NoteValue::from_raw(20), asset: asset2_note1.asset(), }, TestOutputInfo { @@ -838,11 +845,11 @@ fn swap_order_and_swap_bundle() { ], vec![ TestOutputInfo { - value: NoteValue::from_raw(10), + value: NoteValue::from_raw(22), asset: asset2_note1.asset(), }, TestOutputInfo { - value: NoteValue::from_raw(5), + value: NoteValue::from_raw(10), asset: asset1_note1.asset(), }, TestOutputInfo { From c8763a3dcf178a944a0f6ca12ffaa92a79634dab Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Tue, 15 Oct 2024 09:58:23 +0200 Subject: [PATCH 10/48] Fix fees --- src/bundle.rs | 2 +- tests/zsa.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bundle.rs b/src/bundle.rs index 7a5402326..e28cfec09 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -584,7 +584,7 @@ impl + std::iter::Sum> SwapBundle { } impl SwapBundle { - /// Returns the list of action groups that make up this swapbundle. + /// Returns the list of action groups that make up this swap bundle. pub fn action_groups(&self) -> &Vec> { &self.action_groups } diff --git a/tests/zsa.rs b/tests/zsa.rs index adf175af9..459b3d4e2 100644 --- a/tests/zsa.rs +++ b/tests/zsa.rs @@ -823,7 +823,7 @@ fn swap_order_and_swap_bundle() { asset: asset2_note1.asset(), }, TestOutputInfo { - value: NoteValue::from_raw(95), + value: NoteValue::from_raw(195), asset: AssetBase::native(), }, ], @@ -853,7 +853,7 @@ fn swap_order_and_swap_bundle() { asset: asset1_note1.asset(), }, TestOutputInfo { - value: NoteValue::from_raw(95), + value: NoteValue::from_raw(195), asset: AssetBase::native(), }, ], From 5d6c2c42575151c09199b987772674c1bec3cfbb Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Tue, 15 Oct 2024 11:46:47 +0200 Subject: [PATCH 11/48] Fix errors from zsa1 rebasing --- tests/zsa.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/zsa.rs b/tests/zsa.rs index 459b3d4e2..ed5879148 100644 --- a/tests/zsa.rs +++ b/tests/zsa.rs @@ -722,8 +722,8 @@ fn swap_order_and_swap_bundle() { // Create notes for user1 let keys1 = prepare_keys(); - let asset_descr1 = "zsa_asset1"; - let (asset1_note1, asset1_note2) = issue_zsa_notes(asset_descr1, &keys1); + let asset_descr1 = b"zsa_asset1".to_vec(); + let (asset1_note1, asset1_note2) = issue_zsa_notes(&asset_descr1, &keys1); let user1_native_note1 = create_native_note(&keys1); let user1_native_note2 = create_native_note(&keys1); @@ -731,8 +731,8 @@ fn swap_order_and_swap_bundle() { // Create notes for user2 let keys2 = prepare_keys(); - let asset_descr2 = "zsa_asset2"; - let (asset2_note1, asset2_note2) = issue_zsa_notes(asset_descr2, &keys2); + let asset_descr2 = b"zsa_asset2".to_vec(); + let (asset2_note1, asset2_note2) = issue_zsa_notes(&asset_descr2, &keys2); let user2_native_note1 = create_native_note(&keys2); let user2_native_note2 = create_native_note(&keys2); From 44beb0f05188287616c9d6dcf7317b0a2160c1cc Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Tue, 15 Oct 2024 13:25:15 +0200 Subject: [PATCH 12/48] Move new functionalities and structs into swap_bundle.rs file --- src/bundle.rs | 82 +++--------------------------------------- src/lib.rs | 5 +++ src/swap_bundle.rs | 88 ++++++++++++++++++++++++++++++++++++++++++++++ tests/builder.rs | 3 +- tests/zsa.rs | 24 +++++-------- 5 files changed, 109 insertions(+), 93 deletions(-) create mode 100644 src/swap_bundle.rs diff --git a/src/bundle.rs b/src/bundle.rs index e28cfec09..cce8f8be7 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -32,7 +32,6 @@ use crate::{ note::{AssetBase, Note}, note_encryption::{OrchardDomain, OrchardDomainCommon}, orchard_flavor::OrchardFlavor, - orchard_flavor::OrchardZSA, primitives::redpallas::{self, Binding, SpendAuth}, tree::Anchor, value::{NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum}, @@ -544,82 +543,6 @@ impl Authorization for EffectsOnly { type SpendAuth = (); } -/// A swap bundle to be applied to the ledger. -#[derive(Clone, Debug)] -pub struct SwapBundle { - /// The list of action groups that make up this swap bundle. - action_groups: Vec>, - /// The net value moved out of this swap. - /// - /// This is the sum of Orchard spends minus the sum of Orchard outputs. - value_balance: V, - /// The binding signature for this swap. - binding_signature: redpallas::Signature, -} - -impl + std::iter::Sum> SwapBundle { - /// Constructs a `Bundle` from its constituent parts. - pub fn new( - rng: R, - action_groups: Vec>, - ) -> Self { - let value_balance = action_groups.iter().map(|a| *a.value_balance()).sum(); - let bsk = action_groups - .iter() - .map(|a| ValueCommitTrapdoor::from_bsk(a.authorization().bsk)) - .sum::() - .into_bsk(); - let sighash: [u8; 32] = BundleCommitment(hash_action_groups_txid_data( - action_groups.iter().collect(), - value_balance, - )) - .into(); - let binding_signature = bsk.sign(rng, &sighash); - SwapBundle { - action_groups, - value_balance, - binding_signature, - } - } -} - -impl SwapBundle { - /// Returns the list of action groups that make up this swap bundle. - pub fn action_groups(&self) -> &Vec> { - &self.action_groups - } - - /// Returns the binding signature of this swap bundle. - pub fn binding_signature(&self) -> &redpallas::Signature { - &self.binding_signature - } -} - -impl> SwapBundle { - /// Computes a commitment to the effects of this swap bundle, suitable for inclusion - /// within a transaction ID. - pub fn commitment(&self) -> BundleCommitment { - BundleCommitment(hash_action_groups_txid_data( - self.action_groups.iter().collect(), - self.value_balance, - )) - } - - /// Returns the transaction binding validating key for this swap bundle. - pub fn binding_validating_key(&self) -> redpallas::VerificationKey { - let actions = self - .action_groups - .iter() - .flat_map(|ag| ag.actions()) - .collect::>(); - derive_bvk( - actions, - self.value_balance, - std::iter::empty::<(AssetBase, NoteValue)>(), - ) - } -} - /// Authorizing data for a bundle of actions, ready to be committed to the ledger. #[derive(Debug, Clone)] pub struct Authorized { @@ -694,6 +617,11 @@ impl ActionGroupAuthorized { pub fn proof(&self) -> &Proof { &self.proof } + + /// Return the binding signature key of the authorizing data. + pub fn bsk(&self) -> redpallas::SigningKey { + self.bsk + } } impl Bundle { diff --git a/src/lib.rs b/src/lib.rs index 4c9deded7..0ce6676c5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,6 +37,11 @@ pub mod issuance_auth; pub mod issuance_sighash_versioning; pub mod keys; pub mod note; +pub mod supply_info; +pub mod swap_bundle; + +pub mod note_encryption; + pub mod orchard_flavor; pub mod orchard_sighash_versioning; pub mod pczt; diff --git a/src/swap_bundle.rs b/src/swap_bundle.rs new file mode 100644 index 000000000..d8c6f11df --- /dev/null +++ b/src/swap_bundle.rs @@ -0,0 +1,88 @@ +//! Structs related to swap bundles. + +use crate::{ + bundle::commitments::hash_action_groups_txid_data, + bundle::{derive_bvk, ActionGroupAuthorized, Bundle, BundleCommitment}, + note::AssetBase, + orchard_flavor::OrchardZSA, + primitives::redpallas::{self, Binding}, + value::{NoteValue, ValueCommitTrapdoor}, +}; + +use k256::elliptic_curve::rand_core::{CryptoRng, RngCore}; + +/// A swap bundle to be applied to the ledger. +#[derive(Clone, Debug)] +pub struct SwapBundle { + /// The list of action groups that make up this swap bundle. + action_groups: Vec>, + /// The net value moved out of this swap. + /// + /// This is the sum of Orchard spends minus the sum of Orchard outputs. + value_balance: V, + /// The binding signature for this swap. + binding_signature: redpallas::Signature, +} + +impl + std::iter::Sum> SwapBundle { + /// Constructs a `Bundle` from its constituent parts. + pub fn new( + rng: R, + action_groups: Vec>, + ) -> Self { + let value_balance = action_groups.iter().map(|a| *a.value_balance()).sum(); + let bsk = action_groups + .iter() + .map(|a| ValueCommitTrapdoor::from_bsk(a.authorization().bsk())) + .sum::() + .into_bsk(); + let sighash: [u8; 32] = BundleCommitment(hash_action_groups_txid_data( + action_groups.iter().collect(), + value_balance, + )) + .into(); + let binding_signature = bsk.sign(rng, &sighash); + SwapBundle { + action_groups, + value_balance, + binding_signature, + } + } +} + +impl SwapBundle { + /// Returns the list of action groups that make up this swap bundle. + pub fn action_groups(&self) -> &Vec> { + &self.action_groups + } + + /// Returns the binding signature of this swap bundle. + pub fn binding_signature(&self) -> &redpallas::Signature { + &self.binding_signature + } +} + +impl> SwapBundle { + /// Computes a commitment to the effects of this swap bundle, suitable for inclusion + /// within a transaction ID. + pub fn commitment(&self) -> BundleCommitment { + BundleCommitment(hash_action_groups_txid_data( + self.action_groups.iter().collect(), + self.value_balance, + )) + } + + /// Returns the transaction binding validating key for this swap bundle. + pub fn binding_validating_key(&self) -> redpallas::VerificationKey { + let actions = self + .action_groups + .iter() + .flat_map(|ag| ag.actions()) + .collect::>(); + derive_bvk( + actions, + self.value_balance, + std::iter::empty::<(AssetBase, NoteValue)>(), + ) + } +} diff --git a/tests/builder.rs b/tests/builder.rs index b8824d5ad..69693a8af 100644 --- a/tests/builder.rs +++ b/tests/builder.rs @@ -1,11 +1,12 @@ use incrementalmerkletree::{Hashable, Marking, Retention}; use orchard::{ builder::{Builder, BundleType}, - bundle::{ActionGroupAuthorized, Authorized, Flags, SwapBundle}, + bundle::{ActionGroupAuthorized, Authorized, Flags}, circuit::{ProvingKey, VerifyingKey}, keys::{FullViewingKey, PreparedIncomingViewingKey, Scope, SpendAuthorizingKey, SpendingKey}, note::{AssetBase, ExtractedNoteCommitment}, orchard_flavor::{OrchardFlavor, OrchardVanilla, OrchardZSA}, + swap_bundle::SwapBundle, primitives::{OrchardDomain, OrchardPrimitives}, tree::{MerkleHashOrchard, MerklePath}, value::NoteValue, diff --git a/tests/zsa.rs b/tests/zsa.rs index ed5879148..910ae092d 100644 --- a/tests/zsa.rs +++ b/tests/zsa.rs @@ -1,31 +1,25 @@ mod builder; use crate::builder::{verify_action_group, verify_bundle, verify_swap_bundle}; -use bridgetree::BridgeTree; -use incrementalmerkletree::Hashable; -use orchard::bundle::{ActionGroupAuthorized, Authorized, SwapBundle}; -use orchard::issuance::{verify_issue_bundle, IssueBundle, IssueInfo, Signed, Unauthorized}; -use orchard::keys::{IssuanceAuthorizingKey, IssuanceValidatingKey}; -use orchard::note::{AssetBase, ExtractedNoteCommitment}; -use orchard::tree::{MerkleHashOrchard, MerklePath}; use orchard::{ builder::{Builder, BundleType}, - bundle::Authorized, + bundle::{ActionGroupAuthorized, Authorized}, circuit::{ProvingKey, VerifyingKey}, - issuance::{ - compute_asset_desc_hash, verify_issue_bundle, AwaitingNullifier, IssueBundle, IssueInfo, - Signed, - }, - issuance_auth::{IssueAuthKey, IssueValidatingKey, ZSASchnorr}, + issuance::{verify_issue_bundle, IssueBundle, IssueInfo, Signed, Unauthorized}, keys::{FullViewingKey, PreparedIncomingViewingKey, Scope, SpendAuthorizingKey, SpendingKey}, - note::{AssetBase, ExtractedNoteCommitment, Nullifier}, + keys::{IssuanceAuthorizingKey, IssuanceValidatingKey}, + note::{AssetBase, ExtractedNoteCommitment}, + note_encryption::OrchardDomain, orchard_flavor::OrchardZSA, - primitives::OrchardDomain, + swap_bundle::SwapBundle, tree::{MerkleHashOrchard, MerklePath}, value::NoteValue, Address, Anchor, Bundle, Note, }; + +use bridgetree::BridgeTree; +use incrementalmerkletree::Hashable; use rand::rngs::OsRng; use shardtree::{store::memory::MemoryShardStore, ShardTree}; use std::collections::HashSet; From 7a0e9f79bc1da8326bbe91ed05e02cf305bc3498 Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Tue, 15 Oct 2024 17:00:18 +0200 Subject: [PATCH 13/48] Create Actiongroup structure --- src/builder.rs | 53 ++++++++++++++--- src/bundle.rs | 21 ++----- src/bundle/commitments.rs | 27 +++++---- src/swap_bundle.rs | 119 +++++++++++++++++++++++++++++++++++--- tests/builder.rs | 11 ++-- tests/zsa.rs | 20 ++++--- 6 files changed, 192 insertions(+), 59 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 6309bd934..758977395 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -807,6 +807,30 @@ impl Builder { ) } + /// Builds an action group containing the given spent notes and outputs. + /// + /// The returned action group will have no proof or signatures; these can be applied with + /// [`ActionGroup::create_proof`] and [`ActionGroup::apply_signatures`] respectively. + pub fn build_action_group>( + self, + rng: impl RngCore, + timelimit: u32, + ) -> Result, Unauthorized>, V>, BuildError> { + let action_group = bundle( + rng, + self.anchor, + self.timelimit, + self.bundle_type, + self.spends, + self.outputs, + self.split_notes, + self.burn, + )? + .unwrap() + .0; + Ok(ActionGroup::from_parts(action_group, timelimit, None)) + } + /// Builds a bundle containing the given spent notes and outputs along with their /// metadata, for inclusion in a PCZT. pub fn build_for_pczt( @@ -1395,12 +1419,19 @@ impl Bundle, V, P> { impl Bundle, V, D> { /// Applies signatures to this action group, in order to authorize it. + #[allow(clippy::type_complexity)] pub fn apply_signatures_for_action_group( self, mut rng: R, sighash: [u8; 32], signing_keys: &[SpendAuthorizingKey], - ) -> Result, BuildError> { + ) -> Result< + ( + redpallas::SigningKey, + Bundle, + ), + BuildError, + > { signing_keys .iter() .fold( @@ -1519,17 +1550,23 @@ impl Bundle Result, BuildError> { + #[allow(clippy::type_complexity)] + pub fn finalize( + self, + ) -> Result< + ( + redpallas::SigningKey, + Bundle, + ), + BuildError, + > { + let bsk = self.authorization().sigs.bsk; self.try_map_authorization( &mut (), |_, _, maybe| maybe.finalize(), - |_, partial| { - Ok(ActionGroupAuthorized::from_parts( - partial.proof, - partial.sigs.bsk, - )) - }, + |_, partial| Ok(ActionGroupAuthorized::from_parts(partial.proof)), ) + .map(|bundle| (bsk, bundle)) } } diff --git a/src/bundle.rs b/src/bundle.rs index cce8f8be7..ecee37555 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -24,9 +24,7 @@ use memuse::DynamicUsage; use crate::{ action::Action, address::Address, - bundle::commitments::{ - hash_action_groups_txid_data, hash_bundle_auth_data, hash_bundle_txid_data, - }, + bundle::commitments::{hash_bundle_auth_data, hash_bundle_txid_data}, circuit::{Instance, Proof, VerifyingKey}, keys::{IncomingViewingKey, OutgoingViewingKey, PreparedIncomingViewingKey}, note::{AssetBase, Note}, @@ -518,12 +516,7 @@ impl, P: OrchardPrimitives> Bundle BundleCommitment { - match self.timelimit { - Some(_) => { - BundleCommitment(hash_action_groups_txid_data(vec![self], self.value_balance)) - } - None => BundleCommitment(hash_bundle_txid_data(self)), - } + BundleCommitment(hash_bundle_txid_data(self)) } /// Returns the transaction binding validating key for this bundle. @@ -600,7 +593,6 @@ impl Bundle { #[derive(Debug, Clone)] pub struct ActionGroupAuthorized { proof: Proof, - bsk: redpallas::SigningKey, } impl Authorization for ActionGroupAuthorized { @@ -609,19 +601,14 @@ impl Authorization for ActionGroupAuthorized { impl ActionGroupAuthorized { /// Constructs the authorizing data for a bundle of actions from its constituent parts. - pub fn from_parts(proof: Proof, bsk: redpallas::SigningKey) -> Self { - ActionGroupAuthorized { proof, bsk } + pub fn from_parts(proof: Proof) -> Self { + ActionGroupAuthorized { proof } } /// Return the proof component of the authorizing data. pub fn proof(&self) -> &Proof { &self.proof } - - /// Return the binding signature key of the authorizing data. - pub fn bsk(&self) -> redpallas::SigningKey { - self.bsk - } } impl Bundle { diff --git a/src/bundle/commitments.rs b/src/bundle/commitments.rs index 6e912d20a..437b14198 100644 --- a/src/bundle/commitments.rs +++ b/src/bundle/commitments.rs @@ -68,46 +68,45 @@ pub(crate) fn hash_bundle_txid_data, P: Or /// /// [zip244]: https://zips.z.cash/zip-0244 /// [zip226]: https://zips.z.cash/zip-0226 (for ZSA burn field hashing) -pub(crate) fn hash_action_groups_txid_data< - A: Authorization, - V: Copy + Into, - D: OrchardDomainCommon + OrchardHash, ->( - bundles: Vec<&Bundle>, +pub(crate) fn hash_action_groups_txid_data>( + action_groups: Vec<&ActionGroup>, value_balance: V, ) -> Blake2bHash { let mut h = hasher(ZCASH_ORCHARD_HASH_PERSONALIZATION); - for bundle in bundles { + for action_group in action_groups { let mut agh = hasher(ZCASH_ORCHARD_ACTION_GROUP_HASH_PERSONALIZATION); let mut ch = hasher(ZCASH_ORCHARD_ACTIONS_COMPACT_HASH_PERSONALIZATION); let mut mh = hasher(ZCASH_ORCHARD_ACTIONS_MEMOS_HASH_PERSONALIZATION); let mut nh = hasher(ZCASH_ORCHARD_ACTIONS_NONCOMPACT_HASH_PERSONALIZATION); - for action in bundle.actions().iter() { + let action_group_bundle = action_group.action_group(); + for action in action_group_bundle.actions().iter() { ch.update(&action.nullifier().to_bytes()); ch.update(&action.cmx().to_bytes()); ch.update(&action.encrypted_note().epk_bytes); - ch.update(&action.encrypted_note().enc_ciphertext.as_ref()[..D::COMPACT_NOTE_SIZE]); + ch.update( + &action.encrypted_note().enc_ciphertext.as_ref()[..OrchardZSA::COMPACT_NOTE_SIZE], + ); mh.update( &action.encrypted_note().enc_ciphertext.as_ref() - [D::COMPACT_NOTE_SIZE..D::COMPACT_NOTE_SIZE + MEMO_SIZE], + [OrchardZSA::COMPACT_NOTE_SIZE..OrchardZSA::COMPACT_NOTE_SIZE + MEMO_SIZE], ); nh.update(&action.cv_net().to_bytes()); nh.update(&<[u8; 32]>::from(action.rk())); nh.update( &action.encrypted_note().enc_ciphertext.as_ref() - [D::COMPACT_NOTE_SIZE + MEMO_SIZE..], + [OrchardZSA::COMPACT_NOTE_SIZE + MEMO_SIZE..], ); nh.update(&action.encrypted_note().out_ciphertext); } agh.update(ch.finalize().as_bytes()); agh.update(mh.finalize().as_bytes()); agh.update(nh.finalize().as_bytes()); - agh.update(&[bundle.flags().to_byte()]); - agh.update(&bundle.anchor().to_bytes()); - agh.update(&bundle.timelimit().unwrap().to_le_bytes()); + agh.update(&[action_group_bundle.flags().to_byte()]); + agh.update(&action_group_bundle.anchor().to_bytes()); + agh.update(&action_group.timelimit().to_le_bytes()); h.update(agh.finalize().as_bytes()); } diff --git a/src/swap_bundle.rs b/src/swap_bundle.rs index d8c6f11df..dd8b29c93 100644 --- a/src/swap_bundle.rs +++ b/src/swap_bundle.rs @@ -7,15 +7,116 @@ use crate::{ orchard_flavor::OrchardZSA, primitives::redpallas::{self, Binding}, value::{NoteValue, ValueCommitTrapdoor}, + Proof, }; +use crate::builder::{BuildError, InProgress, InProgressSignatures, Unauthorized, Unproven}; +use crate::bundle::Authorization; +use crate::circuit::ProvingKey; +use crate::keys::SpendAuthorizingKey; use k256::elliptic_curve::rand_core::{CryptoRng, RngCore}; +/// An action group. +#[derive(Debug)] +pub struct ActionGroup { + /// The action group main content. + action_group: Bundle, + /// The action group timelimit. + timelimit: u32, + /// The binding signature key for the action group. + /// + /// During the building of the action group, this key is not set. + /// Once the action group is finalized (it contains a proof and for each action, a spend + /// authorizing signature), the key is set. + bsk: Option>, +} + +impl ActionGroup { + /// Constructs an `ActionGroup` from its constituent parts. + pub fn from_parts( + action_group: Bundle, + timelimit: u32, + bsk: Option>, + ) -> Self { + ActionGroup { + action_group, + timelimit, + bsk, + } + } + + /// Returns the action group's main content. + pub fn action_group(&self) -> &Bundle { + &self.action_group + } + + /// Returns the action group's timelimit. + pub fn timelimit(&self) -> u32 { + self.timelimit + } + + /// TODO + pub fn remove_bsk(&mut self) { + self.bsk = None; + } +} + +impl ActionGroup, S>, V> { + /// Creates the proof for this action group. + pub fn create_proof( + self, + pk: &ProvingKey, + mut rng: impl RngCore, + ) -> Result, V>, BuildError> { + let new_action_group = self.action_group.create_proof(pk, &mut rng)?; + Ok(ActionGroup { + action_group: new_action_group, + timelimit: self.timelimit, + bsk: self.bsk, + }) + } +} + +impl ActionGroup, V> { + /// Applies signatures to this action group, in order to authorize it. + pub fn apply_signatures( + self, + mut rng: R, + sighash: [u8; 32], + signing_keys: &[SpendAuthorizingKey], + ) -> Result, BuildError> { + let (bsk, action_group) = signing_keys + .iter() + .fold( + self.action_group + .prepare_for_action_group(&mut rng, sighash), + |partial, ask| partial.sign(&mut rng, ask), + ) + .finalize()?; + Ok(ActionGroup { + action_group, + timelimit: self.timelimit, + bsk: Some(bsk), + }) + } +} + +impl> ActionGroup { + /// Computes a commitment to the effects of this bundle, suitable for inclusion within + /// a transaction ID. + pub fn commitment(&self) -> BundleCommitment { + BundleCommitment(hash_action_groups_txid_data( + vec![self], + *self.action_group.value_balance(), + )) + } +} + /// A swap bundle to be applied to the ledger. -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct SwapBundle { /// The list of action groups that make up this swap bundle. - action_groups: Vec>, + action_groups: Vec>, /// The net value moved out of this swap. /// /// This is the sum of Orchard spends minus the sum of Orchard outputs. @@ -28,12 +129,15 @@ impl + std::iter::Sum> SwapBundle { /// Constructs a `Bundle` from its constituent parts. pub fn new( rng: R, - action_groups: Vec>, + action_groups: Vec>, ) -> Self { - let value_balance = action_groups.iter().map(|a| *a.value_balance()).sum(); + let value_balance = action_groups + .iter() + .map(|a| *a.action_group().value_balance()) + .sum(); let bsk = action_groups .iter() - .map(|a| ValueCommitTrapdoor::from_bsk(a.authorization().bsk())) + .map(|a| ValueCommitTrapdoor::from_bsk(a.bsk.unwrap())) .sum::() .into_bsk(); let sighash: [u8; 32] = BundleCommitment(hash_action_groups_txid_data( @@ -42,6 +146,7 @@ impl + std::iter::Sum> SwapBundle { )) .into(); let binding_signature = bsk.sign(rng, &sighash); + // TODO Remove bsk for each action_group SwapBundle { action_groups, value_balance, @@ -52,7 +157,7 @@ impl + std::iter::Sum> SwapBundle { impl SwapBundle { /// Returns the list of action groups that make up this swap bundle. - pub fn action_groups(&self) -> &Vec> { + pub fn action_groups(&self) -> &Vec> { &self.action_groups } @@ -77,7 +182,7 @@ impl> SwapBundle { let actions = self .action_groups .iter() - .flat_map(|ag| ag.actions()) + .flat_map(|ag| ag.action_group().actions()) .collect::>(); derive_bvk( actions, diff --git a/tests/builder.rs b/tests/builder.rs index 69693a8af..98a279893 100644 --- a/tests/builder.rs +++ b/tests/builder.rs @@ -63,14 +63,15 @@ pub fn verify_swap_bundle(swap_bundle: &SwapBundle, vks: Vec<&VerifyingKey> // Verify an action group // - verify the proof // - verify the signature on each action -pub fn verify_action_group( - bundle: &Bundle, +pub fn verify_action_group( + action_group: &ActionGroup, vk: &VerifyingKey, ) { - assert!(matches!(bundle.verify_proof(vk), Ok(()))); + let action_group_bundle = action_group.action_group(); + assert!(matches!(action_group_bundle.verify_proof(vk), Ok(()))); - let sighash: [u8; 32] = bundle.commitment().into(); - for action in bundle.actions() { + let sighash: [u8; 32] = action_group.commitment().into(); + for action in action_group_bundle.actions() { assert_eq!(action.rk().verify(&sighash, action.authorization()), Ok(())); } } diff --git a/tests/zsa.rs b/tests/zsa.rs index 910ae092d..0e781651b 100644 --- a/tests/zsa.rs +++ b/tests/zsa.rs @@ -12,7 +12,7 @@ use orchard::{ note::{AssetBase, ExtractedNoteCommitment}, note_encryption::OrchardDomain, orchard_flavor::OrchardZSA, - swap_bundle::SwapBundle, + swap_bundle::{ActionGroup, SwapBundle}, tree::{MerkleHashOrchard, MerklePath}, value::NoteValue, Address, Anchor, Bundle, Note, @@ -99,15 +99,16 @@ fn build_and_sign_bundle( fn build_and_sign_action_group( builder: Builder, + timelimit: u32, mut rng: OsRng, pk: &ProvingKey, sk: &SpendingKey, -) -> Bundle { - let unauthorized = builder.build(&mut rng).unwrap().unwrap().0; +) -> ActionGroup { + let unauthorized = builder.build_action_group(&mut rng, timelimit).unwrap(); let sighash = unauthorized.commitment().into(); let proven = unauthorized.create_proof(pk, &mut rng).unwrap(); proven - .apply_signatures_for_action_group(rng, sighash, &[SpendAuthorizingKey::from(sk)]) + .apply_signatures(rng, sighash, &[SpendAuthorizingKey::from(sk)]) .unwrap() } @@ -349,9 +350,9 @@ fn build_and_verify_action_group( timelimit: u32, expected_num_actions: usize, keys: &Keychain, -) -> Result, String> { +) -> Result, String> { let rng = OsRng; - let shielded_bundle: Bundle<_, i64, OrchardZSA> = { + let shielded_bundle: ActionGroup<_, i64> = { let mut builder = Builder::new(BundleType::DEFAULT_ZSA, anchor, Some(timelimit)); spends @@ -372,11 +373,14 @@ fn build_and_verify_action_group( builder.add_split_note(keys.fvk().clone(), spend.note, spend.merkle_path().clone()) }) .map_err(|err| err.to_string())?; - build_and_sign_action_group(builder, rng, keys.pk(), keys.sk()) + build_and_sign_action_group(builder, timelimit, rng, keys.pk(), keys.sk()) }; verify_action_group(&shielded_bundle, &keys.vk); - assert_eq!(shielded_bundle.actions().len(), expected_num_actions); + assert_eq!( + shielded_bundle.action_group().actions().len(), + expected_num_actions + ); // TODO // assert!(verify_unique_spent_nullifiers(&shielded_bundle)); Ok(shielded_bundle) From 45a142aaa03068c7e0db893889918b8ce37d0d64 Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Tue, 15 Oct 2024 17:18:37 +0200 Subject: [PATCH 14/48] Remove timelimit from Builder and Bundle --- benches/circuit.rs | 1 - benches/note_decryption.rs | 1 - src/builder.rs | 26 ++++++++++++-------------- src/bundle.rs | 6 ++---- tests/builder.rs | 4 +--- tests/zsa.rs | 6 +++--- 6 files changed, 18 insertions(+), 26 deletions(-) diff --git a/benches/circuit.rs b/benches/circuit.rs index 3b1343375..579a34675 100644 --- a/benches/circuit.rs +++ b/benches/circuit.rs @@ -34,7 +34,6 @@ fn criterion_benchmark(c: &mut Criterion) { let mut builder = Builder::new( BundleType::DEFAULT_VANILLA, Anchor::from_bytes([0; 32]).unwrap(), - None, ); for _ in 0..num_recipients { builder diff --git a/benches/note_decryption.rs b/benches/note_decryption.rs index e1a9a578c..8f2ce8373 100644 --- a/benches/note_decryption.rs +++ b/benches/note_decryption.rs @@ -53,7 +53,6 @@ fn bench_note_decryption(c: &mut Criterion) { let mut builder = Builder::new( BundleType::DEFAULT_VANILLA, Anchor::from_bytes([0; 32]).unwrap(), - None, ); // The builder pads to two actions, and shuffles their order. Add two recipients // so the first action is always decryptable. diff --git a/src/builder.rs b/src/builder.rs index 758977395..5cd9dfa42 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -161,8 +161,8 @@ pub enum BuildError { BurnDuplicateAsset, /// There is no available split note for this asset. NoSplitNoteAvailable, - /// Timelimit is set (thus it is an ActionGroup builder) but burn is not empty. - TimelimitSetAndBurnNotEmpty, + /// Burn is not empty, but we are building an action group. + BurnNotEmptyInActionGroup, } impl fmt::Display for BuildError { @@ -187,9 +187,9 @@ impl fmt::Display for BuildError { BurnZero => f.write_str("Burning is not possible for zero values"), BurnDuplicateAsset => f.write_str("Duplicate assets are not allowed when burning"), NoSplitNoteAvailable => f.write_str("No split note has been provided for this asset"), - TimelimitSetAndBurnNotEmpty => f.write_str( - "Timelimit is set (thus it is an ActionGroup builder) but burn is not empty", - ), + BurnNotEmptyInActionGroup => { + f.write_str("Burn is not empty, but we are building an action group") + } } } } @@ -634,13 +634,11 @@ pub struct Builder { burn: BTreeMap, bundle_type: BundleType, anchor: Anchor, - // When timelimit is set, the Builder will build an ActionGroup (burn must be empty) - timelimit: Option, } impl Builder { /// Constructs a new empty builder for an Orchard bundle. - pub fn new(bundle_type: BundleType, anchor: Anchor, timelimit: Option) -> Self { + pub fn new(bundle_type: BundleType, anchor: Anchor) -> Self { Builder { spends: vec![], outputs: vec![], @@ -648,7 +646,6 @@ impl Builder { burn: BTreeMap::new(), bundle_type, anchor, - timelimit, } } @@ -798,12 +795,12 @@ impl Builder { bundle( rng, self.anchor, - self.timelimit, self.bundle_type, self.spends, self.outputs, self.split_notes, self.burn, + true, ) } @@ -816,15 +813,18 @@ impl Builder { rng: impl RngCore, timelimit: u32, ) -> Result, Unauthorized>, V>, BuildError> { + if !self.burn.is_empty() { + return Err(BuildError::BurnNotEmptyInActionGroup); + } let action_group = bundle( rng, self.anchor, - self.timelimit, self.bundle_type, self.spends, self.outputs, self.split_notes, self.burn, + false, )? .unwrap() .0; @@ -1032,7 +1032,6 @@ pub fn bundle, FL: OrchardFlavor>( fn build_bundle( mut rng: R, anchor: Anchor, - timelimit: Option, bundle_type: BundleType, spends: Vec, outputs: Vec, @@ -1654,7 +1653,7 @@ pub mod testing { mut self, ) -> Bundle { let fvk = FullViewingKey::from(&self.sk); - let mut builder = Builder::new(BundleType::DEFAULT_ZSA, self.anchor, None); + let mut builder = Builder::new(BundleType::DEFAULT_ZSA, self.anchor); for (note, path) in self.notes.into_iter() { builder.add_spend(fvk.clone(), note, path).unwrap(); @@ -1786,7 +1785,6 @@ mod tests { let mut builder = Builder::new( BundleType::DEFAULT_VANILLA, EMPTY_ROOTS[MERKLE_DEPTH_ORCHARD].into(), - None, ); builder diff --git a/src/bundle.rs b/src/bundle.rs index ecee37555..38eda63bb 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -255,7 +255,7 @@ impl Bundle { value_balance: V, burn: Vec<(AssetBase, NoteValue)>, anchor: Anchor, - timelimit: Option, + expiry_height: u32, authorization: A, ) -> Self { Bundle { @@ -264,8 +264,7 @@ impl Bundle { value_balance, burn, anchor, - // For the OrchardZSA protocol, `expiry_height` is set to 0, indicating no expiry. - expiry_height: 0, + expiry_height, authorization, } } @@ -837,7 +836,6 @@ pub mod testing { balances.into_iter().sum::>().unwrap(), burn, anchor, - None, Authorized { proof: Proof::new(fake_proof), binding_signature: VerBindingSig::new(P::default_sighash_version(), sk.sign(rng, &fake_sighash)), diff --git a/tests/builder.rs b/tests/builder.rs index 98a279893..a2df08c60 100644 --- a/tests/builder.rs +++ b/tests/builder.rs @@ -136,7 +136,6 @@ fn bundle_chain() -> ([u8; 32], [u8; 32]) { bundle_required: false, }, anchor, - None, ); let note_value = NoteValue::from_raw(5000); assert_eq!( @@ -195,7 +194,6 @@ fn bundle_chain() -> ([u8; 32], [u8; 32]) { bundle_required: false, }, anchor, - None, ); assert!(builder.add_spend(fvk.clone(), note, merkle_path).is_err()); @@ -205,7 +203,7 @@ fn bundle_chain() -> ([u8; 32], [u8; 32]) { let (shielded_bundle, orchard_digest_2): (Bundle<_, i64, FL>, [u8; 32]) = { let (merkle_path, anchor) = build_merkle_path(¬e); - let mut builder = Builder::new(FL::DEFAULT_BUNDLE_TYPE, anchor, None); + let mut builder = Builder::new(FL::DEFAULT_BUNDLE_TYPE, anchor); assert_eq!(builder.add_spend(fvk, note, merkle_path), Ok(())); assert_eq!( builder.add_output( diff --git a/tests/zsa.rs b/tests/zsa.rs index 0e781651b..ecbdd46fa 100644 --- a/tests/zsa.rs +++ b/tests/zsa.rs @@ -252,7 +252,7 @@ fn create_native_note(keys: &Keychain) -> Note { // Use the empty tree. let anchor = MerkleHashOrchard::empty_root(32.into()).into(); - let mut builder = Builder::new(BundleType::Coinbase, anchor, None); + let mut builder = Builder::new(BundleType::Coinbase, anchor); assert_eq!( builder.add_output( None, @@ -308,7 +308,7 @@ fn build_and_verify_bundle( ) -> Result<(), String> { let rng = OsRng; let shielded_bundle: Bundle<_, i64, OrchardZSA> = { - let mut builder = Builder::new(BundleType::DEFAULT_ZSA, anchor, None); + let mut builder = Builder::new(BundleType::DEFAULT_ZSA, anchor); spends .iter() @@ -353,7 +353,7 @@ fn build_and_verify_action_group( ) -> Result, String> { let rng = OsRng; let shielded_bundle: ActionGroup<_, i64> = { - let mut builder = Builder::new(BundleType::DEFAULT_ZSA, anchor, Some(timelimit)); + let mut builder = Builder::new(BundleType::DEFAULT_ZSA, anchor); spends .iter() From dc72696cff5913e675da84106606b01011c209d5 Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Thu, 17 Oct 2024 09:57:30 +0200 Subject: [PATCH 15/48] Move ActionGroupAuthorized into swap_bundle.rs file --- src/builder.rs | 6 +++--- src/swap_bundle.rs | 37 +++++++++++++++++++++++++++++++++++-- tests/builder.rs | 3 ++- tests/zsa.rs | 4 ++-- 4 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 5cd9dfa42..30d1c1b69 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -11,12 +11,11 @@ use rand::{prelude::SliceRandom, CryptoRng, RngCore}; use zcash_note_encryption::NoteEncryption; -use crate::builder::BuildError::{BurnNative, BurnZero}; -use crate::bundle::ActionGroupAuthorized; use crate::{ address::Address, builder::BuildError::{BurnNative, BurnZero}, - bundle::{Authorization, Authorized, Bundle, Flags}, + bundle::{derive_bvk, Authorization, Authorized, Bundle, Flags}, + circuit::{Circuit, Instance, OrchardCircuit, Proof, ProvingKey}, keys::{ FullViewingKey, OutgoingViewingKey, Scope, SpendAuthorizingKey, SpendValidatingKey, SpendingKey, @@ -24,6 +23,7 @@ use crate::{ note::{AssetBase, ExtractedNoteCommitment, Note, Nullifier, Rho, TransmittedNoteCiphertext}, orchard_sighash_versioning::{VerBindingSig, VerSpendAuthSig}, primitives::redpallas::{self, Binding, SpendAuth}, + swap_bundle::{ActionGroup, ActionGroupAuthorized}, primitives::{OrchardDomain, OrchardPrimitives}, tree::{Anchor, MerklePath}, value::{self, NoteValue, OverflowError, ValueCommitTrapdoor, ValueCommitment, ValueSum}, diff --git a/src/swap_bundle.rs b/src/swap_bundle.rs index dd8b29c93..5261e02df 100644 --- a/src/swap_bundle.rs +++ b/src/swap_bundle.rs @@ -2,7 +2,7 @@ use crate::{ bundle::commitments::hash_action_groups_txid_data, - bundle::{derive_bvk, ActionGroupAuthorized, Bundle, BundleCommitment}, + bundle::{derive_bvk, Bundle, BundleCommitment}, note::AssetBase, orchard_flavor::OrchardZSA, primitives::redpallas::{self, Binding}, @@ -12,8 +12,10 @@ use crate::{ use crate::builder::{BuildError, InProgress, InProgressSignatures, Unauthorized, Unproven}; use crate::bundle::Authorization; -use crate::circuit::ProvingKey; +use crate::circuit::{ProvingKey, VerifyingKey}; use crate::keys::SpendAuthorizingKey; +use crate::note_encryption::OrchardDomainCommon; +use crate::primitives::redpallas::SpendAuth; use k256::elliptic_curve::rand_core::{CryptoRng, RngCore}; /// An action group. @@ -155,6 +157,37 @@ impl + std::iter::Sum> SwapBundle { } } +/// Authorizing data for an action group, ready to be sent to the matcher. +#[derive(Debug, Clone)] +pub struct ActionGroupAuthorized { + proof: Proof, +} + +impl Authorization for ActionGroupAuthorized { + type SpendAuth = redpallas::Signature; +} + +impl ActionGroupAuthorized { + /// Constructs the authorizing data for a bundle of actions from its constituent parts. + pub fn from_parts(proof: Proof) -> Self { + ActionGroupAuthorized { proof } + } + + /// Return the proof component of the authorizing data. + pub fn proof(&self) -> &Proof { + &self.proof + } +} + +impl Bundle { + /// Verifies the proof for this bundle. + pub fn verify_proof(&self, vk: &VerifyingKey) -> Result<(), halo2_proofs::plonk::Error> { + self.authorization() + .proof() + .verify(vk, &self.to_instances()) + } +} + impl SwapBundle { /// Returns the list of action groups that make up this swap bundle. pub fn action_groups(&self) -> &Vec> { diff --git a/tests/builder.rs b/tests/builder.rs index a2df08c60..48f22e66c 100644 --- a/tests/builder.rs +++ b/tests/builder.rs @@ -1,13 +1,14 @@ use incrementalmerkletree::{Hashable, Marking, Retention}; use orchard::{ builder::{Builder, BundleType}, - bundle::{ActionGroupAuthorized, Authorized, Flags}, + bundle::{Authorized, Flags}, circuit::{ProvingKey, VerifyingKey}, keys::{FullViewingKey, PreparedIncomingViewingKey, Scope, SpendAuthorizingKey, SpendingKey}, note::{AssetBase, ExtractedNoteCommitment}, orchard_flavor::{OrchardFlavor, OrchardVanilla, OrchardZSA}, swap_bundle::SwapBundle, primitives::{OrchardDomain, OrchardPrimitives}, + swap_bundle::{ActionGroup, ActionGroupAuthorized, SwapBundle}, tree::{MerkleHashOrchard, MerklePath}, value::NoteValue, Anchor, Bundle, Note, diff --git a/tests/zsa.rs b/tests/zsa.rs index ecbdd46fa..787815725 100644 --- a/tests/zsa.rs +++ b/tests/zsa.rs @@ -4,7 +4,7 @@ use crate::builder::{verify_action_group, verify_bundle, verify_swap_bundle}; use orchard::{ builder::{Builder, BundleType}, - bundle::{ActionGroupAuthorized, Authorized}, + bundle::Authorized, circuit::{ProvingKey, VerifyingKey}, issuance::{verify_issue_bundle, IssueBundle, IssueInfo, Signed, Unauthorized}, keys::{FullViewingKey, PreparedIncomingViewingKey, Scope, SpendAuthorizingKey, SpendingKey}, @@ -12,7 +12,7 @@ use orchard::{ note::{AssetBase, ExtractedNoteCommitment}, note_encryption::OrchardDomain, orchard_flavor::OrchardZSA, - swap_bundle::{ActionGroup, SwapBundle}, + swap_bundle::{ActionGroup, ActionGroupAuthorized, SwapBundle}, tree::{MerkleHashOrchard, MerklePath}, value::NoteValue, Address, Anchor, Bundle, Note, From c7a9631aff5b737f38b46f16cf701ef3f37ab3c2 Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Thu, 17 Oct 2024 14:32:03 +0200 Subject: [PATCH 16/48] When building a SwapBundle, remove bsk for each action group --- src/builder.rs | 12 +++++------- src/bundle/commitments.rs | 17 +---------------- src/swap_bundle.rs | 35 ++++++++++++++++++++--------------- tests/builder.rs | 2 ++ tests/zsa.rs | 14 ++++++++------ 5 files changed, 36 insertions(+), 44 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 30d1c1b69..e2be73db7 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -161,7 +161,7 @@ pub enum BuildError { BurnDuplicateAsset, /// There is no available split note for this asset. NoSplitNoteAvailable, - /// Burn is not empty, but we are building an action group. + /// Burning is not allowed in an ActionGroup. BurnNotEmptyInActionGroup, } @@ -187,9 +187,7 @@ impl fmt::Display for BuildError { BurnZero => f.write_str("Burning is not possible for zero values"), BurnDuplicateAsset => f.write_str("Duplicate assets are not allowed when burning"), NoSplitNoteAvailable => f.write_str("No split note has been provided for this asset"), - BurnNotEmptyInActionGroup => { - f.write_str("Burn is not empty, but we are building an action group") - } + BurnNotEmptyInActionGroup => f.write_str("Burning is not possible for action group"), } } } @@ -1298,7 +1296,7 @@ impl InProgressSignatures for PartiallyAuthorized { type SpendAuth = MaybeSigned; } -/// Marker for a partially-authorized bundle, in the process of being signed. +/// Marker for a partially-authorized action group, in the process of being signed. #[derive(Debug)] pub struct ActionGroupPartiallyAuthorized { bsk: redpallas::SigningKey, @@ -1368,7 +1366,7 @@ impl Bundle Bundle, V, D> { - /// Loads the sighash into this bundle, preparing it for signing. + /// Loads the sighash into this action group, preparing it for signing. /// /// This API ensures that all signatures are created over the same sighash. pub fn prepare_for_action_group( @@ -1546,7 +1544,7 @@ impl Bundle, V, } impl Bundle, V, D> { - /// Finalizes this bundle, enabling it to be included in a transaction. + /// Finalizes this action group. /// /// Returns an error if any signatures are missing. #[allow(clippy::type_complexity)] diff --git a/src/bundle/commitments.rs b/src/bundle/commitments.rs index 437b14198..de4bde21f 100644 --- a/src/bundle/commitments.rs +++ b/src/bundle/commitments.rs @@ -52,22 +52,7 @@ pub(crate) fn hash_bundle_txid_data, P: Or P::hash_bundle_txid_data(bundle) } -/// TODO update description -/// Write disjoint parts of each ActionGroup as 3 separate hashes: -/// * \[(nullifier, cmx, ephemeral_key, enc_ciphertext\[..52\])*\] personalized -/// with ZCASH_ORCHARD_ACTIONS_COMPACT_HASH_PERSONALIZATION -/// * \[enc_ciphertext\[52..564\]*\] (memo ciphertexts) personalized -/// with ZCASH_ORCHARD_ACTIONS_MEMOS_HASH_PERSONALIZATION -/// * \[(cv, rk, enc_ciphertext\[564..\], out_ciphertext)*\] personalized -/// with ZCASH_ORCHARD_ACTIONS_NONCOMPACT_HASH_PERSONALIZATION -/// as defined in [ZIP-244: Transaction Identifier Non-Malleability][zip244] -/// -/// Then, hash these together along with (flags, anchor_orchard, timelimit). -/// -/// The final hash is personalized with ZCASH_ORCHARD_HASH_PERSONALIZATION. -/// -/// [zip244]: https://zips.z.cash/zip-0244 -/// [zip226]: https://zips.z.cash/zip-0226 (for ZSA burn field hashing) +/// Evaluate sighash for the given action groups pub(crate) fn hash_action_groups_txid_data>( action_groups: Vec<&ActionGroup>, value_balance: V, diff --git a/src/swap_bundle.rs b/src/swap_bundle.rs index 5261e02df..06cfb64fb 100644 --- a/src/swap_bundle.rs +++ b/src/swap_bundle.rs @@ -1,21 +1,21 @@ //! Structs related to swap bundles. use crate::{ - bundle::commitments::hash_action_groups_txid_data, - bundle::{derive_bvk, Bundle, BundleCommitment}, + builder::{BuildError, InProgress, InProgressSignatures, Unauthorized, Unproven}, + bundle::{ + commitments::hash_action_groups_txid_data, derive_bvk, Authorization, Bundle, + BundleCommitment, + }, + circuit::{ProvingKey, VerifyingKey}, + keys::SpendAuthorizingKey, note::AssetBase, + note_encryption::OrchardDomainCommon, orchard_flavor::OrchardZSA, - primitives::redpallas::{self, Binding}, + primitives::redpallas::{self, Binding, SpendAuth}, value::{NoteValue, ValueCommitTrapdoor}, Proof, }; -use crate::builder::{BuildError, InProgress, InProgressSignatures, Unauthorized, Unproven}; -use crate::bundle::Authorization; -use crate::circuit::{ProvingKey, VerifyingKey}; -use crate::keys::SpendAuthorizingKey; -use crate::note_encryption::OrchardDomainCommon; -use crate::primitives::redpallas::SpendAuth; use k256::elliptic_curve::rand_core::{CryptoRng, RngCore}; /// An action group. @@ -57,8 +57,13 @@ impl ActionGroup { self.timelimit } - /// TODO - pub fn remove_bsk(&mut self) { + /// Returns the action group's binding signature key. + pub fn bsk(&self) -> Option<&redpallas::SigningKey> { + self.bsk.as_ref() + } + + /// Remove bsk from this action group + fn remove_bsk(&mut self) { self.bsk = None; } } @@ -104,7 +109,7 @@ impl ActionGroup, V> { } impl> ActionGroup { - /// Computes a commitment to the effects of this bundle, suitable for inclusion within + /// Computes a commitment to the effects of this action group, suitable for inclusion within /// a transaction ID. pub fn commitment(&self) -> BundleCommitment { BundleCommitment(hash_action_groups_txid_data( @@ -128,10 +133,10 @@ pub struct SwapBundle { } impl + std::iter::Sum> SwapBundle { - /// Constructs a `Bundle` from its constituent parts. + /// Constructs a `SwapBundle` from its action groups. pub fn new( rng: R, - action_groups: Vec>, + mut action_groups: Vec>, ) -> Self { let value_balance = action_groups .iter() @@ -148,7 +153,7 @@ impl + std::iter::Sum> SwapBundle { )) .into(); let binding_signature = bsk.sign(rng, &sighash); - // TODO Remove bsk for each action_group + action_groups.iter_mut().for_each(|ag| ag.remove_bsk()); SwapBundle { action_groups, value_balance, diff --git a/tests/builder.rs b/tests/builder.rs index 48f22e66c..de72ead31 100644 --- a/tests/builder.rs +++ b/tests/builder.rs @@ -51,6 +51,8 @@ pub fn verify_swap_bundle(swap_bundle: &SwapBundle, vks: Vec<&VerifyingKey> assert_eq!(vks.len(), swap_bundle.action_groups().len()); for (action_group, vk) in swap_bundle.action_groups().iter().zip(vks.iter()) { verify_action_group(action_group, vk); + // Verify that bsk is None + assert!(action_group.bsk().is_none()); } let sighash: [u8; 32] = swap_bundle.commitment().into(); diff --git a/tests/zsa.rs b/tests/zsa.rs index 787815725..467b53749 100644 --- a/tests/zsa.rs +++ b/tests/zsa.rs @@ -20,6 +20,7 @@ use orchard::{ use bridgetree::BridgeTree; use incrementalmerkletree::Hashable; +use orchard::bundle::Authorization; use rand::rngs::OsRng; use shardtree::{store::memory::MemoryShardStore, ShardTree}; use std::collections::HashSet; @@ -352,7 +353,7 @@ fn build_and_verify_action_group( keys: &Keychain, ) -> Result, String> { let rng = OsRng; - let shielded_bundle: ActionGroup<_, i64> = { + let shielded_action_group: ActionGroup<_, i64> = { let mut builder = Builder::new(BundleType::DEFAULT_ZSA, anchor); spends @@ -376,14 +377,15 @@ fn build_and_verify_action_group( build_and_sign_action_group(builder, timelimit, rng, keys.pk(), keys.sk()) }; - verify_action_group(&shielded_bundle, &keys.vk); + verify_action_group(&shielded_action_group, &keys.vk); assert_eq!( - shielded_bundle.action_group().actions().len(), + shielded_action_group.action_group().actions().len(), expected_num_actions ); - // TODO - // assert!(verify_unique_spent_nullifiers(&shielded_bundle)); - Ok(shielded_bundle) + assert!(verify_unique_spent_nullifiers( + shielded_action_group.action_group() + )); + Ok(shielded_action_group) } fn verify_unique_spent_nullifiers(bundle: &Bundle) -> bool { From 1c654c6632a286e1847e36d3c7ad0fc13c5bcf9a Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Fri, 18 Oct 2024 11:00:21 +0200 Subject: [PATCH 17/48] Add and update comments --- src/swap_bundle.rs | 20 ++++++++++++---- tests/builder.rs | 3 ++- tests/zsa.rs | 58 ++++++++++++++++++++++++++++------------------ 3 files changed, 53 insertions(+), 28 deletions(-) diff --git a/src/swap_bundle.rs b/src/swap_bundle.rs index 06cfb64fb..d349a35b5 100644 --- a/src/swap_bundle.rs +++ b/src/swap_bundle.rs @@ -28,8 +28,8 @@ pub struct ActionGroup { /// The binding signature key for the action group. /// /// During the building of the action group, this key is not set. - /// Once the action group is finalized (it contains a proof and for each action, a spend - /// authorizing signature), the key is set. + /// Once the action group is finalized (it contains a spend authorizing signature for each + /// action and a proof), the key is set. bsk: Option>, } @@ -63,6 +63,10 @@ impl ActionGroup { } /// Remove bsk from this action group + /// + /// When creating a SwapBundle from a list of action groups, we evaluate the binding signature + /// by signing the sighash with the summ of the bsk of each action group. + /// Then, we remove the bsk of each action group as it is no longer needed. fn remove_bsk(&mut self) { self.bsk = None; } @@ -109,8 +113,7 @@ impl ActionGroup, V> { } impl> ActionGroup { - /// Computes a commitment to the effects of this action group, suitable for inclusion within - /// a transaction ID. + /// Computes a commitment to the content of this action group. pub fn commitment(&self) -> BundleCommitment { BundleCommitment(hash_action_groups_txid_data( vec![self], @@ -138,22 +141,29 @@ impl + std::iter::Sum> SwapBundle { rng: R, mut action_groups: Vec>, ) -> Self { + // Evaluate the swap value balance by summing the value balance of each action group. let value_balance = action_groups .iter() .map(|a| *a.action_group().value_balance()) .sum(); + // Evaluate the swap bsk by summing the bsk of each action group. let bsk = action_groups .iter() .map(|a| ValueCommitTrapdoor::from_bsk(a.bsk.unwrap())) .sum::() .into_bsk(); + // Evaluate the swap sighash let sighash: [u8; 32] = BundleCommitment(hash_action_groups_txid_data( action_groups.iter().collect(), value_balance, )) .into(); + // Evaluate the swap binding signature which is equal to the signature of the swap sigash + // with the swap binding signature key bsk. let binding_signature = bsk.sign(rng, &sighash); + // Remove the bsk of each action group as it is no longer needed. action_groups.iter_mut().for_each(|ag| ag.remove_bsk()); + // Create the swap bundle SwapBundle { action_groups, value_balance, @@ -173,7 +183,7 @@ impl Authorization for ActionGroupAuthorized { } impl ActionGroupAuthorized { - /// Constructs the authorizing data for a bundle of actions from its constituent parts. + /// Constructs the authorizing data for an action group from its proof. pub fn from_parts(proof: Proof) -> Self { ActionGroupAuthorized { proof } } diff --git a/tests/builder.rs b/tests/builder.rs index de72ead31..a671d8a61 100644 --- a/tests/builder.rs +++ b/tests/builder.rs @@ -46,7 +46,8 @@ pub fn verify_bundle( // Verify a swap bundle // - verify each action group (its proof and for each action, the spend authorization signature) -// - verify the binding signature +// - verify that bsk is None for each action group +// - verify the swap binding signature pub fn verify_swap_bundle(swap_bundle: &SwapBundle, vks: Vec<&VerifyingKey>) { assert_eq!(vks.len(), swap_bundle.action_groups().len()); for (action_group, vk) in swap_bundle.action_groups().iter().zip(vks.iter()) { diff --git a/tests/zsa.rs b/tests/zsa.rs index 467b53749..3356cd5e4 100644 --- a/tests/zsa.rs +++ b/tests/zsa.rs @@ -1,7 +1,9 @@ mod builder; use crate::builder::{verify_action_group, verify_bundle, verify_swap_bundle}; - +use bridgetree::BridgeTree; +use incrementalmerkletree::Hashable; +use orchard::bundle::Authorization; use orchard::{ builder::{Builder, BundleType}, bundle::Authorized, @@ -17,10 +19,6 @@ use orchard::{ value::NoteValue, Address, Anchor, Bundle, Note, }; - -use bridgetree::BridgeTree; -use incrementalmerkletree::Hashable; -use orchard::bundle::Authorization; use rand::rngs::OsRng; use shardtree::{store::memory::MemoryShardStore, ShardTree}; use std::collections::HashSet; @@ -715,9 +713,17 @@ fn zsa_issue_and_transfer() { } } -/// Create several swap orders and combine them to create a SwapBundle +/// Create several action groups and combine them to create a SwapBundle #[test] fn swap_order_and_swap_bundle() { + // --------------------------- Swap description-------------------------------- + // User1: + // - spends 10 asset1 + // - receives 20 asset2 + // User2: + // - spends 20 asset2 + // - receives 10 asset1 + // --------------------------- Setup ----------------------------------------- // Create notes for user1 let keys1 = prepare_keys(); @@ -796,37 +802,36 @@ fn swap_order_and_swap_bundle() { merkle_path: merkle_path_user2_native_note2, }; - // --------------------------- Swap description-------------------------------- - // User1: - // - spends 10 asset1 - // - receives 20 asset2 - // User2: - // - spends 20 asset2 - // - receives 10 asset1 - // --------------------------- Tests ----------------------------------------- // 1. Create and verify ActionGroup for user1 let action_group1 = build_and_verify_action_group( vec![ - &asset1_spend1, - &asset1_spend2, - &user1_native_note1_spend, - &user1_native_note2_spend, + &asset1_spend1, // 40 asset1 + &asset1_spend2, // 2 asset1 + &user1_native_note1_spend, // 100 ZEC + &user1_native_note2_spend, // 100 ZEC ], vec![ + // User1 would like to spend 10 asset1. + // Thus, he would like to keep 40+2-10=32 asset1. TestOutputInfo { value: NoteValue::from_raw(32), asset: asset1_note1.asset(), }, + // User1 would like to receive 20 asset2. TestOutputInfo { value: NoteValue::from_raw(20), asset: asset2_note1.asset(), }, + // User1 would like to pay 5 ZEC as a fee. + // Thus, he would like to keep 100+100-5=195 ZEC. TestOutputInfo { value: NoteValue::from_raw(195), asset: AssetBase::native(), }, ], + // We must provide a split note for asset2 because we have no spend note for this asset. + // This note will not be spent. It is only used to check the correctness of asset2. vec![&asset2_spend1], anchor, 0, @@ -838,25 +843,32 @@ fn swap_order_and_swap_bundle() { // 2. Create and verify ActionGroup for user2 let action_group2 = build_and_verify_action_group( vec![ - &asset2_spend1, - &asset2_spend2, - &user2_native_note1_spend, - &user2_native_note2_spend, + &asset2_spend1, // 40 asset2 + &asset2_spend2, // 2 asset2 + &user2_native_note1_spend, // 100 ZEC + &user2_native_note2_spend, // 100 ZEC ], vec![ + // User2 would like to spend 20 asset2. + // Thus, he would like to keep 40+2-20=22 asset2. TestOutputInfo { value: NoteValue::from_raw(22), asset: asset2_note1.asset(), }, + // User2 would like to receive 10 asset1. TestOutputInfo { value: NoteValue::from_raw(10), asset: asset1_note1.asset(), }, + // User2 would like to pay 5 ZEC as a fee. + // Thus, he would like to keep 100+100-5=195 ZEC. TestOutputInfo { value: NoteValue::from_raw(195), asset: AssetBase::native(), }, ], + // We must provide a split note for asset1 because we have no spend note for this asset. + // This note will not be spent. It is only used to check the correctness of asset1. vec![&asset1_spend1], anchor, 0, @@ -867,7 +879,9 @@ fn swap_order_and_swap_bundle() { // 3. Matcher fees action group let action_group_matcher = build_and_verify_action_group( + // The matcher spends nothing. vec![], + // The matcher receives 10 ZEC as a fee from user1 and user2. vec![TestOutputInfo { value: NoteValue::from_raw(10), asset: AssetBase::native(), From e63c96b662537f0fae346555daee31786bab45c8 Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Fri, 18 Oct 2024 11:53:10 +0200 Subject: [PATCH 18/48] Update hash_action_group and hash_swap_bundle --- src/bundle/commitments.rs | 91 ++++++++++++++++++++++++--------------- src/swap_bundle.rs | 15 +++---- 2 files changed, 61 insertions(+), 45 deletions(-) diff --git a/src/bundle/commitments.rs b/src/bundle/commitments.rs index de4bde21f..2ef8bea7e 100644 --- a/src/bundle/commitments.rs +++ b/src/bundle/commitments.rs @@ -52,47 +52,68 @@ pub(crate) fn hash_bundle_txid_data, P: Or P::hash_bundle_txid_data(bundle) } -/// Evaluate sighash for the given action groups -pub(crate) fn hash_action_groups_txid_data>( +/// Construct the commitment for the absent bundle as defined in +/// [ZIP-244: Transaction Identifier Non-Malleability][zip244] +/// +/// [zip244]: https://zips.z.cash/zip-0244 +pub fn hash_bundle_txid_empty() -> Blake2bHash { + hasher(ZCASH_ORCHARD_HASH_PERSONALIZATION).finalize() +} + +/// Construct the commitment for an action group as defined in +/// [ZIP-228: Asset Swaps for Zcash Shielded Assets][zip228] +/// +/// [zip228]: https://zips.z.cash/zip-0228 +pub(crate) fn hash_action_group>( + action_group: &ActionGroup, +) -> Blake2bHash { + let mut agh = hasher(ZCASH_ORCHARD_ACTION_GROUP_HASH_PERSONALIZATION); + let mut ch = hasher(ZCASH_ORCHARD_ACTIONS_COMPACT_HASH_PERSONALIZATION); + let mut mh = hasher(ZCASH_ORCHARD_ACTIONS_MEMOS_HASH_PERSONALIZATION); + let mut nh = hasher(ZCASH_ORCHARD_ACTIONS_NONCOMPACT_HASH_PERSONALIZATION); + for action in action_group.action_group().actions().iter() { + ch.update(&action.nullifier().to_bytes()); + ch.update(&action.cmx().to_bytes()); + ch.update(&action.encrypted_note().epk_bytes); + ch.update( + &action.encrypted_note().enc_ciphertext.as_ref()[..OrchardZSA::COMPACT_NOTE_SIZE], + ); + + mh.update( + &action.encrypted_note().enc_ciphertext.as_ref() + [OrchardZSA::COMPACT_NOTE_SIZE..OrchardZSA::COMPACT_NOTE_SIZE + MEMO_SIZE], + ); + + nh.update(&action.cv_net().to_bytes()); + nh.update(&<[u8; 32]>::from(action.rk())); + nh.update( + &action.encrypted_note().enc_ciphertext.as_ref() + [OrchardZSA::COMPACT_NOTE_SIZE + MEMO_SIZE..], + ); + nh.update(&action.encrypted_note().out_ciphertext); + } + + agh.update(ch.finalize().as_bytes()); + agh.update(mh.finalize().as_bytes()); + agh.update(nh.finalize().as_bytes()); + agh.update(&[action_group.action_group().flags().to_byte()]); + agh.update(&action_group.action_group().anchor().to_bytes()); + agh.update(&action_group.timelimit().to_le_bytes()); + agh.finalize() +} + +/// Construct the commitment for a swap bundle as defined in +/// [ZIP-228: Asset Swaps for Zcash Shielded Assets][zip228] +/// +/// [zip228]: https://zips.z.cash/zip-0228 +pub(crate) fn hash_swap_bundle>( action_groups: Vec<&ActionGroup>, value_balance: V, ) -> Blake2bHash { let mut h = hasher(ZCASH_ORCHARD_HASH_PERSONALIZATION); for action_group in action_groups { - let mut agh = hasher(ZCASH_ORCHARD_ACTION_GROUP_HASH_PERSONALIZATION); - let mut ch = hasher(ZCASH_ORCHARD_ACTIONS_COMPACT_HASH_PERSONALIZATION); - let mut mh = hasher(ZCASH_ORCHARD_ACTIONS_MEMOS_HASH_PERSONALIZATION); - let mut nh = hasher(ZCASH_ORCHARD_ACTIONS_NONCOMPACT_HASH_PERSONALIZATION); - let action_group_bundle = action_group.action_group(); - for action in action_group_bundle.actions().iter() { - ch.update(&action.nullifier().to_bytes()); - ch.update(&action.cmx().to_bytes()); - ch.update(&action.encrypted_note().epk_bytes); - ch.update( - &action.encrypted_note().enc_ciphertext.as_ref()[..OrchardZSA::COMPACT_NOTE_SIZE], - ); - - mh.update( - &action.encrypted_note().enc_ciphertext.as_ref() - [OrchardZSA::COMPACT_NOTE_SIZE..OrchardZSA::COMPACT_NOTE_SIZE + MEMO_SIZE], - ); - - nh.update(&action.cv_net().to_bytes()); - nh.update(&<[u8; 32]>::from(action.rk())); - nh.update( - &action.encrypted_note().enc_ciphertext.as_ref() - [OrchardZSA::COMPACT_NOTE_SIZE + MEMO_SIZE..], - ); - nh.update(&action.encrypted_note().out_ciphertext); - } - agh.update(ch.finalize().as_bytes()); - agh.update(mh.finalize().as_bytes()); - agh.update(nh.finalize().as_bytes()); - agh.update(&[action_group_bundle.flags().to_byte()]); - agh.update(&action_group_bundle.anchor().to_bytes()); - agh.update(&action_group.timelimit().to_le_bytes()); - h.update(agh.finalize().as_bytes()); + h.update(hash_action_group(action_group).as_bytes()); } h.update(&value_balance.into().to_le_bytes()); diff --git a/src/swap_bundle.rs b/src/swap_bundle.rs index d349a35b5..db5197c8a 100644 --- a/src/swap_bundle.rs +++ b/src/swap_bundle.rs @@ -2,10 +2,8 @@ use crate::{ builder::{BuildError, InProgress, InProgressSignatures, Unauthorized, Unproven}, - bundle::{ - commitments::hash_action_groups_txid_data, derive_bvk, Authorization, Bundle, - BundleCommitment, - }, + bundle::commitments::{hash_action_group, hash_swap_bundle}, + bundle::{derive_bvk, Authorization, Bundle, BundleCommitment}, circuit::{ProvingKey, VerifyingKey}, keys::SpendAuthorizingKey, note::AssetBase, @@ -115,10 +113,7 @@ impl ActionGroup, V> { impl> ActionGroup { /// Computes a commitment to the content of this action group. pub fn commitment(&self) -> BundleCommitment { - BundleCommitment(hash_action_groups_txid_data( - vec![self], - *self.action_group.value_balance(), - )) + BundleCommitment(hash_action_group(self)) } } @@ -153,7 +148,7 @@ impl + std::iter::Sum> SwapBundle { .sum::() .into_bsk(); // Evaluate the swap sighash - let sighash: [u8; 32] = BundleCommitment(hash_action_groups_txid_data( + let sighash: [u8; 32] = BundleCommitment(hash_swap_bundle( action_groups.iter().collect(), value_balance, )) @@ -219,7 +214,7 @@ impl> SwapBundle { /// Computes a commitment to the effects of this swap bundle, suitable for inclusion /// within a transaction ID. pub fn commitment(&self) -> BundleCommitment { - BundleCommitment(hash_action_groups_txid_data( + BundleCommitment(hash_swap_bundle( self.action_groups.iter().collect(), self.value_balance, )) From 831cd50f7e4b2859decfcaa0a56c9061acb3f8cb Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Tue, 29 Oct 2024 09:30:27 +0100 Subject: [PATCH 19/48] Replace split_notes by reference_notes --- src/builder.rs | 20 ++++++++++---------- tests/zsa.rs | 14 +++++++++----- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index e2be73db7..6897e782f 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -628,10 +628,10 @@ pub type UnauthorizedBundleWithMetadata = (UnauthorizedBundle, Bun pub struct Builder { spends: Vec, outputs: Vec, - split_notes: BTreeMap, burn: BTreeMap, bundle_type: BundleType, anchor: Anchor, + reference_notes: HashMap, } impl Builder { @@ -640,10 +640,10 @@ impl Builder { Builder { spends: vec![], outputs: vec![], - split_notes: BTreeMap::new(), burn: BTreeMap::new(), bundle_type, anchor, + reference_notes: HashMap::new(), } } @@ -702,8 +702,8 @@ impl Builder { Ok(()) } - /// Add a reference split note which could be used to create Actions. - pub fn add_split_note( + /// Add a reference note which could be used to create Actions. + pub fn add_reference_note( &mut self, fvk: FullViewingKey, note: Note, @@ -716,7 +716,7 @@ impl Builder { return Err(SpendError::AnchorMismatch); } - self.split_notes.entry(note.asset()).or_insert(spend); + self.reference_notes.entry(note.asset()).or_insert(spend); Ok(()) } @@ -796,8 +796,8 @@ impl Builder { self.bundle_type, self.spends, self.outputs, - self.split_notes, self.burn, + self.reference_notes, true, ) } @@ -820,8 +820,8 @@ impl Builder { self.bundle_type, self.spends, self.outputs, - self.split_notes, self.burn, + self.reference_notes, false, )? .unwrap() @@ -954,7 +954,7 @@ fn pad_spend( // For native asset, extends with dummy notes Ok(SpendInfo::dummy(AssetBase::native(), &mut rng)) } else { - // For ZSA asset, extends with split_notes. + // For ZSA asset, extends with split note. // If SpendInfo is none, return an error (no split note are available for this asset) spend .map(|s| s.create_split_spend(&mut rng)) @@ -1033,8 +1033,8 @@ fn build_bundle( bundle_type: BundleType, spends: Vec, outputs: Vec, - split_notes: BTreeMap, burn: BTreeMap, + reference_notes: BTreeMap, finisher: impl FnOnce( Vec, // pre-actions Flags, // flags @@ -1077,7 +1077,7 @@ fn build_bundle( let mut first_spend = spends.first().map(|(s, _)| s.clone()); if first_spend.is_none() { - first_spend = split_notes.get(&asset).cloned(); + first_spend = reference_notes.get(&asset).cloned(); } let mut indexed_spends = spends diff --git a/tests/zsa.rs b/tests/zsa.rs index 3356cd5e4..780bda904 100644 --- a/tests/zsa.rs +++ b/tests/zsa.rs @@ -344,7 +344,7 @@ fn build_and_verify_bundle( fn build_and_verify_action_group( spends: Vec<&TestSpendInfo>, outputs: Vec, - split_notes: Vec<&TestSpendInfo>, + reference_notes: Vec<&TestSpendInfo>, anchor: Anchor, timelimit: u32, expected_num_actions: usize, @@ -366,10 +366,14 @@ fn build_and_verify_action_group( builder.add_output(None, keys.recipient, output.value, output.asset, None) }) .map_err(|err| err.to_string())?; - split_notes + reference_notes .iter() .try_for_each(|spend| { - builder.add_split_note(keys.fvk().clone(), spend.note, spend.merkle_path().clone()) + builder.add_reference_note( + keys.fvk().clone(), + spend.note, + spend.merkle_path().clone(), + ) }) .map_err(|err| err.to_string())?; build_and_sign_action_group(builder, timelimit, rng, keys.pk(), keys.sk()) @@ -830,7 +834,7 @@ fn swap_order_and_swap_bundle() { asset: AssetBase::native(), }, ], - // We must provide a split note for asset2 because we have no spend note for this asset. + // We must provide a reference note for asset2 because we have no spend note for this asset. // This note will not be spent. It is only used to check the correctness of asset2. vec![&asset2_spend1], anchor, @@ -867,7 +871,7 @@ fn swap_order_and_swap_bundle() { asset: AssetBase::native(), }, ], - // We must provide a split note for asset1 because we have no spend note for this asset. + // We must provide a reference note for asset1 because we have no spend note for this asset. // This note will not be spent. It is only used to check the correctness of asset1. vec![&asset1_spend1], anchor, From affdb39ef2b0e0dd1a1a384b93b861903cd562c2 Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Tue, 29 Oct 2024 10:14:45 +0100 Subject: [PATCH 20/48] Create SpecificBuilderParams --- src/builder.rs | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 6897e782f..b1e907ee0 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -796,9 +796,7 @@ impl Builder { self.bundle_type, self.spends, self.outputs, - self.burn, - self.reference_notes, - true, + SpecificBuilderParams::BundleParams(self.burn), ) } @@ -820,9 +818,7 @@ impl Builder { self.bundle_type, self.spends, self.outputs, - self.burn, - self.reference_notes, - false, + SpecificBuilderParams::ActionGroupParams(self.reference_notes), )? .unwrap() .0; @@ -962,6 +958,19 @@ fn pad_spend( } } +/// Specific parameters for the builder to build a bundle. +/// +/// If it is a BundleParams, it contains burn info. +/// If it is an ActionGroupParams, it contains reference notes. +/// Checking that bsk and bvk are consistent will be only performed for BundleParams. +#[derive(Debug)] +pub enum SpecificBuilderParams { + /// BundleParams contains burn info + BundleParams(HashMap), + /// ActionGroupParams contains reference notes + ActionGroupParams(HashMap), +} + /// Builds a bundle containing the given spent notes, outputs and burns. /// /// The returned bundle will have no proof or signatures; these can be applied with @@ -1035,6 +1044,7 @@ fn build_bundle( outputs: Vec, burn: BTreeMap, reference_notes: BTreeMap, + specific_params: SpecificBuilderParams, finisher: impl FnOnce( Vec, // pre-actions Flags, // flags @@ -1076,8 +1086,12 @@ fn build_bundle( let num_asset_pre_actions = spends.len().max(outputs.len()); let mut first_spend = spends.first().map(|(s, _)| s.clone()); - if first_spend.is_none() { - first_spend = reference_notes.get(&asset).cloned(); + if let SpecificBuilderParams::ActionGroupParams(ref reference_notes) = + specific_params + { + if first_spend.is_none() { + first_spend = reference_notes.get(&asset).cloned(); + } } let mut indexed_spends = spends From 8e7d804db2f4214bb6afe518a1248eb532baf088 Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Tue, 29 Oct 2024 10:15:06 +0100 Subject: [PATCH 21/48] Update SwapBundle creation --- src/swap_bundle.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/swap_bundle.rs b/src/swap_bundle.rs index db5197c8a..aad252a27 100644 --- a/src/swap_bundle.rs +++ b/src/swap_bundle.rs @@ -143,8 +143,13 @@ impl + std::iter::Sum> SwapBundle { .sum(); // Evaluate the swap bsk by summing the bsk of each action group. let bsk = action_groups - .iter() - .map(|a| ValueCommitTrapdoor::from_bsk(a.bsk.unwrap())) + .iter_mut() + .map(|ag| { + let bsk = ValueCommitTrapdoor::from_bsk(ag.bsk.unwrap()); + // Remove the bsk of each action group as it is no longer needed. + ag.remove_bsk(); + bsk + }) .sum::() .into_bsk(); // Evaluate the swap sighash @@ -156,8 +161,6 @@ impl + std::iter::Sum> SwapBundle { // Evaluate the swap binding signature which is equal to the signature of the swap sigash // with the swap binding signature key bsk. let binding_signature = bsk.sign(rng, &sighash); - // Remove the bsk of each action group as it is no longer needed. - action_groups.iter_mut().for_each(|ag| ag.remove_bsk()); // Create the swap bundle SwapBundle { action_groups, From 5b1feb688946cbcdb23300a39e98d028b6ad9d6a Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Tue, 29 Oct 2024 15:24:52 +0100 Subject: [PATCH 22/48] Add miner fees in swap tests --- tests/zsa.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/zsa.rs b/tests/zsa.rs index 780bda904..f5dab782a 100644 --- a/tests/zsa.rs +++ b/tests/zsa.rs @@ -885,9 +885,10 @@ fn swap_order_and_swap_bundle() { let action_group_matcher = build_and_verify_action_group( // The matcher spends nothing. vec![], - // The matcher receives 10 ZEC as a fee from user1 and user2. + // The matcher receives 5 ZEC as a fee from user1 and user2. + // The 5 ZEC remaining from user1 and user2 are miner fees. vec![TestOutputInfo { - value: NoteValue::from_raw(10), + value: NoteValue::from_raw(5), asset: AssetBase::native(), }], vec![], From 7a91e37e5a0eeb904256a291d33827560a5abd31 Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Tue, 29 Oct 2024 16:56:45 +0100 Subject: [PATCH 23/48] Create reference keys in swap tests All set of keys (user1, user2, matcher, reference) must be different. --- tests/zsa.rs | 114 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 103 insertions(+), 11 deletions(-) diff --git a/tests/zsa.rs b/tests/zsa.rs index f5dab782a..c6ee6d06a 100644 --- a/tests/zsa.rs +++ b/tests/zsa.rs @@ -102,12 +102,20 @@ fn build_and_sign_action_group( mut rng: OsRng, pk: &ProvingKey, sk: &SpendingKey, + reference_sk: &SpendingKey, ) -> ActionGroup { let unauthorized = builder.build_action_group(&mut rng, timelimit).unwrap(); let sighash = unauthorized.commitment().into(); let proven = unauthorized.create_proof(pk, &mut rng).unwrap(); proven - .apply_signatures(rng, sighash, &[SpendAuthorizingKey::from(sk)]) + .apply_signatures( + rng, + sighash, + &[ + SpendAuthorizingKey::from(sk), + SpendAuthorizingKey::from(reference_sk), + ], + ) .unwrap() } @@ -244,6 +252,63 @@ fn issue_zsa_notes( (*reference_note, *note1, *note2) } +fn issue_zsa_notes_with_reference_note( + asset_descr: &[u8], + keys: &Keychain, + reference_address: Address, +) -> (Note, Note, Note) { + let mut rng = OsRng; + // Create a issuance bundle + let unauthorized_asset = IssueBundle::new( + keys.ik().clone(), + asset_descr.to_owned(), + Some(IssueInfo { + recipient: keys.recipient, + value: NoteValue::from_raw(40), + }), + &mut rng, + ); + + assert!(unauthorized_asset.is_ok()); + + let (mut unauthorized, _) = unauthorized_asset.unwrap(); + + assert!(unauthorized + .add_recipient( + asset_descr, + keys.recipient, + NoteValue::from_raw(2), + &mut rng, + ) + .is_ok()); + + assert!(unauthorized + .add_recipient( + asset_descr, + reference_address, + NoteValue::from_raw(0), + &mut rng, + ) + .is_ok()); + + let issue_bundle = sign_issue_bundle(unauthorized, keys.isk()); + + // Take notes from first action + let notes = issue_bundle.get_all_notes(); + let note1 = notes[0]; + let note2 = notes[1]; + let note3 = notes[2]; + + assert!(verify_issue_bundle( + &issue_bundle, + issue_bundle.commitment().into(), + &HashSet::new(), + ) + .is_ok()); + + (*note1, *note2, *note3) +} + fn create_native_note(keys: &Keychain) -> Note { let mut rng = OsRng; @@ -341,6 +406,7 @@ fn build_and_verify_bundle( Ok(()) } +#[allow(clippy::too_many_arguments)] fn build_and_verify_action_group( spends: Vec<&TestSpendInfo>, outputs: Vec, @@ -349,6 +415,7 @@ fn build_and_verify_action_group( timelimit: u32, expected_num_actions: usize, keys: &Keychain, + reference_keys: &Keychain, ) -> Result, String> { let rng = OsRng; let shielded_action_group: ActionGroup<_, i64> = { @@ -370,13 +437,20 @@ fn build_and_verify_action_group( .iter() .try_for_each(|spend| { builder.add_reference_note( - keys.fvk().clone(), + reference_keys.fvk().clone(), spend.note, spend.merkle_path().clone(), ) }) .map_err(|err| err.to_string())?; - build_and_sign_action_group(builder, timelimit, rng, keys.pk(), keys.sk()) + build_and_sign_action_group( + builder, + timelimit, + rng, + keys.pk(), + keys.sk(), + reference_keys.sk(), + ) }; verify_action_group(&shielded_action_group, &keys.vk); @@ -729,26 +803,29 @@ fn swap_order_and_swap_bundle() { // - receives 10 asset1 // --------------------------- Setup ----------------------------------------- + let reference_keys = prepare_keys(0); // Create notes for user1 - let keys1 = prepare_keys(); + let keys1 = prepare_keys(5); let asset_descr1 = b"zsa_asset1".to_vec(); - let (asset1_note1, asset1_note2) = issue_zsa_notes(&asset_descr1, &keys1); + let (asset1_note1, asset1_note2, asset1_reference_note) = + issue_zsa_notes_with_reference_note(&asset_descr1, &keys1, reference_keys.recipient); let user1_native_note1 = create_native_note(&keys1); let user1_native_note2 = create_native_note(&keys1); // Create notes for user2 - let keys2 = prepare_keys(); + let keys2 = prepare_keys(10); let asset_descr2 = b"zsa_asset2".to_vec(); - let (asset2_note1, asset2_note2) = issue_zsa_notes(&asset_descr2, &keys2); + let (asset2_note1, asset2_note2, asset2_reference_note) = + issue_zsa_notes_with_reference_note(&asset_descr2, &keys2, reference_keys.recipient); let user2_native_note1 = create_native_note(&keys2); let user2_native_note2 = create_native_note(&keys2); // Create matcher keys - let matcher_keys = prepare_keys(); + let matcher_keys = prepare_keys(15); // Create Merkle tree with all notes let (merkle_paths, anchor) = build_merkle_paths(vec![ @@ -760,9 +837,11 @@ fn swap_order_and_swap_bundle() { &asset2_note2, &user2_native_note1, &user2_native_note2, + &asset1_reference_note, + &asset2_reference_note, ]); - assert_eq!(merkle_paths.len(), 8); + assert_eq!(merkle_paths.len(), 10); let merkle_path_asset1_note1 = merkle_paths[0].clone(); let merkle_path_asset1_note2 = merkle_paths[1].clone(); let merkle_path_user1_native_note1 = merkle_paths[2].clone(); @@ -771,6 +850,8 @@ fn swap_order_and_swap_bundle() { let merkle_path_asset2_note2 = merkle_paths[5].clone(); let merkle_path_user2_native_note1 = merkle_paths[6].clone(); let merkle_path_user2_native_note2 = merkle_paths[7].clone(); + let merkle_path_asset1_reference_note = merkle_paths[8].clone(); + let merkle_path_asset2_reference_note = merkle_paths[9].clone(); // Create TestSpendInfo let asset1_spend1 = TestSpendInfo { @@ -805,6 +886,14 @@ fn swap_order_and_swap_bundle() { note: user2_native_note2, merkle_path: merkle_path_user2_native_note2, }; + let asset1_reference_spend_note = TestSpendInfo { + note: asset1_reference_note, + merkle_path: merkle_path_asset1_reference_note, + }; + let asset2_reference_spend_note = TestSpendInfo { + note: asset2_reference_note, + merkle_path: merkle_path_asset2_reference_note, + }; // --------------------------- Tests ----------------------------------------- // 1. Create and verify ActionGroup for user1 @@ -836,11 +925,12 @@ fn swap_order_and_swap_bundle() { ], // We must provide a reference note for asset2 because we have no spend note for this asset. // This note will not be spent. It is only used to check the correctness of asset2. - vec![&asset2_spend1], + vec![&asset2_reference_spend_note], anchor, 0, 5, &keys1, + &reference_keys, ) .unwrap(); @@ -873,11 +963,12 @@ fn swap_order_and_swap_bundle() { ], // We must provide a reference note for asset1 because we have no spend note for this asset. // This note will not be spent. It is only used to check the correctness of asset1. - vec![&asset1_spend1], + vec![&asset1_reference_spend_note], anchor, 0, 5, &keys2, + &reference_keys, ) .unwrap(); @@ -896,6 +987,7 @@ fn swap_order_and_swap_bundle() { 0, 2, &matcher_keys, + &reference_keys, ) .unwrap(); From c4f01ad54dfb46f27333688bd36d005b6c489ec7 Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Tue, 29 Oct 2024 20:37:16 +0100 Subject: [PATCH 24/48] Add some swap tests --- tests/zsa.rs | 334 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 229 insertions(+), 105 deletions(-) diff --git a/tests/zsa.rs b/tests/zsa.rs index c6ee6d06a..87b3e6806 100644 --- a/tests/zsa.rs +++ b/tests/zsa.rs @@ -794,15 +794,7 @@ fn zsa_issue_and_transfer() { /// Create several action groups and combine them to create a SwapBundle #[test] fn swap_order_and_swap_bundle() { - // --------------------------- Swap description-------------------------------- - // User1: - // - spends 10 asset1 - // - receives 20 asset2 - // User2: - // - spends 20 asset2 - // - receives 10 asset1 - - // --------------------------- Setup ----------------------------------------- + // ----- Setup ----- let reference_keys = prepare_keys(0); // Create notes for user1 let keys1 = prepare_keys(5); @@ -895,106 +887,238 @@ fn swap_order_and_swap_bundle() { merkle_path: merkle_path_asset2_reference_note, }; - // --------------------------- Tests ----------------------------------------- - // 1. Create and verify ActionGroup for user1 - let action_group1 = build_and_verify_action_group( - vec![ - &asset1_spend1, // 40 asset1 - &asset1_spend2, // 2 asset1 - &user1_native_note1_spend, // 100 ZEC - &user1_native_note2_spend, // 100 ZEC - ], - vec![ - // User1 would like to spend 10 asset1. - // Thus, he would like to keep 40+2-10=32 asset1. - TestOutputInfo { - value: NoteValue::from_raw(32), - asset: asset1_note1.asset(), - }, - // User1 would like to receive 20 asset2. - TestOutputInfo { - value: NoteValue::from_raw(20), - asset: asset2_note1.asset(), - }, - // User1 would like to pay 5 ZEC as a fee. - // Thus, he would like to keep 100+100-5=195 ZEC. - TestOutputInfo { - value: NoteValue::from_raw(195), + // ----- Test 1: custom assets swap ----- + // User1: + // - spends 10 asset1 + // - receives 20 asset2 + // User2: + // - spends 20 asset2 + // - receives 10 asset1 + + { + // 1. Create and verify ActionGroup for user1 + let action_group1 = build_and_verify_action_group( + vec![ + &asset1_spend1, // 40 asset1 + &asset1_spend2, // 2 asset1 + &user1_native_note1_spend, // 100 ZEC + &user1_native_note2_spend, // 100 ZEC + ], + vec![ + // User1 would like to spend 10 asset1. + // Thus, he would like to keep 40+2-10=32 asset1. + TestOutputInfo { + value: NoteValue::from_raw(32), + asset: asset1_note1.asset(), + }, + // User1 would like to receive 20 asset2. + TestOutputInfo { + value: NoteValue::from_raw(20), + asset: asset2_note1.asset(), + }, + // User1 would like to pay 5 ZEC as a fee. + // Thus, he would like to keep 100+100-5=195 ZEC. + TestOutputInfo { + value: NoteValue::from_raw(195), + asset: AssetBase::native(), + }, + ], + // We must provide a reference note for asset2 because we have no spend note for this asset. + // This note will not be spent. It is only used to check the correctness of asset2. + vec![&asset2_reference_spend_note], + anchor, + 0, + 5, + &keys1, + &reference_keys, + ) + .unwrap(); + + // 2. Create and verify ActionGroup for user2 + let action_group2 = build_and_verify_action_group( + vec![ + &asset2_spend1, // 40 asset2 + &asset2_spend2, // 2 asset2 + &user2_native_note1_spend, // 100 ZEC + ], + vec![ + // User2 would like to spend 20 asset2. + // Thus, he would like to keep 40+2-20=22 asset2. + TestOutputInfo { + value: NoteValue::from_raw(22), + asset: asset2_note1.asset(), + }, + // User2 would like to receive 10 asset1. + TestOutputInfo { + value: NoteValue::from_raw(10), + asset: asset1_note1.asset(), + }, + // User2 would like to pay 5 ZEC as a fee. + // Thus, he would like to keep 100-5=95 ZEC. + TestOutputInfo { + value: NoteValue::from_raw(95), + asset: AssetBase::native(), + }, + ], + // We must provide a reference note for asset1 because we have no spend note for this asset. + // This note will not be spent. It is only used to check the correctness of asset1. + vec![&asset1_reference_spend_note], + anchor, + 0, + 4, + &keys2, + &reference_keys, + ) + .unwrap(); + + // 3. Matcher fees action group + let action_group_matcher = build_and_verify_action_group( + // The matcher spends nothing. + vec![], + // The matcher receives 5 ZEC as a fee from user1 and user2. + // The 5 ZEC remaining from user1 and user2 are miner fees. + vec![TestOutputInfo { + value: NoteValue::from_raw(5), asset: AssetBase::native(), - }, - ], - // We must provide a reference note for asset2 because we have no spend note for this asset. - // This note will not be spent. It is only used to check the correctness of asset2. - vec![&asset2_reference_spend_note], - anchor, - 0, - 5, - &keys1, - &reference_keys, - ) - .unwrap(); + }], + vec![], + anchor, + 0, + 2, + &matcher_keys, + &reference_keys, + ) + .unwrap(); - // 2. Create and verify ActionGroup for user2 - let action_group2 = build_and_verify_action_group( - vec![ - &asset2_spend1, // 40 asset2 - &asset2_spend2, // 2 asset2 - &user2_native_note1_spend, // 100 ZEC - &user2_native_note2_spend, // 100 ZEC - ], - vec![ - // User2 would like to spend 20 asset2. - // Thus, he would like to keep 40+2-20=22 asset2. - TestOutputInfo { - value: NoteValue::from_raw(22), - asset: asset2_note1.asset(), - }, - // User2 would like to receive 10 asset1. - TestOutputInfo { + // 4. Create a SwapBundle from the three previous ActionGroups + let swap_bundle = SwapBundle::new( + OsRng, + vec![action_group1, action_group2, action_group_matcher], + ); + verify_swap_bundle(&swap_bundle, vec![&keys1.vk, &keys2.vk, &matcher_keys.vk]); + } + + // ----- Test 2: custom asset / ZEC swap ----- + // User1: + // - spends 10 asset1 + // - receives 150 ZEC + // User2: + // - spends 150 ZEC + // - receives 10 asset1 + // Only user2 pays the fees + + { + // 1. Create and verify ActionGroup for user1 + let action_group1 = build_and_verify_action_group( + vec![ + &asset1_spend1, // 40 asset1 + &asset1_spend2, // 2 asset1 + ], + vec![ + // User1 would like to spend 10 asset1. + // Thus, he would like to keep 40+2-10=32 asset1. + TestOutputInfo { + value: NoteValue::from_raw(32), + asset: asset1_note1.asset(), + }, + // User1 would like to receive 150 ZEC. + TestOutputInfo { + value: NoteValue::from_raw(150), + asset: AssetBase::native(), + }, + ], + // No need of reference note for receiving ZEC + vec![], + anchor, + 0, + 3, + &keys1, + &reference_keys, + ) + .unwrap(); + + // 2. Create and verify ActionGroup for user2 + let action_group2 = build_and_verify_action_group( + vec![ + &user2_native_note1_spend, // 100 ZEC + &user2_native_note2_spend, // 100 ZEC + ], + vec![ + // User2 would like to send 150 ZEC to user1 and pays 15 ZEC as fees . + // Thus, he would like to keep 100+100-150-15=35 ZEC. + TestOutputInfo { + value: NoteValue::from_raw(35), + asset: AssetBase::native(), + }, + // User2 would like to receive 10 asset1. + TestOutputInfo { + value: NoteValue::from_raw(10), + asset: asset1_note1.asset(), + }, + ], + // We must provide a reference note for asset1 because we have no spend note for this asset. + // This note will not be spent. It is only used to check the correctness of asset1. + vec![&asset1_reference_spend_note], + anchor, + 0, + 3, + &keys2, + &reference_keys, + ) + .unwrap(); + + // 3. Matcher fees action group + let action_group_matcher = build_and_verify_action_group( + // The matcher spends nothing. + vec![], + // The matcher receives 10 ZEC as a fee from user2. + // The 5 ZEC remaining from user2 are miner fees. + vec![TestOutputInfo { value: NoteValue::from_raw(10), - asset: asset1_note1.asset(), - }, - // User2 would like to pay 5 ZEC as a fee. - // Thus, he would like to keep 100+100-5=195 ZEC. - TestOutputInfo { - value: NoteValue::from_raw(195), asset: AssetBase::native(), - }, - ], - // We must provide a reference note for asset1 because we have no spend note for this asset. - // This note will not be spent. It is only used to check the correctness of asset1. - vec![&asset1_reference_spend_note], - anchor, - 0, - 5, - &keys2, - &reference_keys, - ) - .unwrap(); + }], + vec![], + anchor, + 0, + 2, + &matcher_keys, + &reference_keys, + ) + .unwrap(); - // 3. Matcher fees action group - let action_group_matcher = build_and_verify_action_group( - // The matcher spends nothing. - vec![], - // The matcher receives 5 ZEC as a fee from user1 and user2. - // The 5 ZEC remaining from user1 and user2 are miner fees. - vec![TestOutputInfo { - value: NoteValue::from_raw(5), - asset: AssetBase::native(), - }], - vec![], - anchor, - 0, - 2, - &matcher_keys, - &reference_keys, - ) - .unwrap(); + // 4. Create a SwapBundle from the three previous ActionGroups + let swap_bundle = SwapBundle::new( + OsRng, + vec![action_group1, action_group2, action_group_matcher], + ); + verify_swap_bundle(&swap_bundle, vec![&keys1.vk, &keys2.vk, &matcher_keys.vk]); + } - // 4. Create a SwapBundle from the three previous ActionGroups - let swap_bundle = SwapBundle::new( - OsRng, - vec![action_group1, action_group2, action_group_matcher], - ); - verify_swap_bundle(&swap_bundle, vec![&keys1.vk, &keys2.vk, &matcher_keys.vk]); + // ----- Test 3: ZSA transaction using Swap ----- + // User1 would like to join his two asset1 notes + { + // 1. Create and verify ActionGroup + let action_group = build_and_verify_action_group( + vec![ + &asset1_spend1, // 40 asset1 + &asset1_spend2, // 2 asset1 + ], + vec![TestOutputInfo { + value: NoteValue::from_raw(42), + asset: asset1_note1.asset(), + }], + // No need of reference note + vec![], + anchor, + 0, + 2, + &keys1, + &reference_keys, + ) + .unwrap(); + + // 2. Create a SwapBundle from the previous ActionGroup + let swap_bundle = SwapBundle::new(OsRng, vec![action_group]); + verify_swap_bundle(&swap_bundle, vec![&keys1.vk]); + } } From a4ef3a3fc0da4baa8eb241620bd90f9bb0a65cc3 Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Thu, 31 Oct 2024 09:01:08 +0100 Subject: [PATCH 25/48] Rename sighsh by action_group_digest --- src/builder.rs | 23 ++++++++++++----------- src/swap_bundle.rs | 6 +++--- tests/builder.rs | 9 +++++++-- tests/zsa.rs | 18 ++++++++++++++---- 4 files changed, 36 insertions(+), 20 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index b1e907ee0..4ce167e7a 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -800,7 +800,7 @@ impl Builder { ) } - /// Builds an action group containing the given spent notes and outputs. + /// Builds an action group containing the given spent and output notes. /// /// The returned action group will have no proof or signatures; these can be applied with /// [`ActionGroup::create_proof`] and [`ActionGroup::apply_signatures`] respectively. @@ -950,7 +950,7 @@ fn pad_spend( // For native asset, extends with dummy notes Ok(SpendInfo::dummy(AssetBase::native(), &mut rng)) } else { - // For ZSA asset, extends with split note. + // For ZSA asset, extends with split_notes. // If SpendInfo is none, return an error (no split note are available for this asset) spend .map(|s| s.create_split_spend(&mut rng)) @@ -1314,7 +1314,7 @@ impl InProgressSignatures for PartiallyAuthorized { #[derive(Debug)] pub struct ActionGroupPartiallyAuthorized { bsk: redpallas::SigningKey, - sighash: [u8; 32], + action_group_digest: [u8; 32], } impl InProgressSignatures for ActionGroupPartiallyAuthorized { @@ -1380,20 +1380,20 @@ impl Bundle Bundle, V, D> { - /// Loads the sighash into this action group, preparing it for signing. + /// Loads the action_group_digest into this action group, preparing it for signing. /// - /// This API ensures that all signatures are created over the same sighash. + /// This API ensures that all signatures are created over the same action_group_digest. pub fn prepare_for_action_group( self, mut rng: R, - sighash: [u8; 32], + action_group_digest: [u8; 32], ) -> Bundle, V, D> { self.map_authorization( &mut rng, |rng, _, SigningMetadata { dummy_ask, parts }| { // We can create signatures for dummy spends immediately. dummy_ask - .map(|ask| ask.randomize(&parts.alpha).sign(rng, &sighash)) + .map(|ask| ask.randomize(&parts.alpha).sign(rng, &action_group_digest)) .map(MaybeSigned::Signature) .unwrap_or(MaybeSigned::SigningMetadata(parts)) }, @@ -1401,7 +1401,7 @@ impl Bundle Bundle, V, D> { pub fn apply_signatures_for_action_group( self, mut rng: R, - sighash: [u8; 32], + action_group_digest: [u8; 32], signing_keys: &[SpendAuthorizingKey], ) -> Result< ( @@ -1446,7 +1446,7 @@ impl Bundle, V, D> { signing_keys .iter() .fold( - self.prepare_for_action_group(&mut rng, sighash), + self.prepare_for_action_group(&mut rng, action_group_digest), |partial, ask| partial.sign(&mut rng, ask), ) .finalize() @@ -1466,7 +1466,8 @@ impl |rng, partial, maybe| match maybe { MaybeSigned::SigningMetadata(parts) if parts.ak == expected_ak => { MaybeSigned::Signature( - ask.randomize(&parts.alpha).sign(rng, &partial.sigs.sighash), + ask.randomize(&parts.alpha) + .sign(rng, &partial.sigs.action_group_digest), ) } s => s, diff --git a/src/swap_bundle.rs b/src/swap_bundle.rs index aad252a27..02e266e7b 100644 --- a/src/swap_bundle.rs +++ b/src/swap_bundle.rs @@ -63,7 +63,7 @@ impl ActionGroup { /// Remove bsk from this action group /// /// When creating a SwapBundle from a list of action groups, we evaluate the binding signature - /// by signing the sighash with the summ of the bsk of each action group. + /// by signing the sighash with the sum of the bsk of each action group. /// Then, we remove the bsk of each action group as it is no longer needed. fn remove_bsk(&mut self) { self.bsk = None; @@ -91,14 +91,14 @@ impl ActionGroup, V> { pub fn apply_signatures( self, mut rng: R, - sighash: [u8; 32], + action_group_digest: [u8; 32], signing_keys: &[SpendAuthorizingKey], ) -> Result, BuildError> { let (bsk, action_group) = signing_keys .iter() .fold( self.action_group - .prepare_for_action_group(&mut rng, sighash), + .prepare_for_action_group(&mut rng, action_group_digest), |partial, ask| partial.sign(&mut rng, ask), ) .finalize()?; diff --git a/tests/builder.rs b/tests/builder.rs index a671d8a61..0f371683c 100644 --- a/tests/builder.rs +++ b/tests/builder.rs @@ -74,9 +74,14 @@ pub fn verify_action_group( let action_group_bundle = action_group.action_group(); assert!(matches!(action_group_bundle.verify_proof(vk), Ok(()))); - let sighash: [u8; 32] = action_group.commitment().into(); + let action_group_digest: [u8; 32] = action_group.commitment().into(); for action in action_group_bundle.actions() { - assert_eq!(action.rk().verify(&sighash, action.authorization()), Ok(())); + assert_eq!( + action + .rk() + .verify(&action_group_digest, action.authorization()), + Ok(()) + ); } } diff --git a/tests/zsa.rs b/tests/zsa.rs index 87b3e6806..5cd3746b6 100644 --- a/tests/zsa.rs +++ b/tests/zsa.rs @@ -105,12 +105,12 @@ fn build_and_sign_action_group( reference_sk: &SpendingKey, ) -> ActionGroup { let unauthorized = builder.build_action_group(&mut rng, timelimit).unwrap(); - let sighash = unauthorized.commitment().into(); + let action_group_digest = unauthorized.commitment().into(); let proven = unauthorized.create_proof(pk, &mut rng).unwrap(); proven .apply_signatures( rng, - sighash, + action_group_digest, &[ SpendAuthorizingKey::from(sk), SpendAuthorizingKey::from(reference_sk), @@ -282,6 +282,7 @@ fn issue_zsa_notes_with_reference_note( ) .is_ok()); + // Create a reference note (with a note value equal to 0) assert!(unauthorized .add_recipient( asset_descr, @@ -791,11 +792,12 @@ fn zsa_issue_and_transfer() { } } -/// Create several action groups and combine them to create a SwapBundle +/// Create several action groups and combine them to create some swap bundles. #[test] fn swap_order_and_swap_bundle() { // ----- Setup ----- let reference_keys = prepare_keys(0); + // Create notes for user1 let keys1 = prepare_keys(5); @@ -891,9 +893,14 @@ fn swap_order_and_swap_bundle() { // User1: // - spends 10 asset1 // - receives 20 asset2 + // - pays 5 ZEC as fees // User2: // - spends 20 asset2 // - receives 10 asset1 + // - pays 5 ZEC as fees + // Matcher: + // - receives 5 ZEC as fees from user1 and user2 + // 5 ZEC are remaining for miner fees { // 1. Create and verify ActionGroup for user1 @@ -1005,7 +1012,10 @@ fn swap_order_and_swap_bundle() { // User2: // - spends 150 ZEC // - receives 10 asset1 - // Only user2 pays the fees + // - pays 15 ZEC as fees + // Matcher: + // - receives 10 ZEC as fees from user2 + // 5 ZEC are remaining for miner fees { // 1. Create and verify ActionGroup for user1 From 1ec39147cbc385fd4b3d7943e23c5d03e0ac4e1a Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Thu, 31 Oct 2024 09:24:40 +0100 Subject: [PATCH 26/48] Add ReferenceNotesAndKeys structure --- tests/zsa.rs | 99 ++++++++++++++++++++++++++++------------------------ 1 file changed, 54 insertions(+), 45 deletions(-) diff --git a/tests/zsa.rs b/tests/zsa.rs index 5cd3746b6..822268282 100644 --- a/tests/zsa.rs +++ b/tests/zsa.rs @@ -102,20 +102,18 @@ fn build_and_sign_action_group( mut rng: OsRng, pk: &ProvingKey, sk: &SpendingKey, - reference_sk: &SpendingKey, + reference_sk: Option<&SpendingKey>, ) -> ActionGroup { let unauthorized = builder.build_action_group(&mut rng, timelimit).unwrap(); let action_group_digest = unauthorized.commitment().into(); let proven = unauthorized.create_proof(pk, &mut rng).unwrap(); + + let mut signing_keys = vec![SpendAuthorizingKey::from(sk)]; + if let Some(reference_sk) = reference_sk { + signing_keys.push(SpendAuthorizingKey::from(reference_sk)); + } proven - .apply_signatures( - rng, - action_group_digest, - &[ - SpendAuthorizingKey::from(sk), - SpendAuthorizingKey::from(reference_sk), - ], - ) + .apply_signatures(rng, action_group_digest, signing_keys.as_slice()) .unwrap() } @@ -411,12 +409,11 @@ fn build_and_verify_bundle( fn build_and_verify_action_group( spends: Vec<&TestSpendInfo>, outputs: Vec, - reference_notes: Vec<&TestSpendInfo>, anchor: Anchor, timelimit: u32, expected_num_actions: usize, keys: &Keychain, - reference_keys: &Keychain, + references: Option, ) -> Result, String> { let rng = OsRng; let shielded_action_group: ActionGroup<_, i64> = { @@ -434,23 +431,28 @@ fn build_and_verify_action_group( builder.add_output(None, keys.recipient, output.value, output.asset, None) }) .map_err(|err| err.to_string())?; - reference_notes - .iter() - .try_for_each(|spend| { - builder.add_reference_note( - reference_keys.fvk().clone(), - spend.note, - spend.merkle_path().clone(), - ) - }) - .map_err(|err| err.to_string())?; + if let Some(ref references) = references { + references + .reference_notes + .iter() + .try_for_each(|spend| { + builder.add_reference_note( + references.reference_keys.fvk().clone(), + spend.note, + spend.merkle_path().clone(), + ) + }) + .map_err(|err| err.to_string())?; + } build_and_sign_action_group( builder, timelimit, rng, keys.pk(), keys.sk(), - reference_keys.sk(), + references + .as_ref() + .map(|notes_keys| notes_keys.reference_keys.sk()), ) }; @@ -792,9 +794,14 @@ fn zsa_issue_and_transfer() { } } +pub struct ReferenceNotesAndKeys<'a> { + reference_notes: Vec<&'a TestSpendInfo>, + reference_keys: &'a Keychain, +} + /// Create several action groups and combine them to create some swap bundles. #[test] -fn swap_order_and_swap_bundle() { +fn action_group_and_swap_bundle() { // ----- Setup ----- let reference_keys = prepare_keys(0); @@ -930,14 +937,16 @@ fn swap_order_and_swap_bundle() { asset: AssetBase::native(), }, ], - // We must provide a reference note for asset2 because we have no spend note for this asset. - // This note will not be spent. It is only used to check the correctness of asset2. - vec![&asset2_reference_spend_note], anchor, 0, 5, &keys1, - &reference_keys, + // We must provide a reference note for asset2 because we have no spend note for this asset. + // This note will not be spent. It is only used to check the correctness of asset2. + Some(ReferenceNotesAndKeys { + reference_notes: vec![&asset2_reference_spend_note], + reference_keys: &reference_keys, + }), ) .unwrap(); @@ -967,14 +976,16 @@ fn swap_order_and_swap_bundle() { asset: AssetBase::native(), }, ], - // We must provide a reference note for asset1 because we have no spend note for this asset. - // This note will not be spent. It is only used to check the correctness of asset1. - vec![&asset1_reference_spend_note], anchor, 0, 4, &keys2, - &reference_keys, + // We must provide a reference note for asset1 because we have no spend note for this asset. + // This note will not be spent. It is only used to check the correctness of asset1. + Some(ReferenceNotesAndKeys { + reference_notes: vec![&asset1_reference_spend_note], + reference_keys: &reference_keys, + }), ) .unwrap(); @@ -988,12 +999,11 @@ fn swap_order_and_swap_bundle() { value: NoteValue::from_raw(5), asset: AssetBase::native(), }], - vec![], anchor, 0, 2, &matcher_keys, - &reference_keys, + None, ) .unwrap(); @@ -1037,13 +1047,12 @@ fn swap_order_and_swap_bundle() { asset: AssetBase::native(), }, ], - // No need of reference note for receiving ZEC - vec![], anchor, 0, 3, &keys1, - &reference_keys, + // No need of reference note for receiving ZEC + None, ) .unwrap(); @@ -1066,14 +1075,16 @@ fn swap_order_and_swap_bundle() { asset: asset1_note1.asset(), }, ], - // We must provide a reference note for asset1 because we have no spend note for this asset. - // This note will not be spent. It is only used to check the correctness of asset1. - vec![&asset1_reference_spend_note], anchor, 0, 3, &keys2, - &reference_keys, + // We must provide a reference note for asset1 because we have no spend note for this asset. + // This note will not be spent. It is only used to check the correctness of asset1. + Some(ReferenceNotesAndKeys { + reference_notes: vec![&asset1_reference_spend_note], + reference_keys: &reference_keys, + }), ) .unwrap(); @@ -1087,12 +1098,11 @@ fn swap_order_and_swap_bundle() { value: NoteValue::from_raw(10), asset: AssetBase::native(), }], - vec![], anchor, 0, 2, &matcher_keys, - &reference_keys, + None, ) .unwrap(); @@ -1117,13 +1127,12 @@ fn swap_order_and_swap_bundle() { value: NoteValue::from_raw(42), asset: asset1_note1.asset(), }], - // No need of reference note - vec![], anchor, 0, 2, &keys1, - &reference_keys, + // No need of reference note + None, ) .unwrap(); From b603651b3529b3f8a1f953d7c6794e7ec5fd9e09 Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Thu, 31 Oct 2024 14:50:41 +0100 Subject: [PATCH 27/48] Remove allow clippy too_many_arguments on build_and_verify_action_group --- tests/zsa.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/zsa.rs b/tests/zsa.rs index 822268282..f97ef44c6 100644 --- a/tests/zsa.rs +++ b/tests/zsa.rs @@ -405,7 +405,6 @@ fn build_and_verify_bundle( Ok(()) } -#[allow(clippy::too_many_arguments)] fn build_and_verify_action_group( spends: Vec<&TestSpendInfo>, outputs: Vec, From da62fd725d4b0052cfcd56018b29f73755ac3e54 Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Tue, 5 Nov 2024 16:39:18 +0100 Subject: [PATCH 28/48] Add reference notes into issuance.rs --- src/issuance.rs | 48 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/src/issuance.rs b/src/issuance.rs index 89356b320..e96634143 100644 --- a/src/issuance.rs +++ b/src/issuance.rs @@ -38,6 +38,11 @@ use Error::{ MissingReferenceNoteOnFirstIssuance, ValueOverflow, }; +use crate::constants::reference_keys::ReferenceKeys; +use crate::supply_info::{AssetSupply, SupplyInfo}; +use crate::value::{NoteValue, ValueSum}; +use crate::{Address, Note}; + /// Checks if a given note is a reference note. /// /// A reference note satisfies the following conditions: @@ -54,6 +59,8 @@ pub struct IssueBundle { ik: IssueValidatingKey, /// The list of issue actions that make up this bundle. actions: NonEmpty, + /// The list of reference notes created in this bundle. + reference_notes: HashMap, /// The authorization for this action. authorization: T, } @@ -335,11 +342,13 @@ impl IssueBundle { pub fn from_parts( ik: IssueValidatingKey, actions: NonEmpty, + reference_notes: HashMap, authorization: T, ) -> Self { IssueBundle { ik, actions, + reference_notes, authorization, } } @@ -353,6 +362,7 @@ impl IssueBundle { IssueBundle { ik: self.ik, actions: self.actions, + reference_notes: self.reference_notes, authorization: map_auth(authorization), } } @@ -383,6 +393,19 @@ impl IssueBundle { notes.push(create_reference_note(asset, &mut rng)); }; + let reference_note = Note::new( + ReferenceKeys::recipient(), + NoteValue::zero(), + asset, + Rho::from_nf_old(Nullifier::dummy(&mut rng)), + &mut rng, + ); + + let mut notes = vec![]; + if first_issuance { + notes.push(reference_note); + } + let action = match issue_info { None => IssueAction { asset_desc_hash, @@ -409,6 +432,13 @@ impl IssueBundle { }; ( + let reference_notes = if first_issuance { + HashMap::from([(asset, reference_note)]) + } else { + HashMap::new() + }; + + Ok(( IssueBundle { ik, actions: NonEmpty::new(action), @@ -457,6 +487,11 @@ impl IssueBundle { } None => { // Insert a new IssueAction. + let notes = if first_issuance { + vec![reference_note, note] + } else { + vec![note] + }; self.actions.push(IssueAction { asset_desc_hash, notes, @@ -465,6 +500,10 @@ impl IssueBundle { } }; + if first_issuance { + self.reference_notes.insert(asset, reference_note); + } + Ok(asset) } @@ -519,6 +558,7 @@ impl IssueBundle { IssueBundle { ik: self.ik, actions: self.actions, + reference_notes: self.reference_notes, authorization: Prepared { sighash }, } } @@ -962,11 +1002,6 @@ mod tests { ) } - /// This function computes the identity point on the Pallas curve and returns an Asset Base with that value. - fn identity_point() -> AssetBase { - let identity_point = (Point::generator() * -Scalar::one()) + Point::generator(); - AssetBase::from_bytes(&identity_point.to_bytes()).unwrap() - } #[test] fn action_verify_valid() { @@ -1989,6 +2024,7 @@ pub mod testing { use proptest::collection::vec; use proptest::prelude::*; use proptest::prop_compose; + use std::collections::HashMap; prop_compose! { /// Generate a uniformly distributed ZSA Schnorr signature @@ -2055,6 +2091,7 @@ pub mod testing { IssueBundle { ik, actions, + reference_notes: HashMap::new(), authorization: Prepared { sighash: fake_sighash } } } @@ -2076,6 +2113,7 @@ pub mod testing { IssueBundle { ik, actions, + reference_notes: HashMap::new(), authorization: Signed { signature: fake_sig }, } } From 1acef4f60ba49944416171df000b66e506899ddf Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Tue, 5 Nov 2024 17:16:45 +0100 Subject: [PATCH 29/48] Fix zsa tests due to new IssueBundle format --- src/builder.rs | 24 +++++++++--- tests/zsa.rs | 104 ++++++++++++++++++------------------------------- 2 files changed, 56 insertions(+), 72 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 4ce167e7a..b191f2370 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -16,6 +16,7 @@ use crate::{ builder::BuildError::{BurnNative, BurnZero}, bundle::{derive_bvk, Authorization, Authorized, Bundle, Flags}, circuit::{Circuit, Instance, OrchardCircuit, Proof, ProvingKey}, + constants::reference_keys::ReferenceKeys, keys::{ FullViewingKey, OutgoingViewingKey, Scope, SpendAuthorizingKey, SpendValidatingKey, SpendingKey, @@ -1388,14 +1389,27 @@ impl Bundle Bundle, V, D> { + let reference_ask = SpendAuthorizingKey::from(&ReferenceKeys::sk()); + let reference_ak: SpendValidatingKey = (&reference_ask).into(); self.map_authorization( &mut rng, |rng, _, SigningMetadata { dummy_ask, parts }| { - // We can create signatures for dummy spends immediately. - dummy_ask - .map(|ask| ask.randomize(&parts.alpha).sign(rng, &action_group_digest)) - .map(MaybeSigned::Signature) - .unwrap_or(MaybeSigned::SigningMetadata(parts)) + if let Some(ask) = dummy_ask { + // We can create signatures for dummy spends immediately. + return MaybeSigned::Signature( + ask.randomize(&parts.alpha).sign(rng, &action_group_digest), + ); + } + if parts.ak == reference_ak { + // We can create signatures for reference notes immediately. + MaybeSigned::Signature( + reference_ask + .randomize(&parts.alpha) + .sign(rng, &action_group_digest), + ) + } else { + MaybeSigned::SigningMetadata(parts) + } }, |_rng, auth| InProgress { proof: auth.proof, diff --git a/tests/zsa.rs b/tests/zsa.rs index f97ef44c6..639d9267c 100644 --- a/tests/zsa.rs +++ b/tests/zsa.rs @@ -17,7 +17,7 @@ use orchard::{ swap_bundle::{ActionGroup, ActionGroupAuthorized, SwapBundle}, tree::{MerkleHashOrchard, MerklePath}, value::NoteValue, - Address, Anchor, Bundle, Note, + Address, Anchor, Bundle, Note, ReferenceKeys, }; use rand::rngs::OsRng; use shardtree::{store::memory::MemoryShardStore, ShardTree}; @@ -102,18 +102,13 @@ fn build_and_sign_action_group( mut rng: OsRng, pk: &ProvingKey, sk: &SpendingKey, - reference_sk: Option<&SpendingKey>, ) -> ActionGroup { let unauthorized = builder.build_action_group(&mut rng, timelimit).unwrap(); let action_group_digest = unauthorized.commitment().into(); let proven = unauthorized.create_proof(pk, &mut rng).unwrap(); - let mut signing_keys = vec![SpendAuthorizingKey::from(sk)]; - if let Some(reference_sk) = reference_sk { - signing_keys.push(SpendAuthorizingKey::from(reference_sk)); - } proven - .apply_signatures(rng, action_group_digest, signing_keys.as_slice()) + .apply_signatures(rng, action_group_digest, &[SpendAuthorizingKey::from(sk)]) .unwrap() } @@ -305,7 +300,7 @@ fn issue_zsa_notes_with_reference_note( ) .is_ok()); - (*note1, *note2, *note3) + (*reference_note, *note1, *note2) } fn create_native_note(keys: &Keychain) -> Note { @@ -408,11 +403,11 @@ fn build_and_verify_bundle( fn build_and_verify_action_group( spends: Vec<&TestSpendInfo>, outputs: Vec, + reference_notes: Vec<&TestSpendInfo>, anchor: Anchor, timelimit: u32, expected_num_actions: usize, keys: &Keychain, - references: Option, ) -> Result, String> { let rng = OsRng; let shielded_action_group: ActionGroup<_, i64> = { @@ -430,29 +425,18 @@ fn build_and_verify_action_group( builder.add_output(None, keys.recipient, output.value, output.asset, None) }) .map_err(|err| err.to_string())?; - if let Some(ref references) = references { - references - .reference_notes - .iter() - .try_for_each(|spend| { - builder.add_reference_note( - references.reference_keys.fvk().clone(), - spend.note, - spend.merkle_path().clone(), - ) - }) - .map_err(|err| err.to_string())?; - } - build_and_sign_action_group( - builder, - timelimit, - rng, - keys.pk(), - keys.sk(), - references - .as_ref() - .map(|notes_keys| notes_keys.reference_keys.sk()), - ) + reference_notes + .iter() + .try_for_each(|spend| { + builder.add_reference_note( + ReferenceKeys::fvk(), + spend.note, + spend.merkle_path().clone(), + ) + }) + .map_err(|err| err.to_string())?; + + build_and_sign_action_group(builder, timelimit, rng, keys.pk(), keys.sk()) }; verify_action_group(&shielded_action_group, &keys.vk); @@ -793,23 +777,16 @@ fn zsa_issue_and_transfer() { } } -pub struct ReferenceNotesAndKeys<'a> { - reference_notes: Vec<&'a TestSpendInfo>, - reference_keys: &'a Keychain, -} - /// Create several action groups and combine them to create some swap bundles. #[test] fn action_group_and_swap_bundle() { // ----- Setup ----- - let reference_keys = prepare_keys(0); - // Create notes for user1 let keys1 = prepare_keys(5); let asset_descr1 = b"zsa_asset1".to_vec(); - let (asset1_note1, asset1_note2, asset1_reference_note) = - issue_zsa_notes_with_reference_note(&asset_descr1, &keys1, reference_keys.recipient); + let (asset1_reference_note, asset1_note1, asset1_note2) = + issue_zsa_notes(&asset_descr1, &keys1); let user1_native_note1 = create_native_note(&keys1); let user1_native_note2 = create_native_note(&keys1); @@ -818,8 +795,8 @@ fn action_group_and_swap_bundle() { let keys2 = prepare_keys(10); let asset_descr2 = b"zsa_asset2".to_vec(); - let (asset2_note1, asset2_note2, asset2_reference_note) = - issue_zsa_notes_with_reference_note(&asset_descr2, &keys2, reference_keys.recipient); + let (asset2_reference_note, asset2_note1, asset2_note2) = + issue_zsa_notes(&asset_descr2, &keys2); let user2_native_note1 = create_native_note(&keys2); let user2_native_note2 = create_native_note(&keys2); @@ -936,16 +913,13 @@ fn action_group_and_swap_bundle() { asset: AssetBase::native(), }, ], + // We must provide a reference note for asset2 because we have no spend note for this asset. + // This note will not be spent. It is only used to check the correctness of asset2. + vec![&asset2_reference_spend_note], anchor, 0, 5, &keys1, - // We must provide a reference note for asset2 because we have no spend note for this asset. - // This note will not be spent. It is only used to check the correctness of asset2. - Some(ReferenceNotesAndKeys { - reference_notes: vec![&asset2_reference_spend_note], - reference_keys: &reference_keys, - }), ) .unwrap(); @@ -975,16 +949,13 @@ fn action_group_and_swap_bundle() { asset: AssetBase::native(), }, ], + // We must provide a reference note for asset1 because we have no spend note for this asset. + // This note will not be spent. It is only used to check the correctness of asset1. + vec![&asset1_reference_spend_note], anchor, 0, 4, &keys2, - // We must provide a reference note for asset1 because we have no spend note for this asset. - // This note will not be spent. It is only used to check the correctness of asset1. - Some(ReferenceNotesAndKeys { - reference_notes: vec![&asset1_reference_spend_note], - reference_keys: &reference_keys, - }), ) .unwrap(); @@ -998,11 +969,12 @@ fn action_group_and_swap_bundle() { value: NoteValue::from_raw(5), asset: AssetBase::native(), }], + // No reference note needed + vec![], anchor, 0, 2, &matcher_keys, - None, ) .unwrap(); @@ -1046,12 +1018,12 @@ fn action_group_and_swap_bundle() { asset: AssetBase::native(), }, ], + // No need of reference note for receiving ZEC + vec![], anchor, 0, 3, &keys1, - // No need of reference note for receiving ZEC - None, ) .unwrap(); @@ -1074,16 +1046,13 @@ fn action_group_and_swap_bundle() { asset: asset1_note1.asset(), }, ], + // We must provide a reference note for asset1 because we have no spend note for this asset. + // This note will not be spent. It is only used to check the correctness of asset1. + vec![&asset1_reference_spend_note], anchor, 0, 3, &keys2, - // We must provide a reference note for asset1 because we have no spend note for this asset. - // This note will not be spent. It is only used to check the correctness of asset1. - Some(ReferenceNotesAndKeys { - reference_notes: vec![&asset1_reference_spend_note], - reference_keys: &reference_keys, - }), ) .unwrap(); @@ -1097,11 +1066,12 @@ fn action_group_and_swap_bundle() { value: NoteValue::from_raw(10), asset: AssetBase::native(), }], + // No reference note needed + vec![], anchor, 0, 2, &matcher_keys, - None, ) .unwrap(); @@ -1126,12 +1096,12 @@ fn action_group_and_swap_bundle() { value: NoteValue::from_raw(42), asset: asset1_note1.asset(), }], + // No reference note needed + vec![], anchor, 0, 2, &keys1, - // No need of reference note - None, ) .unwrap(); From bda812107f0b328726b0e7dd56950201ed4428ec Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Thu, 7 Nov 2024 11:32:42 +0100 Subject: [PATCH 30/48] Add recipient at TestOutputInfo --- tests/zsa.rs | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/tests/zsa.rs b/tests/zsa.rs index 639d9267c..e403c980a 100644 --- a/tests/zsa.rs +++ b/tests/zsa.rs @@ -422,7 +422,7 @@ fn build_and_verify_action_group( outputs .iter() .try_for_each(|output| { - builder.add_output(None, keys.recipient, output.value, output.asset, None) + builder.add_output(None, output.recipient, output.value, output.asset, None) }) .map_err(|err| err.to_string())?; reference_notes @@ -900,17 +900,20 @@ fn action_group_and_swap_bundle() { TestOutputInfo { value: NoteValue::from_raw(32), asset: asset1_note1.asset(), + recipient: keys1.recipient, }, // User1 would like to receive 20 asset2. TestOutputInfo { value: NoteValue::from_raw(20), asset: asset2_note1.asset(), + recipient: keys1.recipient, }, // User1 would like to pay 5 ZEC as a fee. // Thus, he would like to keep 100+100-5=195 ZEC. TestOutputInfo { value: NoteValue::from_raw(195), asset: AssetBase::native(), + recipient: keys1.recipient, }, ], // We must provide a reference note for asset2 because we have no spend note for this asset. @@ -936,17 +939,20 @@ fn action_group_and_swap_bundle() { TestOutputInfo { value: NoteValue::from_raw(22), asset: asset2_note1.asset(), + recipient: keys2.recipient, }, // User2 would like to receive 10 asset1. TestOutputInfo { value: NoteValue::from_raw(10), asset: asset1_note1.asset(), + recipient: keys2.recipient, }, // User2 would like to pay 5 ZEC as a fee. // Thus, he would like to keep 100-5=95 ZEC. TestOutputInfo { value: NoteValue::from_raw(95), asset: AssetBase::native(), + recipient: keys2.recipient, }, ], // We must provide a reference note for asset1 because we have no spend note for this asset. @@ -968,6 +974,7 @@ fn action_group_and_swap_bundle() { vec![TestOutputInfo { value: NoteValue::from_raw(5), asset: AssetBase::native(), + recipient: matcher_keys.recipient, }], // No reference note needed vec![], @@ -1011,11 +1018,13 @@ fn action_group_and_swap_bundle() { TestOutputInfo { value: NoteValue::from_raw(32), asset: asset1_note1.asset(), + recipient: keys1.recipient, }, // User1 would like to receive 150 ZEC. TestOutputInfo { value: NoteValue::from_raw(150), asset: AssetBase::native(), + recipient: keys1.recipient, }, ], // No need of reference note for receiving ZEC @@ -1039,11 +1048,13 @@ fn action_group_and_swap_bundle() { TestOutputInfo { value: NoteValue::from_raw(35), asset: AssetBase::native(), + recipient: keys2.recipient, }, // User2 would like to receive 10 asset1. TestOutputInfo { value: NoteValue::from_raw(10), asset: asset1_note1.asset(), + recipient: keys2.recipient, }, ], // We must provide a reference note for asset1 because we have no spend note for this asset. @@ -1065,6 +1076,7 @@ fn action_group_and_swap_bundle() { vec![TestOutputInfo { value: NoteValue::from_raw(10), asset: AssetBase::native(), + recipient: matcher_keys.recipient, }], // No reference note needed vec![], @@ -1084,7 +1096,7 @@ fn action_group_and_swap_bundle() { } // ----- Test 3: ZSA transaction using Swap ----- - // User1 would like to join his two asset1 notes + // User1 would like to send 30 asset1 to User2 { // 1. Create and verify ActionGroup let action_group = build_and_verify_action_group( @@ -1092,10 +1104,18 @@ fn action_group_and_swap_bundle() { &asset1_spend1, // 40 asset1 &asset1_spend2, // 2 asset1 ], - vec![TestOutputInfo { - value: NoteValue::from_raw(42), - asset: asset1_note1.asset(), - }], + vec![ + TestOutputInfo { + value: NoteValue::from_raw(30), + asset: asset1_note1.asset(), + recipient: keys2.recipient, + }, + TestOutputInfo { + value: NoteValue::from_raw(12), + asset: asset1_note1.asset(), + recipient: keys1.recipient, + }, + ], // No reference note needed vec![], anchor, From 5308cadb835dbfef3c4441edcd94de5a74f7d654 Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Tue, 12 Nov 2024 15:23:17 +0100 Subject: [PATCH 31/48] [WIP] Update comments --- src/builder.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/builder.rs b/src/builder.rs index b191f2370..8cf7385d6 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -963,7 +963,6 @@ fn pad_spend( /// /// If it is a BundleParams, it contains burn info. /// If it is an ActionGroupParams, it contains reference notes. -/// Checking that bsk and bvk are consistent will be only performed for BundleParams. #[derive(Debug)] pub enum SpecificBuilderParams { /// BundleParams contains burn info From 9552ad19d199027fa7533e14ded6c7e5e1e2330f Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Thu, 14 Nov 2024 12:51:49 +0100 Subject: [PATCH 32/48] When creating an action group from a builder, return metadata --- src/builder.rs | 19 ++++++++++++++----- tests/zsa.rs | 6 +++++- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 8cf7385d6..96c22f7b0 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -622,6 +622,12 @@ impl BundleMetadata { #[cfg(feature = "circuit")] pub type UnauthorizedBundleWithMetadata = (UnauthorizedBundle, BundleMetadata); +/// A tuple containing an in-progress action group with no proofs or signatures, and its associated metadata. +pub type UnauthorizedActionGroupWithMetadata = ( + ActionGroup, Unauthorized>, V>, + BundleMetadata, +); + /// 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. @@ -809,11 +815,11 @@ impl Builder { self, rng: impl RngCore, timelimit: u32, - ) -> Result, Unauthorized>, V>, BuildError> { + ) -> Result>, BuildError> { if !self.burn.is_empty() { return Err(BuildError::BurnNotEmptyInActionGroup); } - let action_group = bundle( + Ok(bundle( rng, self.anchor, self.bundle_type, @@ -821,9 +827,12 @@ impl Builder { self.outputs, SpecificBuilderParams::ActionGroupParams(self.reference_notes), )? - .unwrap() - .0; - Ok(ActionGroup::from_parts(action_group, timelimit, None)) + .map(|(action_group, metadata)| { + ( + ActionGroup::from_parts(action_group, timelimit, None), + metadata, + ) + })) } /// Builds a bundle containing the given spent notes and outputs along with their diff --git a/tests/zsa.rs b/tests/zsa.rs index e403c980a..fc0ed54eb 100644 --- a/tests/zsa.rs +++ b/tests/zsa.rs @@ -103,7 +103,11 @@ fn build_and_sign_action_group( pk: &ProvingKey, sk: &SpendingKey, ) -> ActionGroup { - let unauthorized = builder.build_action_group(&mut rng, timelimit).unwrap(); + let unauthorized = builder + .build_action_group(&mut rng, timelimit) + .unwrap() + .unwrap() + .0; let action_group_digest = unauthorized.commitment().into(); let proven = unauthorized.create_proof(pk, &mut rng).unwrap(); From 8238744aafbe4ebb7f38e86384d3d6fbe39e7e76 Mon Sep 17 00:00:00 2001 From: Alexey <2365507+alexeykoren@users.noreply.github.com> Date: Sun, 9 Mar 2025 18:45:49 +0100 Subject: [PATCH 33/48] Zsa Swap Utils (#141) This PR contains a set of additions/changes that allow ZSA Swaps to be implemented in librustzcash: - Adds Swap BundleType and Flag - Adds AuthorizedWithProof trait that defines proof() method for both Authorized and ActionGroupAuthorized - Adds utility methods e.g. from_parts() for SwapBundle, is_empty() in Builder - Changes visibility of some methods e.g. dummy() for testing in librustzcash - Removes ActionGroup structure --- benches/circuit.rs | 9 ++- rustfmt.toml | 1 + src/builder.rs | 65 +++++++++---------- src/bundle.rs | 64 +++++++++++++++---- src/bundle/batch.rs | 2 +- src/bundle/commitments.rs | 28 ++------ src/circuit/circuit_vanilla.rs | 3 +- src/circuit/circuit_zsa.rs | 3 +- src/primitives/orchard_primitives_vanilla.rs | 9 ++- src/primitives/orchard_primitives_zsa.rs | 5 ++ src/swap_bundle.rs | 67 ++++++++++++-------- tests/builder.rs | 7 +- tests/zsa.rs | 62 ++++++++++-------- 13 files changed, 196 insertions(+), 129 deletions(-) create mode 100644 rustfmt.toml diff --git a/benches/circuit.rs b/benches/circuit.rs index 579a34675..e084e376c 100644 --- a/benches/circuit.rs +++ b/benches/circuit.rs @@ -8,6 +8,7 @@ use pprof::criterion::{Output, PProfProfiler}; use orchard::{ builder::{Builder, BundleType}, + bundle::Authorization, circuit::{ProvingKey, VerifyingKey}, keys::{FullViewingKey, Scope, SpendingKey}, note::AssetBase, @@ -86,7 +87,13 @@ fn criterion_benchmark(c: &mut Criterion) { .unwrap(); assert!(bundle.verify_proof(&vk).is_ok()); group.bench_function(BenchmarkId::new("bundle", num_recipients), |b| { - b.iter(|| bundle.authorization().proof().verify(&vk, &instances)); + b.iter(|| { + bundle + .authorization() + .proof() + .unwrap() + .verify(&vk, &instances) + }); }); } } diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 000000000..fa091418c --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +reorder_imports = false diff --git a/src/builder.rs b/src/builder.rs index 96c22f7b0..44d72de5d 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -75,10 +75,16 @@ impl BundleType { bundle_required: false, }; + /// The default bundle with all flags enabled, including Asset Swaps. + pub const DEFAULT_SWAP: BundleType = BundleType::Transactional { + flags: Flags::ENABLED_WITH_SWAPS, + bundle_required: false, + }; + /// The DISABLED bundle type does not permit any bundle to be produced, and when used in the /// builder will prevent any spends or outputs from being added. pub const DISABLED: BundleType = BundleType::Transactional { - flags: Flags::from_parts(false, false, false), + flags: Flags::from_parts(false, false, false, false), bundle_required: false, }; @@ -654,6 +660,14 @@ impl Builder { } } + /// Returns true if the builder is empty. + pub fn is_empty(&self) -> bool { + self.spends.is_empty() + && self.outputs.is_empty() + && self.burn.is_empty() + && self.reference_notes.is_empty() + } + /// Adds a note to be spent in this transaction. /// /// - `note` is a spendable note, obtained by trial-decrypting an [`Action`] using the @@ -803,6 +817,7 @@ impl Builder { self.bundle_type, self.spends, self.outputs, + 0, SpecificBuilderParams::BundleParams(self.burn), ) } @@ -810,29 +825,24 @@ impl Builder { /// Builds an action group containing the given spent and output notes. /// /// The returned action group will have no proof or signatures; these can be applied with - /// [`ActionGroup::create_proof`] and [`ActionGroup::apply_signatures`] respectively. + /// [`Bundle::create_proof`] and [`Bundle::apply_signatures`] respectively. pub fn build_action_group>( self, rng: impl RngCore, - timelimit: u32, - ) -> Result>, BuildError> { + expiry_height: u32, + ) -> Result, BuildError> { if !self.burn.is_empty() { return Err(BuildError::BurnNotEmptyInActionGroup); } - Ok(bundle( + bundle( rng, self.anchor, self.bundle_type, self.spends, self.outputs, + expiry_height, SpecificBuilderParams::ActionGroupParams(self.reference_notes), - )? - .map(|(action_group, metadata)| { - ( - ActionGroup::from_parts(action_group, timelimit, None), - metadata, - ) - })) + ) } /// Builds a bundle containing the given spent notes and outputs along with their @@ -1051,6 +1061,7 @@ fn build_bundle( bundle_type: BundleType, spends: Vec, outputs: Vec, + expiry_height: u32, burn: BTreeMap, reference_notes: BTreeMap, specific_params: SpecificBuilderParams, @@ -1213,6 +1224,10 @@ pub struct InProgress { impl Authorization for InProgress { type SpendAuth = S::SpendAuth; + + fn proof(&self) -> Option<&Proof> { + None + } } /// Marker for a bundle without a proof. @@ -1302,8 +1317,8 @@ pub struct SigningMetadata { /// If this action is spending a dummy note, this field holds that note's spend /// authorizing key. /// - /// These keys are used automatically in [`Bundle::prepare`] or - /// [`Bundle::apply_signatures`] to sign dummy spends. + /// These keys are used automatically in [`Bundle::prepare`] or + /// [`Bundle::apply_signatures`] to sign dummy spends. dummy_ask: Option, parts: SigningParts, } @@ -1448,23 +1463,15 @@ impl Bundle, V, P> { }) .finalize() } -} -impl Bundle, V, D> { - /// Applies signatures to this action group, in order to authorize it. + /// Applies signatures to this bundle as an action group, in order to authorize it. #[allow(clippy::type_complexity)] pub fn apply_signatures_for_action_group( self, mut rng: R, action_group_digest: [u8; 32], signing_keys: &[SpendAuthorizingKey], - ) -> Result< - ( - redpallas::SigningKey, - Bundle, - ), - BuildError, - > { + ) -> Result<(Bundle, SigningKey), BuildError> { signing_keys .iter() .fold( @@ -1587,20 +1594,14 @@ impl Bundle Result< - ( - redpallas::SigningKey, - Bundle, - ), - BuildError, - > { + ) -> Result<(Bundle, SigningKey), BuildError> { let bsk = self.authorization().sigs.bsk; self.try_map_authorization( &mut (), |_, _, maybe| maybe.finalize(), |_, partial| Ok(ActionGroupAuthorized::from_parts(partial.proof)), ) - .map(|bundle| (bsk, bundle)) + .map(|bundle| (bundle, bsk)) } } diff --git a/src/bundle.rs b/src/bundle.rs index 38eda63bb..9f772b3a6 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -24,7 +24,7 @@ use memuse::DynamicUsage; use crate::{ action::Action, address::Address, - bundle::commitments::{hash_bundle_auth_data, hash_bundle_txid_data}, + bundle::commitments::{hash_bundle_auth_data, hash_bundle_txid_data, hash_action_group}, circuit::{Instance, Proof, VerifyingKey}, keys::{IncomingViewingKey, OutgoingViewingKey, PreparedIncomingViewingKey}, note::{AssetBase, Note}, @@ -35,6 +35,7 @@ use crate::{ value::{NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum}, Proof, }; +use crate::orchard_flavor::OrchardZSA; #[cfg(feature = "circuit")] use crate::circuit::{Instance, VerifyingKey}; @@ -77,12 +78,18 @@ pub struct Flags { /// If `false`, all notes within [`Action`]s in the transaction's [`Bundle`] are /// guaranteed to be notes with native asset. zsa_enabled: bool, + /// Flag denoting whether Asset Swaps are enabled. + /// + /// If `false`, [`Bundle`] is guaranteed to contain only one ['ActionGroup']. + swaps_enabled: bool, } const FLAG_SPENDS_ENABLED: u8 = 0b0000_0001; const FLAG_OUTPUTS_ENABLED: u8 = 0b0000_0010; const FLAG_ZSA_ENABLED: u8 = 0b0000_0100; -const FLAGS_EXPECTED_UNSET: u8 = !(FLAG_SPENDS_ENABLED | FLAG_OUTPUTS_ENABLED | FLAG_ZSA_ENABLED); +const FLAG_SWAPS_ENABLED: u8 = 0b0000_1000; +const FLAGS_EXPECTED_UNSET: u8 = + !(FLAG_SPENDS_ENABLED | FLAG_OUTPUTS_ENABLED | FLAG_ZSA_ENABLED | FLAG_SWAPS_ENABLED); impl Flags { /// Construct a set of flags from its constituent parts @@ -90,40 +97,54 @@ impl Flags { spends_enabled: bool, outputs_enabled: bool, zsa_enabled: bool, + swaps_enabled: bool, ) -> Self { Flags { spends_enabled, outputs_enabled, zsa_enabled, + swaps_enabled, } } - /// The flag set with both spends and outputs enabled and ZSA disabled. + /// The flag set with both spends and outputs enabled. ZSA and swaps are disabled. pub const ENABLED_WITHOUT_ZSA: Flags = Flags { spends_enabled: true, outputs_enabled: true, zsa_enabled: false, + swaps_enabled: false, }; - /// The flags set with spends, outputs and ZSA enabled. + /// The flags set with spends, outputs and ZSA enabled. Swaps are disabled. pub const ENABLED_WITH_ZSA: Flags = Flags { spends_enabled: true, outputs_enabled: true, zsa_enabled: true, + swaps_enabled: false, + }; + + /// The flags set with spends, outputs, ZSA and swaps enabled. + pub const ENABLED_WITH_SWAPS: Flags = Flags { + spends_enabled: true, + outputs_enabled: true, + zsa_enabled: true, + swaps_enabled: true, }; - /// The flag set with spends and ZSA disabled. + /// The flag set with spends, ZSA and swaps disabled. pub const SPENDS_DISABLED_WITHOUT_ZSA: Flags = Flags { spends_enabled: false, outputs_enabled: true, zsa_enabled: false, + swaps_enabled: false, }; - /// The flag set with spends disabled and ZSA enabled. + /// The flag set with spends disabled and ZSA enabled. Swaps are disabled. pub const SPENDS_DISABLED_WITH_ZSA: Flags = Flags { spends_enabled: false, outputs_enabled: true, zsa_enabled: true, + swaps_enabled: false, }; /// The flag set with outputs disabled and ZSA disabled. @@ -131,6 +152,7 @@ impl Flags { spends_enabled: true, outputs_enabled: false, zsa_enabled: false, + swaps_enabled: false, }; /// Flag denoting whether Orchard spends are enabled in the transaction. @@ -190,6 +212,7 @@ impl Flags { spends_enabled: value & FLAG_SPENDS_ENABLED != 0, outputs_enabled: value & FLAG_OUTPUTS_ENABLED != 0, zsa_enabled: value & FLAG_ZSA_ENABLED != 0, + swaps_enabled: value & FLAG_SWAPS_ENABLED != 0, }) } else { None @@ -201,6 +224,9 @@ impl Flags { pub trait Authorization: fmt::Debug { /// The authorization type of an Orchard action. type SpendAuth: fmt::Debug + Clone; + + /// Return the proof component of the authorizing data. + fn proof(&self) -> Option<&Proof>; } /// A bundle of actions to be applied to the ledger. @@ -535,6 +561,14 @@ impl Authorization for EffectsOnly { type SpendAuth = (); } +impl> Bundle { + /// Computes a commitment to the effects of this bundle, + /// assuming that the bundle represents an action group inside a swap bundle. + pub fn action_group_commitment(&self) -> BundleCommitment { + BundleCommitment(hash_action_group(self)) + } +} + /// Authorizing data for a bundle of actions, ready to be committed to the ledger. #[derive(Debug, Clone)] pub struct Authorized { @@ -544,6 +578,11 @@ pub struct Authorized { impl Authorization for Authorized { type SpendAuth = VerSpendAuthSig; + + /// Return the proof component of the authorizing data. + fn proof(&self) -> Option<&Proof> { + Some(&self.proof) + } } impl Authorized { @@ -555,11 +594,6 @@ impl Authorized { } } - /// Return the proof component of the authorizing data. - pub fn proof(&self) -> &Proof { - &self.proof - } - /// Return the versioned binding signature. pub fn binding_signature(&self) -> &VerBindingSig { &self.binding_signature @@ -584,6 +618,7 @@ impl Bundle { pub fn verify_proof(&self, vk: &VerifyingKey) -> Result<(), halo2_proofs::plonk::Error> { self.authorization() .proof() + .unwrap() .verify(vk, &self.to_instances()) } } @@ -767,8 +802,8 @@ pub mod testing { prop_compose! { /// Create an arbitrary set of flags. - pub fn arb_flags()(spends_enabled in prop::bool::ANY, outputs_enabled in prop::bool::ANY, zsa_enabled in prop::bool::ANY) -> Flags { - Flags::from_parts(spends_enabled, outputs_enabled, zsa_enabled) + pub fn arb_flags()(spends_enabled in prop::bool::ANY, outputs_enabled in prop::bool::ANY, zsa_enabled in prop::bool::ANY, swaps_enabled in prop::bool::ANY) -> Flags { + Flags::from_parts(spends_enabled, outputs_enabled, zsa_enabled, swaps_enabled) } } @@ -803,7 +838,7 @@ pub mod testing { balances.into_iter().sum::>().unwrap(), burn, anchor, - None, + 0, super::EffectsOnly, ) } @@ -836,6 +871,7 @@ pub mod testing { balances.into_iter().sum::>().unwrap(), burn, anchor, + 0, Authorized { proof: Proof::new(fake_proof), binding_signature: VerBindingSig::new(P::default_sighash_version(), sk.sign(rng, &fake_sighash)), diff --git a/src/bundle/batch.rs b/src/bundle/batch.rs index 481dadd17..c9e5df38c 100644 --- a/src/bundle/batch.rs +++ b/src/bundle/batch.rs @@ -62,7 +62,7 @@ impl BatchValidator { bundle .authorization() - .proof() + .proof .add_to_batch(&mut self.proofs, bundle.to_instances()); } diff --git a/src/bundle/commitments.rs b/src/bundle/commitments.rs index 2ef8bea7e..62c965c98 100644 --- a/src/bundle/commitments.rs +++ b/src/bundle/commitments.rs @@ -9,6 +9,7 @@ use crate::{ issuance_sighash_versioning::IssueSighashVersion, orchard_sighash_versioning::OrchardSighashVersion, primitives::OrchardPrimitives, + orchard_flavor::OrchardZSA, }; pub(crate) const ZCASH_ORCHARD_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdOrchardHash"; @@ -65,7 +66,7 @@ pub fn hash_bundle_txid_empty() -> Blake2bHash { /// /// [zip228]: https://zips.z.cash/zip-0228 pub(crate) fn hash_action_group>( - action_group: &ActionGroup, + action_group: &Bundle, ) -> Blake2bHash { let mut agh = hasher(ZCASH_ORCHARD_ACTION_GROUP_HASH_PERSONALIZATION); let mut ch = hasher(ZCASH_ORCHARD_ACTIONS_COMPACT_HASH_PERSONALIZATION); @@ -79,26 +80,11 @@ pub(crate) fn hash_action_group>( &action.encrypted_note().enc_ciphertext.as_ref()[..OrchardZSA::COMPACT_NOTE_SIZE], ); - mh.update( - &action.encrypted_note().enc_ciphertext.as_ref() - [OrchardZSA::COMPACT_NOTE_SIZE..OrchardZSA::COMPACT_NOTE_SIZE + MEMO_SIZE], - ); - - nh.update(&action.cv_net().to_bytes()); - nh.update(&<[u8; 32]>::from(action.rk())); - nh.update( - &action.encrypted_note().enc_ciphertext.as_ref() - [OrchardZSA::COMPACT_NOTE_SIZE + MEMO_SIZE..], - ); - nh.update(&action.encrypted_note().out_ciphertext); - } + OrchardZSA::update_hash_with_actions(&mut agh, action_group); - agh.update(ch.finalize().as_bytes()); - agh.update(mh.finalize().as_bytes()); - agh.update(nh.finalize().as_bytes()); - agh.update(&[action_group.action_group().flags().to_byte()]); - agh.update(&action_group.action_group().anchor().to_bytes()); - agh.update(&action_group.timelimit().to_le_bytes()); + agh.update(&[action_group.flags().to_byte()]); + agh.update(&action_group.anchor().to_bytes()); + agh.update(&action_group.expiry_height().to_le_bytes()); agh.finalize() } @@ -107,7 +93,7 @@ pub(crate) fn hash_action_group>( /// /// [zip228]: https://zips.z.cash/zip-0228 pub(crate) fn hash_swap_bundle>( - action_groups: Vec<&ActionGroup>, + action_groups: Vec<&Bundle>, value_balance: V, ) -> Blake2bHash { let mut h = hasher(ZCASH_ORCHARD_HASH_PERSONALIZATION); diff --git a/src/circuit/circuit_vanilla.rs b/src/circuit/circuit_vanilla.rs index 767e6d6a2..14d1c705c 100644 --- a/src/circuit/circuit_vanilla.rs +++ b/src/circuit/circuit_vanilla.rs @@ -816,13 +816,14 @@ mod tests { let enable_spend = read_bool(&mut r); let enable_output = read_bool(&mut r); let enable_zsa = false; + let enable_swaps = false; let instance = Instance::from_parts( anchor, cv_net, nf_old, rk, cmx, - Flags::from_parts(enable_spend, enable_output, enable_zsa), + Flags::from_parts(enable_spend, enable_output, enable_zsa, enable_swaps), ); let mut proof_bytes = vec![]; diff --git a/src/circuit/circuit_zsa.rs b/src/circuit/circuit_zsa.rs index 41e796d6b..311c05255 100644 --- a/src/circuit/circuit_zsa.rs +++ b/src/circuit/circuit_zsa.rs @@ -1067,13 +1067,14 @@ mod tests { let enable_spend = read_bool(&mut r); let enable_output = read_bool(&mut r); let enable_zsa = read_bool(&mut r); + let enable_swaps = false; let instance = Instance::from_parts( anchor, cv_net, nf_old, rk, cmx, - Flags::from_parts(enable_spend, enable_output, enable_zsa), + Flags::from_parts(enable_spend, enable_output, enable_zsa, enable_swaps), ); let mut proof_bytes = vec![]; diff --git a/src/primitives/orchard_primitives_vanilla.rs b/src/primitives/orchard_primitives_vanilla.rs index 3d5ff74b4..8207ece80 100644 --- a/src/primitives/orchard_primitives_vanilla.rs +++ b/src/primitives/orchard_primitives_vanilla.rs @@ -5,6 +5,13 @@ use alloc::{collections::BTreeMap, vec::Vec}; use blake2b_simd::Hash as Blake2bHash; use zcash_note_encryption::note_bytes::NoteBytesData; +use super::{ + orchard_domain::OrchardDomainCommon, + zcash_note_encryption_domain::{ + build_base_note_plaintext_bytes, Memo, COMPACT_NOTE_SIZE_VANILLA, NOTE_VERSION_BYTE_V2, + }, +}; + use crate::{ bundle::{ commitments::{ @@ -100,7 +107,7 @@ impl OrchardPrimitives for OrchardVanilla { _: &BTreeMap>, ) -> Blake2bHash { let mut h = hasher(ZCASH_ORCHARD_SIGS_HASH_PERSONALIZATION); - h.update(bundle.authorization().proof().as_ref()); + h.update(bundle.authorization().proof().unwrap().as_ref()); for action in bundle.actions().iter() { h.update(&<[u8; 64]>::from(action.authorization().sig())); } diff --git a/src/primitives/orchard_primitives_zsa.rs b/src/primitives/orchard_primitives_zsa.rs index 537d1315d..6c4647f28 100644 --- a/src/primitives/orchard_primitives_zsa.rs +++ b/src/primitives/orchard_primitives_zsa.rs @@ -5,6 +5,11 @@ use alloc::{collections::BTreeMap, vec::Vec}; use blake2b_simd::Hash as Blake2bHash; use zcash_note_encryption::note_bytes::NoteBytesData; +use crate::bundle::commitments::{ + hash_action_group, ZCASH_ORCHARD_ACTION_GROUPS_SIGS_HASH_PERSONALIZATION, + ZCASH_ORCHARD_SIGS_HASH_PERSONALIZATION, +}; +use crate::bundle::Authorized; use crate::{ bundle::{ commitments::{ diff --git a/src/swap_bundle.rs b/src/swap_bundle.rs index 02e266e7b..93fe32aba 100644 --- a/src/swap_bundle.rs +++ b/src/swap_bundle.rs @@ -1,8 +1,7 @@ //! Structs related to swap bundles. use crate::{ - builder::{BuildError, InProgress, InProgressSignatures, Unauthorized, Unproven}, - bundle::commitments::{hash_action_group, hash_swap_bundle}, + bundle::commitments::hash_swap_bundle, bundle::{derive_bvk, Authorization, Bundle, BundleCommitment}, circuit::{ProvingKey, VerifyingKey}, keys::SpendAuthorizingKey, @@ -118,10 +117,10 @@ impl> ActionGroup { } /// A swap bundle to be applied to the ledger. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct SwapBundle { /// The list of action groups that make up this swap bundle. - action_groups: Vec>, + action_groups: Vec>, /// The net value moved out of this swap. /// /// This is the sum of Orchard spends minus the sum of Orchard outputs. @@ -130,26 +129,36 @@ pub struct SwapBundle { binding_signature: redpallas::Signature, } +impl SwapBundle { + /// Constructs a `SwapBundle` from its constituent parts. + pub fn from_parts( + action_groups: Vec>, + value_balance: V, + binding_signature: redpallas::Signature, + ) -> Self { + SwapBundle { + action_groups, + value_balance, + binding_signature, + } + } +} + impl + std::iter::Sum> SwapBundle { - /// Constructs a `SwapBundle` from its action groups. + /// Constructs a `SwapBundle` from its action groups and respective binding signature keys. + /// Keys should go in the same order as the action groups. pub fn new( rng: R, - mut action_groups: Vec>, + action_groups: Vec>, + bsks: Vec>, ) -> Self { + assert_eq!(action_groups.len(), bsks.len()); // Evaluate the swap value balance by summing the value balance of each action group. - let value_balance = action_groups - .iter() - .map(|a| *a.action_group().value_balance()) - .sum(); + let value_balance = action_groups.iter().map(|a| *a.value_balance()).sum(); // Evaluate the swap bsk by summing the bsk of each action group. - let bsk = action_groups - .iter_mut() - .map(|ag| { - let bsk = ValueCommitTrapdoor::from_bsk(ag.bsk.unwrap()); - // Remove the bsk of each action group as it is no longer needed. - ag.remove_bsk(); - bsk - }) + let bsk = bsks + .into_iter() + .map(ValueCommitTrapdoor::from_bsk) .sum::() .into_bsk(); // Evaluate the swap sighash @@ -178,6 +187,11 @@ pub struct ActionGroupAuthorized { impl Authorization for ActionGroupAuthorized { type SpendAuth = redpallas::Signature; + + /// Return the proof component of the authorizing data. + fn proof(&self) -> Option<&Proof> { + Some(&self.proof) + } } impl ActionGroupAuthorized { @@ -185,11 +199,6 @@ impl ActionGroupAuthorized { pub fn from_parts(proof: Proof) -> Self { ActionGroupAuthorized { proof } } - - /// Return the proof component of the authorizing data. - pub fn proof(&self) -> &Proof { - &self.proof - } } impl Bundle { @@ -197,13 +206,14 @@ impl Bundle { pub fn verify_proof(&self, vk: &VerifyingKey) -> Result<(), halo2_proofs::plonk::Error> { self.authorization() .proof() + .unwrap() .verify(vk, &self.to_instances()) } } impl SwapBundle { /// Returns the list of action groups that make up this swap bundle. - pub fn action_groups(&self) -> &Vec> { + pub fn action_groups(&self) -> &Vec> { &self.action_groups } @@ -211,6 +221,13 @@ impl SwapBundle { pub fn binding_signature(&self) -> &redpallas::Signature { &self.binding_signature } + + /// The net value moved out of this swap. + /// + /// This is the sum of Orchard spends minus the sum of Orchard outputs. + pub fn value_balance(&self) -> &V { + &self.value_balance + } } impl> SwapBundle { @@ -228,7 +245,7 @@ impl> SwapBundle { let actions = self .action_groups .iter() - .flat_map(|ag| ag.action_group().actions()) + .flat_map(|ag| ag.actions()) .collect::>(); derive_bvk( actions, diff --git a/tests/builder.rs b/tests/builder.rs index 0f371683c..56596e0e7 100644 --- a/tests/builder.rs +++ b/tests/builder.rs @@ -52,8 +52,6 @@ pub fn verify_swap_bundle(swap_bundle: &SwapBundle, vks: Vec<&VerifyingKey> assert_eq!(vks.len(), swap_bundle.action_groups().len()); for (action_group, vk) in swap_bundle.action_groups().iter().zip(vks.iter()) { verify_action_group(action_group, vk); - // Verify that bsk is None - assert!(action_group.bsk().is_none()); } let sighash: [u8; 32] = swap_bundle.commitment().into(); @@ -68,13 +66,12 @@ pub fn verify_swap_bundle(swap_bundle: &SwapBundle, vks: Vec<&VerifyingKey> // - verify the proof // - verify the signature on each action pub fn verify_action_group( - action_group: &ActionGroup, + action_group_bundle: &Bundle, vk: &VerifyingKey, ) { - let action_group_bundle = action_group.action_group(); assert!(matches!(action_group_bundle.verify_proof(vk), Ok(()))); - let action_group_digest: [u8; 32] = action_group.commitment().into(); + let action_group_digest: [u8; 32] = action_group_bundle.action_group_commitment().into(); for action in action_group_bundle.actions() { assert_eq!( action diff --git a/tests/zsa.rs b/tests/zsa.rs index fc0ed54eb..58878b7c9 100644 --- a/tests/zsa.rs +++ b/tests/zsa.rs @@ -14,7 +14,8 @@ use orchard::{ note::{AssetBase, ExtractedNoteCommitment}, note_encryption::OrchardDomain, orchard_flavor::OrchardZSA, - swap_bundle::{ActionGroup, ActionGroupAuthorized, SwapBundle}, + primitives::redpallas::{Binding, SigningKey}, + swap_bundle::{ActionGroupAuthorized, SwapBundle}, tree::{MerkleHashOrchard, MerklePath}, value::NoteValue, Address, Anchor, Bundle, Note, ReferenceKeys, @@ -102,17 +103,20 @@ fn build_and_sign_action_group( mut rng: OsRng, pk: &ProvingKey, sk: &SpendingKey, -) -> ActionGroup { - let unauthorized = builder - .build_action_group(&mut rng, timelimit) - .unwrap() - .unwrap() - .0; - let action_group_digest = unauthorized.commitment().into(); +) -> ( + Bundle, + SigningKey, +) { + let unauthorized = builder.build_action_group(&mut rng, timelimit).unwrap().0; + let action_group_digest = unauthorized.action_group_commitment().into(); let proven = unauthorized.create_proof(pk, &mut rng).unwrap(); proven - .apply_signatures(rng, action_group_digest, &[SpendAuthorizingKey::from(sk)]) + .apply_signatures_for_action_group( + rng, + action_group_digest, + &[SpendAuthorizingKey::from(sk)], + ) .unwrap() } @@ -404,6 +408,7 @@ fn build_and_verify_bundle( Ok(()) } +#[allow(clippy::type_complexity)] fn build_and_verify_action_group( spends: Vec<&TestSpendInfo>, outputs: Vec, @@ -412,9 +417,15 @@ fn build_and_verify_action_group( timelimit: u32, expected_num_actions: usize, keys: &Keychain, -) -> Result, String> { +) -> Result< + ( + Bundle, + SigningKey, + ), + String, +> { let rng = OsRng; - let shielded_action_group: ActionGroup<_, i64> = { + let (shielded_action_group, bsk) = { let mut builder = Builder::new(BundleType::DEFAULT_ZSA, anchor); spends @@ -444,14 +455,9 @@ fn build_and_verify_action_group( }; verify_action_group(&shielded_action_group, &keys.vk); - assert_eq!( - shielded_action_group.action_group().actions().len(), - expected_num_actions - ); - assert!(verify_unique_spent_nullifiers( - shielded_action_group.action_group() - )); - Ok(shielded_action_group) + assert_eq!(shielded_action_group.actions().len(), expected_num_actions); + assert!(verify_unique_spent_nullifiers(&shielded_action_group)); + Ok((shielded_action_group, bsk)) } fn verify_unique_spent_nullifiers(bundle: &Bundle) -> bool { @@ -891,7 +897,7 @@ fn action_group_and_swap_bundle() { { // 1. Create and verify ActionGroup for user1 - let action_group1 = build_and_verify_action_group( + let (action_group1, bsk1) = build_and_verify_action_group( vec![ &asset1_spend1, // 40 asset1 &asset1_spend2, // 2 asset1 @@ -931,7 +937,7 @@ fn action_group_and_swap_bundle() { .unwrap(); // 2. Create and verify ActionGroup for user2 - let action_group2 = build_and_verify_action_group( + let (action_group2, bsk2) = build_and_verify_action_group( vec![ &asset2_spend1, // 40 asset2 &asset2_spend2, // 2 asset2 @@ -970,7 +976,7 @@ fn action_group_and_swap_bundle() { .unwrap(); // 3. Matcher fees action group - let action_group_matcher = build_and_verify_action_group( + let (action_group_matcher, bsk_matcher) = build_and_verify_action_group( // The matcher spends nothing. vec![], // The matcher receives 5 ZEC as a fee from user1 and user2. @@ -993,6 +999,7 @@ fn action_group_and_swap_bundle() { let swap_bundle = SwapBundle::new( OsRng, vec![action_group1, action_group2, action_group_matcher], + vec![bsk1, bsk2, bsk_matcher], ); verify_swap_bundle(&swap_bundle, vec![&keys1.vk, &keys2.vk, &matcher_keys.vk]); } @@ -1011,7 +1018,7 @@ fn action_group_and_swap_bundle() { { // 1. Create and verify ActionGroup for user1 - let action_group1 = build_and_verify_action_group( + let (action_group1, bsk1) = build_and_verify_action_group( vec![ &asset1_spend1, // 40 asset1 &asset1_spend2, // 2 asset1 @@ -1041,7 +1048,7 @@ fn action_group_and_swap_bundle() { .unwrap(); // 2. Create and verify ActionGroup for user2 - let action_group2 = build_and_verify_action_group( + let (action_group2, bsk2) = build_and_verify_action_group( vec![ &user2_native_note1_spend, // 100 ZEC &user2_native_note2_spend, // 100 ZEC @@ -1072,7 +1079,7 @@ fn action_group_and_swap_bundle() { .unwrap(); // 3. Matcher fees action group - let action_group_matcher = build_and_verify_action_group( + let (action_group_matcher, bsk_matcher) = build_and_verify_action_group( // The matcher spends nothing. vec![], // The matcher receives 10 ZEC as a fee from user2. @@ -1095,6 +1102,7 @@ fn action_group_and_swap_bundle() { let swap_bundle = SwapBundle::new( OsRng, vec![action_group1, action_group2, action_group_matcher], + vec![bsk1, bsk2, bsk_matcher], ); verify_swap_bundle(&swap_bundle, vec![&keys1.vk, &keys2.vk, &matcher_keys.vk]); } @@ -1103,7 +1111,7 @@ fn action_group_and_swap_bundle() { // User1 would like to send 30 asset1 to User2 { // 1. Create and verify ActionGroup - let action_group = build_and_verify_action_group( + let (action_group, bsk1) = build_and_verify_action_group( vec![ &asset1_spend1, // 40 asset1 &asset1_spend2, // 2 asset1 @@ -1130,7 +1138,7 @@ fn action_group_and_swap_bundle() { .unwrap(); // 2. Create a SwapBundle from the previous ActionGroup - let swap_bundle = SwapBundle::new(OsRng, vec![action_group]); + let swap_bundle = SwapBundle::new(OsRng, vec![action_group], vec![bsk1]); verify_swap_bundle(&swap_bundle, vec![&keys1.vk]); } } From 4eea9840be80d13b35ac03d404c3807a4a97c32b Mon Sep 17 00:00:00 2001 From: alexeykoren <2365507+alexeykoren@users.noreply.github.com> Date: Tue, 22 Apr 2025 15:34:47 +0200 Subject: [PATCH 34/48] Add swaps flag serialization --- src/bundle.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/bundle.rs b/src/bundle.rs index 9f772b3a6..d5d1aba80 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -196,6 +196,9 @@ impl Flags { if self.zsa_enabled { value |= FLAG_ZSA_ENABLED; } + if self.swaps_enabled { + value |= FLAG_SWAPS_ENABLED; + } value } From 8ac892dda77a94b8facd0b92bb58bf2d6d439634 Mon Sep 17 00:00:00 2001 From: alexeykoren <2365507+alexeykoren@users.noreply.github.com> Date: Wed, 23 Apr 2025 17:48:54 +0200 Subject: [PATCH 35/48] Remove BurnNotEmptyInActionGroup error --- src/builder.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 44d72de5d..530dc72cc 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -168,8 +168,6 @@ pub enum BuildError { BurnDuplicateAsset, /// There is no available split note for this asset. NoSplitNoteAvailable, - /// Burning is not allowed in an ActionGroup. - BurnNotEmptyInActionGroup, } impl fmt::Display for BuildError { @@ -194,7 +192,6 @@ impl fmt::Display for BuildError { BurnZero => f.write_str("Burning is not possible for zero values"), BurnDuplicateAsset => f.write_str("Duplicate assets are not allowed when burning"), NoSplitNoteAvailable => f.write_str("No split note has been provided for this asset"), - BurnNotEmptyInActionGroup => f.write_str("Burning is not possible for action group"), } } } @@ -831,9 +828,6 @@ impl Builder { rng: impl RngCore, expiry_height: u32, ) -> Result, BuildError> { - if !self.burn.is_empty() { - return Err(BuildError::BurnNotEmptyInActionGroup); - } bundle( rng, self.anchor, From 216a507e9ba678a7fd554a8a0c1af4ad56752507 Mon Sep 17 00:00:00 2001 From: alexeykoren <2365507+alexeykoren@users.noreply.github.com> Date: Tue, 6 May 2025 14:22:28 +0200 Subject: [PATCH 36/48] Add burn to action group bundle --- src/builder.rs | 42 ++++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 530dc72cc..0593e3eb9 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -168,6 +168,8 @@ pub enum BuildError { BurnDuplicateAsset, /// There is no available split note for this asset. NoSplitNoteAvailable, + /// There are no reference notes provided for an action group. + NoReferenceNotesAvailable, } impl fmt::Display for BuildError { @@ -192,6 +194,9 @@ impl fmt::Display for BuildError { BurnZero => f.write_str("Burning is not possible for zero values"), BurnDuplicateAsset => f.write_str("Duplicate assets are not allowed when burning"), NoSplitNoteAvailable => f.write_str("No split note has been provided for this asset"), + NoReferenceNotesAvailable => { + f.write_str("No reference notes have been provided for action group") + } } } } @@ -815,7 +820,9 @@ impl Builder { self.spends, self.outputs, 0, - SpecificBuilderParams::BundleParams(self.burn), + self.burn, + false, + None, ) } @@ -835,7 +842,9 @@ impl Builder { self.spends, self.outputs, expiry_height, - SpecificBuilderParams::ActionGroupParams(self.reference_notes), + self.burn, + true, + Some(self.reference_notes), ) } @@ -972,19 +981,7 @@ fn pad_spend( } } -/// Specific parameters for the builder to build a bundle. -/// -/// If it is a BundleParams, it contains burn info. -/// If it is an ActionGroupParams, it contains reference notes. -#[derive(Debug)] -pub enum SpecificBuilderParams { - /// BundleParams contains burn info - BundleParams(HashMap), - /// ActionGroupParams contains reference notes - ActionGroupParams(HashMap), -} - -/// Builds a bundle containing the given spent notes, outputs and burns. +/// Builds a bundle containing the given spent notes and outputs. /// /// The returned bundle will have no proof or signatures; these can be applied with /// [`Bundle::create_proof`] and [`Bundle::apply_signatures`] respectively. @@ -1057,8 +1054,8 @@ fn build_bundle( outputs: Vec, expiry_height: u32, burn: BTreeMap, + is_action_group: bool, reference_notes: BTreeMap, - specific_params: SpecificBuilderParams, finisher: impl FnOnce( Vec, // pre-actions Flags, // flags @@ -1100,12 +1097,12 @@ fn build_bundle( let num_asset_pre_actions = spends.len().max(outputs.len()); let mut first_spend = spends.first().map(|(s, _)| s.clone()); - if let SpecificBuilderParams::ActionGroupParams(ref reference_notes) = - specific_params - { - if first_spend.is_none() { - first_spend = reference_notes.get(&asset).cloned(); - } + if is_action_group && first_spend.is_none() { + first_spend = reference_notes + .as_ref() + .expect("Reference notes are required for action group") + .get(&asset) + .cloned(); } let mut indexed_spends = spends @@ -1198,6 +1195,7 @@ fn build_bundle( native_value_balance, burn_vec, timelimit, + is_action_group, bundle_meta, rng, ) From a0b344369a18e02b2a9e15de9e3b0059fb297227 Mon Sep 17 00:00:00 2001 From: alexeykoren <2365507+alexeykoren@users.noreply.github.com> Date: Tue, 12 Aug 2025 17:22:29 +0200 Subject: [PATCH 37/48] Post-merge fixes --- src/builder.rs | 41 +++++++++++++------- src/bundle.rs | 4 ++ src/bundle/commitments.rs | 12 ++++++ src/pczt/tx_extractor.rs | 5 +++ src/primitives/orchard_primitives_vanilla.rs | 1 - src/swap_bundle.rs | 14 ++++--- tests/zsa.rs | 16 +++++--- 7 files changed, 66 insertions(+), 27 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 0593e3eb9..04e282ceb 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -41,6 +41,9 @@ use { }, nonempty::NonEmpty, }; +use crate::orchard_flavor::OrchardZSA; +use crate::primitives::redpallas::SigningKey; +use crate::swap_bundle::ActionGroupAuthorized; const MIN_ACTIONS: usize = 2; @@ -646,7 +649,7 @@ pub struct Builder { burn: BTreeMap, bundle_type: BundleType, anchor: Anchor, - reference_notes: HashMap, + reference_notes: BTreeMap, } impl Builder { @@ -658,7 +661,7 @@ impl Builder { burn: BTreeMap::new(), bundle_type, anchor, - reference_notes: HashMap::new(), + reference_notes: BTreeMap::new(), } } @@ -819,8 +822,8 @@ impl Builder { self.bundle_type, self.spends, self.outputs, - 0, self.burn, + 0, false, None, ) @@ -841,8 +844,8 @@ impl Builder { self.bundle_type, self.spends, self.outputs, - expiry_height, self.burn, + expiry_height, true, Some(self.reference_notes), ) @@ -861,6 +864,8 @@ impl Builder { self.spends, self.outputs, self.burn, + false, + Some(self.reference_notes), |pre_actions, flags, value_sum, burn_vec, bundle_meta, mut rng| { // Create the actions. let actions = pre_actions @@ -995,6 +1000,9 @@ pub fn bundle, FL: OrchardFlavor>( spends: Vec, outputs: Vec, burn: BTreeMap, + expiry_height: u32, + is_action_group: bool, + reference_notes: Option>, ) -> Result, BuildError> { build_bundle( rng, @@ -1003,6 +1011,8 @@ pub fn bundle, FL: OrchardFlavor>( spends, outputs, burn, + is_action_group, + reference_notes, |pre_actions, flags, value_balance, burn_vec, bundle_meta, mut rng| { let native_value_balance: i64 = i64::try_from(value_balance).map_err(BuildError::ValueSum)?; @@ -1025,8 +1035,10 @@ pub fn bundle, FL: OrchardFlavor>( let actions = NonEmpty::from_vec(actions).unwrap(); // Verify that bsk and bvk are consistent. - let bvk = derive_bvk(&actions, native_value_balance, &burn_vec); - assert_eq!(redpallas::VerificationKey::from(&bsk), bvk); + if !is_action_group { + let bvk = derive_bvk(&actions, native_value_balance, &burn_vec); + assert_eq!(redpallas::VerificationKey::from(&bsk), bvk); + } Ok(( Bundle::from_parts( @@ -1035,6 +1047,7 @@ pub fn bundle, FL: OrchardFlavor>( result_value_balance, burn_vec, anchor, + expiry_height, InProgress { proof: Unproven { witnesses }, sigs: Unauthorized { bsk }, @@ -1055,7 +1068,7 @@ fn build_bundle( expiry_height: u32, burn: BTreeMap, is_action_group: bool, - reference_notes: BTreeMap, + reference_notes: Option>, finisher: impl FnOnce( Vec, // pre-actions Flags, // flags @@ -1395,7 +1408,7 @@ impl Bundle Bundle, V, D> { +impl Bundle, V, P> { /// Loads the action_group_digest into this action group, preparing it for signing. /// /// This API ensures that all signatures are created over the same action_group_digest. @@ -1403,7 +1416,7 @@ impl Bundle Bundle, V, D> { + ) -> Bundle, V, P> { let reference_ask = SpendAuthorizingKey::from(&ReferenceKeys::sk()); let reference_ak: SpendValidatingKey = (&reference_ask).into(); self.map_authorization( @@ -1463,7 +1476,7 @@ impl Bundle, V, P> { mut rng: R, action_group_digest: [u8; 32], signing_keys: &[SpendAuthorizingKey], - ) -> Result<(Bundle, SigningKey), BuildError> { + ) -> Result<(Bundle, SigningKey), BuildError> { signing_keys .iter() .fold( @@ -1474,9 +1487,7 @@ impl Bundle, V, P> { } } -impl - Bundle, V, D> -{ +impl Bundle, V, P> { /// Signs this action group with the given [`SpendAuthorizingKey`]. /// /// This will apply signatures for all notes controlled by this spending key. @@ -1579,14 +1590,14 @@ impl Bundle, V, } } -impl Bundle, V, D> { +impl Bundle, V, P> { /// Finalizes this action group. /// /// Returns an error if any signatures are missing. #[allow(clippy::type_complexity)] pub fn finalize( self, - ) -> Result<(Bundle, SigningKey), BuildError> { + ) -> Result<(Bundle, SigningKey), BuildError> { let bsk = self.authorization().sigs.bsk; self.try_map_authorization( &mut (), diff --git a/src/bundle.rs b/src/bundle.rs index d5d1aba80..150c3792c 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -35,6 +35,7 @@ use crate::{ value::{NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum}, Proof, }; +use crate::bundle::commitments::hash_action_group; use crate::orchard_flavor::OrchardZSA; #[cfg(feature = "circuit")] @@ -562,6 +563,9 @@ pub struct EffectsOnly; impl Authorization for EffectsOnly { type SpendAuth = (); + + /// Return the proof component of the authorizing data. + fn proof(&self) -> Option<&Proof> { None } } impl> Bundle { diff --git a/src/bundle/commitments.rs b/src/bundle/commitments.rs index 62c965c98..054c51b9e 100644 --- a/src/bundle/commitments.rs +++ b/src/bundle/commitments.rs @@ -11,6 +11,10 @@ use crate::{ primitives::OrchardPrimitives, orchard_flavor::OrchardZSA, }; +use crate::orchard_flavor::OrchardZSA; + +// TODO remove +const MEMO_SIZE: usize = 512; pub(crate) const ZCASH_ORCHARD_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdOrchardHash"; pub(crate) const ZCASH_ORCHARD_ACTION_GROUPS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdOrcActGHash"; @@ -85,6 +89,14 @@ pub(crate) fn hash_action_group>( agh.update(&[action_group.flags().to_byte()]); agh.update(&action_group.anchor().to_bytes()); agh.update(&action_group.expiry_height().to_le_bytes()); + + let mut burn_hasher = hasher(ZCASH_ORCHARD_ZSA_BURN_HASH_PERSONALIZATION); + for burn_item in action_group.burn() { + burn_hasher.update(&burn_item.0.to_bytes()); + burn_hasher.update(&burn_item.1.to_bytes()); + } + + agh.update(burn_hasher.finalize().as_bytes()); agh.finalize() } diff --git a/src/pczt/tx_extractor.rs b/src/pczt/tx_extractor.rs index ba2bcbc5d..700a9dd9d 100644 --- a/src/pczt/tx_extractor.rs +++ b/src/pczt/tx_extractor.rs @@ -99,6 +99,7 @@ impl super::Bundle { value_balance, self.burn.clone(), self.anchor, + self.expiry_height, authorization, )) } else { @@ -129,6 +130,10 @@ pub struct Unbound { impl Authorization for Unbound { type SpendAuth = VerSpendAuthSig; + + fn proof(&self) -> Option<&Proof> { + Some(&self.proof) + } } impl crate::Bundle { diff --git a/src/primitives/orchard_primitives_vanilla.rs b/src/primitives/orchard_primitives_vanilla.rs index 8207ece80..fcffa2286 100644 --- a/src/primitives/orchard_primitives_vanilla.rs +++ b/src/primitives/orchard_primitives_vanilla.rs @@ -6,7 +6,6 @@ use blake2b_simd::Hash as Blake2bHash; use zcash_note_encryption::note_bytes::NoteBytesData; use super::{ - orchard_domain::OrchardDomainCommon, zcash_note_encryption_domain::{ build_base_note_plaintext_bytes, Memo, COMPACT_NOTE_SIZE_VANILLA, NOTE_VERSION_BYTE_V2, }, diff --git a/src/swap_bundle.rs b/src/swap_bundle.rs index 93fe32aba..094ef09fd 100644 --- a/src/swap_bundle.rs +++ b/src/swap_bundle.rs @@ -9,11 +9,14 @@ use crate::{ note_encryption::OrchardDomainCommon, orchard_flavor::OrchardZSA, primitives::redpallas::{self, Binding, SpendAuth}, - value::{NoteValue, ValueCommitTrapdoor}, + value::ValueCommitTrapdoor, Proof, }; +use alloc::vec::Vec; use k256::elliptic_curve::rand_core::{CryptoRng, RngCore}; +use nonempty::NonEmpty; +use crate::primitives::OrchardPrimitives; /// An action group. #[derive(Debug)] @@ -201,7 +204,7 @@ impl ActionGroupAuthorized { } } -impl Bundle { +impl Bundle { /// Verifies the proof for this bundle. pub fn verify_proof(&self, vk: &VerifyingKey) -> Result<(), halo2_proofs::plonk::Error> { self.authorization() @@ -245,12 +248,13 @@ impl> SwapBundle { let actions = self .action_groups .iter() - .flat_map(|ag| ag.actions()) + .flat_map(|ag| ag.actions().iter().cloned()) .collect::>(); + derive_bvk( - actions, + &NonEmpty::from_vec(actions).expect("SwapBundle must have at least one action"), self.value_balance, - std::iter::empty::<(AssetBase, NoteValue)>(), + &[], ) } } diff --git a/tests/zsa.rs b/tests/zsa.rs index 58878b7c9..93111a1e4 100644 --- a/tests/zsa.rs +++ b/tests/zsa.rs @@ -16,7 +16,6 @@ use orchard::{ orchard_flavor::OrchardZSA, primitives::redpallas::{Binding, SigningKey}, swap_bundle::{ActionGroupAuthorized, SwapBundle}, - tree::{MerkleHashOrchard, MerklePath}, value::NoteValue, Address, Anchor, Bundle, Note, ReferenceKeys, }; @@ -24,6 +23,7 @@ use rand::rngs::OsRng; use shardtree::{store::memory::MemoryShardStore, ShardTree}; use std::collections::HashSet; use zcash_note_encryption::try_note_decryption; +use orchard::bundle::Authorization; #[derive(Debug)] struct Keychain { @@ -437,7 +437,7 @@ fn build_and_verify_action_group( outputs .iter() .try_for_each(|output| { - builder.add_output(None, output.recipient, output.value, output.asset, None) + builder.add_output(None, output.recipient, output.value, output.asset, [0; 512]) }) .map_err(|err| err.to_string())?; reference_notes @@ -460,7 +460,7 @@ fn build_and_verify_action_group( Ok((shielded_action_group, bsk)) } -fn verify_unique_spent_nullifiers(bundle: &Bundle) -> bool { +fn verify_unique_spent_nullifiers(bundle: &Bundle) -> bool { let mut seen = HashSet::new(); bundle .actions() @@ -791,8 +791,12 @@ fn zsa_issue_and_transfer() { #[test] fn action_group_and_swap_bundle() { // ----- Setup ----- + + let pk = ProvingKey::build::(); + let vk = VerifyingKey::build::(); + // Create notes for user1 - let keys1 = prepare_keys(5); + let keys1 = prepare_keys(pk.clone(), vk.clone(),5); let asset_descr1 = b"zsa_asset1".to_vec(); let (asset1_reference_note, asset1_note1, asset1_note2) = @@ -802,7 +806,7 @@ fn action_group_and_swap_bundle() { let user1_native_note2 = create_native_note(&keys1); // Create notes for user2 - let keys2 = prepare_keys(10); + let keys2 = prepare_keys(pk.clone(), vk.clone(),10); let asset_descr2 = b"zsa_asset2".to_vec(); let (asset2_reference_note, asset2_note1, asset2_note2) = @@ -812,7 +816,7 @@ fn action_group_and_swap_bundle() { let user2_native_note2 = create_native_note(&keys2); // Create matcher keys - let matcher_keys = prepare_keys(15); + let matcher_keys = prepare_keys(pk, vk,15); // Create Merkle tree with all notes let (merkle_paths, anchor) = build_merkle_paths(vec![ From 8ba53e2254d82a418d250e3fa21d4d70c05ff31a Mon Sep 17 00:00:00 2001 From: alexeykoren <2365507+alexeykoren@users.noreply.github.com> Date: Tue, 30 Sep 2025 13:54:31 +0200 Subject: [PATCH 38/48] Set split_flag to true for reference notes --- src/builder.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/builder.rs b/src/builder.rs index 04e282ceb..a906ac034 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -735,7 +735,7 @@ impl Builder { note: Note, merkle_path: MerklePath, ) -> Result<(), SpendError> { - let spend = SpendInfo::new(fvk, note, merkle_path, false).ok_or(SpendError::FvkMismatch)?; + let spend = SpendInfo::new(fvk, note, merkle_path, true).ok_or(SpendError::FvkMismatch)?; // Consistency check: all anchors must be equal. if !spend.has_matching_anchor(&self.anchor) { From 56e2094067e7bee848f5c0c5747f11f53253b87d Mon Sep 17 00:00:00 2001 From: alexeykoren <2365507+alexeykoren@users.noreply.github.com> Date: Mon, 13 Oct 2025 13:32:44 +0200 Subject: [PATCH 39/48] Use correct split note in test --- tests/zsa.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/zsa.rs b/tests/zsa.rs index 93111a1e4..4c4b0f2ab 100644 --- a/tests/zsa.rs +++ b/tests/zsa.rs @@ -445,7 +445,7 @@ fn build_and_verify_action_group( .try_for_each(|spend| { builder.add_reference_note( ReferenceKeys::fvk(), - spend.note, + spend.note.create_split_note(&mut OsRng), spend.merkle_path().clone(), ) }) From 8bd82e7fb160de9aadb84137fd802c8530afcf11 Mon Sep 17 00:00:00 2001 From: alexeykoren <2365507+alexeykoren@users.noreply.github.com> Date: Wed, 22 Oct 2025 12:52:24 +0200 Subject: [PATCH 40/48] After-merge fixes --- src/builder.rs | 43 ++++--- src/bundle.rs | 56 +-------- src/bundle/commitments.rs | 52 ++++++--- src/issuance.rs | 45 -------- src/lib.rs | 3 - src/primitives/orchard_primitives_vanilla.rs | 6 - src/primitives/orchard_primitives_zsa.rs | 66 +---------- src/swap_bundle.rs | 111 +----------------- tests/builder.rs | 5 +- tests/zsa.rs | 115 +++---------------- 10 files changed, 85 insertions(+), 417 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index a906ac034..4b6c977c4 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -14,8 +14,7 @@ use zcash_note_encryption::NoteEncryption; use crate::{ address::Address, builder::BuildError::{BurnNative, BurnZero}, - bundle::{derive_bvk, Authorization, Authorized, Bundle, Flags}, - circuit::{Circuit, Instance, OrchardCircuit, Proof, ProvingKey}, + bundle::{Authorization, Authorized, Bundle, Flags}, constants::reference_keys::ReferenceKeys, keys::{ FullViewingKey, OutgoingViewingKey, Scope, SpendAuthorizingKey, SpendValidatingKey, @@ -24,7 +23,7 @@ use crate::{ note::{AssetBase, ExtractedNoteCommitment, Note, Nullifier, Rho, TransmittedNoteCiphertext}, orchard_sighash_versioning::{VerBindingSig, VerSpendAuthSig}, primitives::redpallas::{self, Binding, SpendAuth}, - swap_bundle::{ActionGroup, ActionGroupAuthorized}, + swap_bundle::ActionGroupAuthorized, primitives::{OrchardDomain, OrchardPrimitives}, tree::{Anchor, MerklePath}, value::{self, NoteValue, OverflowError, ValueCommitTrapdoor, ValueCommitment, ValueSum}, @@ -43,7 +42,6 @@ use { }; use crate::orchard_flavor::OrchardZSA; use crate::primitives::redpallas::SigningKey; -use crate::swap_bundle::ActionGroupAuthorized; const MIN_ACTIONS: usize = 2; @@ -633,12 +631,6 @@ impl BundleMetadata { #[cfg(feature = "circuit")] pub type UnauthorizedBundleWithMetadata = (UnauthorizedBundle, BundleMetadata); -/// A tuple containing an in-progress action group with no proofs or signatures, and its associated metadata. -pub type UnauthorizedActionGroupWithMetadata = ( - ActionGroup, Unauthorized>, V>, - BundleMetadata, -); - /// 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. @@ -1065,7 +1057,6 @@ fn build_bundle( bundle_type: BundleType, spends: Vec, outputs: Vec, - expiry_height: u32, burn: BTreeMap, is_action_group: bool, reference_notes: Option>, @@ -1207,8 +1198,6 @@ fn build_bundle( flags, native_value_balance, burn_vec, - timelimit, - is_action_group, bundle_meta, rng, ) @@ -1227,6 +1216,19 @@ pub struct InProgress { sigs: S, } +impl InProgress { + /// Mutate the proof using the provided function. + pub fn map_proof(self, f: F) -> InProgress + where + F: FnOnce(P) -> P2, + { + InProgress { + proof: f(self.proof), + sigs: self.sigs, + } + } +} + impl Authorization for InProgress { type SpendAuth = S::SpendAuth; @@ -1424,17 +1426,19 @@ impl Bundle Bundle { - MaybeSigned::Signature( + MaybeSigned::Signature(VerSpendAuthSig::new( + P::default_sighash_version(), ask.randomize(&parts.alpha) .sign(rng, &partial.sigs.action_group_digest), - ) + )) } s => s, }, diff --git a/src/bundle.rs b/src/bundle.rs index 150c3792c..5f3cc86d1 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -14,7 +14,6 @@ use core::fmt; use alloc::collections::BTreeMap; use blake2b_simd::Hash as Blake2bHash; -use k256::elliptic_curve::rand_core::{CryptoRng, RngCore}; use nonempty::NonEmpty; use zcash_note_encryption::{try_note_decryption, try_output_recovery_with_ovk}; @@ -25,21 +24,19 @@ use crate::{ action::Action, address::Address, bundle::commitments::{hash_bundle_auth_data, hash_bundle_txid_data, hash_action_group}, - circuit::{Instance, Proof, VerifyingKey}, keys::{IncomingViewingKey, OutgoingViewingKey, PreparedIncomingViewingKey}, note::{AssetBase, Note}, - note_encryption::{OrchardDomain, OrchardDomainCommon}, - orchard_flavor::OrchardFlavor, - primitives::redpallas::{self, Binding, SpendAuth}, + primitives::redpallas::{self, Binding}, tree::Anchor, value::{NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum}, Proof, }; -use crate::bundle::commitments::hash_action_group; use crate::orchard_flavor::OrchardZSA; #[cfg(feature = "circuit")] use crate::circuit::{Instance, VerifyingKey}; +use crate::orchard_sighash_versioning::{OrchardSighashVersion, VerBindingSig, VerSpendAuthSig}; +use crate::primitives::{OrchardDomain, OrchardPrimitives}; #[cfg(feature = "circuit")] impl Action { @@ -497,21 +494,6 @@ impl Bundle { } } -/// A swap bundle to be applied to the ledger. -#[derive(Clone, Debug)] -pub struct SwapBundle { - /// The list of action groups that make up this swap bundle. - action_groups: Vec>, - /// Orchard-specific transaction-level flags for this swap. - flags: Flags, - /// The net value moved out of this swap. - /// - /// This is the sum of Orchard spends minus the sum of Orchard outputs. - value_balance: V, - /// The binding signature for this swap. - binding_signature: redpallas::Signature, -} - pub(crate) fn derive_bvk, P: OrchardPrimitives>( actions: &NonEmpty>, value_balance: V, @@ -630,38 +612,6 @@ impl Bundle { } } -/// Authorizing data for an action group, ready to be sent to the matcher. -#[derive(Debug, Clone)] -pub struct ActionGroupAuthorized { - proof: Proof, -} - -impl Authorization for ActionGroupAuthorized { - type SpendAuth = redpallas::Signature; -} - -impl ActionGroupAuthorized { - /// Constructs the authorizing data for a bundle of actions from its constituent parts. - pub fn from_parts(proof: Proof) -> Self { - ActionGroupAuthorized { proof } - } - - /// Return the proof component of the authorizing data. - pub fn proof(&self) -> &Proof { - &self.proof - } -} - -impl Bundle { - /// Verifies the proof for this bundle. - #[cfg(feature = "circuit")] - pub fn verify_proof(&self, vk: &VerifyingKey) -> Result<(), halo2_proofs::plonk::Error> { - self.authorization() - .proof() - .verify(vk, &self.to_instances()) - } -} - #[cfg(feature = "std")] impl DynamicUsage for Bundle { fn dynamic_usage(&self) -> usize { diff --git a/src/bundle/commitments.rs b/src/bundle/commitments.rs index 054c51b9e..9bbff501a 100644 --- a/src/bundle/commitments.rs +++ b/src/bundle/commitments.rs @@ -11,7 +11,6 @@ use crate::{ primitives::OrchardPrimitives, orchard_flavor::OrchardZSA, }; -use crate::orchard_flavor::OrchardZSA; // TODO remove const MEMO_SIZE: usize = 512; @@ -72,19 +71,42 @@ pub fn hash_bundle_txid_empty() -> Blake2bHash { pub(crate) fn hash_action_group>( action_group: &Bundle, ) -> Blake2bHash { - let mut agh = hasher(ZCASH_ORCHARD_ACTION_GROUP_HASH_PERSONALIZATION); - let mut ch = hasher(ZCASH_ORCHARD_ACTIONS_COMPACT_HASH_PERSONALIZATION); + let mut agh = hasher(ZCASH_ORCHARD_ACTION_GROUPS_HASH_PERSONALIZATION); + + let mut ch = hasher(ZCASH_ORCHARD_ACTIONS_COMPACT_HASH_PERSONALIZATION_V6); + // TODO Remove mh once new Memo Bundles are implemented (ZIP-231). let mut mh = hasher(ZCASH_ORCHARD_ACTIONS_MEMOS_HASH_PERSONALIZATION); - let mut nh = hasher(ZCASH_ORCHARD_ACTIONS_NONCOMPACT_HASH_PERSONALIZATION); - for action in action_group.action_group().actions().iter() { + let mut nh = hasher(ZCASH_ORCHARD_ACTIONS_NONCOMPACT_HASH_PERSONALIZATION_V6); + + for action in action_group.actions().iter() { ch.update(&action.nullifier().to_bytes()); ch.update(&action.cmx().to_bytes()); ch.update(&action.encrypted_note().epk_bytes); - ch.update( - &action.encrypted_note().enc_ciphertext.as_ref()[..OrchardZSA::COMPACT_NOTE_SIZE], + // TODO Remove once new Memo Bundles are implemented (ZIP-231). + ch.update(&action.encrypted_note().enc_ciphertext.as_ref()[..OrchardZSA::COMPACT_NOTE_SIZE]); + // TODO Uncomment once new Memo Bundles are implemented (ZIP-231). + // ch.update(&action.encrypted_note().enc_ciphertext.as_ref()); + + // TODO Remove once new Memo Bundles are implemented (ZIP-231). + mh.update( + &action.encrypted_note().enc_ciphertext.as_ref() + [OrchardZSA::COMPACT_NOTE_SIZE..OrchardZSA::COMPACT_NOTE_SIZE + MEMO_SIZE], + ); + + nh.update(&action.cv_net().to_bytes()); + nh.update(&<[u8; 32]>::from(action.rk())); + // TODO Remove once new Memo Bundles are implemented (ZIP-231). + nh.update( + &action.encrypted_note().enc_ciphertext.as_ref() + [OrchardZSA::COMPACT_NOTE_SIZE + MEMO_SIZE..], ); + nh.update(&action.encrypted_note().out_ciphertext); + } - OrchardZSA::update_hash_with_actions(&mut agh, action_group); + agh.update(ch.finalize().as_bytes()); + // TODO Remove once new Memo Bundles are implemented (ZIP-231). + agh.update(mh.finalize().as_bytes()); + agh.update(nh.finalize().as_bytes()); agh.update(&[action_group.flags().to_byte()]); agh.update(&action_group.anchor().to_bytes()); @@ -95,7 +117,6 @@ pub(crate) fn hash_action_group>( burn_hasher.update(&burn_item.0.to_bytes()); burn_hasher.update(&burn_item.1.to_bytes()); } - agh.update(burn_hasher.finalize().as_bytes()); agh.finalize() } @@ -118,14 +139,6 @@ pub(crate) fn hash_swap_bundle>( h.finalize() } -/// Construct the commitment for the absent bundle as defined in -/// [ZIP-244: Transaction Identifier Non-Malleability][zip244] -/// -/// [zip244]: https://zips.z.cash/zip-0244 -pub fn hash_bundle_txid_empty() -> Blake2bHash { - hasher(ZCASH_ORCHARD_HASH_PERSONALIZATION).finalize() -} - /// Construct the `orchard_auth_digest` commitment to the authorizing data of an /// authorized bundle as defined in /// [ZIP-244: Transaction Identifier Non-Malleability][zip244] @@ -248,8 +261,7 @@ mod tests { builder::{Builder, BundleType, UnauthorizedBundle}, bundle::{ commitments::{ - get_compact_size, hash_bundle_auth_data, hash_bundle_txid_data, - hash_issue_bundle_auth_data, hash_issue_bundle_txid_data, + hash_bundle_txid_data, }, Authorized, Bundle, }, @@ -267,6 +279,7 @@ mod tests { use alloc::collections::BTreeMap; use nonempty::NonEmpty; use rand::{rngs::StdRng, SeedableRng}; + use crate::bundle::commitments::{get_compact_size, hash_bundle_auth_data, hash_issue_bundle_auth_data, hash_issue_bundle_txid_data}; fn generate_bundle(bundle_type: BundleType) -> UnauthorizedBundle { let rng = StdRng::seed_from_u64(5); @@ -460,3 +473,4 @@ mod tests { ); } } + diff --git a/src/issuance.rs b/src/issuance.rs index e96634143..b0a952b47 100644 --- a/src/issuance.rs +++ b/src/issuance.rs @@ -38,11 +38,6 @@ use Error::{ MissingReferenceNoteOnFirstIssuance, ValueOverflow, }; -use crate::constants::reference_keys::ReferenceKeys; -use crate::supply_info::{AssetSupply, SupplyInfo}; -use crate::value::{NoteValue, ValueSum}; -use crate::{Address, Note}; - /// Checks if a given note is a reference note. /// /// A reference note satisfies the following conditions: @@ -59,8 +54,6 @@ pub struct IssueBundle { ik: IssueValidatingKey, /// The list of issue actions that make up this bundle. actions: NonEmpty, - /// The list of reference notes created in this bundle. - reference_notes: HashMap, /// The authorization for this action. authorization: T, } @@ -342,13 +335,11 @@ impl IssueBundle { pub fn from_parts( ik: IssueValidatingKey, actions: NonEmpty, - reference_notes: HashMap, authorization: T, ) -> Self { IssueBundle { ik, actions, - reference_notes, authorization, } } @@ -362,7 +353,6 @@ impl IssueBundle { IssueBundle { ik: self.ik, actions: self.actions, - reference_notes: self.reference_notes, authorization: map_auth(authorization), } } @@ -393,19 +383,6 @@ impl IssueBundle { notes.push(create_reference_note(asset, &mut rng)); }; - let reference_note = Note::new( - ReferenceKeys::recipient(), - NoteValue::zero(), - asset, - Rho::from_nf_old(Nullifier::dummy(&mut rng)), - &mut rng, - ); - - let mut notes = vec![]; - if first_issuance { - notes.push(reference_note); - } - let action = match issue_info { None => IssueAction { asset_desc_hash, @@ -432,13 +409,6 @@ impl IssueBundle { }; ( - let reference_notes = if first_issuance { - HashMap::from([(asset, reference_note)]) - } else { - HashMap::new() - }; - - Ok(( IssueBundle { ik, actions: NonEmpty::new(action), @@ -487,11 +457,6 @@ impl IssueBundle { } None => { // Insert a new IssueAction. - let notes = if first_issuance { - vec![reference_note, note] - } else { - vec![note] - }; self.actions.push(IssueAction { asset_desc_hash, notes, @@ -500,10 +465,6 @@ impl IssueBundle { } }; - if first_issuance { - self.reference_notes.insert(asset, reference_note); - } - Ok(asset) } @@ -558,7 +519,6 @@ impl IssueBundle { IssueBundle { ik: self.ik, actions: self.actions, - reference_notes: self.reference_notes, authorization: Prepared { sighash }, } } @@ -897,10 +857,8 @@ mod tests { use alloc::collections::BTreeMap; use alloc::string::{String, ToString}; use alloc::vec::Vec; - use group::{Group, GroupEncoding}; use incrementalmerkletree::{Marking, Retention}; use nonempty::NonEmpty; - use pasta_curves::pallas::{Point, Scalar}; use rand::rngs::OsRng; use rand::RngCore; use shardtree::store::memory::MemoryShardStore; @@ -2024,7 +1982,6 @@ pub mod testing { use proptest::collection::vec; use proptest::prelude::*; use proptest::prop_compose; - use std::collections::HashMap; prop_compose! { /// Generate a uniformly distributed ZSA Schnorr signature @@ -2091,7 +2048,6 @@ pub mod testing { IssueBundle { ik, actions, - reference_notes: HashMap::new(), authorization: Prepared { sighash: fake_sighash } } } @@ -2113,7 +2069,6 @@ pub mod testing { IssueBundle { ik, actions, - reference_notes: HashMap::new(), authorization: Signed { signature: fake_sig }, } } diff --git a/src/lib.rs b/src/lib.rs index 0ce6676c5..23a568f0a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,11 +37,8 @@ pub mod issuance_auth; pub mod issuance_sighash_versioning; pub mod keys; pub mod note; -pub mod supply_info; pub mod swap_bundle; -pub mod note_encryption; - pub mod orchard_flavor; pub mod orchard_sighash_versioning; pub mod pczt; diff --git a/src/primitives/orchard_primitives_vanilla.rs b/src/primitives/orchard_primitives_vanilla.rs index fcffa2286..c3c71d559 100644 --- a/src/primitives/orchard_primitives_vanilla.rs +++ b/src/primitives/orchard_primitives_vanilla.rs @@ -5,12 +5,6 @@ use alloc::{collections::BTreeMap, vec::Vec}; use blake2b_simd::Hash as Blake2bHash; use zcash_note_encryption::note_bytes::NoteBytesData; -use super::{ - zcash_note_encryption_domain::{ - build_base_note_plaintext_bytes, Memo, COMPACT_NOTE_SIZE_VANILLA, NOTE_VERSION_BYTE_V2, - }, -}; - use crate::{ bundle::{ commitments::{ diff --git a/src/primitives/orchard_primitives_zsa.rs b/src/primitives/orchard_primitives_zsa.rs index 6c4647f28..fcf03bc54 100644 --- a/src/primitives/orchard_primitives_zsa.rs +++ b/src/primitives/orchard_primitives_zsa.rs @@ -5,22 +5,14 @@ use alloc::{collections::BTreeMap, vec::Vec}; use blake2b_simd::Hash as Blake2bHash; use zcash_note_encryption::note_bytes::NoteBytesData; -use crate::bundle::commitments::{ - hash_action_group, ZCASH_ORCHARD_ACTION_GROUPS_SIGS_HASH_PERSONALIZATION, - ZCASH_ORCHARD_SIGS_HASH_PERSONALIZATION, -}; -use crate::bundle::Authorized; +use crate::bundle::commitments::hash_action_group; use crate::{ bundle::{ commitments::{ - get_compact_size, hasher, ZCASH_ORCHARD_ACTIONS_COMPACT_HASH_PERSONALIZATION_V6, - ZCASH_ORCHARD_ACTIONS_MEMOS_HASH_PERSONALIZATION, - ZCASH_ORCHARD_ACTIONS_NONCOMPACT_HASH_PERSONALIZATION_V6, - ZCASH_ORCHARD_ACTION_GROUPS_HASH_PERSONALIZATION, + get_compact_size, hasher, ZCASH_ORCHARD_ACTION_GROUPS_SIGS_HASH_PERSONALIZATION, ZCASH_ORCHARD_HASH_PERSONALIZATION, ZCASH_ORCHARD_SIGS_HASH_PERSONALIZATION, ZCASH_ORCHARD_SPEND_AUTH_SIGS_HASH_PERSONALIZATION, - ZCASH_ORCHARD_ZSA_BURN_HASH_PERSONALIZATION, }, Authorization, Authorized, }, @@ -31,7 +23,7 @@ use crate::{ orchard_primitives::OrchardPrimitives, zcash_note_encryption_domain::{ build_base_note_plaintext_bytes, Memo, COMPACT_NOTE_SIZE_VANILLA, - COMPACT_NOTE_SIZE_ZSA, MEMO_SIZE, NOTE_VERSION_BYTE_V3, + COMPACT_NOTE_SIZE_ZSA, NOTE_VERSION_BYTE_V3, }, }, Bundle, @@ -71,54 +63,8 @@ impl OrchardPrimitives for OrchardZSA { bundle: &Bundle, ) -> Blake2bHash { let mut h = hasher(ZCASH_ORCHARD_HASH_PERSONALIZATION); - let mut agh = hasher(ZCASH_ORCHARD_ACTION_GROUPS_HASH_PERSONALIZATION); - - let mut ch = hasher(ZCASH_ORCHARD_ACTIONS_COMPACT_HASH_PERSONALIZATION_V6); - // TODO Remove mh once new Memo Bundles are implemented (ZIP-231). - let mut mh = hasher(ZCASH_ORCHARD_ACTIONS_MEMOS_HASH_PERSONALIZATION); - let mut nh = hasher(ZCASH_ORCHARD_ACTIONS_NONCOMPACT_HASH_PERSONALIZATION_V6); - - for action in bundle.actions().iter() { - ch.update(&action.nullifier().to_bytes()); - ch.update(&action.cmx().to_bytes()); - ch.update(&action.encrypted_note().epk_bytes); - // TODO Remove once new Memo Bundles are implemented (ZIP-231). - ch.update(&action.encrypted_note().enc_ciphertext.as_ref()[..Self::COMPACT_NOTE_SIZE]); - // TODO Uncomment once new Memo Bundles are implemented (ZIP-231). - // ch.update(&action.encrypted_note().enc_ciphertext.as_ref()); - - // TODO Remove once new Memo Bundles are implemented (ZIP-231). - mh.update( - &action.encrypted_note().enc_ciphertext.as_ref() - [Self::COMPACT_NOTE_SIZE..Self::COMPACT_NOTE_SIZE + MEMO_SIZE], - ); - - nh.update(&action.cv_net().to_bytes()); - nh.update(&<[u8; 32]>::from(action.rk())); - // TODO Remove once new Memo Bundles are implemented (ZIP-231). - nh.update( - &action.encrypted_note().enc_ciphertext.as_ref() - [Self::COMPACT_NOTE_SIZE + MEMO_SIZE..], - ); - nh.update(&action.encrypted_note().out_ciphertext); - } - - agh.update(ch.finalize().as_bytes()); - // TODO Remove once new Memo Bundles are implemented (ZIP-231). - agh.update(mh.finalize().as_bytes()); - agh.update(nh.finalize().as_bytes()); - - agh.update(&[bundle.flags().to_byte()]); - agh.update(&bundle.anchor().to_bytes()); - agh.update(&bundle.expiry_height().to_le_bytes()); - - let mut burn_hasher = hasher(ZCASH_ORCHARD_ZSA_BURN_HASH_PERSONALIZATION); - for burn_item in bundle.burn() { - burn_hasher.update(&burn_item.0.to_bytes()); - burn_hasher.update(&burn_item.1.to_bytes()); - } - agh.update(burn_hasher.finalize().as_bytes()); - h.update(agh.finalize().as_bytes()); + let agh = hash_action_group(bundle); + h.update(agh.as_bytes()); h.update(&(*bundle.value_balance()).into().to_le_bytes()); h.finalize() @@ -138,7 +84,7 @@ impl OrchardPrimitives for OrchardZSA { ) -> Blake2bHash { let mut h = hasher(ZCASH_ORCHARD_SIGS_HASH_PERSONALIZATION); let mut agh = hasher(ZCASH_ORCHARD_ACTION_GROUPS_SIGS_HASH_PERSONALIZATION); - agh.update(bundle.authorization().proof().as_ref()); + agh.update(bundle.authorization().proof().unwrap().as_ref()); let mut sash = hasher(ZCASH_ORCHARD_SPEND_AUTH_SIGS_HASH_PERSONALIZATION); for action in bundle.actions().iter() { let version_bytes = sighash_version_map diff --git a/src/swap_bundle.rs b/src/swap_bundle.rs index 094ef09fd..339f7ae65 100644 --- a/src/swap_bundle.rs +++ b/src/swap_bundle.rs @@ -3,12 +3,9 @@ use crate::{ bundle::commitments::hash_swap_bundle, bundle::{derive_bvk, Authorization, Bundle, BundleCommitment}, - circuit::{ProvingKey, VerifyingKey}, - keys::SpendAuthorizingKey, - note::AssetBase, - note_encryption::OrchardDomainCommon, + circuit::VerifyingKey, orchard_flavor::OrchardZSA, - primitives::redpallas::{self, Binding, SpendAuth}, + primitives::redpallas::{self, Binding}, value::ValueCommitTrapdoor, Proof, }; @@ -16,109 +13,9 @@ use alloc::vec::Vec; use k256::elliptic_curve::rand_core::{CryptoRng, RngCore}; use nonempty::NonEmpty; +use crate::orchard_sighash_versioning::VerSpendAuthSig; use crate::primitives::OrchardPrimitives; -/// An action group. -#[derive(Debug)] -pub struct ActionGroup { - /// The action group main content. - action_group: Bundle, - /// The action group timelimit. - timelimit: u32, - /// The binding signature key for the action group. - /// - /// During the building of the action group, this key is not set. - /// Once the action group is finalized (it contains a spend authorizing signature for each - /// action and a proof), the key is set. - bsk: Option>, -} - -impl ActionGroup { - /// Constructs an `ActionGroup` from its constituent parts. - pub fn from_parts( - action_group: Bundle, - timelimit: u32, - bsk: Option>, - ) -> Self { - ActionGroup { - action_group, - timelimit, - bsk, - } - } - - /// Returns the action group's main content. - pub fn action_group(&self) -> &Bundle { - &self.action_group - } - - /// Returns the action group's timelimit. - pub fn timelimit(&self) -> u32 { - self.timelimit - } - - /// Returns the action group's binding signature key. - pub fn bsk(&self) -> Option<&redpallas::SigningKey> { - self.bsk.as_ref() - } - - /// Remove bsk from this action group - /// - /// When creating a SwapBundle from a list of action groups, we evaluate the binding signature - /// by signing the sighash with the sum of the bsk of each action group. - /// Then, we remove the bsk of each action group as it is no longer needed. - fn remove_bsk(&mut self) { - self.bsk = None; - } -} - -impl ActionGroup, S>, V> { - /// Creates the proof for this action group. - pub fn create_proof( - self, - pk: &ProvingKey, - mut rng: impl RngCore, - ) -> Result, V>, BuildError> { - let new_action_group = self.action_group.create_proof(pk, &mut rng)?; - Ok(ActionGroup { - action_group: new_action_group, - timelimit: self.timelimit, - bsk: self.bsk, - }) - } -} - -impl ActionGroup, V> { - /// Applies signatures to this action group, in order to authorize it. - pub fn apply_signatures( - self, - mut rng: R, - action_group_digest: [u8; 32], - signing_keys: &[SpendAuthorizingKey], - ) -> Result, BuildError> { - let (bsk, action_group) = signing_keys - .iter() - .fold( - self.action_group - .prepare_for_action_group(&mut rng, action_group_digest), - |partial, ask| partial.sign(&mut rng, ask), - ) - .finalize()?; - Ok(ActionGroup { - action_group, - timelimit: self.timelimit, - bsk: Some(bsk), - }) - } -} - -impl> ActionGroup { - /// Computes a commitment to the content of this action group. - pub fn commitment(&self) -> BundleCommitment { - BundleCommitment(hash_action_group(self)) - } -} - /// A swap bundle to be applied to the ledger. #[derive(Debug, Clone)] pub struct SwapBundle { @@ -189,7 +86,7 @@ pub struct ActionGroupAuthorized { } impl Authorization for ActionGroupAuthorized { - type SpendAuth = redpallas::Signature; + type SpendAuth = VerSpendAuthSig; /// Return the proof component of the authorizing data. fn proof(&self) -> Option<&Proof> { diff --git a/tests/builder.rs b/tests/builder.rs index 56596e0e7..3e97409c3 100644 --- a/tests/builder.rs +++ b/tests/builder.rs @@ -6,9 +6,8 @@ use orchard::{ keys::{FullViewingKey, PreparedIncomingViewingKey, Scope, SpendAuthorizingKey, SpendingKey}, note::{AssetBase, ExtractedNoteCommitment}, orchard_flavor::{OrchardFlavor, OrchardVanilla, OrchardZSA}, - swap_bundle::SwapBundle, primitives::{OrchardDomain, OrchardPrimitives}, - swap_bundle::{ActionGroup, ActionGroupAuthorized, SwapBundle}, + swap_bundle::{ActionGroupAuthorized, SwapBundle}, tree::{MerkleHashOrchard, MerklePath}, value::NoteValue, Anchor, Bundle, Note, @@ -76,7 +75,7 @@ pub fn verify_action_group( assert_eq!( action .rk() - .verify(&action_group_digest, action.authorization()), + .verify(&action_group_digest, action.authorization().sig()), Ok(()) ); } diff --git a/tests/zsa.rs b/tests/zsa.rs index 4c4b0f2ab..24d731e41 100644 --- a/tests/zsa.rs +++ b/tests/zsa.rs @@ -1,18 +1,13 @@ mod builder; use crate::builder::{verify_action_group, verify_bundle, verify_swap_bundle}; -use bridgetree::BridgeTree; -use incrementalmerkletree::Hashable; -use orchard::bundle::Authorization; use orchard::{ builder::{Builder, BundleType}, bundle::Authorized, circuit::{ProvingKey, VerifyingKey}, - issuance::{verify_issue_bundle, IssueBundle, IssueInfo, Signed, Unauthorized}, + issuance::{verify_issue_bundle, IssueBundle, IssueInfo, Signed}, keys::{FullViewingKey, PreparedIncomingViewingKey, Scope, SpendAuthorizingKey, SpendingKey}, - keys::{IssuanceAuthorizingKey, IssuanceValidatingKey}, note::{AssetBase, ExtractedNoteCommitment}, - note_encryption::OrchardDomain, orchard_flavor::OrchardZSA, primitives::redpallas::{Binding, SigningKey}, swap_bundle::{ActionGroupAuthorized, SwapBundle}, @@ -22,8 +17,15 @@ use orchard::{ use rand::rngs::OsRng; use shardtree::{store::memory::MemoryShardStore, ShardTree}; use std::collections::HashSet; +use incrementalmerkletree::{Hashable, Marking, Retention}; +use nonempty::NonEmpty; use zcash_note_encryption::try_note_decryption; use orchard::bundle::Authorization; +use orchard::issuance::{compute_asset_desc_hash, AwaitingNullifier}; +use orchard::issuance_auth::{IssueAuthKey, IssueValidatingKey, ZSASchnorr}; +use orchard::note::Nullifier; +use orchard::primitives::OrchardDomain; +use orchard::tree::{MerkleHashOrchard, MerklePath}; #[derive(Debug)] struct Keychain { @@ -167,40 +169,6 @@ fn build_merkle_paths(notes: Vec<&Note>) -> (Vec, Anchor) { (merkle_paths, root.into()) } -fn build_merkle_paths(notes: Vec<&Note>) -> (Vec, Anchor) { - let mut tree = BridgeTree::::new(100); - - let mut commitments = vec![]; - let mut positions = vec![]; - - // Add leaves - for note in notes { - let cmx: ExtractedNoteCommitment = note.commitment().into(); - commitments.push(cmx); - let leaf = MerkleHashOrchard::from_cmx(&cmx); - tree.append(leaf); - positions.push(tree.mark().unwrap()); - } - - let root = tree.root(0).unwrap(); - let anchor = root.into(); - - // Calculate paths - let mut merkle_paths = vec![]; - for (position, commitment) in positions.iter().zip(commitments.iter()) { - let auth_path = tree.witness(*position, 0).unwrap(); - let merkle_path = MerklePath::from_parts( - u64::from(*position).try_into().unwrap(), - auth_path[..].try_into().unwrap(), - ); - merkle_paths.push(merkle_path.clone()); - assert_eq!(anchor, merkle_path.root(*commitment)); - } - - (merkle_paths, anchor) -} - - fn issue_zsa_notes( asset_descr: &[u8], keys: &Keychain, @@ -253,64 +221,6 @@ fn issue_zsa_notes( (*reference_note, *note1, *note2) } -fn issue_zsa_notes_with_reference_note( - asset_descr: &[u8], - keys: &Keychain, - reference_address: Address, -) -> (Note, Note, Note) { - let mut rng = OsRng; - // Create a issuance bundle - let unauthorized_asset = IssueBundle::new( - keys.ik().clone(), - asset_descr.to_owned(), - Some(IssueInfo { - recipient: keys.recipient, - value: NoteValue::from_raw(40), - }), - &mut rng, - ); - - assert!(unauthorized_asset.is_ok()); - - let (mut unauthorized, _) = unauthorized_asset.unwrap(); - - assert!(unauthorized - .add_recipient( - asset_descr, - keys.recipient, - NoteValue::from_raw(2), - &mut rng, - ) - .is_ok()); - - // Create a reference note (with a note value equal to 0) - assert!(unauthorized - .add_recipient( - asset_descr, - reference_address, - NoteValue::from_raw(0), - &mut rng, - ) - .is_ok()); - - let issue_bundle = sign_issue_bundle(unauthorized, keys.isk()); - - // Take notes from first action - let notes = issue_bundle.get_all_notes(); - let note1 = notes[0]; - let note2 = notes[1]; - let note3 = notes[2]; - - assert!(verify_issue_bundle( - &issue_bundle, - issue_bundle.commitment().into(), - &HashSet::new(), - ) - .is_ok()); - - (*reference_note, *note1, *note2) -} - fn create_native_note(keys: &Keychain) -> Note { let mut rng = OsRng; @@ -798,19 +708,20 @@ fn action_group_and_swap_bundle() { // Create notes for user1 let keys1 = prepare_keys(pk.clone(), vk.clone(),5); + let user1_native_note1 = create_native_note(&keys1); + let user1_native_note2 = create_native_note(&keys1); + let asset_descr1 = b"zsa_asset1".to_vec(); let (asset1_reference_note, asset1_note1, asset1_note2) = - issue_zsa_notes(&asset_descr1, &keys1); + issue_zsa_notes(&asset_descr1, &keys1, &user1_native_note1.nullifier(keys1.fvk())); - let user1_native_note1 = create_native_note(&keys1); - let user1_native_note2 = create_native_note(&keys1); // Create notes for user2 let keys2 = prepare_keys(pk.clone(), vk.clone(),10); let asset_descr2 = b"zsa_asset2".to_vec(); let (asset2_reference_note, asset2_note1, asset2_note2) = - issue_zsa_notes(&asset_descr2, &keys2); + issue_zsa_notes(&asset_descr2, &keys2, &user1_native_note2.nullifier(keys1.fvk())); let user2_native_note1 = create_native_note(&keys2); let user2_native_note2 = create_native_note(&keys2); From f6d87925c6ad32675790b828736f7321f85ecfe8 Mon Sep 17 00:00:00 2001 From: alexeykoren <2365507+alexeykoren@users.noreply.github.com> Date: Thu, 23 Oct 2025 11:27:34 +0200 Subject: [PATCH 41/48] Use versioned sig in swap bundle --- src/swap_bundle.rs | 10 +++++----- tests/builder.rs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/swap_bundle.rs b/src/swap_bundle.rs index 339f7ae65..1d3ae2c98 100644 --- a/src/swap_bundle.rs +++ b/src/swap_bundle.rs @@ -13,7 +13,7 @@ use alloc::vec::Vec; use k256::elliptic_curve::rand_core::{CryptoRng, RngCore}; use nonempty::NonEmpty; -use crate::orchard_sighash_versioning::VerSpendAuthSig; +use crate::orchard_sighash_versioning::{VerBindingSig, VerSpendAuthSig}; use crate::primitives::OrchardPrimitives; /// A swap bundle to be applied to the ledger. @@ -26,7 +26,7 @@ pub struct SwapBundle { /// This is the sum of Orchard spends minus the sum of Orchard outputs. value_balance: V, /// The binding signature for this swap. - binding_signature: redpallas::Signature, + binding_signature: VerBindingSig, } impl SwapBundle { @@ -34,7 +34,7 @@ impl SwapBundle { pub fn from_parts( action_groups: Vec>, value_balance: V, - binding_signature: redpallas::Signature, + binding_signature: VerBindingSig, ) -> Self { SwapBundle { action_groups, @@ -69,7 +69,7 @@ impl + std::iter::Sum> SwapBundle { .into(); // Evaluate the swap binding signature which is equal to the signature of the swap sigash // with the swap binding signature key bsk. - let binding_signature = bsk.sign(rng, &sighash); + let binding_signature = VerBindingSig::new(OrchardZSA::default_sighash_version(),bsk.sign(rng, &sighash)); // Create the swap bundle SwapBundle { action_groups, @@ -118,7 +118,7 @@ impl SwapBundle { } /// Returns the binding signature of this swap bundle. - pub fn binding_signature(&self) -> &redpallas::Signature { + pub fn binding_signature(&self) -> &VerBindingSig { &self.binding_signature } diff --git a/tests/builder.rs b/tests/builder.rs index 3e97409c3..e10c7b956 100644 --- a/tests/builder.rs +++ b/tests/builder.rs @@ -56,7 +56,7 @@ pub fn verify_swap_bundle(swap_bundle: &SwapBundle, vks: Vec<&VerifyingKey> let sighash: [u8; 32] = swap_bundle.commitment().into(); let bvk = swap_bundle.binding_validating_key(); assert_eq!( - bvk.verify(&sighash, swap_bundle.binding_signature()), + bvk.verify(&sighash, swap_bundle.binding_signature().sig()), Ok(()) ); } From cca959a15765390ebeb849a385c2422eed96aea9 Mon Sep 17 00:00:00 2001 From: alexey Date: Sat, 1 Nov 2025 14:17:43 +0100 Subject: [PATCH 42/48] Add burn to bvk --- src/swap_bundle.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/swap_bundle.rs b/src/swap_bundle.rs index 1d3ae2c98..d586cb7a9 100644 --- a/src/swap_bundle.rs +++ b/src/swap_bundle.rs @@ -148,10 +148,16 @@ impl> SwapBundle { .flat_map(|ag| ag.actions().iter().cloned()) .collect::>(); + let burn = self + .action_groups + .iter() + .flat_map(|ag| ag.burn().iter().cloned()) + .collect::>(); + derive_bvk( &NonEmpty::from_vec(actions).expect("SwapBundle must have at least one action"), self.value_balance, - &[], + &burn, ) } } From 874f255d5c7d80e994e37c71f68d5ce9d38bcf39 Mon Sep 17 00:00:00 2001 From: alexeykoren <2365507+alexeykoren@users.noreply.github.com> Date: Tue, 9 Dec 2025 23:25:22 +0100 Subject: [PATCH 43/48] Merge zsa1 --- src/builder.rs | 2 +- src/pczt/tx_extractor.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 4b6d29539..98245e2c7 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -859,7 +859,7 @@ impl Builder { self.burn, false, Some(self.reference_notes), - |pre_actions, flags, value_sum, burn_vec, bundle_meta, mut rng| { + |pre_actions, flags, value_sum, _burn_vec, bundle_meta, mut rng| { // Create the actions. let actions = pre_actions .into_iter() diff --git a/src/pczt/tx_extractor.rs b/src/pczt/tx_extractor.rs index 071ca9808..17100c289 100644 --- a/src/pczt/tx_extractor.rs +++ b/src/pczt/tx_extractor.rs @@ -96,7 +96,7 @@ impl super::Bundle { value_balance, vec![], //No burn in PCZT V1 self.anchor, - self.expiry_height, + 0, // No expiry height in PCZT V1 authorization, )) } else { From 77befb0973ef287b716a52999586ed13f8b6c6f9 Mon Sep 17 00:00:00 2001 From: alexey Date: Wed, 24 Dec 2025 10:52:45 +0100 Subject: [PATCH 44/48] Fmt --- src/builder.rs | 22 +++++++++++---------- src/bundle.rs | 4 +++- src/bundle/commitments.rs | 14 +++++++------ src/issuance.rs | 1 - src/note/asset_base.rs | 4 +--- src/primitives/orchard_primitives_zsa.rs | 3 +-- src/swap_bundle.rs | 5 ++++- src/value.rs | 4 +--- tests/zsa.rs | 25 ++++++++++++++---------- 9 files changed, 45 insertions(+), 37 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 98245e2c7..07151c825 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1065,14 +1065,14 @@ fn build_bundle( |(asset, (spends, outputs))| { let num_asset_pre_actions = spends.len().max(outputs.len()); - let mut first_spend = spends.first().map(|(s, _)| s.clone()); - if is_action_group && first_spend.is_none() { - first_spend = reference_notes - .as_ref() - .expect("Reference notes are required for action group") - .get(&asset) - .cloned(); - } + let mut first_spend = spends.first().map(|(s, _)| s.clone()); + if is_action_group && first_spend.is_none() { + first_spend = reference_notes + .as_ref() + .expect("Reference notes are required for action group") + .get(&asset) + .cloned(); + } let mut indexed_spends = spends .into_iter() @@ -1459,7 +1459,9 @@ impl Bundle, V, P> { } } -impl Bundle, V, P> { +impl + Bundle, V, P> +{ /// Signs this action group with the given [`SpendAuthorizingKey`]. /// /// This will apply signatures for all notes controlled by this spending key. @@ -1483,7 +1485,7 @@ impl Bundle -Bundle, V, P> + Bundle, V, P> { /// Signs this bundle with the given [`SpendAuthorizingKey`]. /// diff --git a/src/bundle.rs b/src/bundle.rs index 5f3cc86d1..3493b42fd 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -547,7 +547,9 @@ impl Authorization for EffectsOnly { type SpendAuth = (); /// Return the proof component of the authorizing data. - fn proof(&self) -> Option<&Proof> { None } + fn proof(&self) -> Option<&Proof> { + None + } } impl> Bundle { diff --git a/src/bundle/commitments.rs b/src/bundle/commitments.rs index b09b334e7..a75a19499 100644 --- a/src/bundle/commitments.rs +++ b/src/bundle/commitments.rs @@ -83,7 +83,9 @@ pub(crate) fn hash_action_group>( ch.update(&action.cmx().to_bytes()); ch.update(&action.encrypted_note().epk_bytes); // TODO Remove once new Memo Bundles are implemented (ZIP-231). - ch.update(&action.encrypted_note().enc_ciphertext.as_ref()[..OrchardZSA::COMPACT_NOTE_SIZE]); + ch.update( + &action.encrypted_note().enc_ciphertext.as_ref()[..OrchardZSA::COMPACT_NOTE_SIZE], + ); // TODO Uncomment once new Memo Bundles are implemented (ZIP-231). // ch.update(&action.encrypted_note().enc_ciphertext.as_ref()); @@ -260,9 +262,7 @@ mod tests { use crate::{ builder::{Builder, BundleType, UnauthorizedBundle}, bundle::{ - commitments::{ - hash_bundle_txid_data, - }, + commitments::{hash_bundle_txid_data}, Authorized, Bundle, }, circuit::ProvingKey, @@ -279,7 +279,10 @@ mod tests { use alloc::collections::BTreeMap; use nonempty::NonEmpty; use rand::{rngs::StdRng, SeedableRng}; - use crate::bundle::commitments::{get_compact_size, hash_bundle_auth_data, hash_issue_bundle_auth_data, hash_issue_bundle_txid_data}; + use crate::bundle::commitments::{ + get_compact_size, hash_bundle_auth_data, hash_issue_bundle_auth_data, + hash_issue_bundle_txid_data, + }; fn generate_bundle(bundle_type: BundleType) -> UnauthorizedBundle { let rng = StdRng::seed_from_u64(5); @@ -469,4 +472,3 @@ mod tests { ); } } - diff --git a/src/issuance.rs b/src/issuance.rs index b0a952b47..f641dcf8e 100644 --- a/src/issuance.rs +++ b/src/issuance.rs @@ -960,7 +960,6 @@ mod tests { ) } - #[test] fn action_verify_valid() { let (ik, test_asset, action) = action_verify_test_params(10, 20, b"Asset 1", None, false); diff --git a/src/note/asset_base.rs b/src/note/asset_base.rs index 9541c162b..cbacd242e 100644 --- a/src/note/asset_base.rs +++ b/src/note/asset_base.rs @@ -164,9 +164,7 @@ pub mod testing { use proptest::prelude::*; - use crate::issuance_auth::{ - testing::arb_issuance_authorizing_key, IssueValidatingKey, ZSASchnorr, - }; + use crate::issuance_auth::{testing::arb_issuance_authorizing_key, IssueValidatingKey, ZSASchnorr}; prop_compose! { /// Generate a uniformly distributed note type diff --git a/src/primitives/orchard_primitives_zsa.rs b/src/primitives/orchard_primitives_zsa.rs index fcf03bc54..5a1926dbd 100644 --- a/src/primitives/orchard_primitives_zsa.rs +++ b/src/primitives/orchard_primitives_zsa.rs @@ -9,8 +9,7 @@ use crate::bundle::commitments::hash_action_group; use crate::{ bundle::{ commitments::{ - get_compact_size, hasher, - ZCASH_ORCHARD_ACTION_GROUPS_SIGS_HASH_PERSONALIZATION, + get_compact_size, hasher, ZCASH_ORCHARD_ACTION_GROUPS_SIGS_HASH_PERSONALIZATION, ZCASH_ORCHARD_HASH_PERSONALIZATION, ZCASH_ORCHARD_SIGS_HASH_PERSONALIZATION, ZCASH_ORCHARD_SPEND_AUTH_SIGS_HASH_PERSONALIZATION, }, diff --git a/src/swap_bundle.rs b/src/swap_bundle.rs index d586cb7a9..91aef76a4 100644 --- a/src/swap_bundle.rs +++ b/src/swap_bundle.rs @@ -69,7 +69,10 @@ impl + std::iter::Sum> SwapBundle { .into(); // Evaluate the swap binding signature which is equal to the signature of the swap sigash // with the swap binding signature key bsk. - let binding_signature = VerBindingSig::new(OrchardZSA::default_sighash_version(),bsk.sign(rng, &sighash)); + let binding_signature = VerBindingSig::new( + OrchardZSA::default_sighash_version(), + bsk.sign(rng, &sighash), + ); // Create the swap bundle SwapBundle { action_groups, diff --git a/src/value.rs b/src/value.rs index 367dff625..f857d2f2f 100644 --- a/src/value.rs +++ b/src/value.rs @@ -528,9 +528,7 @@ mod tests { testing::{arb_note_value_bounded, arb_trapdoor, arb_value_sum_bounded}, OverflowError, ValueCommitTrapdoor, ValueCommitment, ValueSum, MAX_NOTE_VALUE, }; - use crate::{ - note::asset_base::testing::arb_asset_base, note::AssetBase, primitives::redpallas, - }; + use crate::{note::asset_base::testing::arb_asset_base, note::AssetBase, primitives::redpallas}; fn check_binding_signature( native_values: &[(ValueSum, ValueCommitTrapdoor, AssetBase)], diff --git a/tests/zsa.rs b/tests/zsa.rs index 24d731e41..99e22408c 100644 --- a/tests/zsa.rs +++ b/tests/zsa.rs @@ -128,7 +128,6 @@ fn build_merkle_paths(notes: Vec<&Note>) -> (Vec, Anchor) { let max_index = (notes.len() as u32) - 1; - let mut commitments = vec![]; let mut positions = vec![]; @@ -173,7 +172,8 @@ fn issue_zsa_notes( asset_descr: &[u8], keys: &Keychain, first_nullifier: &Nullifier, -) -> (Note, Note, Note) { let mut rng = OsRng; +) -> (Note, Note, Note) { + let mut rng = OsRng; // Create an issuance bundle let asset_desc_hash = compute_asset_desc_hash(&NonEmpty::from_slice(asset_descr).unwrap()); let (mut awaiting_nullifier_bundle, _) = IssueBundle::new( @@ -706,28 +706,33 @@ fn action_group_and_swap_bundle() { let vk = VerifyingKey::build::(); // Create notes for user1 - let keys1 = prepare_keys(pk.clone(), vk.clone(),5); + let keys1 = prepare_keys(pk.clone(), vk.clone(), 5); let user1_native_note1 = create_native_note(&keys1); let user1_native_note2 = create_native_note(&keys1); let asset_descr1 = b"zsa_asset1".to_vec(); - let (asset1_reference_note, asset1_note1, asset1_note2) = - issue_zsa_notes(&asset_descr1, &keys1, &user1_native_note1.nullifier(keys1.fvk())); - + let (asset1_reference_note, asset1_note1, asset1_note2) = issue_zsa_notes( + &asset_descr1, + &keys1, + &user1_native_note1.nullifier(keys1.fvk()), + ); // Create notes for user2 - let keys2 = prepare_keys(pk.clone(), vk.clone(),10); + let keys2 = prepare_keys(pk.clone(), vk.clone(), 10); let asset_descr2 = b"zsa_asset2".to_vec(); - let (asset2_reference_note, asset2_note1, asset2_note2) = - issue_zsa_notes(&asset_descr2, &keys2, &user1_native_note2.nullifier(keys1.fvk())); + let (asset2_reference_note, asset2_note1, asset2_note2) = issue_zsa_notes( + &asset_descr2, + &keys2, + &user1_native_note2.nullifier(keys1.fvk()), + ); let user2_native_note1 = create_native_note(&keys2); let user2_native_note2 = create_native_note(&keys2); // Create matcher keys - let matcher_keys = prepare_keys(pk, vk,15); + let matcher_keys = prepare_keys(pk, vk, 15); // Create Merkle tree with all notes let (merkle_paths, anchor) = build_merkle_paths(vec![ From 975eb257bf516ce95afcf746037d57cc0c13dfbe Mon Sep 17 00:00:00 2001 From: alexey Date: Wed, 24 Dec 2025 11:03:25 +0100 Subject: [PATCH 45/48] Clippy --- src/builder.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/builder.rs b/src/builder.rs index 07151c825..8d369eabd 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1016,6 +1016,7 @@ pub fn bundle, FL: OrchardFlavor>( ) } +#[allow(clippy::too_many_arguments)] fn build_bundle( mut rng: R, anchor: Anchor, From a4ea850cd312301d053c272a1fdb9a13d4dbf449 Mon Sep 17 00:00:00 2001 From: alexeykoren <2365507+alexeykoren@users.noreply.github.com> Date: Tue, 20 Jan 2026 15:07:12 +0100 Subject: [PATCH 46/48] Add circuit cfg guards --- src/builder.rs | 1 + src/swap_bundle.rs | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/builder.rs b/src/builder.rs index 8d369eabd..45fed7b53 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -826,6 +826,7 @@ impl Builder { /// /// The returned action group will have no proof or signatures; these can be applied with /// [`Bundle::create_proof`] and [`Bundle::apply_signatures`] respectively. + #[cfg(feature = "circuit")] pub fn build_action_group>( self, rng: impl RngCore, diff --git a/src/swap_bundle.rs b/src/swap_bundle.rs index 91aef76a4..7a7879213 100644 --- a/src/swap_bundle.rs +++ b/src/swap_bundle.rs @@ -3,7 +3,6 @@ use crate::{ bundle::commitments::hash_swap_bundle, bundle::{derive_bvk, Authorization, Bundle, BundleCommitment}, - circuit::VerifyingKey, orchard_flavor::OrchardZSA, primitives::redpallas::{self, Binding}, value::ValueCommitTrapdoor, @@ -16,6 +15,9 @@ use nonempty::NonEmpty; use crate::orchard_sighash_versioning::{VerBindingSig, VerSpendAuthSig}; use crate::primitives::OrchardPrimitives; +#[cfg(feature = "circuit")] +use crate::circuit::VerifyingKey; + /// A swap bundle to be applied to the ledger. #[derive(Debug, Clone)] pub struct SwapBundle { @@ -104,6 +106,7 @@ impl ActionGroupAuthorized { } } +#[cfg(feature = "circuit")] impl Bundle { /// Verifies the proof for this bundle. pub fn verify_proof(&self, vk: &VerifyingKey) -> Result<(), halo2_proofs::plonk::Error> { From 325651675486b8c2226c0f76d55426c8bc755a8b Mon Sep 17 00:00:00 2001 From: alexeykoren <2365507+alexeykoren@users.noreply.github.com> Date: Tue, 20 Jan 2026 15:22:48 +0100 Subject: [PATCH 47/48] Add circuit cfg guards --- src/builder.rs | 1 + src/bundle.rs | 1 + src/swap_bundle.rs | 8 ++++++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 45fed7b53..fbadd93ef 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -41,6 +41,7 @@ use { }, nonempty::NonEmpty, }; +#[cfg(feature = "circuit")] use crate::orchard_flavor::OrchardZSA; use crate::primitives::redpallas::SigningKey; diff --git a/src/bundle.rs b/src/bundle.rs index 3493b42fd..d308352cf 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -606,6 +606,7 @@ impl Bundle { } /// Verifies the proof for this bundle. + #[cfg(feature = "circuit")] pub fn verify_proof(&self, vk: &VerifyingKey) -> Result<(), halo2_proofs::plonk::Error> { self.authorization() .proof() diff --git a/src/swap_bundle.rs b/src/swap_bundle.rs index 7a7879213..c7c77a7de 100644 --- a/src/swap_bundle.rs +++ b/src/swap_bundle.rs @@ -46,7 +46,7 @@ impl SwapBundle { } } -impl + std::iter::Sum> SwapBundle { +impl + core::ops::Add> SwapBundle { /// Constructs a `SwapBundle` from its action groups and respective binding signature keys. /// Keys should go in the same order as the action groups. pub fn new( @@ -56,7 +56,11 @@ impl + std::iter::Sum> SwapBundle { ) -> Self { assert_eq!(action_groups.len(), bsks.len()); // Evaluate the swap value balance by summing the value balance of each action group. - let value_balance = action_groups.iter().map(|a| *a.value_balance()).sum(); + let value_balance = action_groups + .iter() + .map(|a| *a.value_balance()) + .reduce(|acc, v| acc + v) + .expect("action_groups must not be empty"); // Evaluate the swap bsk by summing the bsk of each action group. let bsk = bsks .into_iter() From d91aaf146364a06de1653e64f93b56fac5b3ca0f Mon Sep 17 00:00:00 2001 From: alexeykoren <2365507+alexeykoren@users.noreply.github.com> Date: Tue, 20 Jan 2026 16:13:00 +0100 Subject: [PATCH 48/48] Update blake --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1df56815f..21d9e83e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -183,9 +183,9 @@ dependencies = [ [[package]] name = "blake2b_simd" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c2f0dc9a68c6317d884f97cc36cf5a3d20ba14ce404227df55e1af708ab04bc" +checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780" dependencies = [ "arrayref", "arrayvec", @@ -386,9 +386,9 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "constant_time_eq" -version = "0.2.6" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a53c0a4d288377e7415b53dcfc3c04da5cdc2cc95c8d5ac178b58f0b861ad6" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "core-foundation" diff --git a/Cargo.toml b/Cargo.toml index 2019a54da..454215306 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ rustdoc-args = ["--cfg", "docsrs", "--html-in-header", "katex-header.html"] [dependencies] aes = "0.8" bitvec = { version = "1", default-features = false } -blake2b_simd = { version = "=1.0.1", default-features = false } +blake2b_simd = { version = "=1.0.2", default-features = false } ff = { version = "0.13", default-features = false } fpe = { version = "0.6", default-features = false, features = ["alloc"] } group = "0.13"