plonky2-semaphore/src/recursion.rs
src/recursion.rsRaw
1use anyhow::Result;
2use plonky2::iop::witness::{PartialWitness, WitnessWrite};
3use plonky2::plonk::circuit_builder::CircuitBuilder;
4use plonky2::plonk::circuit_data::{CircuitConfig, VerifierCircuitData};
5use plonky2::plonk::proof::ProofWithPublicInputs;
6
7use crate::access_set::AccessSet;
8use crate::signal::{Digest, PlonkyProof, Signal, C, F};
9
10impl AccessSet {
11 pub fn aggregate_signals(
12 &self,
13 topic0: Digest,
14 signal0: Signal,
15 topic1: Digest,
16 signal1: Signal,
17 verifier_data: &VerifierCircuitData<F, C, 2>,
18 ) -> Result<(Digest, Digest, PlonkyProof, VerifierCircuitData<F, C, 2>)> {
19 let config = CircuitConfig::standard_recursion_zk_config();
20 let mut builder = CircuitBuilder::new(config);
21 let mut pw = PartialWitness::new();
22
23 let public_inputs0: Vec<F> = self
24 .0
25 .cap
26 .0
27 .iter()
28 .flat_map(|h| h.elements)
29 .chain(signal0.nullifier)
30 .chain(topic0)
31 .collect();
32 let public_inputs1: Vec<F> = self
33 .0
34 .cap
35 .0
36 .iter()
37 .flat_map(|h| h.elements)
38 .chain(signal1.nullifier)
39 .chain(topic1)
40 .collect();
41
42 // `add_virtual_proof_with_pis` is an extended version of the `add_virtual_target`, but
43 // that takes care of adding all the values of the proof and the public inputs (pis).
44 let proof_target0 = builder.add_virtual_proof_with_pis(&verifier_data.common);
45 // set the public inputs
46 builder.register_public_inputs(&proof_target0.public_inputs);
47 // `set_proof_with_pis_target` is an extended version of the `set_target`, but that takes
48 // care of adding all the values of the proof and the public inputs.
49 pw.set_proof_with_pis_target(
50 &proof_target0,
51 &ProofWithPublicInputs {
52 proof: signal0.proof,
53 public_inputs: public_inputs0,
54 },
55 )?;
56 // add & set the verifier data
57 let vd_target =
58 builder.add_virtual_verifier_data(verifier_data.common.config.fri_config.cap_height);
59 pw.set_verifier_data_target(&vd_target, &verifier_data.verifier_only)?;
60
61 // now, the same as we did with the proof0, with the proof1 related values:
62 let proof_target1 = builder.add_virtual_proof_with_pis(&verifier_data.common);
63 builder.register_public_inputs(&proof_target1.public_inputs);
64 pw.set_proof_with_pis_target(
65 &proof_target1,
66 &ProofWithPublicInputs {
67 proof: signal1.proof,
68 public_inputs: public_inputs1,
69 },
70 )?;
71 let vd_target =
72 builder.add_virtual_verifier_data(verifier_data.common.config.fri_config.cap_height);
73 pw.set_verifier_data_target(&vd_target, &verifier_data.verifier_only)?;
74
75 pw.set_cap_target(
76 &vd_target.constants_sigmas_cap,
77 &verifier_data.verifier_only.constants_sigmas_cap,
78 )?;
79
80 builder.verify_proof::<C>(&proof_target0, &vd_target, &verifier_data.common);
81 builder.verify_proof::<C>(&proof_target1, &vd_target, &verifier_data.common);
82
83 let data = builder.build();
84 let recursive_proof = data.prove(pw)?;
85
86 data.verify(recursive_proof.clone())?;
87
88 Ok((
89 signal0.nullifier,
90 signal1.nullifier,
91 recursive_proof.proof,
92 data.verifier_data(),
93 ))
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use anyhow::Result;
100 use plonky2::field::types::{Field, Sample};
101 use plonky2::hash::merkle_tree::MerkleTree;
102 use plonky2::hash::poseidon::PoseidonHash;
103 use plonky2::plonk::config::Hasher;
104 use plonky2::plonk::proof::ProofWithPublicInputs;
105
106 use crate::access_set::AccessSet;
107 use crate::signal::{Digest, F};
108
109 #[test]
110 fn test_recursion() -> Result<()> {
111 let n = 1 << 20;
112 let private_keys: Vec<Digest> = (0..n).map(|_| F::rand_array()).collect();
113 let public_keys: Vec<Vec<F>> = private_keys
114 .iter()
115 .map(|&sk| {
116 PoseidonHash::hash_no_pad(&[sk, [F::ZERO; 4]].concat())
117 .elements
118 .to_vec()
119 })
120 .collect();
121 let access_set = AccessSet(MerkleTree::new(public_keys, 0));
122
123 // first proof
124 let i0 = 12;
125 let topic0 = F::rand_array();
126 let (signal0, vd0) = access_set.make_signal(private_keys[i0], topic0, i0)?;
127 access_set.verify_signal(topic0, signal0.clone(), &vd0)?;
128
129 // second proof
130 let i1 = 42;
131 let topic1 = F::rand_array();
132 let (signal1, vd1) = access_set.make_signal(private_keys[i1], topic1, i1)?;
133 access_set.verify_signal(topic1, signal1.clone(), &vd1)?;
134
135 // generate recursive proof
136 let (nullifier0, nullifier1, recursive_proof, vd2) =
137 access_set.aggregate_signals(topic0, signal0, topic1, signal1, &vd0)?;
138
139 // verify recursive proof
140 let public_inputs: Vec<F> = access_set
141 .0
142 .cap
143 .0
144 .iter()
145 .flat_map(|h| h.elements)
146 .chain(nullifier0)
147 .chain(topic0)
148 .chain(access_set.0.cap.0.iter().flat_map(|h| h.elements))
149 .chain(nullifier1)
150 .chain(topic1)
151 .collect();
152
153 vd2.verify(ProofWithPublicInputs {
154 proof: recursive_proof,
155 public_inputs,
156 })?;
157 Ok(())
158 }
159}