@ -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 |
|||
``` |
@ -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(),
|
|||
))
|
|||
}
|
|||
}
|
@ -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);
|
|||
}
|
|||
}
|
|||
}
|
@ -1 +1,4 @@ |
|||
pub mod semaphore;
|
|||
pub mod access_set;
|
|||
pub mod circuit;
|
|||
pub mod recursion;
|
|||
pub mod signal;
|
@ -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)
|
|||
}
|
|||
}
|
@ -1 +0,0 @@ |
|||
|
@ -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)
|
|||
}
|
|||
}
|