Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
51 changes: 43 additions & 8 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,27 @@ 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].
Comment thread
ConstanceBeguier marked this conversation as resolved.
/// [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 @@ -455,6 +473,23 @@ impl Builder {
) -> Result<Bundle<InProgress<Unproven, Unauthorized>, V>, BuildError> {
let mut pre_actions: Vec<_> = Vec::new();

fn spend_info_extension(
Comment thread
ConstanceBeguier marked this conversation as resolved.
Outdated
first_spend: Option<&SpendInfo>,
Comment thread
ConstanceBeguier marked this conversation as resolved.
Outdated
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);
first_spend.map_or_else(|| dummy, |s| s.create_split_spend(&mut rng))
}
}

// Pair up the spends and recipients, extending with dummy values as necessary.
for (asset, (mut spends, mut recipients)) in
partition_by_asset(&self.spends, &self.recipients, &mut rng)
Expand All @@ -467,12 +502,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();

Comment thread
ConstanceBeguier marked this conversation as resolved.
Outdated
spends.extend(
iter::repeat_with(|| spend_info_extension(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
52 changes: 41 additions & 11 deletions tests/zsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,9 +261,26 @@ 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 spent_nullifiers = bundle
.actions()
.iter()
.map(|action| *action.nullifier())
.collect::<Vec<_>>();
for (i, nullifier) in spent_nullifiers.iter().enumerate() {
for other_nullifier in spent_nullifiers[i + 1..].iter() {
Comment thread
ConstanceBeguier marked this conversation as resolved.
Outdated
if *nullifier == *other_nullifier {
return false;
}
}
}
true
}

/// 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 +332,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 delta1 = 2; // arbitrary number for value manipulation
Comment thread
ConstanceBeguier marked this conversation as resolved.
Outdated
let delta2 = 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() - delta1 - delta2),
asset: zsa_spend_1.note.asset(),
},
TestOutputInfo {
value: NoteValue::from_raw(delta1),
asset: zsa_spend_1.note.asset(),
},
TestOutputInfo {
value: NoteValue::from_raw(delta),
value: NoteValue::from_raw(delta2),
asset: zsa_spend_1.note.asset(),
},
],
vec![],
anchor,
2,
3,
&keys,
)
.unwrap();
Expand All @@ -357,11 +379,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() - delta1),
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() + delta1),
asset: zsa_spend_2.note.asset(),
},
],
Expand Down Expand Up @@ -401,13 +423,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() - delta1 - delta2),
asset: AssetBase::native(),
},
TestOutputInfo {
value: NoteValue::from_raw(delta1),
asset: AssetBase::native(),
},
TestOutputInfo {
value: NoteValue::from_raw(delta2),
asset: AssetBase::native(),
},
],
vec![],
native_anchor,
4,
5,
&keys,
)
.unwrap();
Expand Down Expand Up @@ -450,11 +480,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() + delta1),
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() - delta1),
asset: zsa_spend_t7_2.note.asset(),
},
],
Expand Down