arnaucube
arnaucube/plonky2-semaphorePublic Self-hosted repository
Progress
wborgeaud williamborgeaud@gmail.com
Tue, 08 Mar 2022 17:04:12 +0100
commit

029ecf228e4d250d153acc811716a74449bd2e02

parent

3509a39756b563f7819f641e095d30534d0a8cb7

8 files changed, 299 insertions(+), 4 deletions(-)

jump to
M Cargo.toml → Cargo.toml

@@ -6,4 +6,5 @@

# 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"}+plonky2 = { git = "https://github.com/mir-protocol/plonky2", branch = "semaphore-example" } +anyhow = "1.0.56"
M README.md → 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.+Used as an example in the ZKHack Plonky2 presentation. + +## Compilation +```bash +rustup override set nightly # Requires nightly Rust +cargo test --release +```
A 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<F, PoseidonHash>); + +impl AccessSet { + pub fn verify_signal( + &self, + topic: Digest, + signal: Signal, + verifier_data: &VerifierCircuitData<F, C, 2>, + ) -> Result<()> { + let public_inputs: Vec<F> = 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<F, C, 2>)> { + 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(), + )) + } +}
A 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<F, 2>) -> 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::<PoseidonHash>( + [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::<PoseidonHash>([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<F>, + 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); + } + } +}
M src/lib.rs → src/lib.rs

@@ -1,1 +1,4 @@

-pub mod semaphore; +pub mod access_set; +pub mod circuit; +pub mod recursion; +pub mod signal;
A 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<F, C, 2>, + ) -> (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<F> = self + .0 + .cap + .0 + .iter() + .flat_map(|h| h.elements) + .chain(signal0.nullifier) + .chain(topic0) + .collect(); + let public_inputs1: Vec<F> = 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) + } +}
D src/semaphore.rs

@@ -1,1 +0,0 @@

-
A 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<F, PoseidonGoldilocksConfig, 2>; + +#[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<Digest> = (0..n).map(|_| F::rand_arr()).collect(); + let public_keys: Vec<Vec<F>> = 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) + } +}