From 029ecf228e4d250d153acc811716a74449bd2e02 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Tue, 8 Mar 2022 17:04:12 +0100 Subject: [PATCH] Progress --- Cargo.toml | 3 +- README.md | 8 +++- src/access_set.rs | 62 ++++++++++++++++++++++++++++ src/circuit.rs | 100 ++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 5 ++- src/recursion.rs | 77 +++++++++++++++++++++++++++++++++++ src/semaphore.rs | 1 - src/signal.rs | 47 ++++++++++++++++++++++ 8 files changed, 299 insertions(+), 4 deletions(-) create mode 100644 src/access_set.rs create mode 100644 src/circuit.rs create mode 100644 src/recursion.rs delete mode 100644 src/semaphore.rs create mode 100644 src/signal.rs diff --git a/Cargo.toml b/Cargo.toml index 4579e9e..6cb5a77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -plonky2 = {git = "https://github.com/mir-protocol/plonky2"} \ No newline at end of file +plonky2 = { git = "https://github.com/mir-protocol/plonky2", branch = "semaphore-example" } +anyhow = "1.0.56" \ No newline at end of file diff --git a/README.md b/README.md index cccc1e6..7d67453 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ # Plonky2 implementation of the [Semaphore protocol](http://semaphore.appliedzkp.org/) -Used as an example in the ZKHack Plonky2 presentation. \ No newline at end of file +Used as an example in the ZKHack Plonky2 presentation. + +## Compilation +```bash +rustup override set nightly # Requires nightly Rust +cargo test --release +``` \ No newline at end of file diff --git a/src/access_set.rs b/src/access_set.rs new file mode 100644 index 0000000..ed42fa9 --- /dev/null +++ b/src/access_set.rs @@ -0,0 +1,62 @@ +use anyhow::Result; +use plonky2::hash::merkle_tree::MerkleTree; +use plonky2::hash::poseidon::PoseidonHash; +use plonky2::iop::witness::PartialWitness; +use plonky2::plonk::circuit_builder::CircuitBuilder; +use plonky2::plonk::circuit_data::{CircuitConfig, VerifierCircuitData}; +use plonky2::plonk::config::Hasher; +use plonky2::plonk::proof::ProofWithPublicInputs; + +use crate::signal::{Digest, Signal, C, F}; + +pub struct AccessSet(pub MerkleTree); + +impl AccessSet { + pub fn verify_signal( + &self, + topic: Digest, + signal: Signal, + verifier_data: &VerifierCircuitData, + ) -> Result<()> { + let public_inputs: Vec = self + .0 + .cap + .0 + .iter() + .flat_map(|h| h.elements) + .chain(signal.nullifier) + .chain(topic) + .collect(); + + verifier_data.verify(ProofWithPublicInputs { + proof: signal.proof, + public_inputs, + }) + } + + pub fn make_signal( + &self, + private_key: Digest, + topic: Digest, + public_key_index: usize, + ) -> Result<(Signal, VerifierCircuitData)> { + let nullifier = PoseidonHash::hash_no_pad(&[private_key, topic].concat()).elements; + let config = CircuitConfig::standard_recursion_zk_config(); + let mut builder = CircuitBuilder::new(config); + let mut pw = PartialWitness::new(); + + let targets = self.semaphore_circuit(&mut builder); + self.fill_semaphore_targets(&mut pw, private_key, topic, public_key_index, targets); + + let data = builder.build(); + let proof = data.prove(pw)?; + + Ok(( + Signal { + nullifier, + proof: proof.proof, + }, + data.to_verifier_data(), + )) + } +} diff --git a/src/circuit.rs b/src/circuit.rs new file mode 100644 index 0000000..f6a7258 --- /dev/null +++ b/src/circuit.rs @@ -0,0 +1,100 @@ +use plonky2::field::field_types::Field; +use plonky2::hash::hash_types::{HashOutTarget, MerkleCapTarget}; +use plonky2::hash::merkle_proofs::MerkleProofTarget; +use plonky2::hash::poseidon::PoseidonHash; +use plonky2::iop::target::Target; +use plonky2::iop::witness::{PartialWitness, Witness}; +use plonky2::plonk::circuit_builder::CircuitBuilder; + +use crate::access_set::AccessSet; +use crate::signal::{Digest, F}; + +pub struct SemaphoreTargets { + merkle_root: HashOutTarget, + topic: [Target; 4], + merkle_proof: MerkleProofTarget, + private_key: [Target; 4], + public_key_index: Target, +} + +impl AccessSet { + pub fn tree_height(&self) -> usize { + self.0.leaves.len().trailing_zeros() as usize + } + + pub fn semaphore_circuit(&self, builder: &mut CircuitBuilder) -> SemaphoreTargets { + // Register public inputs. + let merkle_root = builder.add_virtual_hash(); + builder.register_public_inputs(&merkle_root.elements); + let nullifier = builder.add_virtual_hash(); + builder.register_public_inputs(&nullifier.elements); + let topic: [Target; 4] = builder.add_virtual_targets(4).try_into().unwrap(); + builder.register_public_inputs(&topic); + + // Merkle proof + let merkle_proof = MerkleProofTarget { + siblings: builder.add_virtual_hashes(self.tree_height()), + }; + + // Verify public key Merkle proof. + let private_key: [Target; 4] = builder.add_virtual_targets(4).try_into().unwrap(); + let public_key_index = builder.add_virtual_target(); + let public_key_index_bits = builder.split_le(public_key_index, self.tree_height()); + let zero = builder.zero(); + builder.verify_merkle_proof::( + [private_key, [zero; 4]].concat(), + &public_key_index_bits, + &MerkleCapTarget(vec![merkle_root]), + &merkle_proof, + ); + + // Check nullifier. + let should_be_nullifier = + builder.hash_n_to_hash_no_pad::([private_key, topic].concat()); + for i in 0..4 { + builder.connect(nullifier.elements[i], should_be_nullifier.elements[i]); + } + + SemaphoreTargets { + merkle_root, + topic, + merkle_proof, + private_key, + public_key_index, + } + } + + pub fn fill_semaphore_targets( + &self, + pw: &mut PartialWitness, + private_key: Digest, + topic: Digest, + public_key_index: usize, + targets: SemaphoreTargets, + ) { + let SemaphoreTargets { + merkle_root, + topic: topic_target, + merkle_proof: merkle_proof_target, + private_key: private_key_target, + public_key_index: public_key_index_target, + } = targets; + + pw.set_hash_target(merkle_root, self.0.cap.0[0]); + pw.set_targets(&private_key_target, &private_key); + pw.set_targets(&topic_target, &topic); + pw.set_target( + public_key_index_target, + F::from_canonical_usize(public_key_index), + ); + + let merkle_proof = self.0.prove(public_key_index); + for (ht, h) in merkle_proof_target + .siblings + .into_iter() + .zip(merkle_proof.siblings) + { + pw.set_hash_target(ht, h); + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 3ded4e8..a8ef08b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1 +1,4 @@ -pub mod semaphore; +pub mod access_set; +pub mod circuit; +pub mod recursion; +pub mod signal; diff --git a/src/recursion.rs b/src/recursion.rs new file mode 100644 index 0000000..e90a478 --- /dev/null +++ b/src/recursion.rs @@ -0,0 +1,77 @@ +use plonky2::iop::witness::{PartialWitness, Witness}; +use plonky2::plonk::circuit_builder::CircuitBuilder; +use plonky2::plonk::circuit_data::{CircuitConfig, VerifierCircuitData, VerifierCircuitTarget}; +use plonky2::plonk::proof::ProofWithPublicInputs; + +use crate::access_set::AccessSet; +use crate::signal::{Digest, PlonkyProof, Signal, C, F}; + +impl AccessSet { + pub fn aggregate_signals( + &self, + topic0: Digest, + signal0: Signal, + topic1: Digest, + signal1: Signal, + verifier_data: &VerifierCircuitData, + ) -> (Digest, Digest, PlonkyProof) { + let config = CircuitConfig::standard_recursion_zk_config(); + let mut builder = CircuitBuilder::new(config); + let mut pw = PartialWitness::new(); + + let public_inputs0: Vec = self + .0 + .cap + .0 + .iter() + .flat_map(|h| h.elements) + .chain(signal0.nullifier) + .chain(topic0) + .collect(); + let public_inputs1: Vec = self + .0 + .cap + .0 + .iter() + .flat_map(|h| h.elements) + .chain(signal1.nullifier) + .chain(topic1) + .collect(); + + let proof_target0 = builder.add_virtual_proof_with_pis(&verifier_data.common); + pw.set_proof_with_pis_target( + &proof_target0, + &ProofWithPublicInputs { + proof: signal0.proof, + public_inputs: public_inputs0, + }, + ); + let proof_target1 = builder.add_virtual_proof_with_pis(&verifier_data.common); + pw.set_proof_with_pis_target( + &proof_target1, + &ProofWithPublicInputs { + proof: signal1.proof, + public_inputs: public_inputs1, + }, + ); + + let vd_target = VerifierCircuitTarget { + constants_sigmas_cap: builder + .add_virtual_cap(verifier_data.common.config.fri_config.cap_height), + }; + pw.set_cap_target( + &vd_target.constants_sigmas_cap, + &verifier_data.verifier_only.constants_sigmas_cap, + ); + + builder.verify_proof(proof_target0, &vd_target, &verifier_data.common); + builder.verify_proof(proof_target1, &vd_target, &verifier_data.common); + + let data = builder.build(); + let recursive_proof = data.prove(pw).unwrap(); + + data.verify(recursive_proof.clone()).unwrap(); + + (signal0.nullifier, signal1.nullifier, recursive_proof.proof) + } +} diff --git a/src/semaphore.rs b/src/semaphore.rs deleted file mode 100644 index 8b13789..0000000 --- a/src/semaphore.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/signal.rs b/src/signal.rs new file mode 100644 index 0000000..846940e --- /dev/null +++ b/src/signal.rs @@ -0,0 +1,47 @@ +use plonky2::field::goldilocks_field::GoldilocksField; +use plonky2::plonk::config::PoseidonGoldilocksConfig; +use plonky2::plonk::proof::Proof; + +pub type F = GoldilocksField; +pub type Digest = [F; 4]; +pub type C = PoseidonGoldilocksConfig; +pub type PlonkyProof = Proof; + +#[derive(Debug, Clone)] +pub struct Signal { + pub nullifier: Digest, + pub proof: PlonkyProof, +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + use plonky2::field::field_types::Field; + use plonky2::hash::merkle_tree::MerkleTree; + use plonky2::hash::poseidon::PoseidonHash; + use plonky2::plonk::config::Hasher; + + use crate::access_set::AccessSet; + use crate::signal::{Digest, F}; + + #[test] + fn test_semaphore() -> Result<()> { + let n = 1 << 20; + let private_keys: Vec = (0..n).map(|_| F::rand_arr()).collect(); + let public_keys: Vec> = private_keys + .iter() + .map(|&sk| { + PoseidonHash::hash_no_pad(&[sk, [F::ZERO; 4]].concat()) + .elements + .to_vec() + }) + .collect(); + let access_set = AccessSet(MerkleTree::new(public_keys, 0)); + + let i = 12; + let topic = F::rand_arr(); + + let (signal, vd) = access_set.make_signal(private_keys[i], topic, i)?; + access_set.verify_signal(topic, signal, &vd) + } +}