├── .gitignore ├── src ├── lib.rs ├── signal.rs ├── access_set.rs ├── recursion.rs └── circuit.rs ├── README.md └── Cargo.toml /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod access_set; 2 | pub mod circuit; 3 | pub mod recursion; 4 | pub mod signal; 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Plonky2 implementation of the [Semaphore protocol](http://semaphore.appliedzkp.org/) 2 | 3 | Used as an example in the ZKHack Plonky2 presentation. 4 | 5 | ## Compilation 6 | ```bash 7 | rustup override set nightly # Requires nightly Rust 8 | cargo test --release 9 | ``` -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "plonky2-semaphore" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | plonky2 = { git = "https://github.com/mir-protocol/plonky2", branch = "semaphore-example" } 10 | anyhow = "1.0.56" -------------------------------------------------------------------------------- /src/signal.rs: -------------------------------------------------------------------------------- 1 | use plonky2::field::goldilocks_field::GoldilocksField; 2 | use plonky2::plonk::config::PoseidonGoldilocksConfig; 3 | use plonky2::plonk::proof::Proof; 4 | 5 | pub type F = GoldilocksField; 6 | pub type Digest = [F; 4]; 7 | pub type C = PoseidonGoldilocksConfig; 8 | pub type PlonkyProof = Proof; 9 | 10 | #[derive(Debug, Clone)] 11 | pub struct Signal { 12 | pub nullifier: Digest, 13 | pub proof: PlonkyProof, 14 | } 15 | 16 | #[cfg(test)] 17 | mod tests { 18 | use anyhow::Result; 19 | use plonky2::field::field_types::Field; 20 | use plonky2::hash::merkle_tree::MerkleTree; 21 | use plonky2::hash::poseidon::PoseidonHash; 22 | use plonky2::plonk::config::Hasher; 23 | 24 | use crate::access_set::AccessSet; 25 | use crate::signal::{Digest, F}; 26 | 27 | #[test] 28 | fn test_semaphore() -> Result<()> { 29 | let n = 1 << 20; 30 | let private_keys: Vec = (0..n).map(|_| F::rand_arr()).collect(); 31 | let public_keys: Vec> = private_keys 32 | .iter() 33 | .map(|&sk| { 34 | PoseidonHash::hash_no_pad(&[sk, [F::ZERO; 4]].concat()) 35 | .elements 36 | .to_vec() 37 | }) 38 | .collect(); 39 | let access_set = AccessSet(MerkleTree::new(public_keys, 0)); 40 | 41 | let i = 12; 42 | let topic = F::rand_arr(); 43 | 44 | let (signal, vd) = access_set.make_signal(private_keys[i], topic, i)?; 45 | access_set.verify_signal(topic, signal, &vd) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/access_set.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use plonky2::hash::merkle_tree::MerkleTree; 3 | use plonky2::hash::poseidon::PoseidonHash; 4 | use plonky2::iop::witness::PartialWitness; 5 | use plonky2::plonk::circuit_builder::CircuitBuilder; 6 | use plonky2::plonk::circuit_data::{CircuitConfig, VerifierCircuitData}; 7 | use plonky2::plonk::config::Hasher; 8 | use plonky2::plonk::proof::ProofWithPublicInputs; 9 | 10 | use crate::signal::{Digest, Signal, C, F}; 11 | 12 | pub struct AccessSet(pub MerkleTree); 13 | 14 | impl AccessSet { 15 | pub fn verify_signal( 16 | &self, 17 | topic: Digest, 18 | signal: Signal, 19 | verifier_data: &VerifierCircuitData, 20 | ) -> Result<()> { 21 | let public_inputs: Vec = self 22 | .0 23 | .cap 24 | .0 25 | .iter() 26 | .flat_map(|h| h.elements) 27 | .chain(signal.nullifier) 28 | .chain(topic) 29 | .collect(); 30 | 31 | verifier_data.verify(ProofWithPublicInputs { 32 | proof: signal.proof, 33 | public_inputs, 34 | }) 35 | } 36 | 37 | pub fn make_signal( 38 | &self, 39 | private_key: Digest, 40 | topic: Digest, 41 | public_key_index: usize, 42 | ) -> Result<(Signal, VerifierCircuitData)> { 43 | let nullifier = PoseidonHash::hash_no_pad(&[private_key, topic].concat()).elements; 44 | let config = CircuitConfig::standard_recursion_zk_config(); 45 | let mut builder = CircuitBuilder::new(config); 46 | let mut pw = PartialWitness::new(); 47 | 48 | let targets = self.semaphore_circuit(&mut builder); 49 | self.fill_semaphore_targets(&mut pw, private_key, topic, public_key_index, targets); 50 | 51 | let data = builder.build(); 52 | let proof = data.prove(pw)?; 53 | 54 | Ok(( 55 | Signal { 56 | nullifier, 57 | proof: proof.proof, 58 | }, 59 | data.to_verifier_data(), 60 | )) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/recursion.rs: -------------------------------------------------------------------------------- 1 | use plonky2::iop::witness::{PartialWitness, Witness}; 2 | use plonky2::plonk::circuit_builder::CircuitBuilder; 3 | use plonky2::plonk::circuit_data::{CircuitConfig, VerifierCircuitData, VerifierCircuitTarget}; 4 | use plonky2::plonk::proof::ProofWithPublicInputs; 5 | 6 | use crate::access_set::AccessSet; 7 | use crate::signal::{Digest, PlonkyProof, Signal, C, F}; 8 | 9 | impl AccessSet { 10 | pub fn aggregate_signals( 11 | &self, 12 | topic0: Digest, 13 | signal0: Signal, 14 | topic1: Digest, 15 | signal1: Signal, 16 | verifier_data: &VerifierCircuitData, 17 | ) -> (Digest, Digest, PlonkyProof) { 18 | let config = CircuitConfig::standard_recursion_zk_config(); 19 | let mut builder = CircuitBuilder::new(config); 20 | let mut pw = PartialWitness::new(); 21 | 22 | let public_inputs0: Vec = self 23 | .0 24 | .cap 25 | .0 26 | .iter() 27 | .flat_map(|h| h.elements) 28 | .chain(signal0.nullifier) 29 | .chain(topic0) 30 | .collect(); 31 | let public_inputs1: Vec = self 32 | .0 33 | .cap 34 | .0 35 | .iter() 36 | .flat_map(|h| h.elements) 37 | .chain(signal1.nullifier) 38 | .chain(topic1) 39 | .collect(); 40 | 41 | let proof_target0 = builder.add_virtual_proof_with_pis(&verifier_data.common); 42 | pw.set_proof_with_pis_target( 43 | &proof_target0, 44 | &ProofWithPublicInputs { 45 | proof: signal0.proof, 46 | public_inputs: public_inputs0, 47 | }, 48 | ); 49 | let proof_target1 = builder.add_virtual_proof_with_pis(&verifier_data.common); 50 | pw.set_proof_with_pis_target( 51 | &proof_target1, 52 | &ProofWithPublicInputs { 53 | proof: signal1.proof, 54 | public_inputs: public_inputs1, 55 | }, 56 | ); 57 | 58 | let vd_target = VerifierCircuitTarget { 59 | constants_sigmas_cap: builder 60 | .add_virtual_cap(verifier_data.common.config.fri_config.cap_height), 61 | }; 62 | pw.set_cap_target( 63 | &vd_target.constants_sigmas_cap, 64 | &verifier_data.verifier_only.constants_sigmas_cap, 65 | ); 66 | 67 | builder.verify_proof(proof_target0, &vd_target, &verifier_data.common); 68 | builder.verify_proof(proof_target1, &vd_target, &verifier_data.common); 69 | 70 | let data = builder.build(); 71 | let recursive_proof = data.prove(pw).unwrap(); 72 | 73 | data.verify(recursive_proof.clone()).unwrap(); 74 | 75 | (signal0.nullifier, signal1.nullifier, recursive_proof.proof) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/circuit.rs: -------------------------------------------------------------------------------- 1 | use plonky2::field::field_types::Field; 2 | use plonky2::hash::hash_types::{HashOutTarget, MerkleCapTarget}; 3 | use plonky2::hash::merkle_proofs::MerkleProofTarget; 4 | use plonky2::hash::poseidon::PoseidonHash; 5 | use plonky2::iop::target::Target; 6 | use plonky2::iop::witness::{PartialWitness, Witness}; 7 | use plonky2::plonk::circuit_builder::CircuitBuilder; 8 | 9 | use crate::access_set::AccessSet; 10 | use crate::signal::{Digest, F}; 11 | 12 | pub struct SemaphoreTargets { 13 | merkle_root: HashOutTarget, 14 | topic: [Target; 4], 15 | merkle_proof: MerkleProofTarget, 16 | private_key: [Target; 4], 17 | public_key_index: Target, 18 | } 19 | 20 | impl AccessSet { 21 | pub fn tree_height(&self) -> usize { 22 | self.0.leaves.len().trailing_zeros() as usize 23 | } 24 | 25 | pub fn semaphore_circuit(&self, builder: &mut CircuitBuilder) -> SemaphoreTargets { 26 | // Register public inputs. 27 | let merkle_root = builder.add_virtual_hash(); 28 | builder.register_public_inputs(&merkle_root.elements); 29 | let nullifier = builder.add_virtual_hash(); 30 | builder.register_public_inputs(&nullifier.elements); 31 | let topic: [Target; 4] = builder.add_virtual_targets(4).try_into().unwrap(); 32 | builder.register_public_inputs(&topic); 33 | 34 | // Merkle proof 35 | let merkle_proof = MerkleProofTarget { 36 | siblings: builder.add_virtual_hashes(self.tree_height()), 37 | }; 38 | 39 | // Verify public key Merkle proof. 40 | let private_key: [Target; 4] = builder.add_virtual_targets(4).try_into().unwrap(); 41 | let public_key_index = builder.add_virtual_target(); 42 | let public_key_index_bits = builder.split_le(public_key_index, self.tree_height()); 43 | let zero = builder.zero(); 44 | builder.verify_merkle_proof::( 45 | [private_key, [zero; 4]].concat(), 46 | &public_key_index_bits, 47 | &MerkleCapTarget(vec![merkle_root]), 48 | &merkle_proof, 49 | ); 50 | 51 | // Check nullifier. 52 | let should_be_nullifier = 53 | builder.hash_n_to_hash_no_pad::([private_key, topic].concat()); 54 | for i in 0..4 { 55 | builder.connect(nullifier.elements[i], should_be_nullifier.elements[i]); 56 | } 57 | 58 | SemaphoreTargets { 59 | merkle_root, 60 | topic, 61 | merkle_proof, 62 | private_key, 63 | public_key_index, 64 | } 65 | } 66 | 67 | pub fn fill_semaphore_targets( 68 | &self, 69 | pw: &mut PartialWitness, 70 | private_key: Digest, 71 | topic: Digest, 72 | public_key_index: usize, 73 | targets: SemaphoreTargets, 74 | ) { 75 | let SemaphoreTargets { 76 | merkle_root, 77 | topic: topic_target, 78 | merkle_proof: merkle_proof_target, 79 | private_key: private_key_target, 80 | public_key_index: public_key_index_target, 81 | } = targets; 82 | 83 | pw.set_hash_target(merkle_root, self.0.cap.0[0]); 84 | pw.set_targets(&private_key_target, &private_key); 85 | pw.set_targets(&topic_target, &topic); 86 | pw.set_target( 87 | public_key_index_target, 88 | F::from_canonical_usize(public_key_index), 89 | ); 90 | 91 | let merkle_proof = self.0.prove(public_key_index); 92 | for (ht, h) in merkle_proof_target 93 | .siblings 94 | .into_iter() 95 | .zip(merkle_proof.siblings) 96 | { 97 | pw.set_hash_target(ht, h); 98 | } 99 | } 100 | } 101 | --------------------------------------------------------------------------------