Use builder to generate "valid" bundles via proptest.

This commit is contained in:
Kris Nuttycombe 2021-04-27 13:56:36 -06:00
parent 4d89d45332
commit f91088d35b
8 changed files with 123 additions and 124 deletions

View File

@ -46,6 +46,7 @@ rev = "f1e76dbc9abf2b68cc609e874fe39f2a15b75b12"
[dev-dependencies]
criterion = "0.3"
hex = "0.4"
proptest = "1.0.0"
[lib]
bench = false

View File

@ -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()

View File

@ -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<V: TryFrom<i64>(self, mut rng: impl RngCore) -> (Action<SigningMetadata>, Circuit) {
fn build(self, mut rng: impl RngCore) -> (Action<SigningMetadata>, 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<SpendInfo>,
recipients: Vec<RecipientInfo>,
@ -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::<V>))
.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::<ValueCommitment>()
@ -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<V> Bundle<PartiallyAuthorized, V> {
#[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<Note>,
recipient_amounts: Vec<(Address, NoteValue)>,
}
impl ArbitraryBundleInputs {
/// Create a bundle from the set of arbitrary bundle inputs.
fn into_bundle<V: TryFrom<i64>>(self) -> Bundle<Authorized, V> {
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<Authorized, i64> {
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<V: TryFrom<i64> + Debug>() -> impl Strategy<Value = Bundle<Authorized, V>> {
arb_spending_key()
.prop_flat_map(arb_bundle_inputs)
.prop_map(|inputs| inputs.into_bundle::<V>())
}
/// Produce an arbitrary valid Orchard bundle using a specified spending key.
pub fn arb_bundle_with_key<V: TryFrom<i64> + Debug>(
k: SpendingKey,
) -> impl Strategy<Value = Bundle<Authorized, V>> {
arb_bundle_inputs(k).prop_map(|inputs| inputs.into_bundle::<V>())
}
}
#[cfg(test)]

View File

@ -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<Binding>,
}
impl Authorized {
/// Construct a new value with authorizing data.
pub fn new(proof: Proof, binding_signature: redpallas::Signature<Binding>) -> 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<Binding> {
&self.binding_signature
}
}
impl Authorization for Authorized {
type SpendAuth = redpallas::Signature<SpendAuth>;
}
/// A bundle of actions to be applied to the ledger.
#[derive(Debug)]
pub struct Bundle<T: Authorization, V> {
@ -318,58 +287,15 @@ impl<T: Authorization, V> Bundle<T, V> {
/// 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<<Authorized as Authorization>::SpendAuth>,
/// The authorizing data that covers the bundle as a whole
pub authorization: Authorized,
pub struct Authorized {
proof: Proof,
binding_signature: redpallas::Signature<Binding>,
}
/// Errors that may be generated in the process of constructing bundle authorizing data.
#[derive(Debug)]
pub enum BundleAuthError<E> {
/// 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<SpendAuth>;
}
//impl<V> Bundle<Unauthorized, V> {
// /// Compute the authorizing data for a bundle and apply it to the bundle, returning the
// /// authorized result.
// pub fn with_auth<E, F: FnOnce(&Self) -> Result<BundleAuth, E>>(
// self,
// f: F,
// ) -> Result<Bundle<Authorized, V>, BundleAuthError<E>> {
// 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<Binding>) -> 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<Binding> {
&self.binding_signature
}
}
impl<V> Bundle<Authorized, V> {
@ -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,

View File

@ -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 {

View File

@ -17,7 +17,7 @@
#![deny(unsafe_code)]
mod address;
mod builder;
pub mod builder;
pub mod bundle;
mod circuit;
mod constants;

View File

@ -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
}
}

View File

@ -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<ValueSum>;
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;