From e361432c122bae01e2817f3f375734db0d2cd184 Mon Sep 17 00:00:00 2001 From: Constance Beguier Date: Thu, 10 Oct 2024 16:11:40 +0200 Subject: [PATCH] Add ActionGroup tests --- src/builder.rs | 8 +- tests/builder.rs | 18 ++++ tests/zsa.rs | 219 ++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 241 insertions(+), 4 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index a7e661272..8f5fea8f6 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -915,9 +915,11 @@ pub fn bundle, FL: OrchardFlavor>( }) .collect::, BuildError>>()?; - // Verify that bsk and bvk are consistent. - let bvk = derive_bvk(&actions, native_value_balance, burn.iter().cloned()); - assert_eq!(redpallas::VerificationKey::from(&bsk), bvk); + // Verify that bsk and bvk are consistent except for ActionGroup (when timelimit is set) + if timelimit.is_none() { + let bvk = derive_bvk(&actions, native_value_balance, burn.iter().cloned()); + assert_eq!(redpallas::VerificationKey::from(&bsk), bvk); + } Ok(NonEmpty::from_vec(actions).map(|actions| { ( diff --git a/tests/builder.rs b/tests/builder.rs index 790d19305..8a03678f6 100644 --- a/tests/builder.rs +++ b/tests/builder.rs @@ -34,6 +34,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 1dbc4ee86..7907a7db8 100644 --- a/tests/zsa.rs +++ b/tests/zsa.rs @@ -1,6 +1,6 @@ mod builder; -use crate::builder::verify_bundle; +use crate::builder::{verify_action_group, verify_bundle}; use bridgetree::BridgeTree; use incrementalmerkletree::Hashable; use orchard::bundle::Authorized; @@ -8,6 +8,7 @@ use orchard::issuance::{verify_issue_bundle, IssueBundle, IssueInfo, Signed, Una 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}, @@ -136,6 +137,39 @@ pub fn build_merkle_path_with_two_leaves( (merkle_path1, merkle_path2, anchor) } +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: &str, keys: &Keychain) -> (Note, Note) { let mut rng = OsRng; // Create a issuance bundle @@ -269,6 +303,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 unique_nulifiers = Vec::new(); let spent_nullifiers = bundle @@ -559,3 +633,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 +}