diff --git a/Cargo.lock b/Cargo.lock index 6fab48aa..25e5e34a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -66,6 +66,15 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "arrayref" version = "0.3.7" @@ -233,6 +242,17 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7bc6d6292be3a19e6379786dac800f551e5865a5bb51ebbe3064ab80433f403" dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "bls12_381" +version = "0.8.0" +source = "git+https://github.com/heliaxdev/bls12_381.git?rev=d3ebe9dd6488fac1923db120a7498079e55dd838#d3ebe9dd6488fac1923db120a7498079e55dd838" +dependencies = [ + "arbitrary", "ff", "group", "pairing", @@ -518,6 +538,17 @@ dependencies = [ "uuid", ] +[[package]] +name = "derive_arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "digest" version = "0.10.7" @@ -826,7 +857,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8499f7a74008aafbecb2a2e608a3e13e4dd3e84df198b604451efe93f2de6e61" dependencies = [ "bitvec", - "bls12_381", + "bls12_381 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ff", + "group", + "rand_core", + "subtle", +] + +[[package]] +name = "jubjub" +version = "0.10.0" +source = "git+https://github.com/heliaxdev/jubjub.git?rev=a373686962f4e9d0edb3b4716f86ff6bbd9aa86c#a373686962f4e9d0edb3b4716f86ff6bbd9aa86c" +dependencies = [ + "arbitrary", + "bitvec", + "bls12_381 0.8.0 (git+https://github.com/heliaxdev/bls12_381.git?rev=d3ebe9dd6488fac1923db120a7498079e55dd838)", "ff", "group", "rand_core", @@ -887,6 +932,7 @@ checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" name = "masp_note_encryption" version = "1.0.0" dependencies = [ + "arbitrary", "borsh", "chacha20", "chacha20poly1305", @@ -900,12 +946,13 @@ name = "masp_primitives" version = "1.0.0" dependencies = [ "aes", + "arbitrary", "assert_matches", "bip0039", "bitvec", "blake2b_simd", "blake2s_simd", - "bls12_381", + "bls12_381 0.8.0 (git+https://github.com/heliaxdev/bls12_381.git?rev=d3ebe9dd6488fac1923db120a7498079e55dd838)", "borsh", "byteorder", "chacha20poly1305", @@ -915,7 +962,7 @@ dependencies = [ "group", "hex", "incrementalmerkletree", - "jubjub", + "jubjub 0.10.0 (git+https://github.com/heliaxdev/jubjub.git?rev=a373686962f4e9d0edb3b4716f86ff6bbd9aa86c)", "lazy_static", "masp_note_encryption", "memuse", @@ -936,14 +983,14 @@ version = "1.0.0" dependencies = [ "bellman", "blake2b_simd", - "bls12_381", + "bls12_381 0.8.0 (git+https://github.com/heliaxdev/bls12_381.git?rev=d3ebe9dd6488fac1923db120a7498079e55dd838)", "byteorder", "criterion", "directories", "getrandom", "group", "itertools 0.11.0", - "jubjub", + "jubjub 0.10.0 (git+https://github.com/heliaxdev/jubjub.git?rev=a373686962f4e9d0edb3b4716f86ff6bbd9aa86c)", "lazy_static", "masp_primitives", "minreq", @@ -1403,7 +1450,7 @@ dependencies = [ "byteorder", "group", "hex", - "jubjub", + "jubjub 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "pasta_curves", "rand_core", "serde", diff --git a/masp_note_encryption/Cargo.toml b/masp_note_encryption/Cargo.toml index 4d790ffe..8bd27006 100644 --- a/masp_note_encryption/Cargo.toml +++ b/masp_note_encryption/Cargo.toml @@ -25,11 +25,13 @@ chacha20poly1305 = { version = "0.10", default-features = false } rand_core = { version = "0.6", default-features = false } subtle = { version = "2.3", default-features = false } borsh = {version = "1.2.0", features = ["unstable__schema", "derive"]} +arbitrary = {version = "1.3", features = ["derive"], optional = true } [features] default = ["alloc"] alloc = [] pre-zip-212 = [] +arbitrary = ["dep:arbitrary"] [lib] bench = false diff --git a/masp_note_encryption/src/lib.rs b/masp_note_encryption/src/lib.rs index 24d9f4bb..135b2c52 100644 --- a/masp_note_encryption/src/lib.rs +++ b/masp_note_encryption/src/lib.rs @@ -11,7 +11,7 @@ //! [in-band secret distribution scheme]: https://zips.z.cash/protocol/protocol.pdf#saplingandorchardinband //! [`masp_primitives`]: https://github.com/anoma/masp -#![no_std] +#![cfg_attr(not(feature = "arbitrary"), no_std)] #![cfg_attr(docsrs, feature(doc_cfg))] // Catch documentation errors caused by code changes. #![deny(rustdoc::broken_intra_doc_links)] @@ -75,10 +75,10 @@ impl AsRef<[u8]> for OutgoingCipherKey { &self.0 } } - /// Newtype representing the byte encoding of an [`EphemeralPublicKey`]. /// /// [`EphemeralPublicKey`]: Domain::EphemeralPublicKey +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive( BorshSerialize, BorshDeserialize, diff --git a/masp_primitives/Cargo.toml b/masp_primitives/Cargo.toml index 1c696803..6ee5c051 100644 --- a/masp_primitives/Cargo.toml +++ b/masp_primitives/Cargo.toml @@ -43,11 +43,13 @@ num-traits = { version = "0.2.19", git = "https://github.com/heliaxdev/num-trait subtle = "2.2.3" # - Shielded protocols -bls12_381 = "0.8" +# branch "tomas/arbitrary" +bls12_381 = { git = "https://github.com/heliaxdev/bls12_381.git", rev = "d3ebe9dd6488fac1923db120a7498079e55dd838" } ff = "0.13" group = { version = "0.13", features = ["wnaf-memuse"] } incrementalmerkletree = "0.3" -jubjub = "0.10" +# branch "tomas/arbitrary" +jubjub = { git = "https://github.com/heliaxdev/jubjub.git", rev = "a373686962f4e9d0edb3b4716f86ff6bbd9aa86c" } nonempty = "0.7" # - Static constants @@ -74,6 +76,8 @@ aes = "0.8" fpe = "0.6" borsh = {version = "1.2.0", features = ["unstable__schema", "derive"]} +arbitrary = {version = "1.3", features = ["derive"], optional = true } + [dependencies.masp_note_encryption] version = "1.0.0" path = "../masp_note_encryption" @@ -90,6 +94,7 @@ rand_xorshift = "0.3" transparent-inputs = [] test-dependencies = ["proptest"] default = ["transparent-inputs"] +arbitrary = ["dep:arbitrary", "masp_note_encryption/arbitrary", "bls12_381/arbitrary", "jubjub/arbitrary"] [badges] maintenance = { status = "actively-developed" } diff --git a/masp_primitives/src/asset_type.rs b/masp_primitives/src/asset_type.rs index 91de6458..4043361d 100644 --- a/masp_primitives/src/asset_type.rs +++ b/masp_primitives/src/asset_type.rs @@ -15,6 +15,7 @@ use std::{ hash::{Hash, Hasher}, }; +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Debug, BorshSerialize, BorshDeserialize, Clone, Copy, Eq, BorshSchema)] pub struct AssetType { identifier: [u8; ASSET_IDENTIFIER_LENGTH], //32 byte asset type preimage diff --git a/masp_primitives/src/consensus.rs b/masp_primitives/src/consensus.rs index bea5de0e..00edcc81 100644 --- a/masp_primitives/src/consensus.rs +++ b/masp_primitives/src/consensus.rs @@ -13,6 +13,7 @@ use std::ops::{Add, Bound, RangeBounds, Sub}; /// A wrapper type representing blockchain heights. Safe conversion from /// various integer types, as well as addition and subtraction, are provided. +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[repr(transparent)] #[derive( Clone, Copy, Debug, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize, BorshSchema, @@ -244,6 +245,7 @@ pub const ZIP212_GRACE_PERIOD: u32 = 0; /// See [ZIP 200](https://zips.z.cash/zip-0200) for more details. /// /// [`signature_hash`]: crate::transaction::sighash::signature_hash +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum BranchId { /// The consensus rules deployed by [`NetworkUpgrade::MASP`]. diff --git a/masp_primitives/src/lib.rs b/masp_primitives/src/lib.rs index 07765909..9516920b 100644 --- a/masp_primitives/src/lib.rs +++ b/masp_primitives/src/lib.rs @@ -34,3 +34,15 @@ pub use num_traits; #[cfg(test)] mod test_vectors; + +#[cfg(not(feature = "arbitrary"))] +pub trait MaybeArbitrary<'a> {} + +#[cfg(not(feature = "arbitrary"))] +impl<'a, T> MaybeArbitrary<'a> for T {} + +#[cfg(feature = "arbitrary")] +pub trait MaybeArbitrary<'a>: arbitrary::Arbitrary<'a> {} + +#[cfg(feature = "arbitrary")] +impl<'a, T: for<'b> arbitrary::Arbitrary<'b>> MaybeArbitrary<'a> for T {} diff --git a/masp_primitives/src/merkle_tree.rs b/masp_primitives/src/merkle_tree.rs index 87e7fd25..75cd3d8f 100644 --- a/masp_primitives/src/merkle_tree.rs +++ b/masp_primitives/src/merkle_tree.rs @@ -716,6 +716,7 @@ impl BorshDeserialize for IncrementalWitness { } /// A path from a position in a particular commitment tree to the root of that tree. +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq, Eq)] pub struct MerklePath { pub auth_path: Vec<(Node, bool)>, diff --git a/masp_primitives/src/sapling.rs b/masp_primitives/src/sapling.rs index 53c4fce8..8f05a858 100644 --- a/masp_primitives/src/sapling.rs +++ b/masp_primitives/src/sapling.rs @@ -85,6 +85,7 @@ pub fn merkle_hash(depth: usize, lhs: &[u8; 32], rhs: &[u8; 32]) -> [u8; 32] { } /// A node within the Sapling commitment tree. +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Copy, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Default)] pub struct Node { repr: [u8; 32], @@ -261,6 +262,7 @@ impl BorshSchema for ProofGenerationKey { /// A key used to derive the nullifier for a Sapling note. #[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct NullifierDerivingKey(pub jubjub::SubgroupPoint); impl BorshSerialize for NullifierDerivingKey { @@ -301,6 +303,7 @@ impl BorshSchema for NullifierDerivingKey { } #[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct ViewingKey { pub ak: jubjub::SubgroupPoint, pub nk: NullifierDerivingKey, @@ -450,6 +453,7 @@ impl SaplingIvk { } } +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive( Copy, Clone, Debug, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize, BorshSchema, )] @@ -624,6 +628,7 @@ impl BorshSchema for PaymentAddress { /// Before ZIP 212, the note commitment trapdoor `rcm` must be a scalar value. /// After ZIP 212, the note randomness `rseed` is a 32-byte sequence, used to derive /// both the note commitment trapdoor `rcm` and the ephemeral private key `esk`. +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Copy, Clone, Debug)] pub enum Rseed { BeforeZip212(jubjub::Fr), @@ -686,6 +691,7 @@ impl BorshDeserialize for Rseed { } /// Typesafe wrapper for nullifier values. +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive( Copy, Clone, @@ -743,6 +749,7 @@ impl From for u64 { } } +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, Copy)] pub struct Note { /// The asset type that the note represents diff --git a/masp_primitives/src/sapling/keys.rs b/masp_primitives/src/sapling/keys.rs index 0cfe59d1..27cf04ab 100644 --- a/masp_primitives/src/sapling/keys.rs +++ b/masp_primitives/src/sapling/keys.rs @@ -38,12 +38,14 @@ pub enum DecodingError { } /// An outgoing viewing key +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive( Clone, Copy, Debug, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize, BorshSchema, )] pub struct OutgoingViewingKey(pub [u8; 32]); /// A Sapling expanded spending key +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, PartialEq, Eq, Copy)] pub struct ExpandedSpendingKey { pub ask: jubjub::Fr, @@ -128,6 +130,7 @@ impl ExpandedSpendingKey { /// A Sapling key that provides the capability to view incoming and outgoing transactions. #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct FullViewingKey { pub vk: ViewingKey, pub ovk: OutgoingViewingKey, diff --git a/masp_primitives/src/sapling/redjubjub.rs b/masp_primitives/src/sapling/redjubjub.rs index 856a12c7..a528b687 100644 --- a/masp_primitives/src/sapling/redjubjub.rs +++ b/masp_primitives/src/sapling/redjubjub.rs @@ -38,6 +38,7 @@ fn h_star(a: &[u8], b: &[u8]) -> jubjub::Fr { hash_to_scalar(b"MASP__RedJubjubH", a, b) } +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Copy, Clone, Debug, PartialOrd, PartialEq, Ord, Eq, Hash, BorshSchema)] pub struct Signature { rbar: [u8; 32], @@ -46,6 +47,7 @@ pub struct Signature { pub struct PrivateKey(pub jubjub::Fr); +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Debug, Clone, PartialEq, Eq, Copy)] pub struct PublicKey(pub ExtendedPoint); diff --git a/masp_primitives/src/transaction.rs b/masp_primitives/src/transaction.rs index a4cd3f70..2224fae4 100644 --- a/masp_primitives/src/transaction.rs +++ b/masp_primitives/src/transaction.rs @@ -34,11 +34,14 @@ use self::{ }, txid::{to_txid, BlockTxCommitmentDigester, TxIdDigester}, }; +use crate::MaybeArbitrary; use borsh::schema::add_definition; use borsh::schema::Fields; use borsh::schema::{Declaration, Definition}; +use std::marker::PhantomData; use std::ops::RangeInclusive; +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive( Clone, Copy, @@ -60,6 +63,7 @@ pub type GrothProofBytes = [u8; GROTH_PROOF_SIZE]; const MASPV5_TX_VERSION: u32 = 2; const MASPV5_VERSION_GROUP_ID: u32 = 0x26A7270A; +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive( Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize, BorshSchema, )] @@ -114,6 +118,7 @@ impl TxId { /// transaction fields. Note that this is not dependent on epoch, only on transaction encoding. /// For example, if a particular epoch defines a new transaction version but also allows the /// previous version, then only the new version would be added to this enum. +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum TxVersion { MASPv5, @@ -192,11 +197,21 @@ impl BorshSchema for TxVersion { /// Authorization state for a bundle of transaction data. pub trait Authorization { - type TransparentAuth: transparent::Authorization + PartialEq + BorshDeserialize + BorshSerialize; - type SaplingAuth: sapling::Authorization + PartialEq + BorshDeserialize + BorshSerialize; + type TransparentAuth: transparent::Authorization + + PartialEq + + BorshDeserialize + + BorshSerialize + + for<'a> MaybeArbitrary<'a>; + + type SaplingAuth: sapling::Authorization + + PartialEq + + BorshDeserialize + + BorshSerialize + + for<'a> MaybeArbitrary<'a>; } #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct Unproven; +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Debug, PartialEq, Eq, Clone)] pub struct Authorized; @@ -205,14 +220,17 @@ impl Authorization for Authorized { type SaplingAuth = sapling::Authorized; } -pub struct Unauthorized; +pub struct Unauthorized(PhantomData); -impl Authorization for Unauthorized { +impl MaybeArbitrary<'a>> + Authorization for Unauthorized +{ type TransparentAuth = transparent::builder::Unauthorized; - type SaplingAuth = sapling::builder::Unauthorized; + type SaplingAuth = sapling::builder::Unauthorized; } /// A MASP transaction. +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Debug, Clone)] pub struct Transaction { txid: TxId, @@ -233,6 +251,7 @@ impl PartialEq for Transaction { } } +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Debug, PartialEq, Clone)] pub struct TransactionData { version: TxVersion, diff --git a/masp_primitives/src/transaction/builder.rs b/masp_primitives/src/transaction/builder.rs index 8f2f033c..72749516 100644 --- a/masp_primitives/src/transaction/builder.rs +++ b/masp_primitives/src/transaction/builder.rs @@ -30,7 +30,8 @@ use crate::{ txid::TxIdDigester, Transaction, TransactionData, TransparentAddress, TxVersion, Unauthorized, }, - zip32::ExtendedSpendingKey, + zip32::{ExtendedKey, ExtendedSpendingKey}, + MaybeArbitrary, }; #[cfg(feature = "transparent-inputs")] @@ -168,7 +169,11 @@ impl Builder { } } -impl Builder

{ +impl< + P: consensus::Parameters, + K: ExtendedKey + std::fmt::Debug + Clone + PartialEq + for<'a> MaybeArbitrary<'a>, + > Builder +{ /// Creates a new `Builder` targeted for inclusion in the block with the given height, /// using default values for general transaction fields and the default OS random. /// @@ -181,12 +186,16 @@ impl Builder

{ } } -impl Builder

{ +impl< + P: consensus::Parameters, + K: ExtendedKey + std::fmt::Debug + Clone + PartialEq + for<'a> MaybeArbitrary<'a>, + > Builder +{ /// Common utility function for builder construction. /// /// WARNING: THIS MUST REMAIN PRIVATE AS IT ALLOWS CONSTRUCTION /// OF BUILDERS WITH NON-CryptoRng RNGs - fn new_internal(params: P, target_height: BlockHeight) -> Builder

{ + fn new_internal(params: P, target_height: BlockHeight) -> Builder { Builder { params: params.clone(), target_height, @@ -203,7 +212,7 @@ impl Builder

{ /// paths for previous Sapling notes. pub fn add_sapling_spend( &mut self, - extsk: ExtendedSpendingKey, + extsk: K, diversifier: Diversifier, note: Note, merkle_path: MerklePath, @@ -347,7 +356,7 @@ impl Builder

{ ) .map_err(Error::SaplingBuild)?; - let unauthed_tx: TransactionData = TransactionData { + let unauthed_tx: TransactionData> = TransactionData { version, consensus_branch_id: BranchId::for_height(&self.params, self.target_height), lock_time: 0, diff --git a/masp_primitives/src/transaction/components/amount.rs b/masp_primitives/src/transaction/components/amount.rs index 3ed1141d..f27e4fe4 100644 --- a/masp_primitives/src/transaction/components/amount.rs +++ b/masp_primitives/src/transaction/components/amount.rs @@ -50,6 +50,7 @@ pub type I128Sum = ValueSum; pub type U128Sum = ValueSum; +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Default, Debug, PartialEq, Eq, Hash)] pub struct ValueSum< Unit: Hash + Ord + BorshSerialize + BorshDeserialize, diff --git a/masp_primitives/src/transaction/components/sapling.rs b/masp_primitives/src/transaction/components/sapling.rs index 61d87c2c..6fcc7bf8 100644 --- a/masp_primitives/src/transaction/components/sapling.rs +++ b/masp_primitives/src/transaction/components/sapling.rs @@ -25,6 +25,7 @@ use crate::{ redjubjub::{self, PublicKey, Signature}, Nullifier, }, + MaybeArbitrary, }; use super::{amount::I128Sum, GROTH_PROOF_SIZE}; @@ -35,8 +36,8 @@ pub mod builder; pub mod fees; pub trait Authorization: Debug { - type Proof: Clone + Debug + PartialEq + Hash; - type AuthSig: Clone + Debug + PartialEq; + type Proof: Clone + Debug + PartialEq + Hash + for<'a> MaybeArbitrary<'a>; + type AuthSig: Clone + Debug + PartialEq + for<'a> MaybeArbitrary<'a>; } #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -47,6 +48,7 @@ impl Authorization for Unproven { type AuthSig = (); } +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Debug, Copy, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema)] pub struct Authorized { pub binding_sig: redjubjub::Signature, @@ -58,8 +60,8 @@ impl Authorization for Authorized { } pub trait MapAuth { - fn map_proof(&self, p: A::Proof) -> B::Proof; - fn map_auth_sig(&self, s: A::AuthSig) -> B::AuthSig; + fn map_proof(&self, p: A::Proof, pos: usize) -> B::Proof; + fn map_auth_sig(&self, s: A::AuthSig, pos: usize) -> B::AuthSig; fn map_authorization(&self, a: A) -> B; } @@ -73,6 +75,7 @@ impl MapAuth for () { fn map_proof( &self, p: ::Proof, + _pos: usize, ) -> ::Proof { p } @@ -80,6 +83,7 @@ impl MapAuth for () { fn map_auth_sig( &self, s: ::AuthSig, + _pos: usize, ) -> ::AuthSig { s } @@ -89,6 +93,7 @@ impl MapAuth for () { } } +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct Bundle { pub shielded_spends: Vec>, @@ -110,34 +115,37 @@ impl Bundle shielded_spends: self .shielded_spends .into_iter() - .map(|d| SpendDescription { + .enumerate() + .map(|(pos, d)| SpendDescription { cv: d.cv, anchor: d.anchor, nullifier: d.nullifier, rk: d.rk, - zkproof: f.map_proof(d.zkproof), - spend_auth_sig: f.map_auth_sig(d.spend_auth_sig), + zkproof: f.map_proof(d.zkproof, pos), + spend_auth_sig: f.map_auth_sig(d.spend_auth_sig, pos), }) .collect(), shielded_converts: self .shielded_converts .into_iter() - .map(|c| ConvertDescription { + .enumerate() + .map(|(pos, c)| ConvertDescription { cv: c.cv, anchor: c.anchor, - zkproof: f.map_proof(c.zkproof), + zkproof: f.map_proof(c.zkproof, pos), }) .collect(), shielded_outputs: self .shielded_outputs .into_iter() - .map(|o| OutputDescription { + .enumerate() + .map(|(pos, o)| OutputDescription { cv: o.cv, cmu: o.cmu, ephemeral_key: o.ephemeral_key, enc_ciphertext: o.enc_ciphertext, out_ciphertext: o.out_ciphertext, - zkproof: f.map_proof(o.zkproof), + zkproof: f.map_proof(o.zkproof, pos), }) .collect(), value_balance: self.value_balance, @@ -146,6 +154,7 @@ impl Bundle } } +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, PartialEq, Eq)] pub struct SpendDescription { pub cv: jubjub::ExtendedPoint, @@ -299,6 +308,7 @@ impl BorshSchema for SpendDescriptionV5 { } } +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, PartialEq, Eq)] pub struct OutputDescription { pub cv: jubjub::ExtendedPoint, @@ -507,6 +517,8 @@ impl Hash for OutputDescription { self.zkproof.hash(state); } } + +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, PartialEq, Eq)] pub struct ConvertDescription { pub cv: jubjub::ExtendedPoint, diff --git a/masp_primitives/src/transaction/components/sapling/builder.rs b/masp_primitives/src/transaction/components/sapling/builder.rs index 55dfc50a..0f64db30 100644 --- a/masp_primitives/src/transaction/components/sapling/builder.rs +++ b/masp_primitives/src/transaction/components/sapling/builder.rs @@ -8,6 +8,7 @@ use ff::PrimeField; use group::GroupEncoding; use rand::{seq::SliceRandom, CryptoRng, RngCore}; +use crate::MaybeArbitrary; use crate::{ asset_type::AssetType, consensus::{self, BlockHeight}, @@ -33,7 +34,7 @@ use crate::{ }, }, }, - zip32::ExtendedSpendingKey, + zip32::{ExtendedKey, ExtendedSpendingKey}, }; use borsh::schema::add_definition; use borsh::schema::Declaration; @@ -41,7 +42,9 @@ use borsh::schema::Definition; use borsh::schema::Fields; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use std::collections::BTreeMap; +use std::fmt::Debug; use std::io::Write; +use std::marker::PhantomData; /// A subset of the parameters necessary to build a transaction pub trait BuildParams { @@ -49,10 +52,6 @@ pub trait BuildParams { fn spend_rcv(&mut self, i: usize) -> jubjub::Fr; /// Get the spend authorization randomizer for the ith spend description fn spend_alpha(&mut self, i: usize) -> jubjub::Fr; - /// Get the authorization signature for the ith spend description - fn auth_sig(&mut self, i: usize) -> Option; - /// The proof generation key for the ith spend description - fn proof_generation_key(&mut self, i: usize) -> Option; /// Get the commitment value randomness for the ith convert description fn convert_rcv(&mut self, i: usize) -> jubjub::Fr; /// Get the commitment value randomness for the ith output description @@ -63,17 +62,35 @@ pub trait BuildParams { fn output_rseed(&mut self, i: usize) -> [u8; 32]; } +// Allow build parameters to be boxed +impl BuildParams for Box { + fn spend_rcv(&mut self, i: usize) -> jubjub::Fr { + (**self).spend_rcv(i) + } + fn spend_alpha(&mut self, i: usize) -> jubjub::Fr { + (**self).spend_alpha(i) + } + fn convert_rcv(&mut self, i: usize) -> jubjub::Fr { + (**self).convert_rcv(i) + } + fn output_rcv(&mut self, i: usize) -> jubjub::Fr { + (**self).output_rcv(i) + } + fn output_rcm(&mut self, i: usize) -> jubjub::Fr { + (**self).output_rcm(i) + } + fn output_rseed(&mut self, i: usize) -> [u8; 32] { + (**self).output_rseed(i) + } +} + /// Parameters that go into constructing a spend description -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct SpendBuildParams { /// The commitment value randomness - rcv: jubjub::Fr, + pub rcv: jubjub::Fr, /// The spend authorization randomizer - alpha: jubjub::Fr, - /// The authorization signature - auth_sig: Option, - /// The proof generation key - proof_generation_key: Option, + pub alpha: jubjub::Fr, } impl BorshSerialize for SpendBuildParams { @@ -82,10 +99,6 @@ impl BorshSerialize for SpendBuildParams { writer.write_all(&self.rcv.to_repr())?; // Write spend authorization randomizer writer.write_all(&self.alpha.to_repr())?; - // Write the authorization signature - self.auth_sig.serialize(writer)?; - // Write the proof generation key - self.proof_generation_key.serialize(writer)?; Ok(()) } } @@ -102,17 +115,8 @@ impl BorshDeserialize for SpendBuildParams { let alpha = Option::from(jubjub::Fr::from_bytes(&alpha_bytes)).ok_or_else(|| { std::io::Error::new(std::io::ErrorKind::InvalidData, "alpha not in field") })?; - // Read the authorization signature - let auth_sig = Option::::deserialize_reader(reader)?; - // Read the proof generation key - let proof_generation_key = Option::::deserialize_reader(reader)?; // Finally, aggregate the spend parameters - Ok(SpendBuildParams { - rcv, - alpha, - auth_sig, - proof_generation_key, - }) + Ok(SpendBuildParams { rcv, alpha }) } } @@ -141,10 +145,10 @@ impl BorshSchema for SpendBuildParams { } /// Parameters that go into constructing an output description -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Default)] pub struct ConvertBuildParams { /// The commitment value randomness - rcv: jubjub::Fr, + pub rcv: jubjub::Fr, } impl BorshSerialize for ConvertBuildParams { @@ -182,14 +186,14 @@ impl BorshSchema for ConvertBuildParams { } /// Parameters that go into constructing an output description -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Default)] pub struct OutputBuildParams { /// The commitment value randomness - rcv: jubjub::Fr, + pub rcv: jubjub::Fr, /// The note rcm value - rcm: jubjub::Fr, + pub rcm: jubjub::Fr, /// The note's random seed - rseed: [u8; 32], + pub rseed: [u8; 32], } impl BorshSerialize for OutputBuildParams { @@ -261,14 +265,6 @@ impl BuildParams for StoredBuildParams { self.spend_params[i].alpha } - fn auth_sig(&mut self, i: usize) -> Option { - self.spend_params[i].auth_sig - } - - fn proof_generation_key(&mut self, i: usize) -> Option { - self.spend_params[i].proof_generation_key.clone() - } - fn convert_rcv(&mut self, i: usize) -> jubjub::Fr { self.convert_params[i].rcv } @@ -334,8 +330,6 @@ impl RngBuildParams { self.spends.entry(i).or_insert_with(|| SpendBuildParams { rcv: jubjub::Fr::random(&mut self.rng), alpha: jubjub::Fr::random(&mut self.rng), - auth_sig: None, - proof_generation_key: None, }) } @@ -371,14 +365,6 @@ impl BuildParams for RngBuildParams { self.spend_params(i).alpha } - fn auth_sig(&mut self, i: usize) -> Option { - self.spend_params(i).auth_sig - } - - fn proof_generation_key(&mut self, i: usize) -> Option { - self.spend_params(i).proof_generation_key.clone() - } - fn convert_rcv(&mut self, i: usize) -> jubjub::Fr { self.convert_params(i).rcv } @@ -425,6 +411,7 @@ impl fmt::Display for Error { } } +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Debug, Clone, PartialEq)] pub struct SpendDescriptionInfo { extsk: Key, @@ -599,6 +586,7 @@ impl fees::OutputView for SaplingOutputInfo { } /// Metadata about a transaction created by a [`SaplingBuilder`]. +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema)] pub struct SaplingMetadata { spend_indices: Vec, @@ -745,20 +733,24 @@ impl BorshDeserialize for SaplingBui } } +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] -pub struct Unauthorized { +pub struct Unauthorized { tx_metadata: SaplingMetadata, + phantom: PhantomData, } -impl std::fmt::Debug for Unauthorized { +impl std::fmt::Debug for Unauthorized { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { write!(f, "Unauthorized") } } -impl Authorization for Unauthorized { +impl MaybeArbitrary<'a>> Authorization + for Unauthorized +{ type Proof = GrothProofBytes; - type AuthSig = SpendDescriptionInfo; + type AuthSig = SpendDescriptionInfo; } impl SaplingBuilder { @@ -795,14 +787,18 @@ impl SaplingBuilder { } } -impl SaplingBuilder

{ +impl< + P: consensus::Parameters, + K: ExtendedKey + Debug + Clone + PartialEq + for<'a> MaybeArbitrary<'a>, + > SaplingBuilder +{ /// Adds a Sapling note to be spent in this transaction. /// /// Returns an error if the given Merkle path does not have the same anchor as the /// paths for previous Sapling notes. pub fn add_spend( &mut self, - extsk: ExtendedSpendingKey, + extsk: K, diversifier: Diversifier, note: Note, merkle_path: MerklePath, @@ -891,7 +887,7 @@ impl SaplingBuilder

{ bparams: &mut impl BuildParams, target_height: BlockHeight, progress_notifier: Option<&Sender>, - ) -> Result>, Error> { + ) -> Result>>, Error> { // Record initial positions of spends and outputs let value_balance = self.value_balance(); let params = self.params; @@ -930,7 +926,8 @@ impl SaplingBuilder

{ let mut progress = 0u32; // Create Sapling SpendDescriptions - let shielded_spends: Vec> = if !indexed_spends.is_empty() { + let shielded_spends: Vec>> = if !indexed_spends.is_empty() + { let anchor = self .spend_anchor .expect("MASP Spend anchor must be set if MASP spends are present."); @@ -939,7 +936,10 @@ impl SaplingBuilder

{ .into_iter() .enumerate() .map(|(i, (pos, spend))| { - let proof_generation_key = spend.extsk.expsk.proof_generation_key(); + let proof_generation_key = spend + .extsk + .to_proof_generation_key() + .expect("Proof generation key must be known for each MASP spend."); let nullifier = spend.note.nf( &proof_generation_key.to_viewing_key().nk, @@ -1141,7 +1141,10 @@ impl SaplingBuilder

{ shielded_converts, shielded_outputs, value_balance, - authorization: Unauthorized { tx_metadata }, + authorization: Unauthorized { + tx_metadata, + phantom: PhantomData, + }, }) }; @@ -1149,7 +1152,9 @@ impl SaplingBuilder

{ } } -impl SpendDescription { +impl MaybeArbitrary<'a>> + SpendDescription> +{ pub fn apply_signature(&self, spend_auth_sig: Signature) -> SpendDescription { SpendDescription { cv: self.cv, @@ -1162,7 +1167,9 @@ impl SpendDescription { } } -impl Bundle { +impl MaybeArbitrary<'a>> + Bundle> +{ pub fn apply_signatures( self, prover: &Pr, @@ -1183,7 +1190,7 @@ impl Bundle { .enumerate() .map(|(i, spend)| { spend.apply_signature(spend_sig_internal( - PrivateKey(spend.spend_auth_sig.extsk.expsk.ask), + PrivateKey(spend.spend_auth_sig.extsk.to_spending_key().expect("Spend authorization key must be known for each MASP spend.").expsk.ask), bparams.spend_alpha(i), sighash_bytes, rng, diff --git a/masp_primitives/src/transaction/components/transparent.rs b/masp_primitives/src/transaction/components/transparent.rs index ef4758fd..ad83e0c6 100644 --- a/masp_primitives/src/transaction/components/transparent.rs +++ b/masp_primitives/src/transaction/components/transparent.rs @@ -6,6 +6,7 @@ use std::io::{self, Read, Write}; use crate::asset_type::AssetType; use crate::transaction::TransparentAddress; +use crate::MaybeArbitrary; use borsh::schema::add_definition; use borsh::schema::Declaration; use borsh::schema::Definition; @@ -18,9 +19,10 @@ pub mod builder; pub mod fees; pub trait Authorization: fmt::Debug { - type TransparentSig: fmt::Debug + Clone + PartialEq; + type TransparentSig: fmt::Debug + Clone + PartialEq + for<'a> MaybeArbitrary<'a>; } +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Debug, Copy, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub struct Authorized; @@ -29,10 +31,31 @@ impl Authorization for Authorized { } pub trait MapAuth { - fn map_script_sig(&self, s: A::TransparentSig) -> B::TransparentSig; + fn map_script_sig(&self, s: A::TransparentSig, pos: usize) -> B::TransparentSig; fn map_authorization(&self, s: A) -> B; } +/// The identity map. +/// +/// This can be used with [`TransactionData::map_authorization`] when you want to map the +/// authorization of a subset of the transaction's bundles. +/// +/// [`TransactionData::map_authorization`]: crate::transaction::TransactionData::map_authorization +impl MapAuth for () { + fn map_script_sig( + &self, + s: ::TransparentSig, + _pos: usize, + ) -> ::TransparentSig { + s + } + + fn map_authorization(&self, a: Authorized) -> Authorized { + a + } +} + +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Debug, Clone, PartialEq)] pub struct Bundle { pub vin: Vec>, @@ -46,10 +69,11 @@ impl Bundle { vin: self .vin .into_iter() - .map(|txin| TxIn { + .enumerate() + .map(|(pos, txin)| TxIn { asset_type: txin.asset_type, address: txin.address, - transparent_sig: f.map_script_sig(txin.transparent_sig), + transparent_sig: f.map_script_sig(txin.transparent_sig, pos), value: txin.value, }) .collect(), @@ -85,6 +109,7 @@ impl Bundle { } #[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct TxIn { pub asset_type: AssetType, pub value: u64, @@ -162,6 +187,7 @@ impl BorshSchema for TxIn { } #[derive(Clone, Debug, Hash, PartialOrd, PartialEq, Ord, Eq)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct TxOut { pub asset_type: AssetType, pub value: u64, diff --git a/masp_primitives/src/transaction/components/transparent/builder.rs b/masp_primitives/src/transaction/components/transparent/builder.rs index 5cb1fa35..fbbddeb9 100644 --- a/masp_primitives/src/transaction/components/transparent/builder.rs +++ b/masp_primitives/src/transaction/components/transparent/builder.rs @@ -45,6 +45,7 @@ impl fees::InputView for InvalidTransparentInput { } } +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[cfg(feature = "transparent-inputs")] #[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema)] struct TransparentInputInfo { @@ -65,6 +66,7 @@ pub struct TransparentBuilder { vout: Vec, } +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub struct Unauthorized { #[cfg(feature = "transparent-inputs")] diff --git a/masp_primitives/src/zip32.rs b/masp_primitives/src/zip32.rs index c21e0961..cb95f533 100644 --- a/masp_primitives/src/zip32.rs +++ b/masp_primitives/src/zip32.rs @@ -19,8 +19,8 @@ use borsh::{BorshDeserialize, BorshSerialize}; #[deprecated(note = "Please use the types exported from the `zip32::sapling` module instead.")] pub use sapling::{ sapling_address, sapling_default_address, sapling_derive_internal_fvk, sapling_find_address, - DiversifiableFullViewingKey, ExtendedFullViewingKey, ExtendedSpendingKey, - ZIP32_SAPLING_FVFP_PERSONALIZATION, ZIP32_SAPLING_INT_PERSONALIZATION, + DiversifiableFullViewingKey, ExtendedFullViewingKey, ExtendedKey, ExtendedSpendingKey, + PseudoExtendedKey, ZIP32_SAPLING_FVFP_PERSONALIZATION, ZIP32_SAPLING_INT_PERSONALIZATION, ZIP32_SAPLING_MASTER_PERSONALIZATION, }; use std::io::{Read, Write}; @@ -28,6 +28,7 @@ use std::io::{Read, Write}; // ZIP 32 structures /// A child index for a derived key +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum ChildIndex { NonHardened(u32), @@ -81,6 +82,7 @@ impl BorshSchema for ChildIndex { } /// A BIP-32 chain code +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive( Clone, Copy, Debug, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize, BorshSchema, )] diff --git a/masp_primitives/src/zip32/sapling.rs b/masp_primitives/src/zip32/sapling.rs index 83b122e7..025dc172 100644 --- a/masp_primitives/src/zip32/sapling.rs +++ b/masp_primitives/src/zip32/sapling.rs @@ -12,7 +12,7 @@ use crate::{ constants::{PROOF_GENERATION_KEY_GENERATOR, SPENDING_KEY_GENERATOR}, keys::{prf_expand, prf_expand_vec}, sapling::keys::{DecodingError, ExpandedSpendingKey, FullViewingKey, OutgoingViewingKey}, - sapling::SaplingIvk, + sapling::{redjubjub::PrivateKey, ProofGenerationKey, SaplingIvk}, }; use aes::Aes256; use blake2b_simd::Params as Blake2bParams; @@ -23,11 +23,13 @@ use borsh::schema::Fields; use borsh::BorshSchema; use borsh::{BorshDeserialize, BorshSerialize}; use byteorder::{ByteOrder, LittleEndian, ReadBytesExt, WriteBytesExt}; +use ff::PrimeField; use fpe::ff1::{BinaryNumeralString, FF1}; use std::collections::BTreeMap; use std::{ cmp::Ordering, convert::TryInto, + hash::{Hash, Hasher}, io::{self, Error, ErrorKind, Read, Write}, ops::AddAssign, str::FromStr, @@ -143,6 +145,7 @@ impl FvkFingerprint { } /// A Sapling full viewing key tag +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive( Clone, Copy, Debug, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize, BorshSchema, )] @@ -159,6 +162,7 @@ impl FvkTag { } /// A key used to derive diversifiers for a particular child key +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive( Clone, Copy, Debug, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize, BorshSchema, )] @@ -245,6 +249,7 @@ impl DiversifierKey { } /// A Sapling extended spending key +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Eq, Hash, Copy)] pub struct ExtendedSpendingKey { depth: u8, @@ -493,6 +498,7 @@ impl ExtendedSpendingKey { // A Sapling extended full viewing key #[derive(Clone, Eq, Hash, Copy)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct ExtendedFullViewingKey { depth: u8, parent_fvk_tag: FvkTag, @@ -916,6 +922,240 @@ impl Ord for ExtendedSpendingKey { } } +/// Represents the collection of keys that comprise extended spending keys, +/// extended full viewing keys, and proof generation keys. I.e. ask, ak, nsk, +/// nk, ovk, dk, and chain_code. Mathematical relations between these keys are +/// not assumed; i.e. it's not necessarily the case that +/// nk = PROOF_GENERATION_KEY_GENERATOR * nsk or +/// ak = PROOF_GENERATION_KEY_GENERATOR * ask. +pub trait ExtendedKey { + /// Group this collection of keys into an extended full viewing key + fn to_viewing_key(&self) -> ExtendedFullViewingKey; + + /// Group this collection of keys into a proof generation key. Use + /// to_viewing_key when the key nk is required since there's no mathematical + /// relation between nk and nsk in this collection. + fn to_proof_generation_key(&self) -> Option; + + /// Group this collection of keys into an extended spending key. Use + /// to_viewing_key when the keys nk and ak are required since there's no + /// mathematical relation between nk and nsk nor ak and ask in this + /// collection. + fn to_spending_key(&self) -> Option; +} + +/// Represent an extended full viewing key as a collection of keys. +impl ExtendedKey for ExtendedFullViewingKey { + /// Return this key + fn to_viewing_key(&self) -> ExtendedFullViewingKey { + *self + } + + /// Return None since there is insufficient data to construct proof + /// generation key + fn to_proof_generation_key(&self) -> Option { + None + } + + /// Return None since there is insufficient data to construct spending key + #[allow(deprecated)] + fn to_spending_key(&self) -> Option { + None + } +} + +/// Represents an extended spending key as a collection of keys. +impl ExtendedKey for ExtendedSpendingKey { + /// Returns the Sapling derivation of an extended full viewing key from this + /// key + fn to_viewing_key(&self) -> ExtendedFullViewingKey { + self.into() + } + + /// Returns the Sapling derivation of a proof generation key from this key + fn to_proof_generation_key(&self) -> Option { + Some(self.expsk.proof_generation_key()) + } + + /// Returns this key + #[allow(deprecated)] + fn to_spending_key(&self) -> Option { + Some(*self) + } +} + +/// An extended full viewing key bundled with partial authorizations +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[derive(Clone, PartialEq, Eq, Copy, Debug)] +pub struct PseudoExtendedKey { + /// Viewing key for which this key provides authorizations + xfvk: ExtendedFullViewingKey, + /// Spend authorizing key + ask: Option, + /// Proof authorizing key + nsk: Option, +} + +impl Hash for PseudoExtendedKey { + fn hash(&self, state: &mut H) { + self.ask.map(|ask| ask.to_bytes()).hash(state); + self.nsk.map(|nsk| nsk.to_bytes()).hash(state); + self.xfvk.hash(state); + } +} + +impl PseudoExtendedKey { + /// Augment this spending key with proof generation data. Fails if the proof + /// generation key is inconsistent with this key. + pub fn augment_proof_generation_key(&mut self, pgk: ProofGenerationKey) -> Result<(), ()> { + let nk = NullifierDerivingKey(PROOF_GENERATION_KEY_GENERATOR * pgk.nsk); + if nk == self.xfvk.fvk.vk.nk && pgk.ak == self.xfvk.fvk.vk.ak { + self.nsk = Some(pgk.nsk); + Ok(()) + } else { + Err(()) + } + } + + /// Augment this this extended key with spend authorization data. Fails if + /// spend authorizing key is inconsistent with this key. + pub fn augment_spend_authorizing_key(&mut self, ask: PrivateKey) -> Result<(), ()> { + let ak = SPENDING_KEY_GENERATOR * ask.0; + if ak == self.xfvk.fvk.vk.ak { + self.ask = Some(ask.0); + Ok(()) + } else { + Err(()) + } + } + + /// Augment this this extended key with spend authorization data without + /// applying consistency checks + pub fn augment_spend_authorizing_key_unchecked(&mut self, ask: PrivateKey) { + self.ask = Some(ask.0); + } +} + +impl ExtendedKey for PseudoExtendedKey { + /// Returns the extended full viewing key contained in this object + fn to_viewing_key(&self) -> ExtendedFullViewingKey { + self.xfvk + } + + /// Bundle this object into a proof generation key if a proof authorization + /// key has been augmented to this object. + fn to_proof_generation_key(&self) -> Option { + self.nsk.map(|nsk| ProofGenerationKey { + nsk, + ak: self.xfvk.fvk.vk.ak, + }) + } + + /// Bundle this object into an extended spending key if a spend + /// authorization key has been augmented to this object. + #[allow(deprecated)] + fn to_spending_key(&self) -> Option { + self.ask + .zip(self.nsk) + .map(|(ask, nsk)| ExtendedSpendingKey { + depth: self.xfvk.depth, + parent_fvk_tag: self.xfvk.parent_fvk_tag, + child_index: self.xfvk.child_index, + chain_code: self.xfvk.chain_code, + dk: self.xfvk.dk, + expsk: ExpandedSpendingKey { + ask, + nsk, + ovk: self.xfvk.fvk.ovk, + }, + }) + } +} + +impl From for PseudoExtendedKey { + /// Construct a pseudo extended spending key from an extended spending key + #[allow(deprecated)] + fn from(xsk: ExtendedSpendingKey) -> Self { + Self { + xfvk: xsk.to_extended_full_viewing_key(), + ask: Some(xsk.expsk.ask), + nsk: Some(xsk.expsk.nsk), + } + } +} + +impl From for PseudoExtendedKey { + /// Construct a pseudo extended spending key from an extended full viewing key + #[allow(deprecated)] + fn from(xfvk: ExtendedFullViewingKey) -> Self { + Self { + ask: None, + nsk: None, + xfvk, + } + } +} + +impl BorshDeserialize for PseudoExtendedKey { + fn deserialize_reader(reader: &mut R) -> io::Result { + let xfvk = ExtendedFullViewingKey::deserialize_reader(reader)?; + let ask = Option::<[u8; 32]>::deserialize_reader(reader)? + .map(|x| { + Option::from(jubjub::Fr::from_repr(x)) + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "ask not in field")) + }) + .transpose()?; + let nsk = Option::<[u8; 32]>::deserialize_reader(reader)? + .map(|x| { + Option::from(jubjub::Fr::from_repr(x)) + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "nsk not in field")) + }) + .transpose()?; + Ok(Self { xfvk, ask, nsk }) + } +} + +impl BorshSerialize for PseudoExtendedKey { + fn serialize(&self, writer: &mut W) -> io::Result<()> { + self.xfvk.serialize(writer)?; + self.ask.map(|x| x.to_bytes()).serialize(writer)?; + self.nsk.map(|x| x.to_bytes()).serialize(writer) + } +} + +impl BorshSchema for PseudoExtendedKey { + fn add_definitions_recursively(definitions: &mut BTreeMap) { + let definition = Definition::Struct { + fields: Fields::NamedFields(vec![ + ("xfvk".into(), ExtendedFullViewingKey::declaration()), + ("ask".into(), Option::<[u8; 32]>::declaration()), + ("nsk".into(), Option::<[u8; 32]>::declaration()), + ]), + }; + add_definition(Self::declaration(), definition, definitions); + ExtendedFullViewingKey::add_definitions_recursively(definitions); + Option::<[u8; 32]>::add_definitions_recursively(definitions); + } + + fn declaration() -> Declaration { + "PseudoExtendedKey".into() + } +} + +impl PartialOrd for PseudoExtendedKey { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for PseudoExtendedKey { + fn cmp(&self, other: &Self) -> Ordering { + let a = borsh::to_vec(self).expect("unable to canonicalize PseudoExtendedKey"); + let b = borsh::to_vec(other).expect("unable to canonicalize PseudoExtendedKey"); + a.cmp(&b) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/masp_proofs/Cargo.toml b/masp_proofs/Cargo.toml index deaa55c3..1ac3538f 100644 --- a/masp_proofs/Cargo.toml +++ b/masp_proofs/Cargo.toml @@ -20,9 +20,11 @@ masp_primitives = { version = "1.0.0", path = "../masp_primitives" } # Dependencies exposed in a public API: # (Breaking upgrades to these require a breaking upgrade to this crate.) bellman = { version = "0.14", default-features = false, features = ["groth16"] } -bls12_381 = "0.8" +# branch "tomas/arbitrary" +bls12_381 = { git = "https://github.com/heliaxdev/bls12_381.git", rev = "d3ebe9dd6488fac1923db120a7498079e55dd838" } group = "0.13" -jubjub = "0.10" +# branch "tomas/arbitrary" +jubjub = { git = "https://github.com/heliaxdev/jubjub.git", rev = "a373686962f4e9d0edb3b4716f86ff6bbd9aa86c" } lazy_static = "1" minreq = { version = "2.11.0", features = ["https"], optional = true } rand_core = "0.6"