mirror of https://github.com/zcash/orchard.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"
|
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
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
180
src/builder.rs
180
src/builder.rs
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
278
src/bundle.rs
278
src/bundle.rs
|
@ -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),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
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].
|
/// 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
50
src/note.rs
50
src/note.rs
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
144
src/value.rs
144
src/value.rs
|
@ -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()
|
||||||
|
|
Loading…
Reference in New Issue