Generate both spend and output values for actions.

This commit is contained in:
Kris Nuttycombe 2021-05-05 11:10:52 -06:00
parent a789b89135
commit 88b8265165
3 changed files with 103 additions and 48 deletions

View File

@ -177,7 +177,7 @@ pub struct Builder {
} }
impl Builder { impl Builder {
/// Construct a new empty builder for an Orchard bundle. /// Constructs a new empty builder for an Orchard bundle.
pub fn new(flags: Flags, anchor: Anchor) -> Self { pub fn new(flags: Flags, anchor: Anchor) -> Self {
Builder { Builder {
spends: vec![], spends: vec![],
@ -291,6 +291,10 @@ impl Builder {
}) })
.ok_or(OverflowError)?; .ok_or(OverflowError)?;
let result_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)))?;
// Compute the transaction binding signing key. // Compute the transaction binding signing key.
let bsk = pre_actions let bsk = pre_actions
.iter() .iter()
@ -315,14 +319,10 @@ impl Builder {
.collect(); .collect();
let proof = Proof::create(pk, &circuits, &instances)?; 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)))?;
Ok(Bundle::from_parts( Ok(Bundle::from_parts(
NonEmpty::from_vec(actions).unwrap(), NonEmpty::from_vec(actions).unwrap(),
flags, flags,
value_balance, result_value_balance,
anchor, anchor,
Unauthorized { proof, bsk }, Unauthorized { proof, bsk },
)) ))
@ -477,7 +477,7 @@ pub mod testing {
note::testing::arb_note, note::testing::arb_note,
tree::{Anchor, MerklePath}, tree::{Anchor, MerklePath},
value::{ value::{
testing::{arb_positive_note_value, MAX_MONEY}, testing::{arb_positive_note_value, MAX_NOTE_VALUE},
NoteValue, NoteValue,
}, },
Address, Note, Address, Note,
@ -534,15 +534,24 @@ pub mod testing {
prop_compose! { prop_compose! {
/// Produce a random valid Orchard bundle. /// Produce a random valid Orchard bundle.
fn arb_bundle_inputs(sk: SpendingKey)( fn arb_bundle_inputs(sk: SpendingKey)
(
n_notes in 1..30,
n_recipients in 1..30,
)
(
anchor in prop::array::uniform32(prop::num::u8::ANY).prop_map(Anchor), 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 // generate note values that we're certain won't exceed MAX_NOTE_VALUE in total
notes in vec(arb_positive_note_value(MAX_MONEY as u64 / 10000).prop_flat_map(arb_note), 1..30), notes in vec(
recipient_amounts in vec( arb_positive_note_value(MAX_NOTE_VALUE / n_notes as u64).prop_flat_map(arb_note),
arb_address().prop_flat_map( n_notes as usize
|a| arb_positive_note_value(MAX_MONEY as u64 / 10000).prop_map(move |v| (a.clone(), v))
), ),
1..30 recipient_amounts in vec(
arb_address().prop_flat_map(move |a| {
arb_positive_note_value(MAX_NOTE_VALUE / n_recipients as u64)
.prop_map(move |v| (a.clone(), v))
}),
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, rand_7::rngs::StdRng> { ) -> ArbitraryBundleInputs<StdRng, rand_7::rngs::StdRng> {

View File

@ -26,7 +26,7 @@ pub struct Action<A> {
rk: redpallas::VerificationKey<SpendAuth>, rk: redpallas::VerificationKey<SpendAuth>,
/// A commitment to the new note being created. /// A commitment to the new note being created.
cmx: ExtractedNoteCommitment, cmx: ExtractedNoteCommitment,
/// The transmitted note ciphertext /// The transmitted note ciphertext.
encrypted_note: TransmittedNoteCiphertext, encrypted_note: TransmittedNoteCiphertext,
/// A commitment to the net value created or consumed by this action. /// A commitment to the net value created or consumed by this action.
cv_net: ValueCommitment, cv_net: ValueCommitment,
@ -356,6 +356,7 @@ pub struct BundleAuthorizingCommitment;
pub mod testing { pub mod testing {
use nonempty::NonEmpty; use nonempty::NonEmpty;
use rand_7::{rngs::StdRng, SeedableRng}; use rand_7::{rngs::StdRng, SeedableRng};
use reddsa::orchard::SpendAuth;
use proptest::collection::vec; use proptest::collection::vec;
use proptest::prelude::*; use proptest::prelude::*;
@ -371,9 +372,10 @@ pub mod testing {
testing::{ testing::{
arb_binding_signing_key, arb_spendauth_signing_key, arb_spendauth_verification_key, arb_binding_signing_key, arb_spendauth_signing_key, arb_spendauth_verification_key,
}, },
Signature,
}, },
value::{ value::{
testing::{arb_positive_note_value, MAX_MONEY}, testing::{arb_nonnegative_note_value, MAX_NOTE_VALUE},
NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum, NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum,
}, },
Anchor, Anchor,
@ -391,14 +393,14 @@ pub mod testing {
prop_compose! { prop_compose! {
/// Generate an action without authorization data. /// Generate an action without authorization data.
pub fn arb_unauthorized_action(value: NoteValue)( pub fn arb_unauthorized_action(spend_value: NoteValue, output_value: NoteValue)(
nf in arb_nullifier(), nf in arb_nullifier(),
rk in arb_spendauth_verification_key(), rk in arb_spendauth_verification_key(),
note in arb_note(value), note in arb_note(output_value),
) -> Action<()> { ) -> Action<()> {
let cmx = ExtractedNoteCommitment::from(note.commitment()); let cmx = ExtractedNoteCommitment::from(note.commitment());
let cv_net = ValueCommitment::derive( let cv_net = ValueCommitment::derive(
(note.value() - NoteValue::zero()).unwrap(), (spend_value - output_value).unwrap(),
ValueCommitTrapdoor::zero() ValueCommitTrapdoor::zero()
); );
// FIXME: make a real one from the note. // FIXME: make a real one from the note.
@ -418,18 +420,34 @@ pub mod testing {
} }
} }
/// Generate an unauthorized action having spend and output values less than MAX_NOTE_VALUE / n_actions.
pub fn arb_unauthorized_action_n(
n_actions: usize,
) -> impl Strategy<Value = (ValueSum, Action<()>)> {
arb_nonnegative_note_value(MAX_NOTE_VALUE / n_actions as u64).prop_flat_map(
move |spend_value| {
arb_nonnegative_note_value(MAX_NOTE_VALUE / n_actions as u64).prop_flat_map(
move |output_value| {
arb_unauthorized_action(spend_value, output_value)
.prop_map(move |a| ((spend_value - output_value).unwrap(), a))
},
)
},
)
}
prop_compose! { prop_compose! {
/// Generate an action with invalid (random) authorization data. /// Generate an action with invalid (random) authorization data.
pub fn arb_action(value: NoteValue)( pub fn arb_action(spend_value: NoteValue, output_value: NoteValue)(
nf in arb_nullifier(), nf in arb_nullifier(),
sk in arb_spendauth_signing_key(), sk in arb_spendauth_signing_key(),
note in arb_note(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),
) -> Action<redpallas::Signature<reddsa::orchard::SpendAuth>> { ) -> Action<Signature<SpendAuth>> {
let cmx = ExtractedNoteCommitment::from(note.commitment()); let cmx = ExtractedNoteCommitment::from(note.commitment());
let cv_net = ValueCommitment::derive( let cv_net = ValueCommitment::derive(
(note.value() - NoteValue::zero()).unwrap(), (spend_value - output_value).unwrap(),
ValueCommitTrapdoor::zero() ValueCommitTrapdoor::zero()
); );
@ -453,6 +471,22 @@ pub mod testing {
} }
} }
/// Generate an authorized action having spend and output values less than MAX_NOTE_VALUE / n_actions.
pub fn arb_action_n(
n_actions: usize,
) -> impl Strategy<Value = (ValueSum, Action<Signature<SpendAuth>>)> {
arb_nonnegative_note_value(MAX_NOTE_VALUE / n_actions as u64).prop_flat_map(
move |spend_value| {
arb_nonnegative_note_value(MAX_NOTE_VALUE / n_actions as u64).prop_flat_map(
move |output_value| {
arb_action(spend_value, output_value)
.prop_map(move |a| ((spend_value - output_value).unwrap(), a))
},
)
},
)
}
prop_compose! { prop_compose! {
/// Create an arbitrary set of flags. /// Create an arbitrary set of flags.
pub fn arb_flags()(spends_enabled in prop::bool::ANY, outputs_enabled in prop::bool::ANY) -> Flags { pub fn arb_flags()(spends_enabled in prop::bool::ANY, outputs_enabled in prop::bool::ANY) -> Flags {
@ -464,22 +498,19 @@ pub mod testing {
/// Generate an arbitrary unauthorized bundle. This bundle does not /// Generate an arbitrary unauthorized bundle. This bundle does not
/// necessarily respect consensus rules; for that use /// necessarily respect consensus rules; for that use
/// [`crate::builder::testing::arb_bundle`] /// [`crate::builder::testing::arb_bundle`]
pub fn arb_unauthorized_bundle()( pub fn arb_unauthorized_bundle()
acts in vec( ( n_actions in 1usize..100 )
arb_positive_note_value(MAX_MONEY as u64 / 10000).prop_flat_map(|v| (
arb_unauthorized_action(v).prop_map(move |a| (v, a)) acts in vec(arb_unauthorized_action_n(n_actions), n_actions),
),
1..10
),
flags in arb_flags(), flags in arb_flags(),
anchor in prop::array::uniform32(prop::num::u8::ANY).prop_map(Anchor) anchor in prop::array::uniform32(prop::num::u8::ANY).prop_map(Anchor)
) -> Bundle<Unauthorized, ValueSum> { ) -> Bundle<Unauthorized, ValueSum> {
let (note_values, actions): (Vec<NoteValue>, Vec<Action<_>>) = acts.into_iter().unzip(); let (balances, actions): (Vec<ValueSum>, Vec<Action<_>>) = acts.into_iter().unzip();
Bundle::from_parts( Bundle::from_parts(
NonEmpty::from_vec(actions).unwrap(), NonEmpty::from_vec(actions).unwrap(),
flags, flags,
note_values.into_iter().map(ValueSum::from).sum::<Result<ValueSum, _>>().unwrap(), balances.into_iter().sum::<Result<ValueSum, _>>().unwrap(),
anchor, anchor,
Unauthorized Unauthorized
) )
@ -490,13 +521,10 @@ pub mod testing {
/// Generate an arbitrary bundle with fake authorization data. This bundle does not /// Generate an arbitrary bundle with fake authorization data. This bundle does not
/// necessarily respect consensus rules; for that use /// necessarily respect consensus rules; for that use
/// [`crate::builder::testing::arb_bundle`] /// [`crate::builder::testing::arb_bundle`]
pub fn arb_bundle()( pub fn arb_bundle()
acts in vec( ( n_actions in 1usize..100 )
arb_positive_note_value(MAX_MONEY as u64 / 10000).prop_flat_map(|v| (
arb_action(v).prop_map(move |a| (v, a)) acts in vec(arb_action_n(n_actions), n_actions),
),
1..10
),
flags in arb_flags(), flags in arb_flags(),
anchor in prop::array::uniform32(prop::num::u8::ANY).prop_map(Anchor), anchor in prop::array::uniform32(prop::num::u8::ANY).prop_map(Anchor),
sk in arb_binding_signing_key(), sk in arb_binding_signing_key(),
@ -504,13 +532,13 @@ pub mod testing {
fake_proof in vec(prop::num::u8::ANY, 1973), fake_proof in vec(prop::num::u8::ANY, 1973),
fake_sighash in prop::array::uniform32(prop::num::u8::ANY), fake_sighash in prop::array::uniform32(prop::num::u8::ANY),
) -> Bundle<Authorized, ValueSum> { ) -> Bundle<Authorized, ValueSum> {
let (note_values, actions): (Vec<NoteValue>, Vec<Action<_>>) = acts.into_iter().unzip(); let (balances, actions): (Vec<ValueSum>, Vec<Action<_>>) = acts.into_iter().unzip();
let rng = StdRng::from_seed(rng_seed); let rng = StdRng::from_seed(rng_seed);
Bundle::from_parts( Bundle::from_parts(
NonEmpty::from_vec(actions).unwrap(), NonEmpty::from_vec(actions).unwrap(),
flags, flags,
note_values.into_iter().map(ValueSum::from).sum::<Result<ValueSum, _>>().unwrap(), balances.into_iter().sum::<Result<ValueSum, _>>().unwrap(),
anchor, anchor,
Authorized { Authorized {
proof: Proof(fake_proof), proof: Proof(fake_proof),

View File

@ -31,6 +31,15 @@ use subtle::CtOption;
use crate::primitives::redpallas::{self, Binding}; use crate::primitives::redpallas::{self, Binding};
use std::ops::RangeInclusive;
/// The valid range of the scalar multiplication used in ValueCommit^Orchard.
///
/// Defined in a note in [Zcash Protocol Spec § 4.17.4: Action Statement (Orchard)][actionstatement].
///
/// [actionstatement]: https://zips.z.cash/protocol/nu5.pdf#actionstatement
pub const VALUE_SUM_RANGE: RangeInclusive<i128> = -(u64::MAX as i128)..=u64::MAX as i128;
/// A value operation overflowed. /// A value operation overflowed.
#[derive(Debug)] #[derive(Debug)]
pub struct OverflowError; pub struct OverflowError;
@ -74,7 +83,7 @@ impl Sub for NoteValue {
let a = self.0 as i128; let a = self.0 as i128;
let b = rhs.0 as i128; let b = rhs.0 as i128;
a.checked_sub(b) a.checked_sub(b)
.filter(|v| v > &(-(std::u64::MAX as i128)) && v < &(std::u64::MAX as i128)) .filter(|v| VALUE_SUM_RANGE.contains(v))
.map(ValueSum) .map(ValueSum)
} }
} }
@ -105,7 +114,7 @@ impl Add for ValueSum {
fn add(self, rhs: Self) -> Self::Output { fn add(self, rhs: Self) -> Self::Output {
self.0 self.0
.checked_add(rhs.0) .checked_add(rhs.0)
.filter(|v| v > &(-(std::u64::MAX as i128)) && v < &(std::u64::MAX as i128)) .filter(|v| VALUE_SUM_RANGE.contains(v))
.map(ValueSum) .map(ValueSum)
} }
} }
@ -253,9 +262,8 @@ pub mod testing {
use super::{NoteValue, ValueCommitTrapdoor, ValueSum}; use super::{NoteValue, ValueCommitTrapdoor, ValueSum};
/// Zcash's maximum money amount. Used as a bound in proptests so we don't artifically /// Maximum note value.
/// overflow `ValueSum`'s size. pub const MAX_NOTE_VALUE: u64 = u64::MAX - 1;
pub const MAX_MONEY: i64 = 21_000_000 * 1_0000_0000;
prop_compose! { prop_compose! {
/// Generate an arbitrary Pallas scalar. /// Generate an arbitrary Pallas scalar.
@ -283,7 +291,15 @@ pub mod testing {
prop_compose! { prop_compose! {
/// Generate an arbitrary value in the range of valid nonnegative Zcash amounts. /// Generate an arbitrary value in the range of valid nonnegative Zcash amounts.
pub fn arb_note_value()(value in 0u64..(MAX_MONEY as u64)) -> NoteValue { pub fn arb_note_value()(value in 0u64..MAX_NOTE_VALUE) -> NoteValue {
NoteValue(value)
}
}
prop_compose! {
/// Generate an arbitrary value in the range of valid positive Zcash amounts
/// less than a specified value.
pub fn arb_nonnegative_note_value(max: u64)(value in 0u64..max) -> NoteValue {
NoteValue(value) NoteValue(value)
} }
} }
@ -302,11 +318,13 @@ mod tests {
use proptest::prelude::*; use proptest::prelude::*;
use super::{ use super::{
testing::{arb_trapdoor, arb_value_sum, MAX_MONEY}, testing::{arb_trapdoor, arb_value_sum},
OverflowError, ValueCommitTrapdoor, ValueCommitment, ValueSum, OverflowError, ValueCommitTrapdoor, ValueCommitment, ValueSum,
}; };
use crate::primitives::redpallas; use crate::primitives::redpallas;
const MAX_MONEY: i64 = 21_000_000 * 1_0000_0000;
proptest! { proptest! {
#[test] #[test]
fn bsk_consistent_with_bvk( fn bsk_consistent_with_bvk(