mirror of https://github.com/zcash/orchard.git
Do not create split notes with native asset (#65)
Due to privacy considerations, we might incorporate dummy or split notes while generating a bundle. However, to maintain consistency with the previous version, we choose not to include split notes for native asset. In addition, we use a new dummy/split notes for each extend in order to have different nullifiers.
This commit is contained in:
parent
bedc732d6f
commit
32eee6e083
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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.
|
||||
|
|
51
tests/zsa.rs
51
tests/zsa.rs
|
@ -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() {
|
||||
|
@ -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();
|
||||
|
@ -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(),
|
||||
},
|
||||
],
|
||||
|
@ -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();
|
||||
|
@ -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(),
|
||||
},
|
||||
],
|
||||
|
|
Loading…
Reference in New Issue