diff --git a/ironfish-rust/src/transaction/mints.rs b/ironfish-rust/src/transaction/mints.rs index 67181e7291..cc2dbc68cf 100644 --- a/ironfish-rust/src/transaction/mints.rs +++ b/ironfish-rust/src/transaction/mints.rs @@ -58,6 +58,17 @@ impl MintBuilder { self } + pub fn build_circuit( + &self, + proof_generation_key: &ProofGenerationKey, + public_key_randomness: &ironfish_jubjub::Fr, + ) -> MintAsset { + MintAsset { + proof_generation_key: Some(proof_generation_key.clone()), + public_key_randomness: Some(*public_key_randomness), + } + } + pub fn build( &self, proof_generation_key: &ProofGenerationKey, @@ -65,10 +76,7 @@ impl MintBuilder { public_key_randomness: &ironfish_jubjub::Fr, randomized_public_key: &redjubjub::PublicKey, ) -> Result { - let circuit = MintAsset { - proof_generation_key: Some(proof_generation_key.clone()), - public_key_randomness: Some(*public_key_randomness), - }; + let circuit = self.build_circuit(proof_generation_key, public_key_randomness); let proof = groth16::create_random_proof(circuit, &SAPLING.mint_params, &mut thread_rng())?; diff --git a/ironfish-rust/src/transaction/outputs.rs b/ironfish-rust/src/transaction/outputs.rs index 26402f6789..e251afb5ad 100644 --- a/ironfish-rust/src/transaction/outputs.rs +++ b/ironfish-rust/src/transaction/outputs.rs @@ -74,6 +74,24 @@ impl OutputBuilder { ExtendedPoint::from(self.value_commitment.commitment()) } + pub(crate) fn build_circuit( + &self, + proof_generation_key: &ProofGenerationKey, + public_key_randomness: &ironfish_jubjub::Fr, + ) -> (Output, EphemeralKeyPair) { + let key_pair = EphemeralKeyPair::new(); + let circuit = Output { + value_commitment: Some(self.value_commitment.clone()), + payment_address: Some(self.note.owner.0), + commitment_randomness: Some(self.note.randomness), + esk: Some(*key_pair.secret()), + asset_id: *self.note.asset_id().as_bytes(), + proof_generation_key: Some(proof_generation_key.clone()), + ar: Some(*public_key_randomness), + }; + (circuit, key_pair) + } + /// Construct and return the committed [`OutputDescription`] for this receiving calculation. /// /// The [`OutputDescription`] is the publicly visible form of the new note, not @@ -88,17 +106,8 @@ impl OutputBuilder { public_key_randomness: &ironfish_jubjub::Fr, randomized_public_key: &redjubjub::PublicKey, ) -> Result { - let diffie_hellman_keys = EphemeralKeyPair::new(); - - let circuit = Output { - value_commitment: Some(self.value_commitment.clone()), - payment_address: Some(self.note.owner.0), - commitment_randomness: Some(self.note.randomness), - esk: Some(*diffie_hellman_keys.secret()), - asset_id: *self.note.asset_id().as_bytes(), - proof_generation_key: Some(proof_generation_key.clone()), - ar: Some(*public_key_randomness), - }; + let (circuit, diffie_hellman_keys) = + self.build_circuit(proof_generation_key, public_key_randomness); let proof = groth16::create_random_proof(circuit, &SAPLING.output_params, &mut thread_rng())?; diff --git a/ironfish-rust/src/transaction/proposed.rs b/ironfish-rust/src/transaction/proposed.rs index 5470c1ea98..f180d99625 100644 --- a/ironfish-rust/src/transaction/proposed.rs +++ b/ironfish-rust/src/transaction/proposed.rs @@ -8,7 +8,7 @@ use crate::{ asset_identifier::{AssetIdentifier, NATIVE_ASSET}, }, errors::{IronfishError, IronfishErrorKind}, - keys::{PublicAddress, SaplingKey}, + keys::{EphemeralKeyPair, PublicAddress, SaplingKey}, note::Note, transaction::{ burns::{BurnBuilder, BurnDescription}, @@ -32,6 +32,7 @@ use group::GroupEncoding; use ironfish_jubjub::ExtendedPoint; use ironfish_zkp::{ constants::{SPENDING_KEY_GENERATOR, VALUE_COMMITMENT_RANDOMNESS_GENERATOR}, + proofs, redjubjub::{self, Signature}, ProofGenerationKey, }; @@ -195,6 +196,49 @@ impl ProposedTransaction { Ok(()) } + #[allow(clippy::type_complexity)] + pub fn build_circuits( + &mut self, + proof_authorizing_key: ironfish_jubjub::Fr, + view_key: ViewKey, + intended_transaction_fee: i64, + change_goes_to: Option, + ) -> Result< + ( + Vec, + Vec<(proofs::Output, EphemeralKeyPair)>, + Vec, + ), + IronfishError, + > { + let public_address = view_key.public_address()?; + let proof_generation_key = + ProofGenerationKey::new(view_key.authorizing_key, proof_authorizing_key); + + let is_miners_fee = self.outputs.iter().any(|output| output.get_is_miners_fee()); + if !is_miners_fee { + self.add_change_notes(change_goes_to, public_address, intended_transaction_fee)?; + } + + let spend_circuits = self + .spends + .iter() + .map(|spend| spend.build_circuit(&proof_generation_key, &self.public_key_randomness)) + .collect(); + let output_circuits = self + .outputs + .iter() + .map(|output| output.build_circuit(&proof_generation_key, &self.public_key_randomness)) + .collect(); + let mint_circuits = self + .mints + .iter() + .map(|mint| mint.build_circuit(&proof_generation_key, &self.public_key_randomness)) + .collect(); + + Ok((spend_circuits, output_circuits, mint_circuits)) + } + pub fn build( &mut self, proof_authorizing_key: ironfish_jubjub::Fr, diff --git a/ironfish-rust/src/transaction/spends.rs b/ironfish-rust/src/transaction/spends.rs index 535c0d798a..67c4c50500 100644 --- a/ironfish-rust/src/transaction/spends.rs +++ b/ironfish-rust/src/transaction/spends.rs @@ -68,7 +68,7 @@ impl SpendBuilder { /// This is the only time this API thinks about the merkle tree. The witness /// contains the root-hash at the time the witness was created and the path /// to verify the location of that note in the tree. - pub(crate) fn new(note: Note, witness: &W) -> Self { + pub fn new(note: Note, witness: &W) -> Self { let value_commitment = ValueCommitment::new(note.value, note.asset_generator()); SpendBuilder { @@ -89,12 +89,29 @@ impl SpendBuilder { ExtendedPoint::from(self.value_commitment.commitment()) } + pub fn build_circuit( + &self, + proof_generation_key: &ProofGenerationKey, + public_key_randomness: &Fr, + ) -> Spend { + Spend { + value_commitment: Some(self.value_commitment.clone()), + proof_generation_key: Some(proof_generation_key.clone()), + payment_address: Some(self.note.owner.0), + auth_path: self.auth_path.clone(), + commitment_randomness: Some(self.note.randomness), + anchor: Some(self.root_hash), + ar: Some(*public_key_randomness), + sender_address: Some(self.note.sender.0), + } + } + /// Sign this spend with the private key, and return a [`SpendDescription`] /// suitable for serialization. /// /// Verifies the proof before returning to prevent posting broken /// transactions - pub(crate) fn build( + pub fn build( &self, proof_generation_key: &ProofGenerationKey, view_key: &ViewKey, @@ -103,16 +120,7 @@ impl SpendBuilder { ) -> Result { let value_commitment_point = self.value_commitment_point(); - let circuit = Spend { - value_commitment: Some(self.value_commitment.clone()), - proof_generation_key: Some(proof_generation_key.clone()), - payment_address: Some(self.note.owner.0), - auth_path: self.auth_path.clone(), - commitment_randomness: Some(self.note.randomness), - anchor: Some(self.root_hash), - ar: Some(*public_key_randomness), - sender_address: Some(self.note.sender.0), - }; + let circuit = self.build_circuit(proof_generation_key, public_key_randomness); // Proof that the spend was valid and successful for the provided owner // and note.