diff --git a/circuits/CHANGELOG.md b/circuits/CHANGELOG.md index 45a7580..4e5a880 100644 --- a/circuits/CHANGELOG.md +++ b/circuits/CHANGELOG.md @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Removed + +- Remove `TxInputNote` and `TxOutputNote` from public api [#229] +- Remove `transaction` module [#229] + +### Changed + +- Change `TxCircuit::new` constructor [#229] +- Move `TxCircuit` from `transaction` module to root module [#229] + ## [0.2.1] - 2024-07-03 ### Changed @@ -55,6 +65,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Update `poseidon-merkle` to v0.6 [#179] +[#229]: https://github.com/dusk-network/phoenix/issues/229 [#214]: https://github.com/dusk-network/phoenix/issues/214 [#201]: https://github.com/dusk-network/phoenix/issues/201 [#197]: https://github.com/dusk-network/phoenix/issues/197 diff --git a/circuits/src/lib.rs b/circuits/src/lib.rs index 3a662e7..f63db0d 100644 --- a/circuits/src/lib.rs +++ b/circuits/src/lib.rs @@ -13,8 +13,444 @@ mod encryption; mod sender_enc; -/// Transaction structs, and circuit -pub mod transaction; - /// ElGamal asymmetric cipher pub use encryption::elgamal; + +use dusk_jubjub::{JubJubAffine, JubJubScalar, GENERATOR, GENERATOR_NUMS}; +use dusk_plonk::prelude::*; +use dusk_poseidon::{Domain, HashGadget}; +use jubjub_schnorr::{gadgets, Signature as SchnorrSignature, SignatureDouble}; +use poseidon_merkle::{zk::opening_gadget, Item, Opening, Tree}; + +use phoenix_core::{Note, PublicKey, SecretKey, OUTPUT_NOTES}; + +extern crate alloc; +use alloc::vec::Vec; + +/// Declaration of the transaction circuit calling the [`gadget`]. +#[derive(Debug)] +pub struct TxCircuit { + tx_input_notes: [TxInputNote; I], + tx_output_notes: [TxOutputNote; OUTPUT_NOTES], + payload_hash: BlsScalar, + root: BlsScalar, + deposit: u64, + max_fee: u64, + sender_pk: PublicKey, + signatures: (SchnorrSignature, SchnorrSignature), +} + +impl Circuit for TxCircuit { + /// Transaction gadget proving the following properties in ZK for a generic + /// `I` input-notes and [`OUTPUT_NOTES`] output-notes: + /// + /// 1. Membership: every input-note is included in the Merkle tree of notes. + /// 2. Ownership: the sender holds the note secret key for every input-note. + /// 3. Nullification: the nullifier is calculated correctly. + /// 4. Minting: the value commitment for every input-note is computed + /// correctly. + /// 5. Balance integrity: the sum of the values of all input-notes is equal + /// to the sum of the values of all output-notes + the gas fee + /// + a deposit, where a deposit refers to funds being transferred to a + /// contract. + /// 6. Sender-data: Verify that the sender was encrypted correctly for each + /// output-note. + /// + /// The circuit has the following public inputs: + /// - `payload_hash` + /// - `root` + /// - `[nullifier; I]` + /// - `[output_value_commitment; 2]` + /// - `max_fee` + /// - `deposit` + /// - `(npk_0, npk_1)` + /// - `(enc_A_npk_0, enc_B_npk_0)` + /// - `(enc_A_npk_1, enc_B_npk_1)` + fn circuit(&self, composer: &mut Composer) -> Result<(), Error> { + // Make the payload hash a public input of the circuit + let payload_hash = composer.append_public(self.payload_hash); + + // Append the root as public input + let root_pi = composer.append_public(self.root); + + let mut tx_input_notes_sum = Composer::ZERO; + + // Check membership, ownership and nullification of all input notes + for tx_input_note in &self.tx_input_notes { + let ( + note_pk, + note_pk_p, + note_type, + pos, + value, + value_blinder, + nullifier, + signature_u, + signature_r, + signature_r_p, + ) = tx_input_note.append_to_circuit(composer); + + // Verify: 2. Ownership + gadgets::verify_signature_double( + composer, + signature_u, + signature_r, + signature_r_p, + note_pk, + note_pk_p, + payload_hash, + )?; + + // Verify: 3. Nullification + let computed_nullifier = HashGadget::digest( + composer, + Domain::Other, + &[*note_pk_p.x(), *note_pk_p.y(), pos], + )[0]; + composer.assert_equal(computed_nullifier, nullifier); + + // Perform a range check ([0, 2^64 - 1]) on the value of the note + composer.component_range::<32>(value); + + // Sum up all the tx input note values + let constraint = Constraint::new() + .left(1) + .a(tx_input_notes_sum) + .right(1) + .b(value); + tx_input_notes_sum = composer.gate_add(constraint); + + // Commit to the value of the note + let pc_1 = composer.component_mul_generator(value, GENERATOR)?; + let pc_2 = composer + .component_mul_generator(value_blinder, GENERATOR_NUMS)?; + let value_commitment = composer.component_add_point(pc_1, pc_2); + + // Compute the note hash + let note_hash = HashGadget::digest( + composer, + Domain::Other, + &[ + note_type, + *value_commitment.x(), + *value_commitment.y(), + *note_pk.x(), + *note_pk.y(), + pos, + ], + )[0]; + + // Verify: 1. Membership + let root = opening_gadget( + composer, + &tx_input_note.merkle_opening, + note_hash, + ); + composer.assert_equal(root, root_pi); + } + + let mut tx_output_sum = Composer::ZERO; + + // Commit to all tx output notes + for tx_output_note in &self.tx_output_notes { + // Append the witnesses to the circuit + let value = composer.append_witness(tx_output_note.value); + let expected_value_commitment = + composer.append_public_point(tx_output_note.value_commitment); + let value_blinder = + composer.append_witness(tx_output_note.value_blinder); + + // Perform a range check ([0, 2^64 - 1]) on the value of the note + composer.component_range::<32>(value); + + // Sum up all the tx output note values + let constraint = + Constraint::new().left(1).a(tx_output_sum).right(1).b(value); + tx_output_sum = composer.gate_add(constraint); + + // Commit to the value of the note + let pc_1 = composer.component_mul_generator(value, GENERATOR)?; + let pc_2 = composer + .component_mul_generator(value_blinder, GENERATOR_NUMS)?; + let computed_value_commitment = + composer.component_add_point(pc_1, pc_2); + + // Verify: 4. Minting + composer.assert_equal_point( + expected_value_commitment, + computed_value_commitment, + ); + } + + // Append max_fee and deposit as public inputs + let max_fee = composer.append_public(self.max_fee); + let deposit = composer.append_public(self.deposit); + + // Add the deposit and the max fee to the sum of the output-values + let constraint = Constraint::new() + .left(1) + .a(tx_output_sum) + .right(1) + .b(max_fee) + .fourth(1) + .d(deposit); + tx_output_sum = composer.gate_add(constraint); + + // Verify: 5. Balance integrity + composer.assert_equal(tx_input_notes_sum, tx_output_sum); + + // Verify: 6. Sender-data + sender_enc::gadget( + composer, + self.sender_pk, + self.signatures, + [ + self.tx_output_notes[0].note_pk, + self.tx_output_notes[1].note_pk, + ], + [ + self.tx_output_notes[0].sender_blinder, + self.tx_output_notes[1].sender_blinder, + ], + self.tx_output_notes[0].sender_enc, + self.tx_output_notes[1].sender_enc, + payload_hash, + )?; + + Ok(()) + } +} + +impl TxCircuit { + /// Create a new transfer circuit + pub fn new( + input_notes_information: Vec<( + Opening<(), H>, // opening, + Note, // note, + JubJubAffine, // note-pk prime, + u64, // value, + JubJubScalar, // value-blinder, + BlsScalar, // nullifier, + SignatureDouble, // payload signature, + )>, + output_notes_information: [( + u64, // value, + JubJubAffine, // value-commitment, + JubJubScalar, // value-blinder, + JubJubAffine, // note-pk + [(JubJubAffine, JubJubAffine); 2], // sender-enc + [JubJubScalar; 2], // sender-blinder + ); OUTPUT_NOTES], + payload_hash: BlsScalar, + root: BlsScalar, + deposit: u64, + max_fee: u64, + sender_pk: PublicKey, + signatures: (SchnorrSignature, SchnorrSignature), + ) -> Self { + let mut tx_input_notes = + Vec::with_capacity(input_notes_information.len()); + for ( + merkle_opening, + note, + note_pk_p, + value, + value_blinder, + nullifier, + signature, + ) in input_notes_information.into_iter() + { + tx_input_notes.push(TxInputNote { + merkle_opening, + note, + note_pk_p, + value, + value_blinder, + nullifier, + signature, + }); + } + let tx_input_notes: [TxInputNote; I] = tx_input_notes + .try_into() + .expect("The amount of input notes need to match `*`"); + + let tx_output_notes = [ + TxOutputNote { + value: output_notes_information[0].0, + value_commitment: output_notes_information[0].1, + value_blinder: output_notes_information[0].2, + note_pk: output_notes_information[0].3, + sender_enc: output_notes_information[0].4, + sender_blinder: output_notes_information[0].5, + }, + TxOutputNote { + value: output_notes_information[1].0, + value_commitment: output_notes_information[1].1, + value_blinder: output_notes_information[1].2, + note_pk: output_notes_information[1].3, + sender_enc: output_notes_information[1].4, + sender_blinder: output_notes_information[1].5, + }, + ]; + + Self { + tx_input_notes, + tx_output_notes, + payload_hash, + root, + deposit, + max_fee, + sender_pk, + signatures, + } + } +} + +impl Default for TxCircuit { + fn default() -> Self { + let sk = + SecretKey::new(JubJubScalar::default(), JubJubScalar::default()); + + let mut tree = Tree::<(), H>::new(); + let payload_hash = BlsScalar::default(); + + let mut tx_input_notes = Vec::new(); + let note = Note::empty(); + let item = Item { + hash: note.hash(), + data: (), + }; + tree.insert(*note.pos(), item); + + for _ in 0..I { + let merkle_opening = tree.opening(*note.pos()).expect("Tree read."); + let tx_input_note = TxInputNote { + merkle_opening, + note: note.clone(), + note_pk_p: JubJubAffine::default(), + value: 0u64, + value_blinder: JubJubScalar::default(), + nullifier: BlsScalar::default(), + signature: SignatureDouble::default(), + }; + + tx_input_notes.push(tx_input_note); + } + + let tx_output_note_1 = TxOutputNote { + value: 0, + value_commitment: JubJubAffine::default(), + value_blinder: JubJubScalar::default(), + note_pk: JubJubAffine::default(), + sender_enc: [(JubJubAffine::default(), JubJubAffine::default()); 2], + sender_blinder: [JubJubScalar::default(), JubJubScalar::default()], + }; + let tx_output_note_2 = tx_output_note_1.clone(); + + let tx_output_notes = [tx_output_note_1, tx_output_note_2]; + + let root = BlsScalar::default(); + let deposit = u64::default(); + let max_fee = u64::default(); + + let signatures = + (SchnorrSignature::default(), SchnorrSignature::default()); + + Self { + tx_input_notes: tx_input_notes.try_into().unwrap(), + tx_output_notes, + payload_hash, + root, + deposit, + max_fee, + sender_pk: PublicKey::from(&sk), + signatures, + } + } +} + +/// Struct representing a note willing to be spent, in a way +/// suitable for being introduced in the transfer circuit +#[derive(Debug, Clone)] +struct TxInputNote { + /// the merkle opening for the note + pub merkle_opening: Opening<(), H>, + /// the input note + pub note: Note, + /// the note-public-key prime + pub note_pk_p: JubJubAffine, + /// the value associated to the note + pub value: u64, + /// the value blinder used to obfuscate the value + pub value_blinder: JubJubScalar, + /// the nullifier used to spend the note + pub nullifier: BlsScalar, + /// the signature of the payload-hash + pub signature: SignatureDouble, +} + +impl TxInputNote { + fn append_to_circuit( + &self, + composer: &mut Composer, + ) -> ( + WitnessPoint, + WitnessPoint, + Witness, + Witness, + Witness, + Witness, + Witness, + Witness, + WitnessPoint, + WitnessPoint, + ) { + let nullifier = composer.append_public(self.nullifier); + + let note_pk = composer + .append_point(*self.note.stealth_address().note_pk().as_ref()); + let note_pk_p = composer.append_point(self.note_pk_p); + + let note_type = composer + .append_witness(BlsScalar::from(self.note.note_type() as u64)); + let pos = composer.append_witness(BlsScalar::from(*self.note.pos())); + + let value = composer.append_witness(self.value); + let value_blinder = composer.append_witness(self.value_blinder); + + let signature_u = composer.append_witness(*self.signature.u()); + let signature_r = composer.append_point(self.signature.R()); + let signature_r_p = composer.append_point(self.signature.R_prime()); + + ( + note_pk, + note_pk_p, + note_type, + pos, + value, + value_blinder, + nullifier, + signature_u, + signature_r, + signature_r_p, + ) + } +} + +/// Struct representing a note willing to be created, in a way +/// suitable for being introduced in the transfer circuit +#[derive(Debug, Clone)] +struct TxOutputNote { + /// The value of the note + pub value: u64, + /// The value-commitment of the note + pub value_commitment: JubJubAffine, + /// The blinder used to calculate the value commitment + pub value_blinder: JubJubScalar, + /// the public key of the note + pub note_pk: JubJubAffine, + /// The encrypted sender information of the note + pub sender_enc: [(JubJubAffine, JubJubAffine); 2], + /// The blinder used to encrypt the sender + pub sender_blinder: [JubJubScalar; 2], +} diff --git a/circuits/src/transaction.rs b/circuits/src/transaction.rs index 5842a7e..7dafdf4 100644 --- a/circuits/src/transaction.rs +++ b/circuits/src/transaction.rs @@ -3,439 +3,3 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. // // Copyright (c) DUSK NETWORK. All rights reserved. - -use dusk_jubjub::{ - JubJubAffine, JubJubScalar, GENERATOR, GENERATOR_NUMS, - GENERATOR_NUMS_EXTENDED, -}; -use dusk_plonk::prelude::*; -use dusk_poseidon::{Domain, Hash, HashGadget}; -use jubjub_schnorr::{gadgets, Signature as SchnorrSignature, SignatureDouble}; -use poseidon_merkle::{zk::opening_gadget, Item, Opening, Tree}; - -use rand::rngs::StdRng; -use rand::{CryptoRng, RngCore, SeedableRng}; - -extern crate alloc; -use alloc::vec::Vec; - -use phoenix_core::{ - Error as PhoenixError, Note, PublicKey, SecretKey, ViewKey, OUTPUT_NOTES, -}; - -use crate::sender_enc; - -/// Struct representing a note willing to be spent, in a way -/// suitable for being introduced in the transfer circuit -#[derive(Debug, Clone)] -pub struct TxInputNote { - /// the merkle opening for the note - pub merkle_opening: Opening<(), H>, - /// the input note - pub note: Note, - /// the note-public-key prime - pub note_pk_p: JubJubAffine, - /// the value associated to the note - pub value: u64, - /// the value blinder used to obfuscate the value - pub value_blinder: JubJubScalar, - /// the nullifier used to spend the note - pub nullifier: BlsScalar, - /// the signature of the payload-hash - pub signature: SignatureDouble, -} - -#[derive(Debug, Clone)] -struct WitnessTxInputNote { - note_pk: WitnessPoint, - note_pk_p: WitnessPoint, - note_type: Witness, - pos: Witness, - value: Witness, - value_blinder: Witness, - nullifier: Witness, - signature_u: Witness, - signature_r: WitnessPoint, - signature_r_p: WitnessPoint, -} - -impl TxInputNote { - /// Create a tx input note - pub fn new( - rng: &mut (impl RngCore + CryptoRng), - note: &Note, - merkle_opening: poseidon_merkle::Opening<(), H>, - sk: &SecretKey, - payload_hash: BlsScalar, - ) -> Result, PhoenixError> { - let note_sk = sk.gen_note_sk(note.stealth_address()); - let note_pk_p = - JubJubAffine::from(GENERATOR_NUMS_EXTENDED * note_sk.as_ref()); - - let vk = ViewKey::from(sk); - let value = note.value(Some(&vk))?; - let value_blinder = note.value_blinder(Some(&vk))?; - - let nullifier = Hash::digest( - Domain::Other, - &[note_pk_p.get_u(), note_pk_p.get_v(), (*note.pos()).into()], - )[0]; - - let signature = note_sk.sign_double(rng, payload_hash); - - Ok(crate::transaction::TxInputNote { - merkle_opening, - note: note.clone(), - note_pk_p, - value, - value_blinder, - nullifier, - signature, - }) - } - - fn append_to_circuit(&self, composer: &mut Composer) -> WitnessTxInputNote { - let nullifier = composer.append_public(self.nullifier); - - let note_pk = composer - .append_point(*self.note.stealth_address().note_pk().as_ref()); - let note_pk_p = composer.append_point(self.note_pk_p); - - let note_type = composer - .append_witness(BlsScalar::from(self.note.note_type() as u64)); - let pos = composer.append_witness(BlsScalar::from(*self.note.pos())); - - let value = composer.append_witness(self.value); - let value_blinder = composer.append_witness(self.value_blinder); - - let signature_u = composer.append_witness(*self.signature.u()); - let signature_r = composer.append_point(self.signature.R()); - let signature_r_p = composer.append_point(self.signature.R_prime()); - - WitnessTxInputNote { - note_pk, - note_pk_p, - - note_type, - pos, - value, - value_blinder, - - nullifier, - - signature_u, - signature_r, - signature_r_p, - } - } -} - -/// Struct representing a note willing to be created, in a way -/// suitable for being introduced in the transfer circuit -#[derive(Debug, Clone)] -pub struct TxOutputNote { - value: u64, - value_commitment: JubJubAffine, - value_blinder: JubJubScalar, - note_pk: JubJubAffine, - sender_enc: [(JubJubAffine, JubJubAffine); 2], -} - -impl TxOutputNote { - /// Create a new `TxOutputNote`. - pub fn new( - value: u64, - value_commitment: JubJubAffine, - value_blinder: JubJubScalar, - note_pk: JubJubAffine, - sender_enc: [(JubJubAffine, JubJubAffine); 2], - ) -> Self { - Self { - value, - value_commitment, - value_blinder, - note_pk, - sender_enc, - } - } -} - -/// Declaration of the transaction circuit calling the [`gadget`]. -#[derive(Debug)] -pub struct TxCircuit { - tx_input_notes: [TxInputNote; I], - tx_output_notes: [TxOutputNote; OUTPUT_NOTES], - payload_hash: BlsScalar, - root: BlsScalar, - deposit: u64, - max_fee: u64, - sender_pk: PublicKey, - signatures: (SchnorrSignature, SchnorrSignature), - sender_blinder: [[JubJubScalar; 2]; OUTPUT_NOTES], -} - -impl Default for TxCircuit { - fn default() -> Self { - let sk = - SecretKey::new(JubJubScalar::default(), JubJubScalar::default()); - - let mut tree = Tree::<(), H>::new(); - let payload_hash = BlsScalar::default(); - - let mut tx_input_notes = Vec::new(); - let note = Note::empty(); - let item = Item { - hash: note.hash(), - data: (), - }; - tree.insert(*note.pos(), item); - - for _ in 0..I { - let merkle_opening = tree.opening(*note.pos()).expect("Tree read."); - let tx_input_note = TxInputNote::new( - &mut StdRng::seed_from_u64(0xb001), - ¬e, - merkle_opening, - &sk, - payload_hash, - ) - .expect("Note created properly."); - - tx_input_notes.push(tx_input_note); - } - - let tx_output_note_1 = TxOutputNote { - value: 0, - value_commitment: JubJubAffine::default(), - value_blinder: JubJubScalar::default(), - note_pk: JubJubAffine::default(), - sender_enc: [(JubJubAffine::default(), JubJubAffine::default()); 2], - }; - let tx_output_note_2 = tx_output_note_1.clone(); - - let tx_output_notes = [tx_output_note_1, tx_output_note_2]; - - let root = BlsScalar::default(); - let deposit = u64::default(); - let max_fee = u64::default(); - - let signatures = - (SchnorrSignature::default(), SchnorrSignature::default()); - let sender_blinder = - [[JubJubScalar::default(), JubJubScalar::default()]; OUTPUT_NOTES]; - - Self { - tx_input_notes: tx_input_notes.try_into().unwrap(), - tx_output_notes, - payload_hash, - root, - deposit, - max_fee, - sender_pk: PublicKey::from(&sk), - signatures, - sender_blinder, - } - } -} - -impl TxCircuit { - /// Create a new transfer circuit - pub fn new( - tx_input_notes: [TxInputNote; I], - tx_output_notes: [TxOutputNote; OUTPUT_NOTES], - payload_hash: BlsScalar, - root: BlsScalar, - deposit: u64, - max_fee: u64, - sender_pk: PublicKey, - signatures: (SchnorrSignature, SchnorrSignature), - sender_blinder: [[JubJubScalar; 2]; OUTPUT_NOTES], - ) -> Self { - Self { - tx_input_notes, - tx_output_notes, - payload_hash, - root, - deposit, - max_fee, - sender_pk, - signatures, - sender_blinder, - } - } -} - -impl Circuit for TxCircuit { - /// Transaction gadget proving the following properties in ZK for a generic - /// `I` [`TxInputNote`] and [`OUTPUT_NOTES`] [`TxOutputNote`]: - /// - /// 1. Membership: every [`TxInputNote`] is included in the Merkle tree of - /// notes. - /// 2. Ownership: the sender holds the note secret key for every - /// [`TxInputNote`]. - /// 3. Nullification: the nullifier is calculated correctly. - /// 4. Minting: the value commitment for every [`TxOutputNote`] is computed - /// correctly. - /// 5. Balance integrity: the sum of the values of all [`TxInputNote`] is - /// equal to the sum of the values of all [`TxOutputNote`] + the gas fee - /// + a deposit, where a deposit refers to funds being transfered to a - /// contract. - /// 6. Sender-data: Verify that the sender was encrypted correctly. - /// - /// The circuit has the following public inputs: - /// - `payload_hash` - /// - `root` - /// - `[nullifier; I]` - /// - `[output_value_commitment; 2]` - /// - `max_fee` - /// - `deposit` - /// - `(npk_0, npk_1)` - /// - `(enc_A_npk_0, enc_B_npk_0)` - /// - `(enc_A_npk_1, enc_B_npk_1)` - fn circuit(&self, composer: &mut Composer) -> Result<(), Error> { - // Make the payload hash a public input of the circuit - let payload_hash = composer.append_public(self.payload_hash); - - // Append the root as public input - let root_pi = composer.append_public(self.root); - - let mut tx_input_notes_sum = Composer::ZERO; - - // Check membership, ownership and nullification of all input notes - for tx_input_note in &self.tx_input_notes { - let w_tx_input_note = tx_input_note.append_to_circuit(composer); - - // Verify: 2. Ownership - gadgets::verify_signature_double( - composer, - w_tx_input_note.signature_u, - w_tx_input_note.signature_r, - w_tx_input_note.signature_r_p, - w_tx_input_note.note_pk, - w_tx_input_note.note_pk_p, - payload_hash, - )?; - - // Verify: 3. Nullification - let nullifier = HashGadget::digest( - composer, - Domain::Other, - &[ - *w_tx_input_note.note_pk_p.x(), - *w_tx_input_note.note_pk_p.y(), - w_tx_input_note.pos, - ], - )[0]; - composer.assert_equal(nullifier, w_tx_input_note.nullifier); - - // Perform a range check ([0, 2^64 - 1]) on the value of the note - composer.component_range::<32>(w_tx_input_note.value); - - // Sum up all the tx input note values - let constraint = Constraint::new() - .left(1) - .a(tx_input_notes_sum) - .right(1) - .b(w_tx_input_note.value); - tx_input_notes_sum = composer.gate_add(constraint); - - // Commit to the value of the note - let pc_1 = composer - .component_mul_generator(w_tx_input_note.value, GENERATOR)?; - let pc_2 = composer.component_mul_generator( - w_tx_input_note.value_blinder, - GENERATOR_NUMS, - )?; - let value_commitment = composer.component_add_point(pc_1, pc_2); - - // Compute the note hash - let note_hash = HashGadget::digest( - composer, - Domain::Other, - &[ - w_tx_input_note.note_type, - *value_commitment.x(), - *value_commitment.y(), - *w_tx_input_note.note_pk.x(), - *w_tx_input_note.note_pk.y(), - w_tx_input_note.pos, - ], - )[0]; - - // Verify: 1. Membership - let root = opening_gadget( - composer, - &tx_input_note.merkle_opening, - note_hash, - ); - composer.assert_equal(root, root_pi); - } - - let mut tx_output_sum = Composer::ZERO; - - // Commit to all tx output notes - for tx_output_note in &self.tx_output_notes { - // Append the witnesses to the circuit - let value = composer.append_witness(tx_output_note.value); - let expected_value_commitment = - composer.append_public_point(tx_output_note.value_commitment); - let value_blinder = - composer.append_witness(tx_output_note.value_blinder); - - // Perform a range check ([0, 2^64 - 1]) on the value OF THE NOTE - composer.component_range::<32>(value); - - // Sum up all the tx output note values - let constraint = - Constraint::new().left(1).a(tx_output_sum).right(1).b(value); - tx_output_sum = composer.gate_add(constraint); - - // Commit to the value of the note - let pc_1 = composer.component_mul_generator(value, GENERATOR)?; - let pc_2 = composer - .component_mul_generator(value_blinder, GENERATOR_NUMS)?; - let computed_value_commitment = - composer.component_add_point(pc_1, pc_2); - - // Verify: 4. Minting - composer.assert_equal_point( - expected_value_commitment, - computed_value_commitment, - ); - } - - // Append max_fee and deposit as public inputs - let max_fee = composer.append_public(self.max_fee); - let deposit = composer.append_public(self.deposit); - - // Add the deposit and the max fee to the sum of the output-values - let constraint = Constraint::new() - .left(1) - .a(tx_output_sum) - .right(1) - .b(max_fee) - .fourth(1) - .d(deposit); - tx_output_sum = composer.gate_add(constraint); - - // Verify: 5. Balance integrity - composer.assert_equal(tx_input_notes_sum, tx_output_sum); - - // Verify: 6. Sender-data - sender_enc::gadget( - composer, - self.sender_pk, - self.signatures, - [ - self.tx_output_notes[0].note_pk, - self.tx_output_notes[1].note_pk, - ], - self.sender_blinder, - self.tx_output_notes[0].sender_enc, - self.tx_output_notes[1].sender_enc, - payload_hash, - )?; - - Ok(()) - } -} diff --git a/circuits/tests/transaction.rs b/circuits/tests/transaction.rs index c2a16a0..156cd94 100644 --- a/circuits/tests/transaction.rs +++ b/circuits/tests/transaction.rs @@ -8,17 +8,19 @@ use rand::rngs::StdRng; use rand::SeedableRng; use rand::{CryptoRng, RngCore}; -use dusk_jubjub::JubJubScalar; +use dusk_jubjub::{JubJubAffine, JubJubScalar, GENERATOR_NUMS_EXTENDED}; use dusk_plonk::prelude::*; use ff::Field; use jubjub_schnorr::{ SecretKey as SchnorrSecretKey, Signature as SchnorrSignature, + SignatureDouble, }; -use poseidon_merkle::{Item, Tree}; +use poseidon_merkle::{Item, Opening, Tree}; -use phoenix_circuits::transaction::{TxCircuit, TxInputNote, TxOutputNote}; +use phoenix_circuits::TxCircuit; use phoenix_core::{ - elgamal, value_commitment, Note, PublicKey, SecretKey, OUTPUT_NOTES, + elgamal, value_commitment, Note, PublicKey, SecretKey, ViewKey, + OUTPUT_NOTES, }; #[macro_use] @@ -30,7 +32,15 @@ const HEIGHT: usize = 17; struct TestingParameters { pp: PublicParameters, - tx_input_notes: [TxInputNote; 4], + input_notes_information: Vec<( + Opening<(), HEIGHT>, // opening, + Note, // note, + JubJubAffine, // note-pk prime, + u64, // value, + JubJubScalar, // value-blinder, + BlsScalar, // nullifier, + SignatureDouble, // payload signature, + )>, payload_hash: BlsScalar, root: BlsScalar, deposit: u64, @@ -56,7 +66,7 @@ lazy_static! { let payload_hash = BlsScalar::from(1234u64); // create and insert into the tree 4 testing tx input notes - let tx_input_notes = create_test_tx_input_notes::<4>( + let input_notes_information = create_test_input_notes_information( &mut rng, &mut tree, &sender_sk, @@ -100,7 +110,7 @@ lazy_static! { TestingParameters { pp, - tx_input_notes, + input_notes_information, payload_hash, root, deposit, @@ -139,16 +149,28 @@ fn create_and_insert_test_note( note } -fn create_test_tx_input_notes( +fn create_test_input_notes_information( rng: &mut (impl RngCore + CryptoRng), tree: &mut Tree<(), HEIGHT>, sender_sk: &SecretKey, payload_hash: BlsScalar, -) -> [TxInputNote; I] { +) -> Vec<( + Opening<(), HEIGHT>, // opening, + Note, // note, + JubJubAffine, // note-pk prime, + u64, // value, + JubJubScalar, // value-blinder, + BlsScalar, // nullifier, + SignatureDouble, // payload signature, +)> { let sender_pk = PublicKey::from(sender_sk); + let sender_vk = ViewKey::from(sender_sk); + let total_inputs = 4; + // we first need to crate all the notes and insert them into the tree before + // we can fetch their openings let mut notes = Vec::new(); - for i in 0..I { + for i in 0..total_inputs { notes.push(create_and_insert_test_note( rng, tree, @@ -158,30 +180,49 @@ fn create_test_tx_input_notes( )); } - let mut input_notes = Vec::new(); - for i in 0..I { - let merkle_opening = tree.opening(*notes[i].pos()).expect("Tree read."); - let input_note = TxInputNote::new( - rng, - ¬es[i], + let mut input_notes_information = Vec::new(); + for note in notes.into_iter() { + let note_sk = sender_sk.gen_note_sk(note.stealth_address()); + let merkle_opening = tree + .opening(*note.pos()) + .expect("There should be a note at the given position"); + let note_pk_p = + JubJubAffine::from(GENERATOR_NUMS_EXTENDED * note_sk.as_ref()); + let value = note + .value(Some(&sender_vk)) + .expect("sender_sk should own the note"); + let value_blinder = note + .value_blinder(Some(&sender_vk)) + .expect("sender_sk should own the note"); + let nullifier = note.gen_nullifier(&sender_sk); + let signature = note_sk.sign_double(rng, payload_hash); + input_notes_information.push(( merkle_opening, - sender_sk, - payload_hash, - ) - .expect("Note created properly."); - - input_notes.push(input_note); + note, + note_pk_p, + value, + value_blinder, + nullifier, + signature, + )); } - input_notes.try_into().unwrap() + input_notes_information } -fn create_tx_output_note( +fn create_output_note_information( rng: &mut (impl RngCore + CryptoRng), value: u64, note_pk: JubJubAffine, sender_blinder: [JubJubScalar; 2], -) -> TxOutputNote { +) -> ( + u64, // value, + JubJubAffine, // value-commitment, + JubJubScalar, // value-blinder, + JubJubAffine, // note-pk + [(JubJubAffine, JubJubAffine); 2], // sender-enc + [JubJubScalar; 2], // sender-blinder +) { let value_blinder = JubJubScalar::random(&mut *rng); let value_commitment = value_commitment(value, value_blinder); @@ -196,12 +237,13 @@ fn create_tx_output_note( let sender_enc_a = (sender_enc_a.0.into(), sender_enc_a.1.into()); let sender_enc_b = (sender_enc_b.0.into(), sender_enc_b.1.into()); - TxOutputNote::new( + ( value, value_commitment, value_blinder, note_pk, [sender_enc_a, sender_enc_b], + sender_blinder, ) } @@ -213,17 +255,18 @@ fn test_transfer_circuit_1_2() { Compiler::compile::>(&TP.pp, LABEL) .expect("failed to compile circuit"); - let input_notes = [TP.tx_input_notes[0].clone()]; + let input_notes_information = + [TP.input_notes_information[0].clone()].to_vec(); // create 2 testing tx output notes - let tx_output_notes = [ - create_tx_output_note( + let output_notes_information = [ + create_output_note_information( &mut rng, 10, TP.output_npk[0], TP.sender_blinder[0], ), - create_tx_output_note( + create_output_note_information( &mut rng, 5, TP.output_npk[1], @@ -234,16 +277,15 @@ fn test_transfer_circuit_1_2() { let (proof, public_inputs) = prover .prove( &mut rng, - &TxCircuit::new( - input_notes, - tx_output_notes, + &TxCircuit::::new( + input_notes_information, + output_notes_information, TP.payload_hash, TP.root, TP.deposit, TP.max_fee, TP.sender_pk, TP.signatures, - [TP.sender_blinder[0], TP.sender_blinder[1]], ), ) .expect("failed to prove"); @@ -261,18 +303,21 @@ fn test_transfer_circuit_2_2() { Compiler::compile::>(&TP.pp, LABEL) .expect("failed to compile circuit"); - let input_notes = - [TP.tx_input_notes[0].clone(), TP.tx_input_notes[1].clone()]; + let input_notes_information = [ + TP.input_notes_information[0].clone(), + TP.input_notes_information[1].clone(), + ] + .to_vec(); // create 2 testing tx output notes - let tx_output_notes = [ - create_tx_output_note( + let output_notes_information = [ + create_output_note_information( &mut rng, 35, TP.output_npk[0], TP.sender_blinder[0], ), - create_tx_output_note( + create_output_note_information( &mut rng, 5, TP.output_npk[1], @@ -283,16 +328,15 @@ fn test_transfer_circuit_2_2() { let (proof, public_inputs) = prover .prove( &mut rng, - &TxCircuit::new( - input_notes, - tx_output_notes, + &TxCircuit::::new( + input_notes_information, + output_notes_information, TP.payload_hash, TP.root, TP.deposit, TP.max_fee, TP.sender_pk, TP.signatures, - [TP.sender_blinder[0], TP.sender_blinder[1]], ), ) .expect("failed to prove"); @@ -310,21 +354,22 @@ fn test_transfer_circuit_3_2() { Compiler::compile::>(&TP.pp, LABEL) .expect("failed to compile circuit"); - let input_notes = [ - TP.tx_input_notes[0].clone(), - TP.tx_input_notes[1].clone(), - TP.tx_input_notes[2].clone(), - ]; + let input_notes_information = [ + TP.input_notes_information[0].clone(), + TP.input_notes_information[1].clone(), + TP.input_notes_information[2].clone(), + ] + .to_vec(); // create 2 testing tx output notes - let tx_output_notes = [ - create_tx_output_note( + let output_notes_information = [ + create_output_note_information( &mut rng, 35, TP.output_npk[0], TP.sender_blinder[0], ), - create_tx_output_note( + create_output_note_information( &mut rng, 30, TP.output_npk[1], @@ -335,16 +380,15 @@ fn test_transfer_circuit_3_2() { let (proof, public_inputs) = prover .prove( &mut rng, - &TxCircuit::new( - input_notes, - tx_output_notes, + &TxCircuit::::new( + input_notes_information, + output_notes_information, TP.payload_hash, TP.root, TP.deposit, TP.max_fee, TP.sender_pk, TP.signatures, - [TP.sender_blinder[0], TP.sender_blinder[1]], ), ) .expect("failed to prove"); @@ -363,14 +407,14 @@ fn test_transfer_circuit_4_2() { .expect("failed to compile circuit"); // create 2 testing tx output notes - let tx_output_notes = [ - create_tx_output_note( + let output_notes_information = [ + create_output_note_information( &mut rng, 60, TP.output_npk[0], TP.sender_blinder[0], ), - create_tx_output_note( + create_output_note_information( &mut rng, 30, TP.output_npk[1], @@ -381,16 +425,15 @@ fn test_transfer_circuit_4_2() { let (proof, public_inputs) = prover .prove( &mut rng, - &TxCircuit::new( - TP.tx_input_notes.clone(), - tx_output_notes, + &TxCircuit::::new( + TP.input_notes_information.clone(), + output_notes_information, TP.payload_hash, TP.root, TP.deposit, TP.max_fee, TP.sender_pk, TP.signatures, - [TP.sender_blinder[0], TP.sender_blinder[1]], ), ) .expect("failed to prove");