Skip to content

Commit

Permalink
Add ActionGroup tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ConstanceBeguier committed Oct 10, 2024
1 parent 0c061af commit e361432
Show file tree
Hide file tree
Showing 3 changed files with 241 additions and 4 deletions.
8 changes: 5 additions & 3 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -915,9 +915,11 @@ pub fn bundle<V: TryFrom<i64>, FL: OrchardFlavor>(
})
.collect::<Result<Vec<(AssetBase, NoteValue)>, 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| {
(
Expand Down
18 changes: 18 additions & 0 deletions tests/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,24 @@ pub fn verify_bundle<FL: OrchardFlavor>(
);
}

// 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<FL: OrchardFlavor>(
bundle: &Bundle<Authorized, i64, FL>,
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();
Expand Down
219 changes: 218 additions & 1 deletion tests/zsa.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
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;
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},
Expand Down Expand Up @@ -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<MerklePath>, Anchor) {
let mut tree = BridgeTree::<MerkleHashOrchard, u32, 32>::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
Expand Down Expand Up @@ -269,6 +303,46 @@ fn build_and_verify_bundle(
Ok(())
}

fn build_and_verify_action_group(
spends: Vec<&TestSpendInfo>,
outputs: Vec<TestOutputInfo>,
split_notes: Vec<&TestSpendInfo>,
anchor: Anchor,
timelimit: u32,
expected_num_actions: usize,
keys: &Keychain,
) -> Result<Bundle<Authorized, i64, OrchardZSA>, 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<Authorized, i64, OrchardZSA>) -> bool {
let mut unique_nulifiers = Vec::new();
let spent_nullifiers = bundle
Expand Down Expand Up @@ -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
}

0 comments on commit e361432

Please sign in to comment.