From 0c7c9c3622edcd87b7f6d1cd3f3b0223c7d2a604 Mon Sep 17 00:00:00 2001 From: xevisalle Date: Wed, 15 May 2024 18:18:38 +0200 Subject: [PATCH] WIP: Add circuit 3 --- Cargo.toml | 3 +- src/note.rs | 31 +++-- src/transfer.rs | 219 ++++++++++++++++++++++++--------- tests/note_test.rs | 7 +- tests/transfer_gadget.rs | 254 +++++++++++++++++++++++++-------------- 5 files changed, 349 insertions(+), 165 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b02d735..79c76f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,11 +24,12 @@ aes-gcm = "0.10" zeroize = { version = "1", default-features = false, features = ["derive"] } rkyv = { version = "0.7", optional = true, default-features = false } bytecheck = { version = "0.6", optional = true, default-features = false } +rand = "0.8" [dev-dependencies] assert_matches = "1.3" -rand = "0.8" rkyv = { version = "0.7", default-features = false, features = ["size_32"] } +lazy_static = "1.4" [features] default = [] # "alloc" is suggested as default feature but would be breaking change diff --git a/src/note.rs b/src/note.rs index 076c559..56e3f89 100644 --- a/src/note.rs +++ b/src/note.rs @@ -326,50 +326,49 @@ impl Note { } #[cfg(feature = "zk")] - /// TBC + /// Create a circuit input note pub fn create_input_note( &self, + merkle_opening: poseidon_merkle::Opening<(), H, A>, sk: &SecretKey, skeleteon_hash: BlsScalar, rng: &mut (impl RngCore + CryptoRng), - ) -> crate::transfer::InputNote { + ) -> Result, Error> { let note_sk = sk.gen_note_sk(self); let note_pk_p = JubJubAffine::from(GENERATOR_NUMS_EXTENDED * note_sk.as_ref()); let vk = ViewKey::from(sk); - let value = self.value(Some(&vk)).expect("Decryption correct."); - let blinding_factor = self - .blinding_factor(Some(&vk)) - .expect("Decryption correct."); + let value = self.value(Some(&vk))?; + let blinding_factor = self.blinding_factor(Some(&vk))?; let nullifier = hash(&[note_pk_p.get_u(), note_pk_p.get_v(), self.pos.into()]); let signature = note_sk.sign_double(rng, skeleteon_hash); - crate::transfer::InputNote { + Ok(crate::transfer::InputNote { + merkle_opening, note: self.clone(), - note_pk_p: note_pk_p.into(), + note_pk_p, value, blinding_factor, nullifier, signature, - } + }) } #[cfg(feature = "zk")] - /// TBC - /// TODO: return result + /// Create a circuit output note pub fn create_output_note( &self, vk: &ViewKey, - ) -> crate::transfer::OutputNote { - crate::transfer::OutputNote { - value: self.value(Some(&vk)).unwrap(), + ) -> Result { + Ok(crate::transfer::OutputNote { + value: self.value(Some(vk))?, value_commitment: self.value_commitment.into(), - blinding_factor: self.blinding_factor(Some(&vk)).unwrap(), - } + blinding_factor: self.blinding_factor(Some(vk))?, + }) } } diff --git a/src/transfer.rs b/src/transfer.rs index 3aa453d..39e7ba1 100644 --- a/src/transfer.rs +++ b/src/transfer.rs @@ -8,13 +8,24 @@ use dusk_jubjub::{JubJubScalar, GENERATOR, GENERATOR_NUMS}; use dusk_plonk::prelude::*; use dusk_poseidon::sponge; use jubjub_schnorr::{gadgets, SignatureDouble}; -use poseidon_merkle::{zk::opening_gadget, Tree}; +use poseidon_merkle::{zk::opening_gadget, Item, Opening, Tree}; + +use rand::rngs::StdRng; +use rand_core::SeedableRng; + +extern crate alloc; +use alloc::vec::Vec; use crate::Note; +use crate::{SecretKey, ViewKey}; + +const OUTPUT: usize = 2; -/// TBC +/// Struct representing a note willing to be spent, in a way +/// suitable for being introduced in the transfer circuit #[derive(Debug, Clone)] pub struct InputNote { + pub(crate) merkle_opening: Opening<(), H, A>, pub(crate) note: Note, pub(crate) note_pk_p: JubJubAffine, pub(crate) value: u64, @@ -23,9 +34,8 @@ pub struct InputNote { pub(crate) signature: SignatureDouble, } -/// TBC #[derive(Debug, Clone)] -pub struct WitnessInputNote { +struct WitnessInputNote { note_pk: WitnessPoint, note_pk_p: WitnessPoint, note_type: Witness, @@ -39,11 +49,7 @@ pub struct WitnessInputNote { } impl InputNote { - /// TBC - pub fn append_to_circuit( - &self, - composer: &mut Composer, - ) -> WitnessInputNote { + fn append_to_circuit(&self, composer: &mut Composer) -> WitnessInputNote { let nullifier = composer.append_public(self.nullifier); let note_pk = composer @@ -79,7 +85,8 @@ impl InputNote { } } -/// TBC +/// Struct representing a note willing to be created, in a way +/// suitable for being introduced in the transfer circuit #[derive(Debug, Clone)] pub struct OutputNote { pub(crate) value: u64, @@ -87,20 +94,15 @@ pub struct OutputNote { pub(crate) blinding_factor: JubJubScalar, } -/// TBC #[derive(Debug, Clone)] -pub struct WitnessOutputNote { +struct WitnessOutputNote { value: Witness, value_commitment: WitnessPoint, blinding_factor: Witness, } impl OutputNote { - /// TBC - pub fn append_to_circuit( - &self, - composer: &mut Composer, - ) -> WitnessOutputNote { + fn append_to_circuit(&self, composer: &mut Composer) -> WitnessOutputNote { let value = composer.append_witness(self.value); let value_commitment = composer.append_public_point(self.value_commitment); @@ -114,19 +116,19 @@ impl OutputNote { } } -const OUTPUT: usize = 2; - -/// Transfer -// TODO: return result +/// Transfer gadget expecting I input notes to be spent and O output +/// notes to be created. pub fn gadget( composer: &mut Composer, input_notes: &[InputNote; I], output_notes: &[OutputNote; OUTPUT], - skeleton_hash: BlsScalar, - tree: &Tree<(), H, A>, -) { - let skeleton_hash_pi = composer.append_public(skeleton_hash); - let root_pi = composer.append_public(tree.root().hash); + skeleton_hash: &BlsScalar, + root: &BlsScalar, + crossover: u64, + max_fee: u64, +) -> Result<(), Error> { + let skeleton_hash_pi = composer.append_public(*skeleton_hash); + let root_pi = composer.append_public(*root); let mut input_notes_sum = Composer::ZERO; @@ -144,8 +146,7 @@ pub fn gadget( w_input_note.note_pk, w_input_note.note_pk_p, skeleton_hash_pi, - ) - .unwrap(); + )?; // COMPUTE AND ASSERT THE NULLIFIER let nullifier = sponge::gadget( @@ -170,15 +171,12 @@ pub fn gadget( input_notes_sum = composer.gate_add(constraint); // COMMIT TO THE VALUE OF THE NOTE - let pc_1 = composer - .component_mul_generator(w_input_note.value, GENERATOR) - .unwrap(); - let pc_2 = composer - .component_mul_generator( - w_input_note.blinding_factor, - GENERATOR_NUMS, - ) - .unwrap(); + let pc_1 = + composer.component_mul_generator(w_input_note.value, GENERATOR)?; + let pc_2 = composer.component_mul_generator( + w_input_note.blinding_factor, + GENERATOR_NUMS, + )?; let value_commitment = composer.component_add_point(pc_1, pc_2); // COMPUTE THE NOTE HASH @@ -195,14 +193,12 @@ pub fn gadget( ); // VERIFY THE MERKLE OPENING - let merkle_opening = tree - .opening(*input_note.note.pos()) - .expect("Tree was read successfully"); - let root = opening_gadget(composer, &merkle_opening, note_hash); + let root = + opening_gadget(composer, &input_note.merkle_opening, note_hash); composer.assert_equal(root, root_pi); } - let mut output_notes_sum = Composer::ZERO; + let mut output_sum = Composer::ZERO; // COMMIT TO ALL OUTPUT NOTES for output_note in output_notes { @@ -215,21 +211,18 @@ pub fn gadget( // SUM UP ALL THE CREATED NOTE VALUES let constraint = Constraint::new() .left(1) - .a(output_notes_sum) + .a(output_sum) .right(1) .b(w_output_note.value); - output_notes_sum = composer.gate_add(constraint); + output_sum = composer.gate_add(constraint); // COMMIT TO THE VALUE OF THE NOTE - let pc_1 = composer - .component_mul_generator(w_output_note.value, GENERATOR) - .unwrap(); - let pc_2 = composer - .component_mul_generator( - w_output_note.blinding_factor, - GENERATOR_NUMS, - ) - .unwrap(); + let pc_1 = + composer.component_mul_generator(w_output_note.value, GENERATOR)?; + let pc_2 = composer.component_mul_generator( + w_output_note.blinding_factor, + GENERATOR_NUMS, + )?; let value_commitment = composer.component_add_point(pc_1, pc_2); composer.assert_equal_point( @@ -238,7 +231,123 @@ pub fn gadget( ); } + let max_fee = composer.append_public(max_fee); + let crossover = composer.append_public(crossover); + + // SUM UP THE CROSSOVER AND THE MAX FEE + let constraint = Constraint::new() + .left(1) + .a(output_sum) + .right(1) + .b(max_fee) + .fourth(1) + .d(crossover); + output_sum = composer.gate_add(constraint); + // VERIFY BALANCE - // TODO: insert crossover and fee - composer.assert_equal(input_notes_sum, output_notes_sum); + composer.assert_equal(input_notes_sum, output_sum); + + Ok(()) +} + +/// Declaration of the transfer circuit +#[derive(Debug)] +pub struct TransferCircuit { + input_notes: [InputNote; I], + output_notes: [OutputNote; OUTPUT], + skeleton_hash: BlsScalar, + root: BlsScalar, + crossover: u64, + max_fee: u64, +} + +impl Default + for TransferCircuit +{ + fn default() -> Self { + let mut rng = StdRng::seed_from_u64(0xbeef); + + let sk = SecretKey::random(&mut rng); + let vk = ViewKey::from(&sk); + + let mut tree = Tree::<(), H, A>::new(); + let skeleton_hash = BlsScalar::default(); + + let mut 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 input_note = note + .create_input_note(merkle_opening, &sk, skeleton_hash, &mut rng) + .expect("Note created properly."); + + input_notes.push(input_note); + } + + let output_note_1 = note + .create_output_note(&vk) + .expect("Note created properly."); + let output_note_2 = note + .create_output_note(&vk) + .expect("Note created properly."); + + let output_notes = [output_note_1, output_note_2]; + + let root = BlsScalar::default(); + let crossover = u64::default(); + let max_fee = u64::default(); + + Self { + input_notes: input_notes.try_into().unwrap(), + output_notes, + skeleton_hash, + root, + crossover, + max_fee, + } + } +} + +impl TransferCircuit { + /// Create a new transfer circuit + pub fn new( + input_notes: [InputNote; I], + output_notes: [OutputNote; OUTPUT], + skeleton_hash: BlsScalar, + root: BlsScalar, + crossover: u64, + max_fee: u64, + ) -> Self { + Self { + input_notes, + output_notes, + skeleton_hash, + root, + crossover, + max_fee, + } + } +} + +impl Circuit + for TransferCircuit +{ + fn circuit(&self, composer: &mut Composer) -> Result<(), Error> { + gadget::( + composer, + &self.input_notes, + &self.output_notes, + &self.skeleton_hash, + &self.root, + self.crossover, + self.max_fee, + )?; + Ok(()) + } } diff --git a/tests/note_test.rs b/tests/note_test.rs index b8b7814..508f4e8 100644 --- a/tests/note_test.rs +++ b/tests/note_test.rs @@ -4,12 +4,10 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -use core::convert::TryInto; use dusk_jubjub::{JubJubScalar, GENERATOR_EXTENDED, GENERATOR_NUMS_EXTENDED}; use ff::Field; use phoenix_core::{ - Crossover, Error, Fee, Note, NoteType, Ownable, PublicKey, SecretKey, - ViewKey, + Error, Note, NoteType, Ownable, PublicKey, SecretKey, ViewKey, }; use rand_core::OsRng; @@ -164,7 +162,7 @@ fn note_keys_consistency() { assert!(!wrong_vk.owns(¬e)); assert!(vk.owns(¬e)); } - +/* #[should_panic] #[test] fn fee_and_crossover_generation() { @@ -258,3 +256,4 @@ fn transparent_from_fee_remainder_with_invalid_consumed() -> Result<(), Error> { Ok(()) } +*/ diff --git a/tests/transfer_gadget.rs b/tests/transfer_gadget.rs index 9358f2c..a85117c 100644 --- a/tests/transfer_gadget.rs +++ b/tests/transfer_gadget.rs @@ -6,149 +6,225 @@ use rand_core::{CryptoRng, OsRng, RngCore}; -use phoenix_core::transfer::{gadget, InputNote, OutputNote}; -use phoenix_core::{Note, PublicKey, SecretKey, ViewKey}; +use phoenix_core::transfer::{InputNote, OutputNote}; +use phoenix_core::{ + transfer::TransferCircuit, Note, PublicKey, SecretKey, ViewKey, +}; use poseidon_merkle::{Item, Tree}; use dusk_plonk::prelude::*; +#[macro_use] +extern crate lazy_static; + static LABEL: &[u8; 12] = b"dusk-network"; -const CAPACITY: usize = 15; // capacity required for the setup -const OUTPUT: usize = 2; -const H: usize = 2; -const A: usize = 4; -const I: usize = 2; - -#[derive(Debug)] -pub struct TransferCircuit { - input_notes: [InputNote; I], - output_notes: [OutputNote; OUTPUT], +const CAPACITY: usize = 17; // capacity required for the setup + +const HEIGHT: usize = 17; +const ARITY: usize = 4; + +struct TestingParameters { + pp: PublicParameters, + input_notes: [InputNote; 4], + output_notes: [OutputNote; 2], skeleton_hash: BlsScalar, - tree: Tree<(), H, A>, + root: BlsScalar, + crossover: u64, + max_fee: u64, } -impl Default for TransferCircuit { - fn default() -> Self { +lazy_static! { + static ref TP: TestingParameters = { + let pp = PublicParameters::setup(1 << CAPACITY, &mut OsRng).unwrap(); + let mut rng = OsRng; let sk = SecretKey::random(&mut rng); - let mut tree = Tree::<(), H, A>::new(); - - let input_note_1 = create_test_input_note(&mut tree, 0, &sk, &mut rng); - let input_note_2 = create_test_input_note(&mut tree, 1, &sk, &mut rng); + let mut tree = Tree::<(), HEIGHT, ARITY>::new(); - let input_notes = [input_note_1, input_note_2]; + let skeleton_hash = BlsScalar::from(1234u64); - let output_note_1 = create_test_output_note(&sk, &mut rng); - let output_note_2 = create_test_output_note(&sk, &mut rng); + // create and insert into the tree 4 testing notes + let input_notes = + create_test_input_notes::<4>(&mut tree, &sk, skeleton_hash, &mut rng); - let output_notes = [output_note_1, output_note_2]; + // retrieve the root from the tree after inserting the notes + let root = tree.root().hash; - let skeleton_hash = BlsScalar::default(); + // create 2 testing circuit output notes + let output_notes = [ + create_test_output_note(&sk, &mut rng), + create_test_output_note(&sk, &mut rng), + ]; - Self { - input_notes, - output_notes, - skeleton_hash, - tree, - } - } -} + let crossover = 0; + let max_fee = 0; -impl TransferCircuit { - pub fn new( - input_notes: [InputNote; I], - output_notes: [OutputNote; OUTPUT], - skeleton_hash: BlsScalar, - tree: Tree<(), H, A>, - ) -> Self { - Self { - input_notes, - output_notes, - skeleton_hash, - tree, - } - } -} - -impl Circuit for TransferCircuit { - fn circuit(&self, composer: &mut Composer) -> Result<(), Error> { - gadget::( - composer, - &self.input_notes, - &self.output_notes, - self.skeleton_hash, - &self.tree, - ); - Ok(()) - } + TestingParameters { pp, input_notes, output_notes, skeleton_hash, root, crossover, max_fee } + }; } -fn create_test_input_note( - tree: &mut Tree<(), H, A>, +fn create_and_insert_test_note( + tree: &mut Tree<(), HEIGHT, ARITY>, + pk: &PublicKey, pos: u64, - sk: &SecretKey, + value: u64, rng: &mut (impl RngCore + CryptoRng), -) -> InputNote { - let pk = PublicKey::from(sk); - let value = 25; - - let mut note = Note::transparent(rng, &pk, value); - +) -> Note { + let mut note = Note::transparent(rng, pk, value); note.set_pos(pos); let item = Item { hash: note.hash(), data: (), }; - tree.insert(*note.pos(), item); - let skeleton_hash = BlsScalar::from(1234u64); + note +} + +fn create_test_input_notes( + tree: &mut Tree<(), HEIGHT, ARITY>, + sk: &SecretKey, + skeleton_hash: BlsScalar, + rng: &mut (impl RngCore + CryptoRng), +) -> [InputNote; I] { + let pk = PublicKey::from(sk); + + let mut notes = Vec::new(); + for i in 0..I { + notes.push(create_and_insert_test_note( + tree, + &pk, + i.try_into().unwrap(), + 0, + rng, + )); + } - note.create_input_note(&sk, skeleton_hash, rng) + 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 = notes[i] + .create_input_note(merkle_opening, &sk, skeleton_hash, rng) + .expect("Note created properly."); + + input_notes.push(input_note); + } + + input_notes.try_into().unwrap() } fn create_test_output_note( sk: &SecretKey, rng: &mut (impl RngCore + CryptoRng), ) -> OutputNote { - let note = Note::transparent(rng, &PublicKey::from(sk), 25); + let note = Note::transparent(rng, &PublicKey::from(sk), 0); note.create_output_note(&ViewKey::from(sk)) + .expect("Note created properly.") } #[test] -fn test_transfer_circuit() { - let mut rng = OsRng; - let sk = SecretKey::random(&mut rng); +fn test_transfer_circuit_1_2() { + let (prover, verifier) = + Compiler::compile::>(&TP.pp, LABEL) + .expect("failed to compile circuit"); - let mut tree = Tree::<(), H, A>::new(); + let input_notes = [TP.input_notes[0].clone()]; - let input_note_1 = create_test_input_note(&mut tree, 0, &sk, &mut rng); - let input_note_2 = create_test_input_note(&mut tree, 1, &sk, &mut rng); + let (proof, public_inputs) = prover + .prove( + &mut OsRng, + &TransferCircuit::new( + input_notes, + TP.output_notes.clone(), + TP.skeleton_hash, + TP.root, + TP.crossover, + TP.max_fee, + ), + ) + .expect("failed to prove"); - let input_notes = [input_note_1, input_note_2]; + verifier + .verify(&proof, &public_inputs) + .expect("failed to verify proof"); +} + +#[test] +fn test_transfer_circuit_2_2() { + let (prover, verifier) = + Compiler::compile::>(&TP.pp, LABEL) + .expect("failed to compile circuit"); - let output_note_1 = create_test_output_note(&sk, &mut rng); - let output_note_2 = create_test_output_note(&sk, &mut rng); + let input_notes = [TP.input_notes[0].clone(), TP.input_notes[1].clone()]; - let output_notes = [output_note_1, output_note_2]; + let (proof, public_inputs) = prover + .prove( + &mut OsRng, + &TransferCircuit::new( + input_notes, + TP.output_notes.clone(), + TP.skeleton_hash, + TP.root, + TP.crossover, + TP.max_fee, + ), + ) + .expect("failed to prove"); - let skeleton_hash = BlsScalar::from(1234u64); + verifier + .verify(&proof, &public_inputs) + .expect("failed to verify proof"); +} - let pp = PublicParameters::setup(1 << CAPACITY, &mut OsRng).unwrap(); +#[test] +fn test_transfer_circuit_3_2() { + let (prover, verifier) = + Compiler::compile::>(&TP.pp, LABEL) + .expect("failed to compile circuit"); - let (prover, verifier) = Compiler::compile::(&pp, LABEL) - .expect("failed to compile circuit"); + let input_notes = [ + TP.input_notes[0].clone(), + TP.input_notes[1].clone(), + TP.input_notes[2].clone(), + ]; let (proof, public_inputs) = prover .prove( &mut OsRng, &TransferCircuit::new( input_notes, - output_notes, - skeleton_hash, - tree, + TP.output_notes.clone(), + TP.skeleton_hash, + TP.root, + TP.crossover, + TP.max_fee, + ), + ) + .expect("failed to prove"); + + verifier + .verify(&proof, &public_inputs) + .expect("failed to verify proof"); +} + +#[test] +fn test_transfer_circuit_4_2() { + let (prover, verifier) = + Compiler::compile::>(&TP.pp, LABEL) + .expect("failed to compile circuit"); + + let (proof, public_inputs) = prover + .prove( + &mut OsRng, + &TransferCircuit::new( + TP.input_notes.clone(), + TP.output_notes.clone(), + TP.skeleton_hash, + TP.root, + TP.crossover, + TP.max_fee, ), ) .expect("failed to prove");