mirror of https://github.com/zcash/halo2.git
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:
commit
3117187280
|
@ -25,6 +25,7 @@ blake2b_simd = "0.5"
|
|||
ff = "0.9"
|
||||
fpe = "0.4"
|
||||
group = "0.9"
|
||||
proptest = { version = "1.0.0", optional = true }
|
||||
rand = "0.8"
|
||||
rand_7 = { package = "rand", version = "0.7" }
|
||||
nonempty = "0.6"
|
||||
|
@ -50,6 +51,9 @@ proptest = "1.0.0"
|
|||
[lib]
|
||||
bench = false
|
||||
|
||||
[features]
|
||||
test-dependencies = ["proptest"]
|
||||
|
||||
[[bench]]
|
||||
name = "small"
|
||||
harness = false
|
||||
|
|
|
@ -15,7 +15,7 @@ use crate::{
|
|||
/// let sk = SpendingKey::from_bytes([7; 32]).unwrap();
|
||||
/// let address = FullViewingKey::from(&sk).default_address();
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Address {
|
||||
d: Diversifier,
|
||||
pk_d: DiversifiedTransmissionKey,
|
||||
|
@ -38,3 +38,21 @@ impl Address {
|
|||
&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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
180
src/builder.rs
180
src/builder.rs
|
@ -1,5 +1,6 @@
|
|||
//! Logic for building Orchard components of transactions.
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::iter;
|
||||
|
||||
use ff::Field;
|
||||
|
@ -8,23 +9,29 @@ use pasta_curves::pallas;
|
|||
use rand::RngCore;
|
||||
|
||||
use crate::{
|
||||
address::Address,
|
||||
bundle::{Action, Authorization, Authorized, Bundle, Flags},
|
||||
circuit::{Circuit, Proof, ProvingKey},
|
||||
keys::{
|
||||
FullViewingKey, OutgoingViewingKey, SpendAuthorizingKey, SpendValidatingKey, SpendingKey,
|
||||
},
|
||||
note::{Note, TransmittedNoteCiphertext},
|
||||
primitives::redpallas::{self, Binding, SpendAuth},
|
||||
tree::{Anchor, MerklePath},
|
||||
value::{self, NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum},
|
||||
Address, EncryptedNote, Note,
|
||||
value::{self, NoteValue, OverflowError, ValueCommitTrapdoor, ValueCommitment, ValueSum},
|
||||
};
|
||||
|
||||
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),
|
||||
}
|
||||
|
||||
|
@ -110,7 +117,7 @@ impl ActionInfo {
|
|||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
|
||||
|
@ -137,7 +144,11 @@ impl ActionInfo {
|
|||
let cm_new = note.commitment();
|
||||
|
||||
// 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(
|
||||
|
@ -158,6 +169,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>,
|
||||
|
@ -166,6 +178,7 @@ pub struct Builder {
|
|||
}
|
||||
|
||||
impl Builder {
|
||||
/// Constructs a new empty builder for an Orchard bundle.
|
||||
pub fn new(flags: Flags, anchor: Anchor) -> Self {
|
||||
Builder {
|
||||
spends: vec![],
|
||||
|
@ -233,11 +246,11 @@ impl Builder {
|
|||
///
|
||||
/// This API assumes that none of the notes being spent are controlled by (threshold)
|
||||
/// multisignatures, and immediately constructs the bundle proof.
|
||||
fn build(
|
||||
fn build<V: TryFrom<i64>>(
|
||||
mut self,
|
||||
mut rng: impl RngCore,
|
||||
pk: &ProvingKey,
|
||||
) -> Result<Bundle<Unauthorized>, Error> {
|
||||
) -> Result<Bundle<Unauthorized, V>, Error> {
|
||||
// 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
|
||||
|
@ -272,11 +285,16 @@ impl Builder {
|
|||
let anchor = self.anchor;
|
||||
|
||||
// Determine the value balance for this bundle, ensuring it is valid.
|
||||
let value_balance: ValueSum = pre_actions
|
||||
let value_balance = pre_actions
|
||||
.iter()
|
||||
.fold(Ok(ValueSum::zero()), |acc, action| {
|
||||
.fold(Some(ValueSum::zero()), |acc, action| {
|
||||
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.
|
||||
let bsk = pre_actions
|
||||
|
@ -305,7 +323,7 @@ impl Builder {
|
|||
Ok(Bundle::from_parts(
|
||||
NonEmpty::from_vec(actions).unwrap(),
|
||||
flags,
|
||||
value_balance,
|
||||
result_value_balance,
|
||||
anchor,
|
||||
Unauthorized { proof, bsk },
|
||||
))
|
||||
|
@ -349,7 +367,7 @@ impl Authorization for PartiallyAuthorized {
|
|||
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.
|
||||
///
|
||||
/// This API ensures that all signatures are created over the same sighash.
|
||||
|
@ -357,7 +375,7 @@ impl Bundle<Unauthorized> {
|
|||
self,
|
||||
mut rng: R,
|
||||
sighash: [u8; 32],
|
||||
) -> Bundle<PartiallyAuthorized> {
|
||||
) -> Bundle<PartiallyAuthorized, V> {
|
||||
self.authorize(
|
||||
&mut rng,
|
||||
|rng, _, SigningMetadata { dummy_ask, ak }| {
|
||||
|
@ -381,7 +399,7 @@ impl Bundle<Unauthorized> {
|
|||
mut rng: R,
|
||||
sighash: [u8; 32],
|
||||
signing_keys: &[SpendAuthorizingKey],
|
||||
) -> Result<Bundle<Authorized>, Error> {
|
||||
) -> Result<Bundle<Authorized, V>, Error> {
|
||||
signing_keys
|
||||
.iter()
|
||||
.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`].
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// 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(
|
||||
&mut (),
|
||||
|_, _, (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)]
|
||||
mod tests {
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use super::Builder;
|
||||
use crate::{
|
||||
bundle::Flags,
|
||||
bundle::{Authorized, Bundle, Flags},
|
||||
circuit::ProvingKey,
|
||||
keys::{FullViewingKey, SpendingKey},
|
||||
tree::Anchor,
|
||||
value::{NoteValue, ValueSum},
|
||||
value::NoteValue,
|
||||
};
|
||||
|
||||
#[test]
|
||||
|
@ -461,16 +601,16 @@ mod tests {
|
|||
let fvk = FullViewingKey::from(&sk);
|
||||
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
|
||||
.add_recipient(None, recipient, NoteValue::from_raw(5000), None)
|
||||
.unwrap();
|
||||
let bundle = dbg!(builder
|
||||
let bundle: Bundle<Authorized, i64> = dbg!(builder
|
||||
.build(&mut rng, &pk)
|
||||
.unwrap()
|
||||
.prepare(rand_7::rngs::OsRng, [0; 32]))
|
||||
.finalize()
|
||||
.unwrap();
|
||||
assert_eq!(bundle.value_balance(), &ValueSum::from_raw(-5000))
|
||||
assert_eq!(bundle.value_balance(), &(-5000))
|
||||
}
|
||||
}
|
||||
|
|
278
src/bundle.rs
278
src/bundle.rs
|
@ -4,10 +4,10 @@ use nonempty::NonEmpty;
|
|||
|
||||
use crate::{
|
||||
circuit::{Instance, Proof},
|
||||
note::{EncryptedNote, ExtractedNoteCommitment, Nullifier},
|
||||
note::{ExtractedNoteCommitment, Nullifier, TransmittedNoteCiphertext},
|
||||
primitives::redpallas::{self, Binding, SpendAuth},
|
||||
tree::Anchor,
|
||||
value::{ValueCommitment, ValueSum},
|
||||
value::ValueCommitment,
|
||||
};
|
||||
|
||||
/// 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
|
||||
/// the two. TODO: Determine which is more efficient (circuit size vs bundle size).
|
||||
#[derive(Debug)]
|
||||
pub struct Action<T> {
|
||||
pub struct Action<A> {
|
||||
/// The nullifier of the note being spent.
|
||||
nf: Nullifier,
|
||||
/// The randomized verification key for the note being spent.
|
||||
rk: redpallas::VerificationKey<SpendAuth>,
|
||||
/// A commitment to the new note being created.
|
||||
cmx: ExtractedNoteCommitment,
|
||||
/// The encrypted output note.
|
||||
encrypted_note: EncryptedNote,
|
||||
/// The transmitted note ciphertext.
|
||||
encrypted_note: TransmittedNoteCiphertext,
|
||||
/// A commitment to the net value created or consumed by this action.
|
||||
cv_net: ValueCommitment,
|
||||
/// The authorization for this action.
|
||||
authorization: T,
|
||||
authorization: A,
|
||||
}
|
||||
|
||||
impl<T> Action<T> {
|
||||
|
@ -40,7 +40,7 @@ impl<T> Action<T> {
|
|||
nf: Nullifier,
|
||||
rk: redpallas::VerificationKey<SpendAuth>,
|
||||
cmx: ExtractedNoteCommitment,
|
||||
encrypted_note: EncryptedNote,
|
||||
encrypted_note: TransmittedNoteCiphertext,
|
||||
cv_net: ValueCommitment,
|
||||
authorization: T,
|
||||
) -> Self {
|
||||
|
@ -70,7 +70,7 @@ impl<T> Action<T> {
|
|||
}
|
||||
|
||||
/// Returns the encrypted note ciphertext.
|
||||
pub fn encrypted_note(&self) -> &EncryptedNote {
|
||||
pub fn encrypted_note(&self) -> &TransmittedNoteCiphertext {
|
||||
&self.encrypted_note
|
||||
}
|
||||
|
||||
|
@ -174,7 +174,7 @@ pub trait Authorization {
|
|||
|
||||
/// A bundle of actions to be applied to the ledger.
|
||||
#[derive(Debug)]
|
||||
pub struct Bundle<T: Authorization> {
|
||||
pub struct Bundle<T: Authorization, V> {
|
||||
/// The list of actions that make up this bundle.
|
||||
actions: NonEmpty<Action<T::SpendAuth>>,
|
||||
/// 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.
|
||||
///
|
||||
/// 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.
|
||||
anchor: Anchor,
|
||||
/// The authorization for this bundle.
|
||||
authorization: T,
|
||||
}
|
||||
|
||||
impl<T: Authorization> Bundle<T> {
|
||||
impl<T: Authorization, V> Bundle<T, V> {
|
||||
/// Constructs a `Bundle` from its constituent parts.
|
||||
pub fn from_parts(
|
||||
actions: NonEmpty<Action<T::SpendAuth>>,
|
||||
flags: Flags,
|
||||
value_balance: ValueSum,
|
||||
value_balance: V,
|
||||
anchor: Anchor,
|
||||
authorization: T,
|
||||
) -> Self {
|
||||
|
@ -220,7 +220,7 @@ impl<T: Authorization> Bundle<T> {
|
|||
/// 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.
|
||||
pub fn value_balance(&self) -> &ValueSum {
|
||||
pub fn value_balance(&self) -> &V {
|
||||
&self.value_balance
|
||||
}
|
||||
|
||||
|
@ -242,13 +242,28 @@ impl<T: Authorization> Bundle<T> {
|
|||
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.
|
||||
pub fn authorize<R, U: Authorization>(
|
||||
self,
|
||||
context: &mut R,
|
||||
mut spend_auth: impl FnMut(&mut R, &T, T::SpendAuth) -> U::SpendAuth,
|
||||
step: impl FnOnce(&mut R, T) -> U,
|
||||
) -> Bundle<U> {
|
||||
) -> Bundle<U, V> {
|
||||
let authorization = self.authorization;
|
||||
Bundle {
|
||||
actions: self
|
||||
|
@ -267,7 +282,7 @@ impl<T: Authorization> Bundle<T> {
|
|||
context: &mut R,
|
||||
mut spend_auth: impl FnMut(&mut R, &T, T::SpendAuth) -> Result<U::SpendAuth, E>,
|
||||
step: impl FnOnce(&mut R, T) -> Result<U, E>,
|
||||
) -> Result<Bundle<U>, E> {
|
||||
) -> Result<Bundle<U, V>, E> {
|
||||
let authorization = self.authorization;
|
||||
let new_actions = self
|
||||
.actions
|
||||
|
@ -304,9 +319,19 @@ 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 Bundle<Authorized> {
|
||||
impl<V> Bundle<Authorized, V> {
|
||||
/// Computes a commitment to the authorizing data within for this 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.
|
||||
#[derive(Debug)]
|
||||
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),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,11 +7,10 @@ use halo2::{
|
|||
use pasta_curves::{pallas, vesta};
|
||||
|
||||
use crate::{
|
||||
note::ExtractedNoteCommitment,
|
||||
note::{nullifier::Nullifier, ExtractedNoteCommitment},
|
||||
primitives::redpallas::{SpendAuth, VerificationKey},
|
||||
tree::Anchor,
|
||||
value::ValueCommitment,
|
||||
Nullifier,
|
||||
};
|
||||
|
||||
pub(crate) mod gadget;
|
||||
|
@ -120,6 +119,12 @@ impl Instance {
|
|||
#[derive(Debug)]
|
||||
pub struct Proof(Vec<u8>);
|
||||
|
||||
impl AsRef<[u8]> for Proof {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Proof {
|
||||
/// Creates a proof for the given circuits and instances.
|
||||
pub fn create(
|
||||
|
@ -156,6 +161,11 @@ impl Proof {
|
|||
Err(plonk::Error::ConstraintSystemFailure)
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs a new Proof value.
|
||||
pub fn new(bytes: Vec<u8>) -> Self {
|
||||
Proof(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -174,6 +184,7 @@ mod tests {
|
|||
value::{ValueCommitTrapdoor, ValueCommitment},
|
||||
};
|
||||
|
||||
// TODO: recast as a proptest
|
||||
#[test]
|
||||
fn round_trip() {
|
||||
let mut rng = OsRng;
|
||||
|
@ -195,7 +206,7 @@ mod tests {
|
|||
(
|
||||
Circuit {},
|
||||
Instance {
|
||||
anchor: Anchor,
|
||||
anchor: Anchor([0; 32]),
|
||||
cv_net,
|
||||
nf_old,
|
||||
rk,
|
||||
|
|
38
src/keys.rs
38
src/keys.rs
|
@ -25,7 +25,7 @@ use crate::{
|
|||
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
|
||||
///
|
||||
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SpendingKey([u8; 32]);
|
||||
|
||||
impl SpendingKey {
|
||||
|
@ -109,7 +109,7 @@ impl From<&SpendingKey> for SpendAuthorizingKey {
|
|||
/// $\mathsf{ak}$ but stored here as a RedPallas verification key.
|
||||
///
|
||||
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SpendValidatingKey(redpallas::VerificationKey<SpendAuth>);
|
||||
|
||||
impl From<&SpendAuthorizingKey> for SpendValidatingKey {
|
||||
|
@ -138,7 +138,7 @@ impl SpendValidatingKey {
|
|||
/// [`Nullifier`]: crate::note::Nullifier
|
||||
/// [`Note`]: crate::note::Note
|
||||
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct NullifierDerivingKey(pallas::Base);
|
||||
|
||||
impl From<&SpendingKey> for NullifierDerivingKey {
|
||||
|
@ -158,7 +158,7 @@ impl NullifierDerivingKey {
|
|||
/// 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)]
|
||||
struct CommitIvkRandomness(pallas::Scalar);
|
||||
|
||||
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].
|
||||
///
|
||||
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FullViewingKey {
|
||||
ak: SpendValidatingKey,
|
||||
nk: NullifierDerivingKey,
|
||||
|
@ -287,7 +287,7 @@ impl DiversifierKey {
|
|||
/// 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 Diversifier([u8; 11]);
|
||||
|
||||
impl Diversifier {
|
||||
|
@ -343,7 +343,7 @@ impl IncomingViewingKey {
|
|||
/// 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 OutgoingViewingKey([u8; 32]);
|
||||
|
||||
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].
|
||||
///
|
||||
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct DiversifiedTransmissionKey(pallas::Point);
|
||||
|
||||
impl DiversifiedTransmissionKey {
|
||||
|
@ -374,3 +374,25 @@ impl DiversifiedTransmissionKey {
|
|||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,16 +17,19 @@
|
|||
#![deny(unsafe_code)]
|
||||
|
||||
mod address;
|
||||
mod builder;
|
||||
pub mod builder;
|
||||
pub mod bundle;
|
||||
mod circuit;
|
||||
mod constants;
|
||||
pub mod keys;
|
||||
mod note;
|
||||
pub mod note;
|
||||
pub mod primitives;
|
||||
mod spec;
|
||||
mod tree;
|
||||
pub mod value;
|
||||
|
||||
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;
|
||||
|
|
50
src/note.rs
50
src/note.rs
|
@ -1,3 +1,4 @@
|
|||
//! Data structures used for note construction.
|
||||
use group::GroupEncoding;
|
||||
use pasta_curves::pallas;
|
||||
use rand::RngCore;
|
||||
|
@ -9,10 +10,10 @@ use crate::{
|
|||
Address,
|
||||
};
|
||||
|
||||
mod commitment;
|
||||
pub(crate) mod commitment;
|
||||
pub use self::commitment::{ExtractedNoteCommitment, NoteCommitment};
|
||||
|
||||
mod nullifier;
|
||||
pub(crate) mod nullifier;
|
||||
pub use self::nullifier::Nullifier;
|
||||
|
||||
/// The ZIP 212 seed randomness for a note.
|
||||
|
@ -135,4 +136,47 @@ impl Note {
|
|||
|
||||
/// An encrypted note.
|
||||
#[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,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::iter;
|
|||
|
||||
use bitvec::{array::BitArray, order::Lsb0};
|
||||
use ff::PrimeField;
|
||||
use pasta_curves::pallas;
|
||||
use pasta_curves::{arithmetic::FieldExt, pallas};
|
||||
use subtle::CtOption;
|
||||
|
||||
use crate::{
|
||||
|
@ -59,6 +59,18 @@ impl NoteCommitment {
|
|||
#[derive(Clone, Debug)]
|
||||
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 {
|
||||
fn from(cm: NoteCommitment) -> Self {
|
||||
ExtractedNoteCommitment(extract_p(&cm.0))
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use group::Group;
|
||||
use halo2::arithmetic::CurveExt;
|
||||
use pasta_curves::pallas;
|
||||
use pasta_curves::{arithmetic::FieldExt, pallas};
|
||||
use rand::RngCore;
|
||||
use subtle::CtOption;
|
||||
|
||||
use super::NoteCommitment;
|
||||
use crate::{
|
||||
|
@ -11,7 +12,7 @@ use crate::{
|
|||
|
||||
/// A unique nullifier for a note.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Nullifier(pub(super) pallas::Base);
|
||||
pub struct Nullifier(pub(crate) pallas::Base);
|
||||
|
||||
impl Nullifier {
|
||||
/// 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)))
|
||||
}
|
||||
|
||||
/// 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$.
|
||||
///
|
||||
/// 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)))
|
||||
}
|
||||
}
|
||||
|
||||
/// 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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -96,6 +96,18 @@ impl VerificationKey<SpendAuth> {
|
|||
#[derive(Debug)]
|
||||
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 {
|
||||
use super::{Binding, SpendAuth};
|
||||
|
||||
|
@ -105,3 +117,49 @@ pub(crate) mod private {
|
|||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use rand::RngCore;
|
|||
|
||||
/// The root of an Orchard commitment tree.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Anchor;
|
||||
pub struct Anchor(pub [u8; 32]);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MerklePath;
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
144
src/value.rs
144
src/value.rs
|
@ -14,8 +14,8 @@
|
|||
//! [`Action`]: crate::bundle::Action
|
||||
//! [`Bundle`]: crate::bundle::Bundle
|
||||
|
||||
use std::convert::TryInto;
|
||||
use std::fmt;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::fmt::{self, Debug};
|
||||
use std::iter::Sum;
|
||||
use std::ops::{Add, Sub};
|
||||
|
||||
|
@ -27,9 +27,23 @@ use pasta_curves::{
|
|||
pallas,
|
||||
};
|
||||
use rand::RngCore;
|
||||
use subtle::CtOption;
|
||||
|
||||
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.
|
||||
#[derive(Debug)]
|
||||
pub struct OverflowError;
|
||||
|
@ -66,18 +80,21 @@ impl 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 {
|
||||
let a: i64 = self.0.try_into().map_err(|_| OverflowError)?;
|
||||
let b: i64 = rhs.0.try_into().map_err(|_| OverflowError)?;
|
||||
Ok(ValueSum(a - b))
|
||||
let a = self.0 as i128;
|
||||
let b = rhs.0 as i128;
|
||||
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)]
|
||||
pub struct ValueSum(i64);
|
||||
pub struct ValueSum(i128);
|
||||
|
||||
impl ValueSum {
|
||||
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
|
||||
/// enforce any additional constraints on the value's valid range themselves.
|
||||
pub fn from_raw(value: i64) -> Self {
|
||||
ValueSum(value)
|
||||
ValueSum(value as i128)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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> {
|
||||
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 V = hasher(b"v");
|
||||
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() {
|
||||
-pallas::Scalar::from_u64((-value.0) as u64)
|
||||
-pallas::Scalar::from_u64(abs_value)
|
||||
} else {
|
||||
pallas::Scalar::from_u64(value.0 as u64)
|
||||
pallas::Scalar::from_u64(abs_value)
|
||||
};
|
||||
|
||||
ValueCommitment(V * value + R * rcv.0)
|
||||
|
@ -204,22 +240,29 @@ impl ValueCommitment {
|
|||
// TODO: impl From<pallas::Point> for redpallas::VerificationKey.
|
||||
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)]
|
||||
mod tests {
|
||||
/// Generators for property testing.
|
||||
#[cfg(any(test, feature = "test-dependencies"))]
|
||||
pub mod testing {
|
||||
use pasta_curves::{arithmetic::FieldExt, pallas};
|
||||
use proptest::prelude::*;
|
||||
|
||||
use super::{OverflowError, ValueCommitTrapdoor, ValueCommitment, ValueSum};
|
||||
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;
|
||||
use super::{NoteValue, ValueCommitTrapdoor, ValueSum, MAX_NOTE_VALUE, VALUE_SUM_RANGE};
|
||||
|
||||
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.
|
||||
let mut buf = [0; 64];
|
||||
buf[..32].copy_from_slice(&bytes);
|
||||
|
@ -228,21 +271,68 @@ mod tests {
|
|||
}
|
||||
|
||||
prop_compose! {
|
||||
fn arb_value_sum(bound: i64)(value in -bound..bound) -> ValueSum {
|
||||
ValueSum(value)
|
||||
/// Generate an arbitrary [`ValueSum`] in the range of valid Zcash values.
|
||||
pub fn arb_value_sum()(value in VALUE_SUM_RANGE) -> ValueSum {
|
||||
ValueSum(value as i128)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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! {
|
||||
#[test]
|
||||
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
|
||||
.iter()
|
||||
|
|
Loading…
Reference in New Issue