diff --git a/Cargo.toml b/Cargo.toml index 53baa37b..d0ff6f78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ rev = "f1e76dbc9abf2b68cc609e874fe39f2a15b75b12" [dev-dependencies] criterion = "0.3" hex = "0.4" +proptest = "1.0.0" [lib] bench = false diff --git a/src/address.rs b/src/address.rs index ffefc5af..69543d12 100644 --- a/src/address.rs +++ b/src/address.rs @@ -49,7 +49,7 @@ pub mod testing { use super::Address; prop_compose! { - /// Generate an arbitrary random seed + /// Generates an arbitrary payment address. pub(crate) fn arb_address()(sk in arb_spending_key()) -> Address { let fvk = FullViewingKey::from(&sk); fvk.default_address() diff --git a/src/builder.rs b/src/builder.rs index a497bdcd..422db07c 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -2,7 +2,6 @@ use std::convert::TryFrom; use std::iter; -use std::marker::PhantomData; use ff::Field; use nonempty::NonEmpty; @@ -17,16 +16,21 @@ use crate::{ }, primitives::redpallas::{self, Binding, SpendAuth}, tree::{Anchor, MerklePath}, - value::{self, NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum}, + value::{self, NoteValue, OverflowError, ValueCommitTrapdoor, ValueCommitment, ValueSum}, Address, Note, TransmittedNoteCiphertext, }; const MIN_ACTIONS: usize = 2; +/// An error type for the kinds of errors that can occur during bundle construction. #[derive(Debug)] pub enum Error { + /// A bundle could not be built because required signatures were missing. MissingSignatures, + /// An error occurred in the process of producing a proof for a bundle. Proof(halo2::plonk::Error), + /// An overflow error occurred while attempting to construct the value + /// for a bundle. ValueSum(value::OverflowError), } @@ -121,7 +125,7 @@ impl ActionInfo { /// Defined in [Zcash Protocol Spec § 4.7.3: Sending Notes (Orchard)][orchardsend]. /// /// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend - fn build(self, mut rng: impl RngCore) -> (Action, Circuit) { + fn build(self, mut rng: impl RngCore) -> (Action, Circuit) { let v_net = self.value_sum().expect("already checked this"); let cv_net = ValueCommitment::derive(v_net, self.rcv); @@ -164,6 +168,7 @@ impl ActionInfo { /// A builder that constructs a [`Bundle`] from a set of notes to be spent, and recipients /// to receive funds. +#[derive(Debug)] pub struct Builder { spends: Vec, recipients: Vec, @@ -172,6 +177,7 @@ pub struct Builder { } impl Builder { + /// Construct a new empty builder for an Orchard bundle. pub fn new(flags: Flags, anchor: Anchor) -> Self { Builder { spends: vec![], @@ -282,7 +288,8 @@ impl Builder { .iter() .fold(Some(ValueSum::zero()), |acc, action| { acc? + action.value_sum()? - }).ok_or(Error::ValueSum(value::OverflowError))?; + }) + .ok_or(OverflowError)?; // Compute the transaction binding signing key. let bsk = pre_actions @@ -292,10 +299,8 @@ impl Builder { .into_bsk(); // Create the actions. - let (actions, circuits): (Vec<_>, Vec<_>) = pre_actions - .into_iter() - .map(|a| a.build(&mut rng, PhantomData::)) - .unzip(); + let (actions, circuits): (Vec<_>, Vec<_>) = + pre_actions.into_iter().map(|a| a.build(&mut rng)).unzip(); // Verify that bsk and bvk are consistent. let bvk = (actions.iter().map(|a| a.cv_net()).sum::() @@ -310,7 +315,9 @@ impl Builder { .collect(); let proof = Proof::create(pk, &circuits, &instances)?; - let value_balance: V = i64::try_from(value_balance).map_err(Error::ValueSum).and_then(|i| V::try_from(i).map_err(|_| Error::ValueSum(value::OverflowError)))?; + let value_balance: V = i64::try_from(value_balance) + .map_err(Error::ValueSum) + .and_then(|i| V::try_from(i).map_err(|_| Error::ValueSum(value::OverflowError)))?; Ok(Bundle::from_parts( NonEmpty::from_vec(actions).unwrap(), @@ -453,27 +460,80 @@ impl Bundle { #[cfg(any(test, feature = "test-dependencies"))] pub mod testing { use rand::rngs::OsRng; + use std::convert::TryFrom; + use std::fmt::Debug; use proptest::collection::vec; use proptest::prelude::*; - //use pasta_curves::{pallas}; - use crate::{ address::testing::arb_address, bundle::{Authorized, Bundle, Flags}, circuit::ProvingKey, - keys::{FullViewingKey, OutgoingViewingKey, SpendingKey}, + keys::{ + testing::arb_spending_key, FullViewingKey, OutgoingViewingKey, SpendAuthorizingKey, + SpendingKey, + }, note::testing::arb_note, tree::{Anchor, MerklePath}, - value::testing::{arb_positive_note_value, MAX_MONEY}, + value::{ + testing::{arb_positive_note_value, MAX_MONEY}, + NoteValue, + }, + Address, Note, }; use super::Builder; + /// An intermediate type used for construction of arbitrary + /// bundle values. This type is required because of a limitation + /// of the proptest prop_compose! macro which does not correctly + /// handle polymorphic generator functions. Instead of generating + /// a bundle directly, we generate the bundle inputs, and then + /// are able to use the `build` function to construct the bundle + /// from these inputs, but using a `ValueBalance` implementation that + /// is defined by the end user. + #[derive(Debug)] + struct ArbitraryBundleInputs { + sk: SpendingKey, + anchor: Anchor, + notes: Vec, + recipient_amounts: Vec<(Address, NoteValue)>, + } + + impl ArbitraryBundleInputs { + /// Create a bundle from the set of arbitrary bundle inputs. + fn into_bundle>(self) -> Bundle { + let fvk = FullViewingKey::from(&self.sk); + let ovk = OutgoingViewingKey::from(&fvk); + let flags = Flags::from_parts(true, true); + let mut builder = Builder::new(flags, self.anchor); + + for note in self.notes.into_iter() { + builder.add_spend(fvk.clone(), note, MerklePath).unwrap(); + } + + for (addr, value) in self.recipient_amounts.into_iter() { + builder + .add_recipient(Some(ovk.clone()), addr, value, None) + .unwrap(); + } + + let mut rng = OsRng; + let pk = ProvingKey::build(); + builder + .build(&mut rng, &pk) + .unwrap() + .prepare(rand_7::rngs::OsRng, [0; 32]) + .sign(rand_7::rngs::OsRng, &SpendAuthorizingKey::from(&self.sk)) + .finalize() + .unwrap() + } + } + prop_compose! { /// Produce a random valid Orchard bundle. - pub fn arb_bundle(sk: SpendingKey)( + fn arb_bundle_inputs(sk: SpendingKey)( anchor in prop::array::uniform32(prop::num::u8::ANY).prop_map(Anchor), // generate note values that we're certain won't exceed MAX_MONEY in total notes in vec(arb_positive_note_value(MAX_MONEY as u64 / 10000).prop_flat_map(arb_note), 1..30), @@ -483,30 +543,29 @@ pub mod testing { ), 1..30 ), - ) -> Bundle { - let fvk = FullViewingKey::from(&sk); - let ovk = OutgoingViewingKey::from(&fvk); - let flags = Flags::from_parts(true, true); - let mut builder = Builder::new(flags, anchor); - - for note in notes.into_iter() { - builder.add_spend(fvk.clone(), note, MerklePath).unwrap(); + ) -> ArbitraryBundleInputs { + ArbitraryBundleInputs { + sk: sk.clone(), + anchor, + notes, + recipient_amounts } - - for (addr, value) in recipient_amounts.into_iter() { - builder.add_recipient(Some(ovk.clone()), addr, value, None).unwrap(); - } - - let mut rng = OsRng; - let pk = ProvingKey::build(); - builder - .build(&mut rng, &pk) - .unwrap() - .prepare(rand_7::rngs::OsRng, [0; 32]) - .finalize() - .unwrap() } } + + /// Produce an arbitrary valid Orchard bundle using a random spending key. + pub fn arb_bundle + Debug>() -> impl Strategy> { + arb_spending_key() + .prop_flat_map(arb_bundle_inputs) + .prop_map(|inputs| inputs.into_bundle::()) + } + + /// Produce an arbitrary valid Orchard bundle using a specified spending key. + pub fn arb_bundle_with_key + Debug>( + k: SpendingKey, + ) -> impl Strategy> { + arb_bundle_inputs(k).prop_map(|inputs| inputs.into_bundle::()) + } } #[cfg(test)] diff --git a/src/bundle.rs b/src/bundle.rs index 02ea5480..faa092f4 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -172,37 +172,6 @@ pub trait Authorization { type SpendAuth; } -/// Authorizing data for a bundle of actions, ready to be committed to the ledger. -#[derive(Debug)] -pub struct Authorized { - proof: Proof, - binding_signature: redpallas::Signature, -} - -impl Authorized { - /// Construct a new value with authorizing data. - pub fn new(proof: Proof, binding_signature: redpallas::Signature) -> Self { - Authorized { - proof, - binding_signature, - } - } - - /// Return the proof component of the authorizing data. - pub fn proof(&self) -> &Proof { - &self.proof - } - - /// Return the binding signature. - pub fn binding_signature(&self) -> &redpallas::Signature { - &self.binding_signature - } -} - -impl Authorization for Authorized { - type SpendAuth = redpallas::Signature; -} - /// A bundle of actions to be applied to the ledger. #[derive(Debug)] pub struct Bundle { @@ -318,58 +287,15 @@ impl Bundle { /// Authorizing data for a bundle of actions, ready to be committed to the ledger. #[derive(Debug)] -pub struct BundleAuth { - /// The authorizing data for the actions in a bundle - pub action_authorizations: NonEmpty<::SpendAuth>, - /// The authorizing data that covers the bundle as a whole - pub authorization: Authorized, +pub struct Authorized { + proof: Proof, + binding_signature: redpallas::Signature, } -/// Errors that may be generated in the process of constructing bundle authorizing data. -#[derive(Debug)] -pub enum BundleAuthError { - /// An error produced by the underlying computation of authorizing data for a bundle - Wrapped(E), - /// Authorizing data for the bundle could not be matched to bundle contents. - AuthLengthMismatch(usize, usize), +impl Authorization for Authorized { + type SpendAuth = redpallas::Signature; } -//impl Bundle { -// /// Compute the authorizing data for a bundle and apply it to the bundle, returning the -// /// authorized result. -// pub fn with_auth Result>( -// self, -// f: F, -// ) -> Result, BundleAuthError> { -// let auth = f(&self).map_err(BundleAuthError::Wrapped)?; -// let actions_len = self.actions.len(); -// -// if actions_len != auth.action_authorizations.len() { -// Err(BundleAuthError::AuthLengthMismatch( -// actions_len, -// auth.action_authorizations.len(), -// )) -// } else { -// let actions = NonEmpty::from_vec( -// self.actions -// .into_iter() -// .zip(auth.action_authorizations.into_iter()) -// .map(|(act, a)| act.map(|_| a)) -// .collect(), -// ) -// .ok_or(BundleAuthError::AuthLengthMismatch(actions_len, 0))?; -// -// Ok(Bundle { -// actions, -// flags: self.flags, -// value_balance: self.value_balance, -// anchor: self.anchor, -// authorization: auth.authorization, -// }) -// } -// } -//} - impl Authorized { /// Constructs the authorizing data for a bundle of actions from its constituent parts. pub fn from_parts(proof: Proof, binding_signature: redpallas::Signature) -> Self { @@ -378,6 +304,16 @@ impl Authorized { binding_signature, } } + + /// Return the proof component of the authorizing data. + pub fn proof(&self) -> &Proof { + &self.proof + } + + /// Return the binding signature. + pub fn binding_signature(&self) -> &redpallas::Signature { + &self.binding_signature + } } impl Bundle { @@ -417,8 +353,7 @@ pub mod testing { }, primitives::redpallas::testing::arb_spendauth_verification_key, value::{ - testing::{arb_note_value}, - NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum, + testing::arb_note_value, NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum, }, Anchor, }; @@ -489,7 +424,7 @@ pub mod testing { NonEmpty::from_vec(actions).unwrap(), flags, values.into_iter().fold( - ValueSum::zero(), + ValueSum::zero(), |acc, cv| (acc + (cv - NoteValue::zero()).unwrap()).unwrap() ), anchor, diff --git a/src/keys.rs b/src/keys.rs index 8bc584c9..f51ea2f3 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -25,7 +25,7 @@ use crate::{ /// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents]. /// /// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct SpendingKey([u8; 32]); impl SpendingKey { diff --git a/src/lib.rs b/src/lib.rs index 972e02fb..9ecfc867 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,7 +17,7 @@ #![deny(unsafe_code)] mod address; -mod builder; +pub mod builder; pub mod bundle; mod circuit; mod constants; diff --git a/src/tree.rs b/src/tree.rs index 6e8ad2b5..e0d14582 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -10,7 +10,6 @@ pub struct MerklePath; impl MerklePath { /// Generates a dummy Merkle path for use in dummy spent notes. pub(crate) fn dummy(_rng: &mut impl RngCore) -> Self { - // TODO MerklePath } } diff --git a/src/value.rs b/src/value.rs index 8b912484..29712bbe 100644 --- a/src/value.rs +++ b/src/value.rs @@ -72,7 +72,9 @@ impl Sub for NoteValue { fn sub(self, rhs: Self) -> Self::Output { let a = self.0 as i128; let b = rhs.0 as i128; - a.checked_sub(b).filter(|v| v > &(-(std::u64::MAX as i128))).map(ValueSum) + a.checked_sub(b) + .filter(|v| v > &(-(std::u64::MAX as i128))) + .map(ValueSum) } } @@ -99,7 +101,10 @@ impl Add for ValueSum { type Output = Option; fn add(self, rhs: Self) -> Self::Output { - self.0.checked_add(rhs.0).filter(|v| v < &(std::u64::MAX as i128)).map(ValueSum) + self.0 + .checked_add(rhs.0) + .filter(|v| v < &(std::u64::MAX as i128)) + .map(ValueSum) } } @@ -284,7 +289,7 @@ mod tests { use super::{ testing::{arb_trapdoor, arb_value_sum, MAX_MONEY}, - OverflowError, ValueCommitTrapdoor, ValueCommitment, ValueSum + OverflowError, ValueCommitTrapdoor, ValueCommitment, ValueSum, }; use crate::primitives::redpallas;