diff --git a/masp_primitives/src/sapling.rs b/masp_primitives/src/sapling.rs index e004c19b..0cc757b9 100644 --- a/masp_primitives/src/sapling.rs +++ b/masp_primitives/src/sapling.rs @@ -696,7 +696,7 @@ impl From for u64 { } #[derive(Clone, Debug, Copy)] -pub struct Note { +pub struct Note { /// The asset type that the note represents pub asset_type: AssetType, /// The value of the note @@ -706,7 +706,7 @@ pub struct Note { /// The public key of the address, g_d^ivk pub pk_d: jubjub::SubgroupPoint, /// rseed - pub rseed: Rseed, + pub rseed: R, } impl PartialEq for Note { @@ -826,7 +826,7 @@ impl Note { } } -impl BorshSchema for Note { +impl BorshSchema for Note { fn add_definitions_recursively(definitions: &mut BTreeMap) { let definition = Definition::Struct { fields: Fields::NamedFields(vec![ @@ -834,22 +834,22 @@ impl BorshSchema for Note { ("value".into(), u64::declaration()), ("g_d".into(), <[u8; 32]>::declaration()), ("pk_d".into(), <[u8; 32]>::declaration()), - ("rseed".into(), Rseed::declaration()), + ("rseed".into(), T::declaration()), ]), }; add_definition(Self::declaration(), definition, definitions); AssetType::add_definitions_recursively(definitions); u64::add_definitions_recursively(definitions); <[u8; 32]>::add_definitions_recursively(definitions); - Rseed::add_definitions_recursively(definitions); + T::add_definitions_recursively(definitions); } fn declaration() -> Declaration { - "Note".into() + format!("Note<{}>", T::declaration()) } } -impl BorshSerialize for Note { +impl BorshSerialize for Note { fn serialize(&self, writer: &mut W) -> io::Result<()> { // Write asset type self.asset_type.serialize(writer)?; @@ -865,7 +865,7 @@ impl BorshSerialize for Note { } } -impl BorshDeserialize for Note { +impl BorshDeserialize for Note { fn deserialize_reader(reader: &mut R) -> io::Result { // Read asset type let asset_type = AssetType::deserialize_reader(reader)?; @@ -880,7 +880,7 @@ impl BorshDeserialize for Note { let pk_d = Option::from(jubjub::SubgroupPoint::from_bytes(&pk_d_bytes)) .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "pk_d not in field"))?; // Read note plaintext lead byte - let rseed = Rseed::deserialize_reader(reader)?; + let rseed = T::deserialize_reader(reader)?; // Finally construct note object Ok(Note { asset_type, diff --git a/masp_primitives/src/sapling/prover.rs b/masp_primitives/src/sapling/prover.rs index 946641d1..e2af4168 100644 --- a/masp_primitives/src/sapling/prover.rs +++ b/masp_primitives/src/sapling/prover.rs @@ -38,12 +38,14 @@ pub trait TxProver { value: u64, anchor: bls12_381::Scalar, merkle_path: MerklePath, + rcv: jubjub::Fr, ) -> Result<([u8; GROTH_PROOF_SIZE], jubjub::ExtendedPoint, PublicKey), ()>; /// Create the value commitment and proof for a MASP OutputDescription, /// while accumulating its value commitment randomness inside the context for later /// use. /// + #[allow(clippy::too_many_arguments)] fn output_proof( &self, ctx: &mut Self::SaplingProvingContext, @@ -52,6 +54,7 @@ pub trait TxProver { rcm: jubjub::Fr, asset_type: AssetType, value: u64, + rcv: jubjub::Fr, ) -> ([u8; GROTH_PROOF_SIZE], jubjub::ExtendedPoint); /// Create the value commitment, and proof for a MASP ConvertDescription, @@ -65,6 +68,7 @@ pub trait TxProver { value: u64, anchor: bls12_381::Scalar, merkle_path: MerklePath, + rcv: jubjub::Fr, ) -> Result<([u8; GROTH_PROOF_SIZE], jubjub::ExtendedPoint), ()>; /// Create the `bindingSig` for a Sapling transaction. All calls to @@ -80,9 +84,6 @@ pub trait TxProver { #[cfg(any(test, feature = "test-dependencies"))] pub mod mock { - use ff::Field; - use rand_core::OsRng; - use crate::{ asset_type::AssetType, constants::SPENDING_KEY_GENERATOR, @@ -115,13 +116,9 @@ pub mod mock { value: u64, _anchor: bls12_381::Scalar, _merkle_path: MerklePath, + rcv: jubjub::Fr, ) -> Result<([u8; GROTH_PROOF_SIZE], jubjub::ExtendedPoint, PublicKey), ()> { - let mut rng = OsRng; - - let cv = asset_type - .value_commitment(value, jubjub::Fr::random(&mut rng)) - .commitment() - .into(); + let cv = asset_type.value_commitment(value, rcv).commitment().into(); let rk = PublicKey(proof_generation_key.ak.into()).randomize(ar, SPENDING_KEY_GENERATOR); @@ -137,13 +134,9 @@ pub mod mock { _rcm: jubjub::Fr, asset_type: AssetType, value: u64, + rcv: jubjub::Fr, ) -> ([u8; GROTH_PROOF_SIZE], jubjub::ExtendedPoint) { - let mut rng = OsRng; - - let cv = asset_type - .value_commitment(value, jubjub::Fr::random(&mut rng)) - .commitment() - .into(); + let cv = asset_type.value_commitment(value, rcv).commitment().into(); ([0u8; GROTH_PROOF_SIZE], cv) } @@ -155,11 +148,10 @@ pub mod mock { value: u64, _anchor: bls12_381::Scalar, _merkle_path: MerklePath, + rcv: jubjub::Fr, ) -> Result<([u8; GROTH_PROOF_SIZE], jubjub::ExtendedPoint), ()> { - let mut rng = OsRng; - let cv = allowed_conversion - .value_commitment(value, jubjub::Fr::random(&mut rng)) + .value_commitment(value, rcv) .commitment() .into(); diff --git a/masp_primitives/src/sapling/util.rs b/masp_primitives/src/sapling/util.rs index 34b64bbe..33dd557e 100644 --- a/masp_primitives/src/sapling/util.rs +++ b/masp_primitives/src/sapling/util.rs @@ -1,10 +1,10 @@ use blake2b_simd::Params; -use ff::Field; -use rand_core::{CryptoRng, RngCore}; use crate::consensus::{self, BlockHeight, NetworkUpgrade}; use super::Rseed; +use ff::Field; +use rand_core::{CryptoRng, RngCore}; pub fn hash_to_scalar(persona: &[u8], a: &[u8], b: &[u8]) -> jubjub::Fr { let mut hasher = Params::new().hash_length(64).personal(persona).to_state(); @@ -19,19 +19,24 @@ pub fn generate_random_rseed( height: BlockHeight, rng: &mut R, ) -> Rseed { - generate_random_rseed_internal(params, height, rng) + if params.is_nu_active(NetworkUpgrade::MASP, height) { + let mut buffer = [0u8; 32]; + rng.fill_bytes(&mut buffer); + Rseed::AfterZip212(buffer) + } else { + Rseed::BeforeZip212(jubjub::Fr::random(rng)) + } } -pub(crate) fn generate_random_rseed_internal( +pub(crate) fn generate_random_rseed_internal( params: &P, height: BlockHeight, - rng: &mut R, + before: jubjub::Fr, + after: [u8; 32], ) -> Rseed { if params.is_nu_active(NetworkUpgrade::MASP, height) { - let mut buffer = [0u8; 32]; - rng.fill_bytes(&mut buffer); - Rseed::AfterZip212(buffer) + Rseed::AfterZip212(after) } else { - Rseed::BeforeZip212(jubjub::Fr::random(rng)) + Rseed::BeforeZip212(before) } } diff --git a/masp_primitives/src/transaction/builder.rs b/masp_primitives/src/transaction/builder.rs index c14e7897..a398ea71 100644 --- a/masp_primitives/src/transaction/builder.rs +++ b/masp_primitives/src/transaction/builder.rs @@ -6,7 +6,7 @@ use std::sync::mpsc::Sender; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; -use rand::{rngs::OsRng, CryptoRng, RngCore}; +use rand::{CryptoRng, RngCore}; use crate::{ asset_type::AssetType, @@ -21,7 +21,7 @@ use crate::{ amount::{BalanceError, I128Sum, U64Sum, ValueSum, MAX_MONEY}, sapling::{ self, - builder::{SaplingBuilder, SaplingMetadata}, + builder::{BuildParams, SaplingBuilder, SaplingMetadata}, }, transparent::{self, builder::TransparentBuilder}, }, @@ -116,9 +116,8 @@ impl Progress { /// Generates a [`Transaction`] from its inputs and outputs. #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] -pub struct Builder> { +pub struct Builder> { params: P, - rng: RN, target_height: BlockHeight, expiry_height: BlockHeight, transparent_builder: TransparentBuilder, @@ -127,7 +126,7 @@ pub struct Builder progress_notifier: Option, } -impl Builder { +impl Builder { /// Returns the network parameters that the builder has been configured for. pub fn params(&self) -> &P { &self.params @@ -169,7 +168,7 @@ impl Builder { } } -impl Builder { +impl 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. /// @@ -178,32 +177,18 @@ impl Builder { /// The expiry height will be set to the given height plus the default transaction /// expiry delta (20 blocks). pub fn new(params: P, target_height: BlockHeight) -> Self { - Builder::new_with_rng(params, target_height, OsRng) + Self::new_internal(params, target_height) } } -impl Builder { - /// Creates a new `Builder` targeted for inclusion in the block with the given height - /// and randomness source, using default values for general transaction fields. - /// - /// # Default values - /// - /// The expiry height will be set to the given height plus the default transaction - /// expiry delta (20 blocks). - pub fn new_with_rng(params: P, target_height: BlockHeight, rng: R) -> Builder { - Self::new_internal(params, rng, target_height) - } -} - -impl Builder { +impl 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, rng: R, target_height: BlockHeight) -> Builder { + fn new_internal(params: P, target_height: BlockHeight) -> Builder

{ Builder { params: params.clone(), - rng, target_height, expiry_height: target_height + DEFAULT_TX_EXPIRY_DELTA, transparent_builder: TransparentBuilder::empty(), @@ -224,7 +209,7 @@ impl Builder { merkle_path: MerklePath, ) -> Result<(), sapling::builder::Error> { self.sapling_builder - .add_spend(&mut self.rng, extsk, diversifier, note, merkle_path) + .add_spend(extsk, diversifier, note, merkle_path) } /// Adds a Sapling note to be spent in this transaction. @@ -254,7 +239,7 @@ impl Builder { return Err(sapling::builder::Error::InvalidAmount); } self.sapling_builder - .add_output(&mut self.rng, ovk, to, asset_type, value, memo) + .add_output(ovk, to, asset_type, value, memo) } /// Adds a transparent coin to be spent in this transaction. @@ -309,6 +294,8 @@ impl Builder { self, prover: &impl TxProver, fee_rule: &FR, + rng: &mut (impl CryptoRng + RngCore), + bparams: &mut impl BuildParams, ) -> Result<(Transaction, SaplingMetadata), Error> { let fee = fee_rule .fee_required( @@ -319,13 +306,15 @@ impl Builder { self.sapling_builder.outputs().len(), ) .map_err(Error::Fee)?; - self.build_internal(prover, fee) + self.build_internal(prover, fee, rng, bparams) } fn build_internal( self, prover: &impl TxProver, fee: U64Sum, + rng: &mut (impl CryptoRng + RngCore), + bparams: &mut impl BuildParams, ) -> Result<(Transaction, SaplingMetadata), Error> { let consensus_branch_id = BranchId::for_height(&self.params, self.target_height); @@ -345,14 +334,14 @@ impl Builder { let transparent_bundle = self.transparent_builder.build(); - let mut rng = self.rng; let mut ctx = prover.new_sapling_proving_context(); let sapling_bundle = self .sapling_builder .build( prover, &mut ctx, - &mut rng, + rng, + bparams, self.target_height, self.progress_notifier.as_ref(), ) @@ -386,7 +375,13 @@ impl Builder { let (sapling_bundle, tx_metadata) = match unauthed_tx .sapling_bundle .map(|b| { - b.apply_signatures(prover, &mut ctx, &mut rng, shielded_sig_commitment.as_ref()) + b.apply_signatures( + prover, + &mut ctx, + rng, + bparams, + shielded_sig_commitment.as_ref(), + ) }) .transpose() .map_err(Error::SaplingBuild)? @@ -410,21 +405,17 @@ impl Builder { } } -pub trait MapBuilder: - sapling::builder::MapBuilder -{ - fn map_rng(&self, s: R1) -> R2; +pub trait MapBuilder: sapling::builder::MapBuilder { fn map_notifier(&self, s: N1) -> N2; } -impl Builder { - pub fn map_builder>( +impl Builder { + pub fn map_builder>( self, f: F, - ) -> Builder { - Builder:: { + ) -> Builder { + Builder:: { params: f.map_params(self.params), - rng: f.map_rng(self.rng), target_height: self.target_height, expiry_height: self.expiry_height, transparent_builder: self.transparent_builder, @@ -436,17 +427,17 @@ impl Builder { #[cfg(any(test, feature = "test-dependencies"))] mod testing { - use rand::RngCore; + use rand::{CryptoRng, RngCore}; use std::convert::Infallible; use super::{Builder, Error, SaplingMetadata}; use crate::{ consensus::{self, BlockHeight}, sapling::prover::mock::MockTxProver, - transaction::{fees::fixed, Transaction}, + transaction::{builder::BuildParams, fees::fixed, Transaction}, }; - impl Builder { + impl Builder

{ /// Creates a new `Builder` targeted for inclusion in the block with the given height /// and randomness source, using default values for general transaction fields. /// @@ -456,12 +447,16 @@ mod testing { /// expiry delta (20 blocks). /// /// WARNING: DO NOT USE IN PRODUCTION - pub fn test_only_new_with_rng(params: P, height: BlockHeight, rng: R) -> Builder { - Self::new_internal(params, rng, height) + pub fn test_only_new_with_rng(params: P, height: BlockHeight) -> Builder

{ + Self::new_internal(params, height) } - pub fn mock_build(self) -> Result<(Transaction, SaplingMetadata), Error> { - self.build(&MockTxProver, &fixed::FeeRule::standard()) + pub fn mock_build( + self, + rng: &mut (impl CryptoRng + RngCore), + bparams: &mut impl BuildParams, + ) -> Result<(Transaction, SaplingMetadata), Error> { + self.build(&MockTxProver, &fixed::FeeRule::standard(), rng, bparams) } } } @@ -558,7 +553,7 @@ mod tests { // Expect a binding signature error, because our inputs aren't valid, but this shows // that a binding signature was attempted assert_eq!( - builder.mock_build(), + builder.mock_build(&mut OsRng, &mut build_s::RngBuildParams::new(OsRng)), Err(Error::SaplingBuild(build_s::Error::BindingSig)) ); } @@ -579,7 +574,7 @@ mod tests { { let builder = Builder::new(TEST_NETWORK, tx_height); assert_eq!( - builder.mock_build(), + builder.mock_build(&mut OsRng, &mut build_s::RngBuildParams::new(OsRng)), Err(Error::InsufficientFunds(I128Sum::from_sum( DEFAULT_FEE.clone() ))) @@ -598,7 +593,7 @@ mod tests { .add_sapling_output(ovk, to, zec(), 50000, MemoBytes::empty()) .unwrap(); assert_eq!( - builder.mock_build(), + builder.mock_build(&mut OsRng, &mut build_s::RngBuildParams::new(OsRng)), Err(Error::InsufficientFunds( I128Sum::from_pair(zec(), 50000).unwrap() + &I128Sum::from_sum(DEFAULT_FEE.clone()) @@ -614,7 +609,7 @@ mod tests { .add_transparent_output(&transparent_address, zec(), 50000) .unwrap(); assert_eq!( - builder.mock_build(), + builder.mock_build(&mut OsRng, &mut build_s::RngBuildParams::new(OsRng)), Err(Error::InsufficientFunds( I128Sum::from_pair(zec(), 50000).unwrap() + &I128Sum::from_sum(DEFAULT_FEE.clone()) @@ -648,7 +643,7 @@ mod tests { .add_transparent_output(&transparent_address, zec(), 20000) .unwrap(); assert_eq!( - builder.mock_build(), + builder.mock_build(&mut OsRng, &mut build_s::RngBuildParams::new(OsRng)), Err(Error::InsufficientFunds( ValueSum::from_pair(zec(), 1).unwrap() )) @@ -683,7 +678,7 @@ mod tests { .add_transparent_output(&transparent_address, zec(), 20000) .unwrap(); assert_eq!( - builder.mock_build(), + builder.mock_build(&mut OsRng, &mut build_s::RngBuildParams::new(OsRng)), Err(Error::SaplingBuild(build_s::Error::BindingSig)) ) } diff --git a/masp_primitives/src/transaction/components/sapling/builder.rs b/masp_primitives/src/transaction/components/sapling/builder.rs index 5ac1d1ae..00f73c85 100644 --- a/masp_primitives/src/transaction/components/sapling/builder.rs +++ b/masp_primitives/src/transaction/components/sapling/builder.rs @@ -5,7 +5,7 @@ use std::sync::mpsc::Sender; use ff::Field; use group::GroupEncoding; -use rand::{seq::SliceRandom, RngCore}; +use rand::{seq::SliceRandom, CryptoRng, RngCore}; use crate::{ asset_type::AssetType, @@ -20,7 +20,7 @@ use crate::{ redjubjub::{PrivateKey, Signature}, spend_sig_internal, util::generate_random_rseed_internal, - Diversifier, Node, Note, PaymentAddress, + Diversifier, Node, Note, PaymentAddress, ProofGenerationKey, Rseed, }, transaction::{ builder::Progress, @@ -42,6 +42,194 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use std::collections::BTreeMap; use std::io::Write; +/// A subset of the parameters necessary to build a transaction +pub trait BuildParams { + /// Get the commitment value randomness for the ith spend description + 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 + fn output_rcv(&mut self, i: usize) -> jubjub::Fr; + /// Get the note RCM for the ith output description + fn output_rcm(&mut self, i: usize) -> jubjub::Fr; + /// Get the random seed for the ith output description + fn output_rseed(&mut self, i: usize) -> [u8; 32]; +} + +/// Parameters that go into constructing a spend description +#[derive(Clone)] +pub struct SpendBuildParams { + /// The commitment value randomness + rcv: jubjub::Fr, + /// The spend authorization randomizer + alpha: jubjub::Fr, + /// The authorization signature + auth_sig: Option, + /// The proof generation key + proof_generation_key: Option, +} + +/// Parameters that go into constructing an output description +#[derive(Clone, Copy)] +pub struct ConvertBuildParams { + /// The commitment value randomness + rcv: jubjub::Fr, +} + +/// Parameters that go into constructing an output description +#[derive(Clone, Copy)] +pub struct OutputBuildParams { + /// The commitment value randomness + rcv: jubjub::Fr, + /// The note rcm value + rcm: jubjub::Fr, + /// The note's random seed + rseed: [u8; 32], +} + +/// Pre-generated random parameters for MASPtTransactions +#[derive(Default, Clone)] +pub struct StoredBuildParams { + /// The parameters required to construct spend descriptions + pub spend_params: Vec, + /// The parameters required to construct convert descriptions + pub convert_params: Vec, + /// The parameters required to construct output descriptions + pub output_params: Vec, +} + +impl BuildParams for StoredBuildParams { + fn spend_rcv(&mut self, i: usize) -> jubjub::Fr { + self.spend_params[i].rcv + } + + fn spend_alpha(&mut self, i: usize) -> jubjub::Fr { + 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 + } + + fn output_rcv(&mut self, i: usize) -> jubjub::Fr { + self.output_params[i].rcv + } + + fn output_rcm(&mut self, i: usize) -> jubjub::Fr { + self.output_params[i].rcm + } + + fn output_rseed(&mut self, i: usize) -> [u8; 32] { + self.output_params[i].rseed + } +} + +/// Lazily generated random parameters for MASP transactions +pub struct RngBuildParams { + /// The RNG used to generate the build parameters + rng: R, + /// The parameters required to construct spend descriptions + spends: BTreeMap, + /// The parameters required to construct convert descriptions + converts: BTreeMap, + /// The parameters required to construct output descriptions + outputs: BTreeMap, +} + +impl RngBuildParams { + /// Construct a build parameter generator using the given RNG + pub fn new(rng: R) -> Self { + Self { + rng, + spends: BTreeMap::new(), + converts: BTreeMap::new(), + outputs: BTreeMap::new(), + } + } +} + +impl RngBuildParams { + /// Get the parameters necessary to build the ith spend description + pub fn spend_params(&mut self, i: usize) -> &SpendBuildParams { + 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, + }) + } + + /// Get the parameters necessary to build the ith convert description + pub fn convert_params(&mut self, i: usize) -> &ConvertBuildParams { + self.converts + .entry(i) + .or_insert_with(|| ConvertBuildParams { + rcv: jubjub::Fr::random(&mut self.rng), + }) + } + + /// Get the parameters necessary to build the ith output description + pub fn output_params(&mut self, i: usize) -> &OutputBuildParams { + self.outputs.entry(i).or_insert_with(|| OutputBuildParams { + rcv: jubjub::Fr::random(&mut self.rng), + rcm: jubjub::Fr::random(&mut self.rng), + rseed: { + let mut buffer = [0u8; 32]; + self.rng.fill_bytes(&mut buffer); + buffer + }, + }) + } +} + +impl BuildParams for RngBuildParams { + fn spend_rcv(&mut self, i: usize) -> jubjub::Fr { + self.spend_params(i).rcv + } + + fn spend_alpha(&mut self, i: usize) -> jubjub::Fr { + 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 + } + + fn output_rcv(&mut self, i: usize) -> jubjub::Fr { + self.output_params(i).rcv + } + + fn output_rcm(&mut self, i: usize) -> jubjub::Fr { + self.output_params(i).rcm + } + + fn output_rseed(&mut self, i: usize) -> [u8; 32] { + self.output_params(i).rseed + } +} + /// If there are any shielded inputs, always have at least two shielded outputs, padding /// with dummy outputs if necessary. See . const MIN_SHIELDED_OUTPUTS: usize = 2; @@ -76,7 +264,6 @@ pub struct SpendDescriptionInfo { extsk: Key, diversifier: Diversifier, note: Note, - alpha: jubjub::Fr, merkle_path: MerklePath, } @@ -86,16 +273,14 @@ impl BorshSchema for SpendDescriptionInfo { fields: Fields::NamedFields(vec![ ("extsk".into(), Key::declaration()), ("diversifier".into(), Diversifier::declaration()), - ("note".into(), Note::declaration()), - ("alpha".into(), <[u8; 32]>::declaration()), + ("note".into(), Note::::declaration()), ("merkle_path".into(), MerklePath::<[u8; 32]>::declaration()), ]), }; add_definition(Self::declaration(), definition, definitions); Key::add_definitions_recursively(definitions); Diversifier::add_definitions_recursively(definitions); - Note::add_definitions_recursively(definitions); - <[u8; 32]>::add_definitions_recursively(definitions); + Note::::add_definitions_recursively(definitions); MerklePath::<[u8; 32]>::add_definitions_recursively(definitions); } @@ -109,7 +294,6 @@ impl BorshSerialize for SpendDescriptionInfo { self.extsk.serialize(writer)?; self.diversifier.serialize(writer)?; self.note.serialize(writer)?; - self.alpha.to_bytes().serialize(writer)?; self.merkle_path.serialize(writer) } } @@ -119,15 +303,11 @@ impl BorshDeserialize for SpendDescriptionInfo { let extsk = Key::deserialize_reader(reader)?; let diversifier = Diversifier::deserialize_reader(reader)?; let note = Note::deserialize_reader(reader)?; - let alpha: Option<_> = - jubjub::Fr::from_bytes(&<[u8; 32]>::deserialize_reader(reader)?).into(); - let alpha = alpha.ok_or_else(|| std::io::Error::from(std::io::ErrorKind::InvalidData))?; let merkle_path = MerklePath::::deserialize_reader(reader)?; Ok(SpendDescriptionInfo { extsk, diversifier, note, - alpha, merkle_path, }) } @@ -159,16 +339,13 @@ pub struct SaplingOutputInfo { /// `None` represents the `ovk = ⊥` case. ovk: Option, to: PaymentAddress, - note: Note, + note: Note<()>, memo: MemoBytes, } impl SaplingOutputInfo { #[allow(clippy::too_many_arguments)] - fn new_internal( - params: &P, - rng: &mut R, - target_height: BlockHeight, + fn new_internal( ovk: Option, to: PaymentAddress, asset_type: AssetType, @@ -180,13 +357,11 @@ impl SaplingOutputInfo { return Err(Error::InvalidAmount); } - let rseed = generate_random_rseed_internal(params, target_height, rng); - let note = Note { g_d, pk_d: *to.pk_d(), value, - rseed, + rseed: (), asset_type, }; @@ -203,19 +378,29 @@ impl SaplingOutputInfo { prover: &Pr, ctx: &mut Pr::SaplingProvingContext, rng: &mut R, + rcv: jubjub::Fr, + rseed: Rseed, ) -> OutputDescription { - let encryptor = sapling_note_encryption::

(self.ovk, self.note, self.to, self.memo); + let note = Note { + rseed, + value: self.note.value, + g_d: self.note.g_d, + pk_d: self.note.pk_d, + asset_type: self.note.asset_type, + }; + let encryptor = sapling_note_encryption::

(self.ovk, note, self.to, self.memo); let (zkproof, cv) = prover.output_proof( ctx, *encryptor.esk(), self.to, - self.note.rcm(), + note.rcm(), self.note.asset_type, self.note.value, + rcv, ); - let cmu = self.note.cmu(); + let cmu = note.cmu(); let enc_ciphertext = encryptor.encrypt_note_plaintext(); let out_ciphertext = encryptor.encrypt_outgoing_plaintext(&cv, &cmu, rng); @@ -449,9 +634,8 @@ impl SaplingBuilder

{ /// /// 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( + pub fn add_spend( &mut self, - mut rng: R, extsk: ExtendedSpendingKey, diversifier: Diversifier, note: Note, @@ -468,8 +652,6 @@ impl SaplingBuilder

{ self.spend_anchor = Some(merkle_path.root(node).into()) } - let alpha = jubjub::Fr::random(&mut rng); - self.value_balance += ValueSum::from_pair(note.asset_type, note.value.into()) .map_err(|_| Error::InvalidAmount)?; @@ -477,7 +659,6 @@ impl SaplingBuilder

{ extsk, diversifier, note, - alpha, merkle_path, }); @@ -520,25 +701,15 @@ impl SaplingBuilder

{ /// Adds a Sapling address to send funds to. #[allow(clippy::too_many_arguments)] - pub fn add_output( + pub fn add_output( &mut self, - mut rng: R, ovk: Option, to: PaymentAddress, asset_type: AssetType, value: u64, memo: MemoBytes, ) -> Result<(), Error> { - let output = SaplingOutputInfo::new_internal( - &self.params, - &mut rng, - self.target_height, - ovk, - to, - asset_type, - value, - memo, - )?; + let output = SaplingOutputInfo::new_internal(ovk, to, asset_type, value, memo)?; self.value_balance -= ValueSum::from_pair(asset_type, value.into()).map_err(|_| Error::InvalidAmount)?; @@ -548,11 +719,12 @@ impl SaplingBuilder

{ Ok(()) } - pub fn build( + pub fn build( self, prover: &Pr, ctx: &mut Pr::SaplingProvingContext, - mut rng: R, + rng: &mut (impl CryptoRng + RngCore), + bparams: &mut impl BuildParams, target_height: BlockHeight, progress_notifier: Option<&Sender>, ) -> Result>, Error> { @@ -585,9 +757,9 @@ impl SaplingBuilder

{ } // Randomize order of inputs and outputs - indexed_spends.shuffle(&mut rng); - indexed_converts.shuffle(&mut rng); - indexed_outputs.shuffle(&mut rng); + indexed_spends.shuffle(rng); + indexed_converts.shuffle(rng); + indexed_outputs.shuffle(rng); // Keep track of the total number of steps computed let total_progress = indexed_spends.len() as u32 + indexed_outputs.len() as u32; @@ -616,11 +788,12 @@ impl SaplingBuilder

{ proof_generation_key, spend.diversifier, spend.note.rseed, - spend.alpha, + bparams.spend_alpha(i), spend.note.asset_type, spend.note.value, anchor, spend.merkle_path.clone(), + bparams.spend_rcv(i), ) .map_err(|_| Error::SpendProof)?; @@ -668,6 +841,7 @@ impl SaplingBuilder

{ convert.value, anchor, convert.merkle_path, + bparams.convert_rcv(i), ) .map_err(|_| Error::ConvertProof)?; @@ -699,11 +873,20 @@ impl SaplingBuilder

{ .into_iter() .enumerate() .map(|(i, output)| { + let rseed = generate_random_rseed_internal( + ¶ms, + target_height, + bparams.output_rcm(i), + bparams.output_rseed(i), + ); + let result = if let Some((pos, output)) = output { // Record the post-randomized output location tx_metadata.output_indices[pos] = i; - output.clone().build::(prover, ctx, &mut rng) + output + .clone() + .build::(prover, ctx, rng, bparams.output_rcv(i), rseed) } else { // This is a dummy output let (dummy_to, dummy_note) = { @@ -722,16 +905,15 @@ impl SaplingBuilder

{ (diversifier, g_d) }; let (pk_d, payment_address) = loop { - let dummy_ivk = jubjub::Fr::random(&mut rng); + let mut buf = [0; 64]; + rng.fill_bytes(&mut buf); + let dummy_ivk = jubjub::Fr::from_bytes_wide(&buf); let pk_d = g_d * dummy_ivk; if let Some(addr) = PaymentAddress::from_parts(diversifier, pk_d) { break (pk_d, addr); } }; - let rseed = - generate_random_rseed_internal(¶ms, target_height, &mut rng); - ( payment_address, Note { @@ -744,7 +926,7 @@ impl SaplingBuilder

{ ) }; - let esk = dummy_note.generate_or_derive_esk_internal(&mut rng); + let esk = dummy_note.generate_or_derive_esk_internal(rng); let epk = dummy_note.g_d * esk; let (zkproof, cv) = prover.output_proof( @@ -754,6 +936,7 @@ impl SaplingBuilder

{ dummy_note.rcm(), dummy_note.asset_type, dummy_note.value, + bparams.output_rcv(i), ); let cmu = dummy_note.cmu(); @@ -816,11 +999,12 @@ impl SpendDescription { } impl Bundle { - pub fn apply_signatures( + pub fn apply_signatures( self, prover: &Pr, ctx: &mut Pr::SaplingProvingContext, rng: &mut R, + bparams: &mut S, sighash_bytes: &[u8; 32], ) -> Result<(Bundle, SaplingMetadata), Error> { let binding_sig = prover @@ -832,10 +1016,11 @@ impl Bundle { shielded_spends: self .shielded_spends .iter() - .map(|spend| { + .enumerate() + .map(|(i, spend)| { spend.apply_signature(spend_sig_internal( PrivateKey(spend.spend_auth_sig.extsk.expsk.ask), - spend.spend_auth_sig.alpha, + bparams.spend_alpha(i), sighash_bytes, rng, )) @@ -915,7 +1100,6 @@ impl SaplingBuilder { extsk: f.map_key(x.extsk), diversifier: x.diversifier, note: x.note, - alpha: x.alpha, merkle_path: x.merkle_path, }) .collect(), @@ -947,7 +1131,7 @@ pub mod testing { zip32::sapling::testing::arb_extended_spending_key, }; - use super::SaplingBuilder; + use super::{RngBuildParams, SaplingBuilder}; prop_compose! { fn arb_bundle()(n_notes in 1..30usize)( @@ -965,6 +1149,7 @@ pub mod testing { diversifiers in vec(prop::array::uniform11(any::()).prop_map(Diversifier), n_notes), target_height in arb_branch_id().prop_flat_map(|b| arb_height(b, &TEST_NETWORK)), rng_seed in prop::array::uniform32(any::()), + bparams_seed in prop::array::uniform32(any::()), fake_sighash_bytes in prop::array::uniform32(any::()), ) -> Bundle { let mut builder = SaplingBuilder::new(TEST_NETWORK, target_height.unwrap()); @@ -972,7 +1157,6 @@ pub mod testing { for ((note, path), diversifier) in spendable_notes.into_iter().zip(commitment_trees.into_iter()).zip(diversifiers.into_iter()) { builder.add_spend( - &mut rng, extsk, diversifier, note, @@ -981,19 +1165,22 @@ pub mod testing { } let prover = MockTxProver; + let mut bparams = RngBuildParams::new(StdRng::from_seed(bparams_seed)); let bundle = builder.build( &prover, &mut (), &mut rng, + &mut bparams, target_height.unwrap(), - None + None, ).unwrap().unwrap(); let (bundle, _) = bundle.apply_signatures( &prover, &mut (), &mut rng, + &mut bparams, &fake_sighash_bytes, ).unwrap(); diff --git a/masp_proofs/src/prover.rs b/masp_proofs/src/prover.rs index 7f7cba20..adcb7258 100644 --- a/masp_proofs/src/prover.rs +++ b/masp_proofs/src/prover.rs @@ -171,6 +171,7 @@ impl TxProver for LocalTxProver { value: u64, anchor: bls12_381::Scalar, merkle_path: MerklePath, + rcv: jubjub::Fr, ) -> Result<([u8; GROTH_PROOF_SIZE], jubjub::ExtendedPoint, PublicKey), ()> { let (proof, cv, rk) = ctx.spend_proof( proof_generation_key, @@ -183,6 +184,7 @@ impl TxProver for LocalTxProver { merkle_path, &self.spend_params, &self.spend_vk, + rcv, )?; let mut zkproof = [0u8; GROTH_PROOF_SIZE]; @@ -201,6 +203,7 @@ impl TxProver for LocalTxProver { rcm: jubjub::Fr, asset_type: AssetType, value: u64, + rcv: jubjub::Fr, ) -> ([u8; GROTH_PROOF_SIZE], jubjub::ExtendedPoint) { let (proof, cv) = ctx.output_proof( esk, @@ -209,6 +212,7 @@ impl TxProver for LocalTxProver { asset_type, value, &self.output_params, + rcv, ); let mut zkproof = [0u8; GROTH_PROOF_SIZE]; @@ -226,6 +230,7 @@ impl TxProver for LocalTxProver { value: u64, anchor: bls12_381::Scalar, merkle_path: MerklePath, + rcv: jubjub::Fr, ) -> Result<([u8; GROTH_PROOF_SIZE], jubjub::ExtendedPoint), ()> { let (proof, cv) = ctx.convert_proof( allowed_conversion, @@ -234,6 +239,7 @@ impl TxProver for LocalTxProver { merkle_path, &self.convert_params, &self.convert_vk, + rcv, )?; let mut zkproof = [0u8; GROTH_PROOF_SIZE]; diff --git a/masp_proofs/src/sapling/prover.rs b/masp_proofs/src/sapling/prover.rs index 8c13348a..bff906b6 100644 --- a/masp_proofs/src/sapling/prover.rs +++ b/masp_proofs/src/sapling/prover.rs @@ -3,7 +3,7 @@ use bellman::{ groth16::{create_random_proof, verify_proof, Parameters, PreparedVerifyingKey, Proof}, }; use bls12_381::Bls12; -use group::{ff::Field, Curve, GroupEncoding}; +use group::{Curve, GroupEncoding}; use masp_primitives::{ asset_type::AssetType, constants::{SPENDING_KEY_GENERATOR, VALUE_COMMITMENT_RANDOMNESS_GENERATOR}, @@ -60,13 +60,11 @@ impl SaplingProvingContext { merkle_path: MerklePath, proving_key: &Parameters, verifying_key: &PreparedVerifyingKey, + rcv: jubjub::Fr, ) -> Result<(Proof, jubjub::ExtendedPoint, PublicKey), ()> { // Initialize secure RNG let mut rng = OsRng; - // We create the randomness of the value commitment - let rcv = jubjub::Fr::random(&mut rng); - // Accumulate the value commitment randomness in the context { let mut tmp = rcv; @@ -161,6 +159,7 @@ impl SaplingProvingContext { /// Create the value commitment and proof for a Sapling OutputDescription, /// while accumulating its value commitment randomness inside the context /// for later use. + #[allow(clippy::too_many_arguments)] pub fn output_proof( &mut self, esk: jubjub::Fr, @@ -169,15 +168,11 @@ impl SaplingProvingContext { asset_type: AssetType, value: u64, proving_key: &Parameters, + rcv: jubjub::Fr, ) -> (Proof, jubjub::ExtendedPoint) { // Initialize secure RNG let mut rng = OsRng; - // We construct ephemeral randomness for the value commitment. This - // randomness is not given back to the caller, but the synthetic - // blinding factor `bsk` is accumulated in the context. - let rcv = jubjub::Fr::random(&mut rng); - // Accumulate the value commitment randomness in the context { let mut tmp = rcv.neg(); // Outputs subtract from the total. @@ -215,6 +210,7 @@ impl SaplingProvingContext { /// Create the value commitment and proof for a ConvertDescription, /// while accumulating its value commitment randomness inside the context /// for later use. + #[allow(clippy::too_many_arguments)] pub fn convert_proof( &mut self, allowed_conversion: AllowedConversion, @@ -223,13 +219,11 @@ impl SaplingProvingContext { merkle_path: MerklePath, proving_key: &Parameters, verifying_key: &PreparedVerifyingKey, + rcv: jubjub::Fr, ) -> Result<(Proof, jubjub::ExtendedPoint), ()> { // Initialize secure RNG let mut rng = OsRng; - // We create the randomness of the value commitment - let rcv = jubjub::Fr::random(&mut rng); - // Accumulate the value commitment randomness in the context { let mut tmp = rcv;