Merge pull request #65 from nuttycom/zip_225_updates

Update Orchard data structures to support ZIP 225 serialization & property testing.

Fixes #34
This commit is contained in:
Kris Nuttycombe 2021-05-06 11:46:27 -06:00 committed by GitHub
commit 3117187280
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 768 additions and 86 deletions

View File

@ -25,6 +25,7 @@ blake2b_simd = "0.5"
ff = "0.9" ff = "0.9"
fpe = "0.4" fpe = "0.4"
group = "0.9" group = "0.9"
proptest = { version = "1.0.0", optional = true }
rand = "0.8" rand = "0.8"
rand_7 = { package = "rand", version = "0.7" } rand_7 = { package = "rand", version = "0.7" }
nonempty = "0.6" nonempty = "0.6"
@ -50,6 +51,9 @@ proptest = "1.0.0"
[lib] [lib]
bench = false bench = false
[features]
test-dependencies = ["proptest"]
[[bench]] [[bench]]
name = "small" name = "small"
harness = false harness = false

View File

@ -15,7 +15,7 @@ use crate::{
/// let sk = SpendingKey::from_bytes([7; 32]).unwrap(); /// let sk = SpendingKey::from_bytes([7; 32]).unwrap();
/// let address = FullViewingKey::from(&sk).default_address(); /// let address = FullViewingKey::from(&sk).default_address();
/// ``` /// ```
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct Address { pub struct Address {
d: Diversifier, d: Diversifier,
pk_d: DiversifiedTransmissionKey, pk_d: DiversifiedTransmissionKey,
@ -38,3 +38,21 @@ impl Address {
&self.pk_d &self.pk_d
} }
} }
/// Generators for property testing.
#[cfg(any(test, feature = "test-dependencies"))]
pub mod testing {
use proptest::prelude::*;
use crate::keys::{testing::arb_spending_key, FullViewingKey};
use super::Address;
prop_compose! {
/// 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

@ -1,5 +1,6 @@
//! Logic for building Orchard components of transactions. //! Logic for building Orchard components of transactions.
use std::convert::TryFrom;
use std::iter; use std::iter;
use ff::Field; use ff::Field;
@ -8,23 +9,29 @@ use pasta_curves::pallas;
use rand::RngCore; use rand::RngCore;
use crate::{ use crate::{
address::Address,
bundle::{Action, Authorization, Authorized, Bundle, Flags}, bundle::{Action, Authorization, Authorized, Bundle, Flags},
circuit::{Circuit, Proof, ProvingKey}, circuit::{Circuit, Proof, ProvingKey},
keys::{ keys::{
FullViewingKey, OutgoingViewingKey, SpendAuthorizingKey, SpendValidatingKey, SpendingKey, FullViewingKey, OutgoingViewingKey, SpendAuthorizingKey, SpendValidatingKey, SpendingKey,
}, },
note::{Note, TransmittedNoteCiphertext},
primitives::redpallas::{self, Binding, SpendAuth}, primitives::redpallas::{self, Binding, SpendAuth},
tree::{Anchor, MerklePath}, tree::{Anchor, MerklePath},
value::{self, NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum}, value::{self, NoteValue, OverflowError, ValueCommitTrapdoor, ValueCommitment, ValueSum},
Address, EncryptedNote, Note,
}; };
const MIN_ACTIONS: usize = 2; const MIN_ACTIONS: usize = 2;
/// An error type for the kinds of errors that can occur during bundle construction.
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
/// A bundle could not be built because required signatures were missing.
MissingSignatures, MissingSignatures,
/// An error occurred in the process of producing a proof for a bundle.
Proof(halo2::plonk::Error), Proof(halo2::plonk::Error),
/// An overflow error occurred while attempting to construct the value
/// for a bundle.
ValueSum(value::OverflowError), ValueSum(value::OverflowError),
} }
@ -110,7 +117,7 @@ impl ActionInfo {
} }
/// Returns the value sum for this action. /// Returns the value sum for this action.
fn value_sum(&self) -> Result<ValueSum, value::OverflowError> { fn value_sum(&self) -> Option<ValueSum> {
self.spend.note.value() - self.output.value self.spend.note.value() - self.output.value
} }
@ -137,7 +144,11 @@ impl ActionInfo {
let cm_new = note.commitment(); let cm_new = note.commitment();
// TODO: Note encryption // TODO: Note encryption
let encrypted_note = EncryptedNote; let encrypted_note = TransmittedNoteCiphertext {
epk_bytes: [0u8; 32],
enc_ciphertext: [0u8; 580],
out_ciphertext: [0u8; 80],
};
( (
Action::from_parts( Action::from_parts(
@ -158,6 +169,7 @@ impl ActionInfo {
/// A builder that constructs a [`Bundle`] from a set of notes to be spent, and recipients /// A builder that constructs a [`Bundle`] from a set of notes to be spent, and recipients
/// to receive funds. /// to receive funds.
#[derive(Debug)]
pub struct Builder { pub struct Builder {
spends: Vec<SpendInfo>, spends: Vec<SpendInfo>,
recipients: Vec<RecipientInfo>, recipients: Vec<RecipientInfo>,
@ -166,6 +178,7 @@ pub struct Builder {
} }
impl Builder { impl Builder {
/// 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![],
@ -233,11 +246,11 @@ impl Builder {
/// ///
/// This API assumes that none of the notes being spent are controlled by (threshold) /// This API assumes that none of the notes being spent are controlled by (threshold)
/// multisignatures, and immediately constructs the bundle proof. /// multisignatures, and immediately constructs the bundle proof.
fn build( fn build<V: TryFrom<i64>>(
mut self, mut self,
mut rng: impl RngCore, mut rng: impl RngCore,
pk: &ProvingKey, pk: &ProvingKey,
) -> Result<Bundle<Unauthorized>, Error> { ) -> Result<Bundle<Unauthorized, V>, Error> {
// Pair up the spends and recipients, extending with dummy values as necessary. // Pair up the spends and recipients, extending with dummy values as necessary.
// //
// TODO: Do we want to shuffle the order like we do for Sapling? And if we do, do // TODO: Do we want to shuffle the order like we do for Sapling? And if we do, do
@ -272,11 +285,16 @@ impl Builder {
let anchor = self.anchor; let anchor = self.anchor;
// Determine the value balance for this bundle, ensuring it is valid. // Determine the value balance for this bundle, ensuring it is valid.
let value_balance: ValueSum = pre_actions let value_balance = pre_actions
.iter() .iter()
.fold(Ok(ValueSum::zero()), |acc, action| { .fold(Some(ValueSum::zero()), |acc, action| {
acc? + action.value_sum()? acc? + action.value_sum()?
})?; })
.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
@ -305,7 +323,7 @@ impl Builder {
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 },
)) ))
@ -349,7 +367,7 @@ impl Authorization for PartiallyAuthorized {
type SpendAuth = (Option<redpallas::Signature<SpendAuth>>, SpendValidatingKey); type SpendAuth = (Option<redpallas::Signature<SpendAuth>>, SpendValidatingKey);
} }
impl Bundle<Unauthorized> { impl<V> Bundle<Unauthorized, V> {
/// Loads the sighash into this bundle, preparing it for signing. /// Loads the sighash into this bundle, preparing it for signing.
/// ///
/// This API ensures that all signatures are created over the same sighash. /// This API ensures that all signatures are created over the same sighash.
@ -357,7 +375,7 @@ impl Bundle<Unauthorized> {
self, self,
mut rng: R, mut rng: R,
sighash: [u8; 32], sighash: [u8; 32],
) -> Bundle<PartiallyAuthorized> { ) -> Bundle<PartiallyAuthorized, V> {
self.authorize( self.authorize(
&mut rng, &mut rng,
|rng, _, SigningMetadata { dummy_ask, ak }| { |rng, _, SigningMetadata { dummy_ask, ak }| {
@ -381,7 +399,7 @@ impl Bundle<Unauthorized> {
mut rng: R, mut rng: R,
sighash: [u8; 32], sighash: [u8; 32],
signing_keys: &[SpendAuthorizingKey], signing_keys: &[SpendAuthorizingKey],
) -> Result<Bundle<Authorized>, Error> { ) -> Result<Bundle<Authorized, V>, Error> {
signing_keys signing_keys
.iter() .iter()
.fold(self.prepare(&mut rng, sighash), |partial, ask| { .fold(self.prepare(&mut rng, sighash), |partial, ask| {
@ -391,7 +409,7 @@ impl Bundle<Unauthorized> {
} }
} }
impl Bundle<PartiallyAuthorized> { impl<V> Bundle<PartiallyAuthorized, V> {
/// Signs this bundle with the given [`SpendAuthorizingKey`]. /// Signs this bundle with the given [`SpendAuthorizingKey`].
/// ///
/// This will apply signatures for all notes controlled by this spending key. /// This will apply signatures for all notes controlled by this spending key.
@ -422,7 +440,7 @@ impl Bundle<PartiallyAuthorized> {
/// Finalizes this bundle, enabling it to be included in a transaction. /// Finalizes this bundle, enabling it to be included in a transaction.
/// ///
/// Returns an error if any signatures are missing. /// Returns an error if any signatures are missing.
pub fn finalize(self) -> Result<Bundle<Authorized>, Error> { pub fn finalize(self) -> Result<Bundle<Authorized, V>, Error> {
self.try_authorize( self.try_authorize(
&mut (), &mut (),
|_, _, (sig, _)| match sig { |_, _, (sig, _)| match sig {
@ -439,17 +457,139 @@ impl Bundle<PartiallyAuthorized> {
} }
} }
/// Generators for property testing.
#[cfg(any(test, feature = "test-dependencies"))]
pub mod testing {
use rand::{rngs::StdRng, CryptoRng, SeedableRng};
use std::convert::TryFrom;
use std::fmt::Debug;
use proptest::collection::vec;
use proptest::prelude::*;
use crate::{
address::testing::arb_address,
bundle::{Authorized, Bundle, Flags},
circuit::ProvingKey,
keys::{
testing::arb_spending_key, FullViewingKey, OutgoingViewingKey, SpendAuthorizingKey,
SpendingKey,
},
note::testing::arb_note,
tree::{Anchor, MerklePath},
value::{testing::arb_positive_note_value, NoteValue, MAX_NOTE_VALUE},
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<R, R7> {
rng: R,
rng_7: R7,
sk: SpendingKey,
anchor: Anchor,
notes: Vec<Note>,
recipient_amounts: Vec<(Address, NoteValue)>,
}
impl<R: RngCore + CryptoRng, R7: rand_7::RngCore + rand_7::CryptoRng> ArbitraryBundleInputs<R, R7> {
/// Create a bundle from the set of arbitrary bundle inputs.
fn into_bundle<V: TryFrom<i64>>(mut 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 pk = ProvingKey::build();
builder
.build(&mut self.rng, &pk)
.unwrap()
.prepare(&mut self.rng_7, [0; 32])
.sign(&mut self.rng_7, &SpendAuthorizingKey::from(&self.sk))
.finalize()
.unwrap()
}
}
prop_compose! {
/// Produce a random valid Orchard bundle.
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),
// generate note values that we're certain won't exceed MAX_NOTE_VALUE in total
notes in vec(
arb_positive_note_value(MAX_NOTE_VALUE / n_notes as u64).prop_flat_map(arb_note),
n_notes as usize
),
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)
) -> ArbitraryBundleInputs<StdRng, rand_7::rngs::StdRng> {
ArbitraryBundleInputs {
rng: StdRng::from_seed(rng_seed),
rng_7: <rand_7::rngs::StdRng as rand_7::SeedableRng>::from_seed(rng_seed),
sk: sk.clone(),
anchor,
notes,
recipient_amounts
}
}
}
/// 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)] #[cfg(test)]
mod tests { mod tests {
use rand::rngs::OsRng; use rand::rngs::OsRng;
use super::Builder; use super::Builder;
use crate::{ use crate::{
bundle::Flags, bundle::{Authorized, Bundle, Flags},
circuit::ProvingKey, circuit::ProvingKey,
keys::{FullViewingKey, SpendingKey}, keys::{FullViewingKey, SpendingKey},
tree::Anchor, tree::Anchor,
value::{NoteValue, ValueSum}, value::NoteValue,
}; };
#[test] #[test]
@ -461,16 +601,16 @@ mod tests {
let fvk = FullViewingKey::from(&sk); let fvk = FullViewingKey::from(&sk);
let recipient = fvk.default_address(); let recipient = fvk.default_address();
let mut builder = Builder::new(Flags::from_parts(true, true), Anchor); let mut builder = Builder::new(Flags::from_parts(true, true), Anchor([0; 32]));
builder builder
.add_recipient(None, recipient, NoteValue::from_raw(5000), None) .add_recipient(None, recipient, NoteValue::from_raw(5000), None)
.unwrap(); .unwrap();
let bundle = dbg!(builder let bundle: Bundle<Authorized, i64> = dbg!(builder
.build(&mut rng, &pk) .build(&mut rng, &pk)
.unwrap() .unwrap()
.prepare(rand_7::rngs::OsRng, [0; 32])) .prepare(rand_7::rngs::OsRng, [0; 32]))
.finalize() .finalize()
.unwrap(); .unwrap();
assert_eq!(bundle.value_balance(), &ValueSum::from_raw(-5000)) assert_eq!(bundle.value_balance(), &(-5000))
} }
} }

View File

@ -4,10 +4,10 @@ use nonempty::NonEmpty;
use crate::{ use crate::{
circuit::{Instance, Proof}, circuit::{Instance, Proof},
note::{EncryptedNote, ExtractedNoteCommitment, Nullifier}, note::{ExtractedNoteCommitment, Nullifier, TransmittedNoteCiphertext},
primitives::redpallas::{self, Binding, SpendAuth}, primitives::redpallas::{self, Binding, SpendAuth},
tree::Anchor, tree::Anchor,
value::{ValueCommitment, ValueSum}, value::ValueCommitment,
}; };
/// An action applied to the global ledger. /// An action applied to the global ledger.
@ -19,19 +19,19 @@ use crate::{
/// Internally, this may both consume a note and create a note, or it may do only one of /// Internally, this may both consume a note and create a note, or it may do only one of
/// the two. TODO: Determine which is more efficient (circuit size vs bundle size). /// the two. TODO: Determine which is more efficient (circuit size vs bundle size).
#[derive(Debug)] #[derive(Debug)]
pub struct Action<T> { pub struct Action<A> {
/// The nullifier of the note being spent. /// The nullifier of the note being spent.
nf: Nullifier, nf: Nullifier,
/// The randomized verification key for the note being spent. /// The randomized verification key for the note being spent.
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 encrypted output note. /// The transmitted note ciphertext.
encrypted_note: EncryptedNote, 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,
/// The authorization for this action. /// The authorization for this action.
authorization: T, authorization: A,
} }
impl<T> Action<T> { impl<T> Action<T> {
@ -40,7 +40,7 @@ impl<T> Action<T> {
nf: Nullifier, nf: Nullifier,
rk: redpallas::VerificationKey<SpendAuth>, rk: redpallas::VerificationKey<SpendAuth>,
cmx: ExtractedNoteCommitment, cmx: ExtractedNoteCommitment,
encrypted_note: EncryptedNote, encrypted_note: TransmittedNoteCiphertext,
cv_net: ValueCommitment, cv_net: ValueCommitment,
authorization: T, authorization: T,
) -> Self { ) -> Self {
@ -70,7 +70,7 @@ impl<T> Action<T> {
} }
/// Returns the encrypted note ciphertext. /// Returns the encrypted note ciphertext.
pub fn encrypted_note(&self) -> &EncryptedNote { pub fn encrypted_note(&self) -> &TransmittedNoteCiphertext {
&self.encrypted_note &self.encrypted_note
} }
@ -174,7 +174,7 @@ pub trait Authorization {
/// A bundle of actions to be applied to the ledger. /// A bundle of actions to be applied to the ledger.
#[derive(Debug)] #[derive(Debug)]
pub struct Bundle<T: Authorization> { pub struct Bundle<T: Authorization, V> {
/// The list of actions that make up this bundle. /// The list of actions that make up this bundle.
actions: NonEmpty<Action<T::SpendAuth>>, actions: NonEmpty<Action<T::SpendAuth>>,
/// Orchard-specific transaction-level flags for this bundle. /// Orchard-specific transaction-level flags for this bundle.
@ -182,19 +182,19 @@ pub struct Bundle<T: Authorization> {
/// The net value moved out of the Orchard shielded pool. /// The net value moved out of the Orchard shielded pool.
/// ///
/// This is the sum of Orchard spends minus the sum of Orchard outputs. /// This is the sum of Orchard spends minus the sum of Orchard outputs.
value_balance: ValueSum, value_balance: V,
/// The root of the Orchard commitment tree that this bundle commits to. /// The root of the Orchard commitment tree that this bundle commits to.
anchor: Anchor, anchor: Anchor,
/// The authorization for this bundle. /// The authorization for this bundle.
authorization: T, authorization: T,
} }
impl<T: Authorization> Bundle<T> { impl<T: Authorization, V> Bundle<T, V> {
/// Constructs a `Bundle` from its constituent parts. /// Constructs a `Bundle` from its constituent parts.
pub fn from_parts( pub fn from_parts(
actions: NonEmpty<Action<T::SpendAuth>>, actions: NonEmpty<Action<T::SpendAuth>>,
flags: Flags, flags: Flags,
value_balance: ValueSum, value_balance: V,
anchor: Anchor, anchor: Anchor,
authorization: T, authorization: T,
) -> Self { ) -> Self {
@ -220,7 +220,7 @@ impl<T: Authorization> Bundle<T> {
/// Returns the net value moved into or out of the Orchard shielded pool. /// Returns the net value moved into or out of the Orchard shielded pool.
/// ///
/// This is the sum of Orchard spends minus the sum Orchard outputs. /// This is the sum of Orchard spends minus the sum Orchard outputs.
pub fn value_balance(&self) -> &ValueSum { pub fn value_balance(&self) -> &V {
&self.value_balance &self.value_balance
} }
@ -242,13 +242,28 @@ impl<T: Authorization> Bundle<T> {
todo!() todo!()
} }
/// Construct a new bundle by applying a transformation that might fail
/// to the value balance.
pub fn try_map_value_balance<V0, E, F: FnOnce(V) -> Result<V0, E>>(
self,
f: F,
) -> Result<Bundle<T, V0>, E> {
Ok(Bundle {
actions: self.actions,
flags: self.flags,
value_balance: f(self.value_balance)?,
anchor: self.anchor,
authorization: self.authorization,
})
}
/// Transitions this bundle from one authorization state to another. /// Transitions this bundle from one authorization state to another.
pub fn authorize<R, U: Authorization>( pub fn authorize<R, U: Authorization>(
self, self,
context: &mut R, context: &mut R,
mut spend_auth: impl FnMut(&mut R, &T, T::SpendAuth) -> U::SpendAuth, mut spend_auth: impl FnMut(&mut R, &T, T::SpendAuth) -> U::SpendAuth,
step: impl FnOnce(&mut R, T) -> U, step: impl FnOnce(&mut R, T) -> U,
) -> Bundle<U> { ) -> Bundle<U, V> {
let authorization = self.authorization; let authorization = self.authorization;
Bundle { Bundle {
actions: self actions: self
@ -267,7 +282,7 @@ impl<T: Authorization> Bundle<T> {
context: &mut R, context: &mut R,
mut spend_auth: impl FnMut(&mut R, &T, T::SpendAuth) -> Result<U::SpendAuth, E>, mut spend_auth: impl FnMut(&mut R, &T, T::SpendAuth) -> Result<U::SpendAuth, E>,
step: impl FnOnce(&mut R, T) -> Result<U, E>, step: impl FnOnce(&mut R, T) -> Result<U, E>,
) -> Result<Bundle<U>, E> { ) -> Result<Bundle<U, V>, E> {
let authorization = self.authorization; let authorization = self.authorization;
let new_actions = self let new_actions = self
.actions .actions
@ -304,9 +319,19 @@ impl Authorized {
binding_signature, 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 Bundle<Authorized> { impl<V> Bundle<Authorized, V> {
/// Computes a commitment to the authorizing data within for this bundle. /// Computes a commitment to the authorizing data within for this bundle.
/// ///
/// This together with `Bundle::commitment` bind the entire bundle. /// This together with `Bundle::commitment` bind the entire bundle.
@ -325,3 +350,224 @@ pub struct BundleCommitment;
/// A commitment to the authorizing data within a bundle of actions. /// A commitment to the authorizing data within a bundle of actions.
#[derive(Debug)] #[derive(Debug)]
pub struct BundleAuthorizingCommitment; pub struct BundleAuthorizingCommitment;
/// Generators for property testing.
#[cfg(any(test, feature = "test-dependencies"))]
pub mod testing {
use nonempty::NonEmpty;
use rand_7::{rngs::StdRng, SeedableRng};
use reddsa::orchard::SpendAuth;
use proptest::collection::vec;
use proptest::prelude::*;
use crate::{
circuit::Proof,
note::{
commitment::ExtractedNoteCommitment, nullifier::testing::arb_nullifier,
testing::arb_note, TransmittedNoteCiphertext,
},
primitives::redpallas::{
self,
testing::{
arb_binding_signing_key, arb_spendauth_signing_key, arb_spendauth_verification_key,
},
},
value::{
testing::arb_note_value_bounded, NoteValue, ValueCommitTrapdoor, ValueCommitment,
ValueSum, MAX_NOTE_VALUE,
},
Anchor,
};
use super::{Action, Authorization, Authorized, Bundle, Flags};
/// Marker for an unauthorized bundle with no proofs or signatures.
#[derive(Debug)]
pub struct Unauthorized;
impl Authorization for Unauthorized {
type SpendAuth = ();
}
prop_compose! {
/// Generate an action without authorization data.
pub fn arb_unauthorized_action(spend_value: NoteValue, output_value: NoteValue)(
nf in arb_nullifier(),
rk in arb_spendauth_verification_key(),
note in arb_note(output_value),
) -> Action<()> {
let cmx = ExtractedNoteCommitment::from(note.commitment());
let cv_net = ValueCommitment::derive(
(spend_value - output_value).unwrap(),
ValueCommitTrapdoor::zero()
);
// FIXME: make a real one from the note.
let encrypted_note = TransmittedNoteCiphertext {
epk_bytes: [0u8; 32],
enc_ciphertext: [0u8; 580],
out_ciphertext: [0u8; 80]
};
Action {
nf,
rk,
cmx,
encrypted_note,
cv_net,
authorization: ()
}
}
}
/// 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,
flags: Flags,
) -> impl Strategy<Value = (ValueSum, Action<()>)> {
let spend_value_gen = if flags.spends_enabled {
Strategy::boxed(arb_note_value_bounded(MAX_NOTE_VALUE / n_actions as u64))
} else {
Strategy::boxed(Just(NoteValue::zero()))
};
spend_value_gen.prop_flat_map(move |spend_value| {
let output_value_gen = if flags.outputs_enabled {
Strategy::boxed(arb_note_value_bounded(MAX_NOTE_VALUE / n_actions as u64))
} else {
Strategy::boxed(Just(NoteValue::zero()))
};
output_value_gen.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! {
/// Generate an action with invalid (random) authorization data.
pub fn arb_action(spend_value: NoteValue, output_value: NoteValue)(
nf in arb_nullifier(),
sk in arb_spendauth_signing_key(),
note in arb_note(output_value),
rng_seed in prop::array::uniform32(prop::num::u8::ANY),
fake_sighash in prop::array::uniform32(prop::num::u8::ANY),
) -> Action<redpallas::Signature<SpendAuth>> {
let cmx = ExtractedNoteCommitment::from(note.commitment());
let cv_net = ValueCommitment::derive(
(spend_value - output_value).unwrap(),
ValueCommitTrapdoor::zero()
);
// FIXME: make a real one from the note.
let encrypted_note = TransmittedNoteCiphertext {
epk_bytes: [0u8; 32],
enc_ciphertext: [0u8; 580],
out_ciphertext: [0u8; 80]
};
let rng = StdRng::from_seed(rng_seed);
Action {
nf,
rk: redpallas::VerificationKey::from(&sk),
cmx,
encrypted_note,
cv_net,
authorization: sk.sign(rng, &fake_sighash),
}
}
}
/// Generate an authorized action having spend and output values less than MAX_NOTE_VALUE / n_actions.
pub fn arb_action_n(
n_actions: usize,
flags: Flags,
) -> impl Strategy<Value = (ValueSum, Action<redpallas::Signature<SpendAuth>>)> {
let spend_value_gen = if flags.spends_enabled {
Strategy::boxed(arb_note_value_bounded(MAX_NOTE_VALUE / n_actions as u64))
} else {
Strategy::boxed(Just(NoteValue::zero()))
};
spend_value_gen.prop_flat_map(move |spend_value| {
let output_value_gen = if flags.outputs_enabled {
Strategy::boxed(arb_note_value_bounded(MAX_NOTE_VALUE / n_actions as u64))
} else {
Strategy::boxed(Just(NoteValue::zero()))
};
output_value_gen.prop_flat_map(move |output_value| {
arb_action(spend_value, output_value)
.prop_map(move |a| ((spend_value - output_value).unwrap(), a))
})
})
}
prop_compose! {
/// Create an arbitrary set of flags.
pub fn arb_flags()(spends_enabled in prop::bool::ANY, outputs_enabled in prop::bool::ANY) -> Flags {
Flags::from_parts(spends_enabled, outputs_enabled)
}
}
prop_compose! {
/// Generate an arbitrary unauthorized bundle. This bundle does not
/// necessarily respect consensus rules; for that use
/// [`crate::builder::testing::arb_bundle`]
pub fn arb_unauthorized_bundle()
(
n_actions in 1usize..100,
flags in arb_flags(),
)
(
acts in vec(arb_unauthorized_action_n(n_actions, flags), n_actions),
anchor in prop::array::uniform32(prop::num::u8::ANY).prop_map(Anchor),
flags in Just(flags)
) -> Bundle<Unauthorized, ValueSum> {
let (balances, actions): (Vec<ValueSum>, Vec<Action<_>>) = acts.into_iter().unzip();
Bundle::from_parts(
NonEmpty::from_vec(actions).unwrap(),
flags,
balances.into_iter().sum::<Result<ValueSum, _>>().unwrap(),
anchor,
Unauthorized
)
}
}
prop_compose! {
/// Generate an arbitrary bundle with fake authorization data. This bundle does not
/// necessarily respect consensus rules; for that use
/// [`crate::builder::testing::arb_bundle`]
pub fn arb_bundle()
(
n_actions in 1usize..100,
flags in arb_flags(),
)
(
acts in vec(arb_action_n(n_actions, flags), n_actions),
anchor in prop::array::uniform32(prop::num::u8::ANY).prop_map(Anchor),
sk in arb_binding_signing_key(),
rng_seed in prop::array::uniform32(prop::num::u8::ANY),
fake_proof in vec(prop::num::u8::ANY, 1973),
fake_sighash in prop::array::uniform32(prop::num::u8::ANY),
flags in Just(flags)
) -> Bundle<Authorized, ValueSum> {
let (balances, actions): (Vec<ValueSum>, Vec<Action<_>>) = acts.into_iter().unzip();
let rng = StdRng::from_seed(rng_seed);
Bundle::from_parts(
NonEmpty::from_vec(actions).unwrap(),
flags,
balances.into_iter().sum::<Result<ValueSum, _>>().unwrap(),
anchor,
Authorized {
proof: Proof::new(fake_proof),
binding_signature: sk.sign(rng, &fake_sighash),
}
)
}
}
}

View File

@ -7,11 +7,10 @@ use halo2::{
use pasta_curves::{pallas, vesta}; use pasta_curves::{pallas, vesta};
use crate::{ use crate::{
note::ExtractedNoteCommitment, note::{nullifier::Nullifier, ExtractedNoteCommitment},
primitives::redpallas::{SpendAuth, VerificationKey}, primitives::redpallas::{SpendAuth, VerificationKey},
tree::Anchor, tree::Anchor,
value::ValueCommitment, value::ValueCommitment,
Nullifier,
}; };
pub(crate) mod gadget; pub(crate) mod gadget;
@ -120,6 +119,12 @@ impl Instance {
#[derive(Debug)] #[derive(Debug)]
pub struct Proof(Vec<u8>); pub struct Proof(Vec<u8>);
impl AsRef<[u8]> for Proof {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl Proof { impl Proof {
/// Creates a proof for the given circuits and instances. /// Creates a proof for the given circuits and instances.
pub fn create( pub fn create(
@ -156,6 +161,11 @@ impl Proof {
Err(plonk::Error::ConstraintSystemFailure) Err(plonk::Error::ConstraintSystemFailure)
} }
} }
/// Constructs a new Proof value.
pub fn new(bytes: Vec<u8>) -> Self {
Proof(bytes)
}
} }
#[cfg(test)] #[cfg(test)]
@ -174,6 +184,7 @@ mod tests {
value::{ValueCommitTrapdoor, ValueCommitment}, value::{ValueCommitTrapdoor, ValueCommitment},
}; };
// TODO: recast as a proptest
#[test] #[test]
fn round_trip() { fn round_trip() {
let mut rng = OsRng; let mut rng = OsRng;
@ -195,7 +206,7 @@ mod tests {
( (
Circuit {}, Circuit {},
Instance { Instance {
anchor: Anchor, anchor: Anchor([0; 32]),
cv_net, cv_net,
nf_old, nf_old,
rk, rk,

View File

@ -25,7 +25,7 @@ use crate::{
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents]. /// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
/// ///
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents /// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct SpendingKey([u8; 32]); pub struct SpendingKey([u8; 32]);
impl SpendingKey { impl SpendingKey {
@ -109,7 +109,7 @@ impl From<&SpendingKey> for SpendAuthorizingKey {
/// $\mathsf{ak}$ but stored here as a RedPallas verification key. /// $\mathsf{ak}$ but stored here as a RedPallas verification key.
/// ///
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents /// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct SpendValidatingKey(redpallas::VerificationKey<SpendAuth>); pub struct SpendValidatingKey(redpallas::VerificationKey<SpendAuth>);
impl From<&SpendAuthorizingKey> for SpendValidatingKey { impl From<&SpendAuthorizingKey> for SpendValidatingKey {
@ -138,7 +138,7 @@ impl SpendValidatingKey {
/// [`Nullifier`]: crate::note::Nullifier /// [`Nullifier`]: crate::note::Nullifier
/// [`Note`]: crate::note::Note /// [`Note`]: crate::note::Note
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents /// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
#[derive(Debug)] #[derive(Debug, Clone)]
pub(crate) struct NullifierDerivingKey(pallas::Base); pub(crate) struct NullifierDerivingKey(pallas::Base);
impl From<&SpendingKey> for NullifierDerivingKey { impl From<&SpendingKey> for NullifierDerivingKey {
@ -158,7 +158,7 @@ impl NullifierDerivingKey {
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents]. /// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
/// ///
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents /// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
#[derive(Debug)] #[derive(Debug, Clone)]
struct CommitIvkRandomness(pallas::Scalar); struct CommitIvkRandomness(pallas::Scalar);
impl From<&SpendingKey> for CommitIvkRandomness { impl From<&SpendingKey> for CommitIvkRandomness {
@ -175,7 +175,7 @@ impl From<&SpendingKey> for CommitIvkRandomness {
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents]. /// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
/// ///
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents /// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct FullViewingKey { pub struct FullViewingKey {
ak: SpendValidatingKey, ak: SpendValidatingKey,
nk: NullifierDerivingKey, nk: NullifierDerivingKey,
@ -287,7 +287,7 @@ impl DiversifierKey {
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents]. /// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
/// ///
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents /// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct Diversifier([u8; 11]); pub struct Diversifier([u8; 11]);
impl Diversifier { impl Diversifier {
@ -343,7 +343,7 @@ impl IncomingViewingKey {
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents]. /// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
/// ///
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents /// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct OutgoingViewingKey([u8; 32]); pub struct OutgoingViewingKey([u8; 32]);
impl From<&FullViewingKey> for OutgoingViewingKey { impl From<&FullViewingKey> for OutgoingViewingKey {
@ -357,7 +357,7 @@ impl From<&FullViewingKey> for OutgoingViewingKey {
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents]. /// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
/// ///
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents /// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
#[derive(Debug)] #[derive(Debug, Clone)]
pub(crate) struct DiversifiedTransmissionKey(pallas::Point); pub(crate) struct DiversifiedTransmissionKey(pallas::Point);
impl DiversifiedTransmissionKey { impl DiversifiedTransmissionKey {
@ -374,3 +374,25 @@ impl DiversifiedTransmissionKey {
self.0.to_bytes() self.0.to_bytes()
} }
} }
/// Generators for property testing.
#[cfg(any(test, feature = "test-dependencies"))]
pub mod testing {
use proptest::prelude::*;
use super::SpendingKey;
prop_compose! {
/// Generate a uniformly distributed fake note commitment value.
pub fn arb_spending_key()(
key in prop::array::uniform32(prop::num::u8::ANY)
.prop_map(SpendingKey::from_bytes)
.prop_filter(
"Values must correspond to valid Orchard spending keys.",
|opt| bool::from(opt.is_some())
)
) -> SpendingKey {
key.unwrap()
}
}
}

View File

@ -17,16 +17,19 @@
#![deny(unsafe_code)] #![deny(unsafe_code)]
mod address; mod address;
mod builder; pub mod builder;
pub mod bundle; pub mod bundle;
mod circuit; mod circuit;
mod constants; mod constants;
pub mod keys; pub mod keys;
mod note; pub mod note;
pub mod primitives; pub mod primitives;
mod spec; mod spec;
mod tree; mod tree;
pub mod value; pub mod value;
pub use address::Address; pub use address::Address;
pub use note::{EncryptedNote, Note, NoteCommitment, Nullifier}; pub use bundle::Bundle;
pub use circuit::Proof;
pub use note::Note;
pub use tree::Anchor;

View File

@ -1,3 +1,4 @@
//! Data structures used for note construction.
use group::GroupEncoding; use group::GroupEncoding;
use pasta_curves::pallas; use pasta_curves::pallas;
use rand::RngCore; use rand::RngCore;
@ -9,10 +10,10 @@ use crate::{
Address, Address,
}; };
mod commitment; pub(crate) mod commitment;
pub use self::commitment::{ExtractedNoteCommitment, NoteCommitment}; pub use self::commitment::{ExtractedNoteCommitment, NoteCommitment};
mod nullifier; pub(crate) mod nullifier;
pub use self::nullifier::Nullifier; pub use self::nullifier::Nullifier;
/// The ZIP 212 seed randomness for a note. /// The ZIP 212 seed randomness for a note.
@ -135,4 +136,47 @@ impl Note {
/// An encrypted note. /// An encrypted note.
#[derive(Debug)] #[derive(Debug)]
pub struct EncryptedNote; pub struct TransmittedNoteCiphertext {
/// The serialization of the ephemeral public key
pub epk_bytes: [u8; 32],
/// The encrypted note ciphertext
pub enc_ciphertext: [u8; 580],
/// An encrypted value that allows the holder of the outgoing cipher
/// key for the note to recover the note plaintext.
pub out_ciphertext: [u8; 80],
}
/// Generators for property testing.
#[cfg(any(test, feature = "test-dependencies"))]
pub mod testing {
use proptest::prelude::*;
use crate::{
address::testing::arb_address, note::nullifier::testing::arb_nullifier, value::NoteValue,
};
use super::{Note, RandomSeed};
prop_compose! {
/// Generate an arbitrary random seed
pub(crate) fn arb_rseed()(elems in prop::array::uniform32(prop::num::u8::ANY)) -> RandomSeed {
RandomSeed(elems)
}
}
prop_compose! {
/// Generate an action without authorization data.
pub fn arb_note(value: NoteValue)(
recipient in arb_address(),
rho in arb_nullifier(),
rseed in arb_rseed(),
) -> Note {
Note {
recipient,
value,
rho,
rseed,
}
}
}
}

View File

@ -2,7 +2,7 @@ use std::iter;
use bitvec::{array::BitArray, order::Lsb0}; use bitvec::{array::BitArray, order::Lsb0};
use ff::PrimeField; use ff::PrimeField;
use pasta_curves::pallas; use pasta_curves::{arithmetic::FieldExt, pallas};
use subtle::CtOption; use subtle::CtOption;
use crate::{ use crate::{
@ -59,6 +59,18 @@ impl NoteCommitment {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ExtractedNoteCommitment(pub(super) pallas::Base); pub struct ExtractedNoteCommitment(pub(super) pallas::Base);
impl ExtractedNoteCommitment {
/// Deserialize the extracted note commitment from a byte array.
pub fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
pallas::Base::from_bytes(bytes).map(ExtractedNoteCommitment)
}
/// Serialize the value commitment to its canonical byte representation.
pub fn to_bytes(&self) -> [u8; 32] {
self.0.to_bytes()
}
}
impl From<NoteCommitment> for ExtractedNoteCommitment { impl From<NoteCommitment> for ExtractedNoteCommitment {
fn from(cm: NoteCommitment) -> Self { fn from(cm: NoteCommitment) -> Self {
ExtractedNoteCommitment(extract_p(&cm.0)) ExtractedNoteCommitment(extract_p(&cm.0))

View File

@ -1,7 +1,8 @@
use group::Group; use group::Group;
use halo2::arithmetic::CurveExt; use halo2::arithmetic::CurveExt;
use pasta_curves::pallas; use pasta_curves::{arithmetic::FieldExt, pallas};
use rand::RngCore; use rand::RngCore;
use subtle::CtOption;
use super::NoteCommitment; use super::NoteCommitment;
use crate::{ use crate::{
@ -11,7 +12,7 @@ use crate::{
/// A unique nullifier for a note. /// A unique nullifier for a note.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Nullifier(pub(super) pallas::Base); pub struct Nullifier(pub(crate) pallas::Base);
impl Nullifier { impl Nullifier {
/// Generates a dummy nullifier for use as $\rho$ in dummy spent notes. /// Generates a dummy nullifier for use as $\rho$ in dummy spent notes.
@ -30,6 +31,16 @@ impl Nullifier {
Nullifier(extract_p(&pallas::Point::random(rng))) Nullifier(extract_p(&pallas::Point::random(rng)))
} }
/// Deserialize the nullifier from a byte array.
pub fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
pallas::Base::from_bytes(bytes).map(Nullifier)
}
/// Serialize the nullifier to its canonical byte representation.
pub fn to_bytes(&self) -> [u8; 32] {
self.0.to_bytes()
}
/// $DeriveNullifier$. /// $DeriveNullifier$.
/// ///
/// Defined in [Zcash Protocol Spec § 4.16: Note Commitments and Nullifiers][commitmentsandnullifiers]. /// Defined in [Zcash Protocol Spec § 4.16: Note Commitments and Nullifiers][commitmentsandnullifiers].
@ -46,3 +57,27 @@ impl Nullifier {
Nullifier(extract_p(&(k * mod_r_p(nk.prf_nf(rho) + psi) + cm.0))) Nullifier(extract_p(&(k * mod_r_p(nk.prf_nf(rho) + psi) + cm.0)))
} }
} }
/// Generators for property testing.
#[cfg(any(test, feature = "test-dependencies"))]
pub mod testing {
use proptest::prelude::*;
use group::GroupEncoding;
use pasta_curves::pallas;
use super::Nullifier;
use crate::spec::extract_p;
prop_compose! {
/// Generate a uniformly distributed nullifier value.
pub fn arb_nullifier()(
coord in prop::array::uniform32(any::<u8>()).prop_map(|b| pallas::Point::from_bytes(&b)).prop_filter(
"Must generate a valid Pallas point",
|p| p.is_some().into()
)
) -> Nullifier {
Nullifier(extract_p(&coord.unwrap()))
}
}
}

View File

@ -96,6 +96,18 @@ impl VerificationKey<SpendAuth> {
#[derive(Debug)] #[derive(Debug)]
pub struct Signature<T: SigType>(reddsa::Signature<T>); pub struct Signature<T: SigType>(reddsa::Signature<T>);
impl<T: SigType> From<[u8; 64]> for Signature<T> {
fn from(bytes: [u8; 64]) -> Self {
Signature(bytes.into())
}
}
impl<T: SigType> From<&Signature<T>> for [u8; 64] {
fn from(sig: &Signature<T>) -> Self {
sig.0.into()
}
}
pub(crate) mod private { pub(crate) mod private {
use super::{Binding, SpendAuth}; use super::{Binding, SpendAuth};
@ -105,3 +117,49 @@ pub(crate) mod private {
impl Sealed for Binding {} impl Sealed for Binding {}
} }
/// Generators for property testing.
#[cfg(any(test, feature = "test-dependencies"))]
pub mod testing {
use std::convert::TryFrom;
use proptest::prelude::*;
use super::{Binding, SigningKey, SpendAuth, VerificationKey};
prop_compose! {
/// Generate a uniformly distributed RedDSA spend authorization signing key.
pub fn arb_spendauth_signing_key()(
sk in prop::array::uniform32(prop::num::u8::ANY)
.prop_map(reddsa::SigningKey::try_from)
.prop_filter("Values must be parseable as valid signing keys", |r| r.is_ok())
) -> SigningKey<SpendAuth> {
SigningKey(sk.unwrap())
}
}
prop_compose! {
/// Generate a uniformly distributed RedDSA binding signing key.
pub fn arb_binding_signing_key()(
sk in prop::array::uniform32(prop::num::u8::ANY)
.prop_map(reddsa::SigningKey::try_from)
.prop_filter("Values must be parseable as valid signing keys", |r| r.is_ok())
) -> SigningKey<Binding> {
SigningKey(sk.unwrap())
}
}
prop_compose! {
/// Generate a uniformly distributed RedDSA spend authorization verification key.
pub fn arb_spendauth_verification_key()(sk in arb_spendauth_signing_key()) -> VerificationKey<SpendAuth> {
VerificationKey::from(&sk)
}
}
prop_compose! {
/// Generate a uniformly distributed RedDSA binding verification key.
pub fn arb_binding_verification_key()(sk in arb_binding_signing_key()) -> VerificationKey<Binding> {
VerificationKey::from(&sk)
}
}
}

View File

@ -2,7 +2,7 @@ use rand::RngCore;
/// The root of an Orchard commitment tree. /// The root of an Orchard commitment tree.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Anchor; pub struct Anchor(pub [u8; 32]);
#[derive(Debug)] #[derive(Debug)]
pub struct MerklePath; pub struct MerklePath;
@ -10,7 +10,6 @@ pub struct MerklePath;
impl MerklePath { impl MerklePath {
/// Generates a dummy Merkle path for use in dummy spent notes. /// Generates a dummy Merkle path for use in dummy spent notes.
pub(crate) fn dummy(_rng: &mut impl RngCore) -> Self { pub(crate) fn dummy(_rng: &mut impl RngCore) -> Self {
// TODO
MerklePath MerklePath
} }
} }

View File

@ -14,8 +14,8 @@
//! [`Action`]: crate::bundle::Action //! [`Action`]: crate::bundle::Action
//! [`Bundle`]: crate::bundle::Bundle //! [`Bundle`]: crate::bundle::Bundle
use std::convert::TryInto; use std::convert::{TryFrom, TryInto};
use std::fmt; use std::fmt::{self, Debug};
use std::iter::Sum; use std::iter::Sum;
use std::ops::{Add, Sub}; use std::ops::{Add, Sub};
@ -27,9 +27,23 @@ use pasta_curves::{
pallas, pallas,
}; };
use rand::RngCore; use rand::RngCore;
use subtle::CtOption;
use crate::primitives::redpallas::{self, Binding}; use crate::primitives::redpallas::{self, Binding};
use std::ops::RangeInclusive;
/// Maximum note value.
pub const MAX_NOTE_VALUE: u64 = u64::MAX;
/// 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> =
-(MAX_NOTE_VALUE as i128)..=MAX_NOTE_VALUE as i128;
/// A value operation overflowed. /// A value operation overflowed.
#[derive(Debug)] #[derive(Debug)]
pub struct OverflowError; pub struct OverflowError;
@ -66,18 +80,21 @@ impl NoteValue {
} }
impl Sub for NoteValue { impl Sub for NoteValue {
type Output = Result<ValueSum, OverflowError>; type Output = Option<ValueSum>;
#[allow(clippy::suspicious_arithmetic_impl)]
fn sub(self, rhs: Self) -> Self::Output { fn sub(self, rhs: Self) -> Self::Output {
let a: i64 = self.0.try_into().map_err(|_| OverflowError)?; let a = self.0 as i128;
let b: i64 = rhs.0.try_into().map_err(|_| OverflowError)?; let b = rhs.0 as i128;
Ok(ValueSum(a - b)) a.checked_sub(b)
.filter(|v| VALUE_SUM_RANGE.contains(v))
.map(ValueSum)
} }
} }
/// A sum of Orchard note values. /// A sum of Orchard note values
#[derive(Clone, Copy, Debug, Default, PartialEq)] #[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct ValueSum(i64); pub struct ValueSum(i128);
impl ValueSum { impl ValueSum {
pub(crate) fn zero() -> Self { pub(crate) fn zero() -> Self {
@ -90,21 +107,39 @@ impl ValueSum {
/// This only enforces that the value is a signed 63-bit integer. Callers should /// This only enforces that the value is a signed 63-bit integer. Callers should
/// enforce any additional constraints on the value's valid range themselves. /// enforce any additional constraints on the value's valid range themselves.
pub fn from_raw(value: i64) -> Self { pub fn from_raw(value: i64) -> Self {
ValueSum(value) ValueSum(value as i128)
} }
} }
impl Add for ValueSum { impl Add for ValueSum {
type Output = Result<ValueSum, OverflowError>; type Output = Option<ValueSum>;
#[allow(clippy::suspicious_arithmetic_impl)]
fn add(self, rhs: Self) -> Self::Output { fn add(self, rhs: Self) -> Self::Output {
self.0.checked_add(rhs.0).map(ValueSum).ok_or(OverflowError) self.0
.checked_add(rhs.0)
.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, cv| acc? + *cv) iter.fold(Ok(ValueSum(0)), |acc, v| (acc? + *v).ok_or(OverflowError))
}
}
impl Sum<ValueSum> for Result<ValueSum, OverflowError> {
fn sum<I: Iterator<Item = ValueSum>>(iter: I) -> Self {
iter.fold(Ok(ValueSum(0)), |acc, v| (acc? + v).ok_or(OverflowError))
}
}
impl TryFrom<ValueSum> for i64 {
type Error = OverflowError;
fn try_from(v: ValueSum) -> Result<i64, Self::Error> {
i64::try_from(v.0).map_err(|_| OverflowError)
} }
} }
@ -190,11 +225,12 @@ impl ValueCommitment {
let hasher = pallas::Point::hash_to_curve("z.cash:Orchard-cv"); let hasher = pallas::Point::hash_to_curve("z.cash:Orchard-cv");
let V = hasher(b"v"); let V = hasher(b"v");
let R = hasher(b"r"); let R = hasher(b"r");
let abs_value = u64::try_from(value.0.abs()).expect("value must be in valid range");
let value = if value.0.is_negative() { let value = if value.0.is_negative() {
-pallas::Scalar::from_u64((-value.0) as u64) -pallas::Scalar::from_u64(abs_value)
} else { } else {
pallas::Scalar::from_u64(value.0 as u64) pallas::Scalar::from_u64(abs_value)
}; };
ValueCommitment(V * value + R * rcv.0) ValueCommitment(V * value + R * rcv.0)
@ -204,22 +240,29 @@ impl ValueCommitment {
// TODO: impl From<pallas::Point> for redpallas::VerificationKey. // TODO: impl From<pallas::Point> for redpallas::VerificationKey.
self.0.to_bytes().try_into().unwrap() self.0.to_bytes().try_into().unwrap()
} }
/// Deserialize a value commitment from its byte representation
pub fn from_bytes(bytes: &[u8; 32]) -> CtOption<ValueCommitment> {
pallas::Point::from_bytes(bytes).map(ValueCommitment)
}
/// Serialize this value commitment to its canonical byte representation.
pub fn to_bytes(&self) -> [u8; 32] {
self.0.to_bytes()
}
} }
#[cfg(test)] /// Generators for property testing.
mod tests { #[cfg(any(test, feature = "test-dependencies"))]
pub mod testing {
use pasta_curves::{arithmetic::FieldExt, pallas}; use pasta_curves::{arithmetic::FieldExt, pallas};
use proptest::prelude::*; use proptest::prelude::*;
use super::{OverflowError, ValueCommitTrapdoor, ValueCommitment, ValueSum}; use super::{NoteValue, ValueCommitTrapdoor, ValueSum, MAX_NOTE_VALUE, VALUE_SUM_RANGE};
use crate::primitives::redpallas;
/// Zcash's maximum money amount. Used as a bound in proptests so we don't artifically
/// overflow `ValueSum`'s size.
const MAX_MONEY: i64 = 21_000_000 * 1_0000_0000;
prop_compose! { prop_compose! {
fn arb_scalar()(bytes in prop::array::uniform32(0u8..)) -> pallas::Scalar { /// Generate an arbitrary Pallas scalar.
pub fn arb_scalar()(bytes in prop::array::uniform32(0u8..)) -> pallas::Scalar {
// Instead of rejecting out-of-range bytes, let's reduce them. // Instead of rejecting out-of-range bytes, let's reduce them.
let mut buf = [0; 64]; let mut buf = [0; 64];
buf[..32].copy_from_slice(&bytes); buf[..32].copy_from_slice(&bytes);
@ -228,21 +271,68 @@ mod tests {
} }
prop_compose! { prop_compose! {
fn arb_value_sum(bound: i64)(value in -bound..bound) -> ValueSum { /// Generate an arbitrary [`ValueSum`] in the range of valid Zcash values.
ValueSum(value) pub fn arb_value_sum()(value in VALUE_SUM_RANGE) -> ValueSum {
ValueSum(value as i128)
} }
} }
prop_compose! { prop_compose! {
fn arb_trapdoor()(rcv in arb_scalar()) -> ValueCommitTrapdoor { /// Generate an arbitrary [`ValueSum`] in the range of valid Zcash values.
pub fn arb_value_sum_bounded(bound: NoteValue)(value in -(bound.0 as i128)..=(bound.0 as i128)) -> ValueSum {
ValueSum(value as i128)
}
}
prop_compose! {
/// Generate an arbitrary ValueCommitTrapdoor
pub fn arb_trapdoor()(rcv in arb_scalar()) -> ValueCommitTrapdoor {
ValueCommitTrapdoor(rcv) ValueCommitTrapdoor(rcv)
} }
} }
prop_compose! {
/// Generate an arbitrary value in the range of valid nonnegative Zcash amounts.
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_note_value_bounded(max: u64)(value in 0u64..max) -> 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_positive_note_value(max: u64)(value in 1u64..max) -> NoteValue {
NoteValue(value)
}
}
}
#[cfg(test)]
mod tests {
use proptest::prelude::*;
use super::{
testing::{arb_note_value_bounded, arb_trapdoor, arb_value_sum_bounded},
OverflowError, ValueCommitTrapdoor, ValueCommitment, ValueSum, MAX_NOTE_VALUE,
};
use crate::primitives::redpallas;
proptest! { proptest! {
#[test] #[test]
fn bsk_consistent_with_bvk( fn bsk_consistent_with_bvk(
values in prop::collection::vec((arb_value_sum(MAX_MONEY), arb_trapdoor()), 1..10), 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()), n_values)
)
)
) { ) {
let value_balance = values let value_balance = values
.iter() .iter()