added note_type to builder

This commit is contained in:
Paul 2022-06-08 15:30:40 +03:00
parent 3a6ed1ed30
commit 48d65ee706
11 changed files with 269 additions and 56 deletions

3
.gitignore vendored
View File

@ -2,3 +2,6 @@
**/*.rs.bk **/*.rs.bk
Cargo.lock Cargo.lock
.vscode .vscode
.idea
action-circuit-layout.png
proptest-regressions/*.txt

View File

@ -6,6 +6,7 @@ use criterion::{BenchmarkId, Criterion};
#[cfg(unix)] #[cfg(unix)]
use pprof::criterion::{Output, PProfProfiler}; use pprof::criterion::{Output, PProfProfiler};
use orchard::note::NoteType;
use orchard::{ use orchard::{
builder::Builder, builder::Builder,
bundle::Flags, bundle::Flags,
@ -32,7 +33,13 @@ fn criterion_benchmark(c: &mut Criterion) {
); );
for _ in 0..num_recipients { for _ in 0..num_recipients {
builder builder
.add_recipient(None, recipient, NoteValue::from_raw(10), None) .add_recipient(
None,
recipient,
NoteValue::from_raw(10),
NoteType::native(),
None,
)
.unwrap(); .unwrap();
} }
let bundle: Bundle<_, i64> = builder.build(rng).unwrap(); let bundle: Bundle<_, i64> = builder.build(rng).unwrap();

View File

@ -4,6 +4,7 @@ use orchard::{
bundle::Flags, bundle::Flags,
circuit::ProvingKey, circuit::ProvingKey,
keys::{FullViewingKey, Scope, SpendingKey}, keys::{FullViewingKey, Scope, SpendingKey},
note::NoteType,
note_encryption::{CompactAction, OrchardDomain}, note_encryption::{CompactAction, OrchardDomain},
value::NoteValue, value::NoteValue,
Anchor, Bundle, Anchor, Bundle,
@ -51,10 +52,22 @@ fn bench_note_decryption(c: &mut Criterion) {
// The builder pads to two actions, and shuffles their order. Add two recipients // The builder pads to two actions, and shuffles their order. Add two recipients
// so the first action is always decryptable. // so the first action is always decryptable.
builder builder
.add_recipient(None, recipient, NoteValue::from_raw(10), None) .add_recipient(
None,
recipient,
NoteValue::from_raw(10),
NoteType::native(),
None,
)
.unwrap(); .unwrap();
builder builder
.add_recipient(None, recipient, NoteValue::from_raw(10), None) .add_recipient(
None,
recipient,
NoteValue::from_raw(10),
NoteType::native(),
None,
)
.unwrap(); .unwrap();
let bundle: Bundle<_, i64> = builder.build(rng).unwrap(); let bundle: Bundle<_, i64> = builder.build(rng).unwrap();
bundle bundle

View File

@ -126,7 +126,7 @@ pub(crate) mod testing {
use proptest::prelude::*; use proptest::prelude::*;
use crate::note::NoteType; use crate::note::note_type::testing::arb_note_type;
use crate::{ use crate::{
note::{ note::{
commitment::ExtractedNoteCommitment, nullifier::testing::arb_nullifier, commitment::ExtractedNoteCommitment, nullifier::testing::arb_nullifier,
@ -147,12 +147,13 @@ pub(crate) mod testing {
nf in arb_nullifier(), nf in arb_nullifier(),
rk in arb_spendauth_verification_key(), rk in arb_spendauth_verification_key(),
note in arb_note(output_value), note in arb_note(output_value),
note_type in arb_note_type()
) -> Action<()> { ) -> Action<()> {
let cmx = ExtractedNoteCommitment::from(note.commitment()); let cmx = ExtractedNoteCommitment::from(note.commitment());
let cv_net = ValueCommitment::derive( let cv_net = ValueCommitment::derive(
spend_value - output_value, spend_value - output_value,
ValueCommitTrapdoor::zero(), ValueCommitTrapdoor::zero(),
NoteType::native() note_type
); );
// FIXME: make a real one from the note. // FIXME: make a real one from the note.
let encrypted_note = TransmittedNoteCiphertext { let encrypted_note = TransmittedNoteCiphertext {
@ -179,12 +180,13 @@ pub(crate) mod testing {
note in arb_note(output_value), note in arb_note(output_value),
rng_seed in prop::array::uniform32(prop::num::u8::ANY), rng_seed in prop::array::uniform32(prop::num::u8::ANY),
fake_sighash in prop::array::uniform32(prop::num::u8::ANY), fake_sighash in prop::array::uniform32(prop::num::u8::ANY),
note_type in arb_note_type()
) -> Action<redpallas::Signature<SpendAuth>> { ) -> Action<redpallas::Signature<SpendAuth>> {
let cmx = ExtractedNoteCommitment::from(note.commitment()); let cmx = ExtractedNoteCommitment::from(note.commitment());
let cv_net = ValueCommitment::derive( let cv_net = ValueCommitment::derive(
spend_value - output_value, spend_value - output_value,
ValueCommitTrapdoor::zero(), ValueCommitTrapdoor::zero(),
NoteType::native() note_type
); );
// FIXME: make a real one from the note. // FIXME: make a real one from the note.

View File

@ -2,6 +2,7 @@
use core::fmt; use core::fmt;
use core::iter; use core::iter;
use std::collections::HashMap;
use ff::Field; use ff::Field;
use nonempty::NonEmpty; use nonempty::NonEmpty;
@ -57,21 +58,23 @@ impl From<value::OverflowError> for Error {
} }
/// Information about a specific note to be spent in an [`Action`]. /// Information about a specific note to be spent in an [`Action`].
#[derive(Debug)] #[derive(Debug, Clone)]
struct SpendInfo { struct SpendInfo {
dummy_sk: Option<SpendingKey>, dummy_sk: Option<SpendingKey>,
fvk: FullViewingKey, fvk: FullViewingKey,
scope: Scope, scope: Scope,
note: Note, note: Note,
merkle_path: MerklePath, merkle_path: MerklePath,
// a flag to indicate whether the value of the note will be counted in the value sum of the action.
split_flag: bool,
} }
impl SpendInfo { impl SpendInfo {
/// Defined in [Zcash Protocol Spec § 4.8.3: Dummy Notes (Orchard)][orcharddummynotes]. /// Defined in [Zcash Protocol Spec § 4.8.3: Dummy Notes (Orchard)][orcharddummynotes].
/// ///
/// [orcharddummynotes]: https://zips.z.cash/protocol/nu5.pdf#orcharddummynotes /// [orcharddummynotes]: https://zips.z.cash/protocol/nu5.pdf#orcharddummynotes
fn dummy(rng: &mut impl RngCore) -> Self { fn dummy(note_type: NoteType, rng: &mut impl RngCore) -> Self {
let (sk, fvk, note) = Note::dummy(rng, None); let (sk, fvk, note) = Note::dummy(rng, None, note_type);
let merkle_path = MerklePath::dummy(rng); let merkle_path = MerklePath::dummy(rng);
SpendInfo { SpendInfo {
@ -82,16 +85,25 @@ impl SpendInfo {
scope: Scope::External, scope: Scope::External,
note, note,
merkle_path, merkle_path,
split_flag: false,
} }
} }
/// Duplicates the spend info and set the split flag to `true`.
fn create_split_spend(&self) -> Self {
let mut split_spend = self.clone();
split_spend.split_flag = true;
split_spend
}
} }
/// Information about a specific recipient to receive funds in an [`Action`]. /// Information about a specific recipient to receive funds in an [`Action`].
#[derive(Debug)] #[derive(Debug, Clone)]
struct RecipientInfo { struct RecipientInfo {
ovk: Option<OutgoingViewingKey>, ovk: Option<OutgoingViewingKey>,
recipient: Address, recipient: Address,
value: NoteValue, value: NoteValue,
note_type: NoteType,
memo: Option<[u8; 512]>, memo: Option<[u8; 512]>,
} }
@ -107,6 +119,7 @@ impl RecipientInfo {
ovk: None, ovk: None,
recipient, recipient,
value: NoteValue::zero(), value: NoteValue::zero(),
note_type: NoteType::native(),
memo: None, memo: None,
} }
} }
@ -130,7 +143,17 @@ impl ActionInfo {
} }
/// Returns the value sum for this action. /// Returns the value sum for this action.
/// Split notes does not contribute to the value sum.
fn value_sum(&self) -> ValueSum { fn value_sum(&self) -> ValueSum {
// TODO: Aurel, uncomment when circuit for split flag is implemented.
// let spent_value = self
// .spend
// .split_flag
// .then(|| self.spend.note.value())
// .unwrap_or_else(NoteValue::zero);
//
// spent_value - self.output.value
self.spend.note.value() - self.output.value self.spend.note.value() - self.output.value
} }
@ -140,8 +163,15 @@ impl ActionInfo {
/// ///
/// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend /// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend
fn build(self, mut rng: impl RngCore) -> (Action<SigningMetadata>, Circuit) { fn build(self, mut rng: impl RngCore) -> (Action<SigningMetadata>, Circuit) {
assert_eq!(
self.output.note_type,
self.spend.note.note_type(),
"spend and recipient note types must be equal"
);
let v_net = self.value_sum(); let v_net = self.value_sum();
let cv_net = ValueCommitment::derive(v_net, self.rcv, NoteType::native()); let note_type = self.output.note_type;
let cv_net = ValueCommitment::derive(v_net, self.rcv, note_type);
let nf_old = self.spend.note.nullifier(&self.spend.fvk); let nf_old = self.spend.note.nullifier(&self.spend.fvk);
let sender_address = self.spend.note.recipient(); let sender_address = self.spend.note.recipient();
@ -151,7 +181,6 @@ impl ActionInfo {
let ak: SpendValidatingKey = self.spend.fvk.clone().into(); let ak: SpendValidatingKey = self.spend.fvk.clone().into();
let alpha = pallas::Scalar::random(&mut rng); let alpha = pallas::Scalar::random(&mut rng);
let rk = ak.randomize(&alpha); let rk = ak.randomize(&alpha);
let note_type = self.spend.note.note_type();
let note = Note::new( let note = Note::new(
self.output.recipient, self.output.recipient,
@ -201,6 +230,7 @@ impl ActionInfo {
g_d_old: Some(sender_address.g_d()), g_d_old: Some(sender_address.g_d()),
pk_d_old: Some(*sender_address.pk_d()), pk_d_old: Some(*sender_address.pk_d()),
v_old: Some(self.spend.note.value()), v_old: Some(self.spend.note.value()),
//split: Some(self.spend.split_flag),
rho_old: Some(rho_old), rho_old: Some(rho_old),
psi_old: Some(psi_old), psi_old: Some(psi_old),
rcm_old: Some(rcm_old), rcm_old: Some(rcm_old),
@ -274,6 +304,7 @@ impl Builder {
scope, scope,
note, note,
merkle_path, merkle_path,
split_flag: false,
}); });
Ok(()) Ok(())
@ -285,6 +316,7 @@ impl Builder {
ovk: Option<OutgoingViewingKey>, ovk: Option<OutgoingViewingKey>,
recipient: Address, recipient: Address,
value: NoteValue, value: NoteValue,
note_type: NoteType,
memo: Option<[u8; 512]>, memo: Option<[u8; 512]>,
) -> Result<(), &'static str> { ) -> Result<(), &'static str> {
if !self.flags.outputs_enabled() { if !self.flags.outputs_enabled() {
@ -295,6 +327,7 @@ impl Builder {
ovk, ovk,
recipient, recipient,
value, value,
note_type,
memo, memo,
}); });
@ -306,23 +339,31 @@ impl Builder {
/// The returned bundle will have no proof or signatures; these can be applied with /// The returned bundle will have no proof or signatures; these can be applied with
/// [`Bundle::create_proof`] and [`Bundle::apply_signatures`] respectively. /// [`Bundle::create_proof`] and [`Bundle::apply_signatures`] respectively.
pub fn build<V: TryFrom<i64>>( pub fn build<V: TryFrom<i64>>(
mut self, self,
mut rng: impl RngCore, mut rng: impl RngCore,
) -> Result<Bundle<InProgress<Unproven, Unauthorized>, V>, Error> { ) -> Result<Bundle<InProgress<Unproven, Unauthorized>, V>, Error> {
let mut pre_actions: Vec<_> = Vec::new();
// Pair up the spends and recipients, extending with dummy values as necessary. // Pair up the spends and recipients, extending with dummy values as necessary.
let pre_actions: Vec<_> = { for (note_type, (mut spends, mut recipients)) in partition(&self.spends, &self.recipients) {
let num_spends = self.spends.len(); let num_spends = spends.len();
let num_recipients = self.recipients.len(); let num_recipients = recipients.len();
let num_actions = [num_spends, num_recipients, MIN_ACTIONS] let num_actions = [num_spends, num_recipients, MIN_ACTIONS]
.iter() .iter()
.max() .max()
.cloned() .cloned()
.unwrap(); .unwrap();
self.spends.extend( // use the first spend to create split spend(s) or create a dummy if empty.
iter::repeat_with(|| SpendInfo::dummy(&mut rng)).take(num_actions - num_spends), let dummy_spend = spends.first().map_or_else(
|| SpendInfo::dummy(note_type, &mut rng),
|s| s.create_split_spend(),
); );
self.recipients.extend(
// Extend the spends and recipients with dummy values.
spends.extend(iter::repeat_with(|| dummy_spend.clone()).take(num_actions - num_spends));
recipients.extend(
iter::repeat_with(|| RecipientInfo::dummy(&mut rng)) iter::repeat_with(|| RecipientInfo::dummy(&mut rng))
.take(num_actions - num_recipients), .take(num_actions - num_recipients),
); );
@ -330,15 +371,16 @@ impl Builder {
// Shuffle the spends and recipients, so that learning the position of a // Shuffle the spends and recipients, so that learning the position of a
// specific spent note or output note doesn't reveal anything on its own about // specific spent note or output note doesn't reveal anything on its own about
// the meaning of that note in the transaction context. // the meaning of that note in the transaction context.
self.spends.shuffle(&mut rng); spends.shuffle(&mut rng);
self.recipients.shuffle(&mut rng); recipients.shuffle(&mut rng);
self.spends pre_actions.extend(
.into_iter() spends
.zip(self.recipients.into_iter()) .into_iter()
.map(|(spend, recipient)| ActionInfo::new(spend, recipient, &mut rng)) .zip(recipients.into_iter())
.collect() .map(|(spend, recipient)| ActionInfo::new(spend, recipient, &mut rng)),
}; );
}
// Move some things out of self that we will need. // Move some things out of self that we will need.
let flags = self.flags; let flags = self.flags;
@ -390,6 +432,30 @@ impl Builder {
} }
} }
/// partition a list of spends and recipients by note types.
fn partition(
spends: &[SpendInfo],
recipients: &[RecipientInfo],
) -> HashMap<NoteType, (Vec<SpendInfo>, Vec<RecipientInfo>)> {
let mut hm = HashMap::new();
for s in spends.iter() {
hm.entry(s.note.note_type())
.or_insert((vec![], vec![]))
.0
.push(s.clone());
}
for r in recipients.iter() {
hm.entry(r.note_type)
.or_insert((vec![], vec![]))
.1
.push(r.clone())
}
hm
}
/// Marker trait representing bundle signatures in the process of being created. /// Marker trait representing bundle signatures in the process of being created.
pub trait InProgressSignatures: fmt::Debug { pub trait InProgressSignatures: fmt::Debug {
/// The authorization type of an Orchard action in the process of being authorized. /// The authorization type of an Orchard action in the process of being authorized.
@ -654,6 +720,7 @@ pub mod testing {
use proptest::collection::vec; use proptest::collection::vec;
use proptest::prelude::*; use proptest::prelude::*;
use crate::note::NoteType;
use crate::{ use crate::{
address::testing::arb_address, address::testing::arb_address,
bundle::{Authorized, Bundle, Flags}, bundle::{Authorized, Bundle, Flags},
@ -681,7 +748,7 @@ pub mod testing {
sk: SpendingKey, sk: SpendingKey,
anchor: Anchor, anchor: Anchor,
notes: Vec<(Note, MerklePath)>, notes: Vec<(Note, MerklePath)>,
recipient_amounts: Vec<(Address, NoteValue)>, recipient_amounts: Vec<(Address, NoteValue, NoteType)>,
} }
impl<R: RngCore + CryptoRng> ArbitraryBundleInputs<R> { impl<R: RngCore + CryptoRng> ArbitraryBundleInputs<R> {
@ -695,12 +762,12 @@ pub mod testing {
builder.add_spend(fvk.clone(), note, path).unwrap(); builder.add_spend(fvk.clone(), note, path).unwrap();
} }
for (addr, value) in self.recipient_amounts.into_iter() { for (addr, value, note_type) in self.recipient_amounts.into_iter() {
let scope = fvk.scope_for_address(&addr).unwrap(); let scope = fvk.scope_for_address(&addr).unwrap();
let ovk = fvk.to_ovk(scope); let ovk = fvk.to_ovk(scope);
builder builder
.add_recipient(Some(ovk.clone()), addr, value, None) .add_recipient(Some(ovk.clone()), addr, value, note_type, None)
.unwrap(); .unwrap();
} }
@ -734,9 +801,12 @@ pub mod testing {
recipient_amounts in vec( recipient_amounts in vec(
arb_address().prop_flat_map(move |a| { arb_address().prop_flat_map(move |a| {
arb_positive_note_value(MAX_NOTE_VALUE / n_recipients as u64) arb_positive_note_value(MAX_NOTE_VALUE / n_recipients as u64)
.prop_map(move |v| (a, v)) .prop_map(move |v| {
// replace note type with arb_note_type
(a,v, NoteType::native())
})
}), }),
n_recipients as usize n_recipients as usize,
), ),
rng_seed in prop::array::uniform32(prop::num::u8::ANY) rng_seed in prop::array::uniform32(prop::num::u8::ANY)
) -> ArbitraryBundleInputs<StdRng> { ) -> ArbitraryBundleInputs<StdRng> {
@ -784,6 +854,8 @@ mod tests {
use rand::rngs::OsRng; use rand::rngs::OsRng;
use super::Builder; use super::Builder;
// use crate::keys::{IssuerAuthorizingKey, IssuerValidatingKey};
use crate::note::NoteType;
use crate::{ use crate::{
bundle::{Authorized, Bundle, Flags}, bundle::{Authorized, Bundle, Flags},
circuit::ProvingKey, circuit::ProvingKey,
@ -808,8 +880,38 @@ mod tests {
); );
builder builder
.add_recipient(None, recipient, NoteValue::from_raw(5000), None) .add_recipient(
None,
recipient,
NoteValue::from_raw(5000),
NoteType::native(),
None,
)
.unwrap(); .unwrap();
//
// builder
// .add_recipient(
// None,
// recipient,
// NoteValue::from_raw(3),
// NoteType::native(),
// None,
// )
// .unwrap();
//
//
// let isk = IssuerAuthorizingKey::from(&sk);
//
// builder
// .add_recipient(
// None,
// recipient,
// NoteValue::from_raw(5),
// NoteType::derive(&IssuerValidatingKey::from(&isk), Vec::new()),
// None,
// )
// .unwrap();
let bundle: Bundle<Authorized, i64> = builder let bundle: Bundle<Authorized, i64> = builder
.build(&mut rng) .build(&mut rng)
.unwrap() .unwrap()

View File

@ -104,6 +104,7 @@ pub struct Circuit {
pub(crate) g_d_old: Option<NonIdentityPallasPoint>, pub(crate) g_d_old: Option<NonIdentityPallasPoint>,
pub(crate) pk_d_old: Option<DiversifiedTransmissionKey>, pub(crate) pk_d_old: Option<DiversifiedTransmissionKey>,
pub(crate) v_old: Option<NoteValue>, pub(crate) v_old: Option<NoteValue>,
// pub(crate) split: Option<bool>,
pub(crate) rho_old: Option<Nullifier>, pub(crate) rho_old: Option<Nullifier>,
pub(crate) psi_old: Option<pallas::Base>, pub(crate) psi_old: Option<pallas::Base>,
pub(crate) rcm_old: Option<NoteCommitTrapdoor>, pub(crate) rcm_old: Option<NoteCommitTrapdoor>,
@ -891,7 +892,7 @@ mod tests {
}; };
fn generate_circuit_instance<R: RngCore>(mut rng: R) -> (Circuit, Instance) { fn generate_circuit_instance<R: RngCore>(mut rng: R) -> (Circuit, Instance) {
let (_, fvk, spent_note) = Note::dummy(&mut rng, None); let (_, fvk, spent_note) = Note::dummy(&mut rng, None, NoteType::native());
let sender_address = spent_note.recipient(); let sender_address = spent_note.recipient();
let nk = *fvk.nk(); let nk = *fvk.nk();
@ -901,7 +902,7 @@ mod tests {
let alpha = pallas::Scalar::random(&mut rng); let alpha = pallas::Scalar::random(&mut rng);
let rk = ak.randomize(&alpha); let rk = ak.randomize(&alpha);
let (_, _, output_note) = Note::dummy(&mut rng, Some(nf_old)); let (_, _, output_note) = Note::dummy(&mut rng, Some(nf_old), NoteType::native());
let cmx = output_note.commitment().into(); let cmx = output_note.commitment().into();
let value = spent_note.value() - output_note.value(); let value = spent_note.value() - output_note.value();
@ -918,6 +919,7 @@ mod tests {
g_d_old: Some(sender_address.g_d()), g_d_old: Some(sender_address.g_d()),
pk_d_old: Some(*sender_address.pk_d()), pk_d_old: Some(*sender_address.pk_d()),
v_old: Some(spent_note.value()), v_old: Some(spent_note.value()),
// split: Some(false),
rho_old: Some(spent_note.rho()), rho_old: Some(spent_note.rho()),
psi_old: Some(spent_note.rseed().psi(&spent_note.rho())), psi_old: Some(spent_note.rseed().psi(&spent_note.rho())),
rcm_old: Some(spent_note.rseed().rcm(&spent_note.rho())), rcm_old: Some(spent_note.rseed().rcm(&spent_note.rho())),
@ -1118,6 +1120,7 @@ mod tests {
psi_new: None, psi_new: None,
rcm_new: None, rcm_new: None,
rcv: None, rcv: None,
// split: None,
}; };
halo2_proofs::dev::CircuitLayout::default() halo2_proofs::dev::CircuitLayout::default()
.show_labels(false) .show_labels(false)

View File

@ -163,6 +163,7 @@ impl Note {
pub(crate) fn dummy( pub(crate) fn dummy(
rng: &mut impl RngCore, rng: &mut impl RngCore,
rho: Option<Nullifier>, rho: Option<Nullifier>,
note_type: NoteType,
) -> (SpendingKey, FullViewingKey, Self) { ) -> (SpendingKey, FullViewingKey, Self) {
let sk = SpendingKey::random(rng); let sk = SpendingKey::random(rng);
let fvk: FullViewingKey = (&sk).into(); let fvk: FullViewingKey = (&sk).into();
@ -171,7 +172,7 @@ impl Note {
let note = Note::new( let note = Note::new(
recipient, recipient,
NoteValue::zero(), NoteValue::zero(),
NoteType::native(), note_type,
rho.unwrap_or_else(|| Nullifier::dummy(rng)), rho.unwrap_or_else(|| Nullifier::dummy(rng)),
rng, rng,
); );
@ -296,7 +297,7 @@ pub mod testing {
} }
prop_compose! { prop_compose! {
/// Generate an action without authorization data. /// Generate an arbitrary note
pub fn arb_note(value: NoteValue)( pub fn arb_note(value: NoteValue)(
recipient in arb_address(), recipient in arb_address(),
rho in arb_nullifier(), rho in arb_nullifier(),

View File

@ -1,13 +1,14 @@
use group::GroupEncoding; use group::GroupEncoding;
use halo2_proofs::arithmetic::CurveExt; use halo2_proofs::arithmetic::CurveExt;
use pasta_curves::pallas; use pasta_curves::pallas;
use std::hash::{Hash, Hasher};
use subtle::{Choice, ConstantTimeEq, CtOption}; use subtle::{Choice, ConstantTimeEq, CtOption};
use crate::constants::fixed_bases::{VALUE_COMMITMENT_PERSONALIZATION, VALUE_COMMITMENT_V_BYTES}; use crate::constants::fixed_bases::{VALUE_COMMITMENT_PERSONALIZATION, VALUE_COMMITMENT_V_BYTES};
use crate::keys::IssuerValidatingKey; use crate::keys::IssuerValidatingKey;
/// Note type identifier. /// Note type identifier.
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, Eq)]
pub struct NoteType(pallas::Point); pub struct NoteType(pallas::Point);
pub const MAX_ASSET_DESCRIPTION_SIZE: usize = 512; pub const MAX_ASSET_DESCRIPTION_SIZE: usize = 512;
@ -62,6 +63,19 @@ impl NoteType {
} }
} }
impl Hash for NoteType {
fn hash<H: Hasher>(&self, h: &mut H) {
h.write(&self.to_bytes());
h.finish();
}
}
impl PartialEq for NoteType {
fn eq(&self, other: &Self) -> bool {
bool::from(self.0.ct_eq(&other.0))
}
}
/// Generators for property testing. /// Generators for property testing.
#[cfg(any(test, feature = "test-dependencies"))] #[cfg(any(test, feature = "test-dependencies"))]
#[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))] #[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))]
@ -83,4 +97,12 @@ pub mod testing {
NoteType::derive(&IssuerValidatingKey::from(&isk), bytes64) NoteType::derive(&IssuerValidatingKey::from(&isk), bytes64)
} }
} }
prop_compose! {
/// Generate the native note type
pub fn native_note_type()(_i in 0..10) -> NoteType {
// TODO: remove _i
NoteType::native()
}
}
} }

View File

@ -77,7 +77,7 @@ impl Anchor {
/// The Merkle path from a leaf of the note commitment tree /// The Merkle path from a leaf of the note commitment tree
/// to its anchor. /// to its anchor.
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct MerklePath { pub struct MerklePath {
position: u32, position: u32,
auth_path: [MerkleHashOrchard; MERKLE_DEPTH_ORCHARD], auth_path: [MerkleHashOrchard; MERKLE_DEPTH_ORCHARD],

View File

@ -38,6 +38,7 @@
use core::fmt::{self, Debug}; use core::fmt::{self, Debug};
use core::iter::Sum; use core::iter::Sum;
use core::ops::{Add, RangeInclusive, Sub}; use core::ops::{Add, RangeInclusive, Sub};
use std::ops::Neg;
use bitvec::{array::BitArray, order::Lsb0}; use bitvec::{array::BitArray, order::Lsb0};
use ff::{Field, PrimeField}; use ff::{Field, PrimeField};
@ -187,6 +188,18 @@ impl Add for ValueSum {
} }
} }
impl Neg for ValueSum {
type Output = Option<ValueSum>;
#[allow(clippy::suspicious_arithmetic_impl)]
fn neg(self) -> Self::Output {
self.0
.checked_neg()
.filter(|v| VALUE_SUM_RANGE.contains(v))
.map(ValueSum)
}
}
impl<'a> Sum<&'a ValueSum> for Result<ValueSum, OverflowError> { impl<'a> Sum<&'a ValueSum> for Result<ValueSum, OverflowError> {
fn sum<I: Iterator<Item = &'a ValueSum>>(iter: I) -> Self { fn sum<I: Iterator<Item = &'a ValueSum>>(iter: I) -> Self {
iter.fold(Ok(ValueSum(0)), |acc, v| (acc? + *v).ok_or(OverflowError)) iter.fold(Ok(ValueSum(0)), |acc, v| (acc? + *v).ok_or(OverflowError))
@ -407,7 +420,7 @@ pub mod testing {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::note::note_type::testing::arb_note_type; use crate::note::note_type::testing::{arb_note_type, native_note_type};
use crate::note::NoteType; use crate::note::NoteType;
use proptest::prelude::*; use proptest::prelude::*;
@ -417,24 +430,42 @@ mod tests {
}; };
use crate::primitives::redpallas; use crate::primitives::redpallas;
fn _bsk_consistent_with_bvk(values: &[(ValueSum, ValueCommitTrapdoor)], note_type: NoteType) { fn _bsk_consistent_with_bvk(
let value_balance = values native_values: &[(ValueSum, ValueCommitTrapdoor, NoteType)],
arb_values: &[(ValueSum, ValueCommitTrapdoor, NoteType)],
neg_trapdoors: &[ValueCommitTrapdoor],
) {
// for each arb value, create a negative value with a different trapdoor
let neg_arb_values: Vec<_> = arb_values
.iter() .iter()
.map(|(value, _)| value) .cloned()
.zip(neg_trapdoors.iter().cloned())
.map(|((value, _, note_type), rcv)| ((-value).unwrap(), rcv, note_type))
.collect();
let native_value_balance = native_values
.iter()
.map(|(value, _, _)| value)
.sum::<Result<ValueSum, OverflowError>>() .sum::<Result<ValueSum, OverflowError>>()
.expect("we generate values that won't overflow"); .expect("we generate values that won't overflow");
let values = [native_values, arb_values, &neg_arb_values].concat();
let bsk = values let bsk = values
.iter() .iter()
.map(|(_, rcv)| rcv) .map(|(_, rcv, _)| rcv)
.sum::<ValueCommitTrapdoor>() .sum::<ValueCommitTrapdoor>()
.into_bsk(); .into_bsk();
let bvk = (values let bvk = (values
.iter() .iter()
.map(|(value, rcv)| ValueCommitment::derive(*value, *rcv, note_type)) .map(|(value, rcv, note_type)| ValueCommitment::derive(*value, *rcv, *note_type))
.sum::<ValueCommitment>() .sum::<ValueCommitment>()
- ValueCommitment::derive(value_balance, ValueCommitTrapdoor::zero(), note_type)) - ValueCommitment::derive(
native_value_balance,
ValueCommitTrapdoor::zero(),
NoteType::native(),
))
.into_bvk(); .into_bvk();
assert_eq!(redpallas::VerificationKey::from(&bsk), bvk); assert_eq!(redpallas::VerificationKey::from(&bsk), bvk);
@ -442,18 +473,34 @@ mod tests {
proptest! { proptest! {
#[test] #[test]
fn bsk_consistent_with_bvk( fn bsk_consistent_with_bvk_native_only(
values in (1usize..10).prop_flat_map(|n_values| native_values in (1usize..10).prop_flat_map(|n_values|
arb_note_value_bounded(MAX_NOTE_VALUE / n_values as u64).prop_flat_map(move |bound| arb_note_value_bounded(MAX_NOTE_VALUE / n_values as u64).prop_flat_map(move |bound|
prop::collection::vec((arb_value_sum_bounded(bound), arb_trapdoor()), n_values) prop::collection::vec((arb_value_sum_bounded(bound), arb_trapdoor(), native_note_type()), n_values)
) )
), ),
arb_note_type in arb_note_type(),
) { ) {
// Test with native note type (zec) // Test with native note type (zec) only
_bsk_consistent_with_bvk(&values, NoteType::native()); _bsk_consistent_with_bvk(&native_values, &[], &[]);
// Test with arbitrary note type }
_bsk_consistent_with_bvk(&values, arb_note_type); }
proptest! {
#[test]
fn bsk_consistent_with_bvk(
native_values in (1usize..10).prop_flat_map(|n_values|
arb_note_value_bounded(MAX_NOTE_VALUE / n_values as u64).prop_flat_map(move |bound|
prop::collection::vec((arb_value_sum_bounded(bound), arb_trapdoor(), native_note_type()), n_values)
)
),
(arb_values,neg_trapdoors) in (1usize..10).prop_flat_map(|n_values|
(arb_note_value_bounded(MAX_NOTE_VALUE / n_values as u64).prop_flat_map(move |bound|
prop::collection::vec((arb_value_sum_bounded(bound), arb_trapdoor(), arb_note_type()), n_values)
), prop::collection::vec(arb_trapdoor(), n_values))
),
) {
// Test with native and arbitrary note types
_bsk_consistent_with_bvk(&native_values, &arb_values, &neg_trapdoors);
} }
} }
} }

View File

@ -1,4 +1,5 @@
use incrementalmerkletree::{bridgetree::BridgeTree, Hashable, Tree}; use incrementalmerkletree::{bridgetree::BridgeTree, Hashable, Tree};
use orchard::note::NoteType;
use orchard::{ use orchard::{
builder::Builder, builder::Builder,
bundle::{Authorized, Flags}, bundle::{Authorized, Flags},
@ -43,7 +44,13 @@ fn bundle_chain() {
let mut builder = Builder::new(Flags::from_parts(false, true), anchor); let mut builder = Builder::new(Flags::from_parts(false, true), anchor);
assert_eq!( assert_eq!(
builder.add_recipient(None, recipient, NoteValue::from_raw(5000), None), builder.add_recipient(
None,
recipient,
NoteValue::from_raw(5000),
NoteType::native(),
None
),
Ok(()) Ok(())
); );
let unauthorized = builder.build(&mut rng).unwrap(); let unauthorized = builder.build(&mut rng).unwrap();
@ -85,7 +92,13 @@ fn bundle_chain() {
let mut builder = Builder::new(Flags::from_parts(true, true), anchor); let mut builder = Builder::new(Flags::from_parts(true, true), anchor);
assert_eq!(builder.add_spend(fvk, note, merkle_path), Ok(())); assert_eq!(builder.add_spend(fvk, note, merkle_path), Ok(()));
assert_eq!( assert_eq!(
builder.add_recipient(None, recipient, NoteValue::from_raw(5000), None), builder.add_recipient(
None,
recipient,
NoteValue::from_raw(5000),
NoteType::native(),
None
),
Ok(()) Ok(())
); );
let unauthorized = builder.build(&mut rng).unwrap(); let unauthorized = builder.build(&mut rng).unwrap();