mirror of https://github.com/zcash/orchard.git
Use builder to generate "valid" bundles via proptest.
This commit is contained in:
parent
4d89d45332
commit
f91088d35b
|
@ -46,6 +46,7 @@ rev = "f1e76dbc9abf2b68cc609e874fe39f2a15b75b12"
|
|||
[dev-dependencies]
|
||||
criterion = "0.3"
|
||||
hex = "0.4"
|
||||
proptest = "1.0.0"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
|
|
@ -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()
|
||||
|
|
129
src/builder.rs
129
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<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)]
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
#![deny(unsafe_code)]
|
||||
|
||||
mod address;
|
||||
mod builder;
|
||||
pub mod builder;
|
||||
pub mod bundle;
|
||||
mod circuit;
|
||||
mod constants;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
11
src/value.rs
11
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<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;
|
||||
|
||||
|
|
Loading…
Reference in New Issue