Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 41 additions & 8 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,28 @@ impl SpendInfo {
}
}

/// Return a copy of this note with the split flag set to `true`.
fn create_split_spend(&self) -> Self {
SpendInfo::new(self.fvk.clone(), self.note, self.merkle_path.clone(), true).unwrap()
/// Creates a split spend, which is identical to origin normal spend except that we use a random
/// fvk to generate a different nullifier. In addition, the split_flag is raised.
///
/// Defined in [Transfer and Burn of Zcash Shielded Assets ZIP-0226 § Split Notes (DRAFT PR)][TransferZSA].
///
/// [TransferZSA]: https://qed-it.github.io/zips/zip-0226.html#split-notes
fn create_split_spend(&self, rng: &mut impl RngCore) -> Self {
let note = self.note;
let merkle_path = self.merkle_path.clone();

let sk = SpendingKey::random(rng);
let fvk: FullViewingKey = (&sk).into();

SpendInfo {
dummy_sk: Some(sk),
fvk,
// We use external scope to avoid unnecessary derivations
scope: Scope::External,
note,
merkle_path,
split_flag: true,
}
}
}

Expand Down Expand Up @@ -467,12 +486,12 @@ impl Builder {
.cloned()
.unwrap();

// use the first spend to create split spend(s) or create a dummy if empty.
let dummy_spend = spends.first().map_or_else(
|| SpendInfo::dummy(asset, &mut rng),
|s| s.create_split_spend(),
let first_spend = spends.first().cloned();

spends.extend(
iter::repeat_with(|| pad_spend(first_spend.as_ref(), asset, &mut rng))
.take(num_actions - num_spends),
);
spends.extend(iter::repeat_with(|| dummy_spend.clone()).take(num_actions - num_spends));

// Extend the recipients with dummy values.
recipients.extend(
Expand Down Expand Up @@ -574,6 +593,20 @@ fn partition_by_asset(
hm
}

/// Returns a dummy/split notes to extend the spends.
fn pad_spend(spend: Option<&SpendInfo>, asset: AssetBase, mut rng: impl RngCore) -> SpendInfo {
if asset.is_native().into() {
// For native asset, extends with dummy notes
SpendInfo::dummy(asset, &mut rng)
} else {
// For ZSA asset, extends with
// - dummy notes if first spend is empty
// - split notes otherwise.
let dummy = SpendInfo::dummy(asset, &mut rng);
spend.map_or_else(|| dummy, |s| s.create_split_spend(&mut rng))
}
}

/// Marker trait representing bundle signatures in the process of being created.
pub trait InProgressSignatures: fmt::Debug {
/// The authorization type of an Orchard action in the process of being authorized.
Expand Down
51 changes: 40 additions & 11 deletions tests/zsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,9 +261,25 @@ fn build_and_verify_bundle(
// Verify the shielded bundle, currently without the proof.
verify_bundle(&shielded_bundle, &keys.vk, true);
assert_eq!(shielded_bundle.actions().len(), expected_num_actions);
assert!(verify_unique_spent_nullifiers(&shielded_bundle));
Ok(())
}

fn verify_unique_spent_nullifiers(bundle: &Bundle<Authorized, i64>) -> bool {
let mut unique_nulifiers = Vec::new();
let spent_nullifiers = bundle
.actions()
.iter()
.map(|action| *action.nullifier())
.collect::<Vec<_>>();
spent_nullifiers.iter().enumerate().all(|(i, item)| {
unique_nulifiers.push(*item);
// Check if the item is already in the unique_nullifiers vector by checking that the first
// position of the item is equal to the current index i.
unique_nulifiers.iter().position(|x| x == item) == Some(i)
})
}

/// Issue several ZSA and native notes and spend them in different combinations, e.g. split and join
#[test]
fn zsa_issue_and_transfer() {
Expand Down Expand Up @@ -315,23 +331,28 @@ fn zsa_issue_and_transfer() {
)
.unwrap();

// 2. Split single ZSA note into 2 notes
let delta = 2; // arbitrary number for value manipulation
// 2. Split single ZSA note into 3 notes
let delta_1 = 2; // arbitrary number for value manipulation
let delta_2 = 5; // arbitrary number for value manipulation
build_and_verify_bundle(
vec![&zsa_spend_1],
vec![
TestOutputInfo {
value: NoteValue::from_raw(zsa_spend_1.note.value().inner() - delta),
value: NoteValue::from_raw(zsa_spend_1.note.value().inner() - delta_1 - delta_2),
asset: zsa_spend_1.note.asset(),
},
TestOutputInfo {
value: NoteValue::from_raw(delta),
value: NoteValue::from_raw(delta_1),
asset: zsa_spend_1.note.asset(),
},
TestOutputInfo {
value: NoteValue::from_raw(delta_2),
asset: zsa_spend_1.note.asset(),
},
],
vec![],
anchor,
2,
3,
&keys,
)
.unwrap();
Expand All @@ -357,11 +378,11 @@ fn zsa_issue_and_transfer() {
vec![&zsa_spend_1, &zsa_spend_2],
vec![
TestOutputInfo {
value: NoteValue::from_raw(zsa_spend_1.note.value().inner() - delta),
value: NoteValue::from_raw(zsa_spend_1.note.value().inner() - delta_1),
asset: zsa_spend_1.note.asset(),
},
TestOutputInfo {
value: NoteValue::from_raw(zsa_spend_2.note.value().inner() + delta),
value: NoteValue::from_raw(zsa_spend_2.note.value().inner() + delta_1),
asset: zsa_spend_2.note.asset(),
},
],
Expand Down Expand Up @@ -401,13 +422,21 @@ fn zsa_issue_and_transfer() {
asset: zsa_spend_1.note.asset(),
},
TestOutputInfo {
value: native_spend.note.value(),
value: NoteValue::from_raw(native_spend.note.value().inner() - delta_1 - delta_2),
asset: AssetBase::native(),
},
TestOutputInfo {
value: NoteValue::from_raw(delta_1),
asset: AssetBase::native(),
},
TestOutputInfo {
value: NoteValue::from_raw(delta_2),
asset: AssetBase::native(),
},
],
vec![],
native_anchor,
4,
5,
&keys,
)
.unwrap();
Expand Down Expand Up @@ -450,11 +479,11 @@ fn zsa_issue_and_transfer() {
vec![&zsa_spend_t7_1, &zsa_spend_t7_2],
vec![
TestOutputInfo {
value: NoteValue::from_raw(zsa_spend_t7_1.note.value().inner() + delta),
value: NoteValue::from_raw(zsa_spend_t7_1.note.value().inner() + delta_1),
asset: zsa_spend_t7_1.note.asset(),
},
TestOutputInfo {
value: NoteValue::from_raw(zsa_spend_t7_2.note.value().inner() - delta),
value: NoteValue::from_raw(zsa_spend_t7_2.note.value().inner() - delta_1),
asset: zsa_spend_t7_2.note.asset(),
},
],
Expand Down