Skip to content

Commit 6ab0bc5

Browse files
authored
recursion circuit's verifier_data_hash include constant_sigmas_cap in the hash, and add explanation (#288)
1 parent 462aaee commit 6ab0bc5

File tree

2 files changed

+71
-17
lines changed

2 files changed

+71
-17
lines changed

src/backends/plonky2/basetypes.rs

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -68,38 +68,45 @@ pub static DEFAULT_VD_SET: LazyLock<VDSet> = LazyLock::new(|| {
6868
#[derive(Clone, Debug)]
6969
pub struct VDSet {
7070
root: Hash,
71-
// (verifier_data, merkleproof)
71+
// (verifier_data's hash, merkleproof)
7272
proofs_map: HashMap<HashOut<F>, MerkleClaimAndProof>,
7373
}
7474
impl VDSet {
7575
/// builds the verifier_datas tree, and returns the root and the proofs
7676
pub fn new(tree_depth: usize, vds: &[VerifierOnlyCircuitData]) -> Result<Self> {
77-
// first of all, sort the vds, so that each set of verifier_datas gets
78-
// the same root
79-
let vds: Vec<&VerifierOnlyCircuitData> = vds
77+
// compute the verifier_data's hashes
78+
let vds_hashes: Vec<HashOut<F>> = vds
8079
.iter()
81-
.sorted_by_key(|vd| RawValue(vd.circuit_digest.elements))
80+
.map(crate::backends::plonky2::recursion::circuit::hash_verifier_data)
81+
.collect::<Vec<_>>();
82+
83+
// before using the hash values, sort them, so that each set of
84+
// verifier_datas gets the same VDSet root
85+
let vds_hashes: Vec<&HashOut<F>> = vds_hashes
86+
.iter()
87+
.sorted_by_key(|vd| RawValue(vd.elements))
8288
.collect::<Vec<_>>();
8389

8490
let array = Array::new(
8591
tree_depth,
86-
vds.iter()
87-
.map(|vd| Value::from(RawValue(vd.circuit_digest.elements)))
92+
vds_hashes
93+
.iter()
94+
.map(|vd| Value::from(RawValue(vd.elements)))
8895
.collect(),
8996
)?;
9097

9198
let root = array.commitment();
9299
let mut proofs_map = HashMap::<HashOut<F>, MerkleClaimAndProof>::new();
93100

94-
for (i, vd) in vds.iter().enumerate() {
101+
for (i, vd) in vds_hashes.iter().enumerate() {
95102
let (value, proof) = array.prove(i)?;
96103
let p = MerkleClaimAndProof {
97104
root,
98105
key: RawValue::from(i as i64),
99106
value: value.raw(),
100107
proof,
101108
};
102-
proofs_map.insert(vd.circuit_digest, p);
109+
proofs_map.insert(**vd, p);
103110
}
104111
Ok(Self { root, proofs_map })
105112
}
@@ -113,12 +120,12 @@ impl VDSet {
113120
) -> Result<Vec<MerkleClaimAndProof>> {
114121
let mut proofs: Vec<MerkleClaimAndProof> = vec![];
115122
for vd in vds {
116-
let p =
117-
self.proofs_map
118-
.get(&vd.circuit_digest)
119-
.ok_or(crate::middleware::Error::custom(
120-
"verifier_data not found in VDSet".to_string(),
121-
))?;
123+
let p = self
124+
.proofs_map
125+
.get(&crate::backends::plonky2::recursion::circuit::hash_verifier_data(vd))
126+
.ok_or(crate::middleware::Error::custom(
127+
"verifier_data not found in VDSet".to_string(),
128+
))?;
122129
proofs.push(p.clone());
123130
}
124131
Ok(proofs)

src/backends/plonky2/recursion/circuit.rs

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ use plonky2::{
1313
self,
1414
field::{extension::quintic::QuinticExtension, types::Field},
1515
gates::{gate::GateRef, noop::NoopGate},
16-
hash::hash_types::HashOutTarget,
16+
hash::{
17+
hash_types::{HashOut, HashOutTarget},
18+
poseidon::PoseidonHash,
19+
},
1720
iop::{
1821
target::Target,
1922
witness::{PartialWitness, WitnessWrite},
@@ -24,6 +27,7 @@ use plonky2::{
2427
CircuitConfig, CircuitData, CommonCircuitData, ProverCircuitData, VerifierCircuitData,
2528
VerifierCircuitTarget, VerifierOnlyCircuitData,
2629
},
30+
config::Hasher,
2731
proof::{ProofWithPublicInputs, ProofWithPublicInputsTarget},
2832
},
2933
util::log2_ceil,
@@ -201,7 +205,18 @@ impl<I: InnerCircuit> RecursiveCircuit<I> {
201205
let verified_proofs = (0..arity)
202206
.map(|i| VerifiedProofTarget {
203207
public_inputs: proofs_targ[i].public_inputs.clone(),
204-
verifier_data_hash: verifier_datas_targ[i].circuit_digest,
208+
// note: here we're hashing the verifier_data as Hash(vd.circuit_digest,
209+
// vd.constant_sigmas_cap), despite the circuit_digest is already a hash containing
210+
// the constant_sigmas_cap. Conceptually we would use the circuit_digest as the hash
211+
// of the verifier_data, but unfortunately, the recursion verification circuit does
212+
// not ensure this link. Alternatively we could calculate an modified
213+
// circuit_digest, hashing as in the original plonky2's circuit_digest but
214+
// additionally checking it in-circuit. But since in terms of circuit costs would
215+
// require a hash (with similar amount of elements), the approach that we do is take
216+
// the already computed circuit_digest and hash it together with the
217+
// constant_sigmas_cap, doing the same computation in-circuit, obtaining a new hash
218+
// that we use to represent the verifier_data.
219+
verifier_data_hash: hash_verifier_data_gadget(builder, &verifier_datas_targ[i]),
205220
})
206221
.collect_vec();
207222

@@ -478,6 +493,38 @@ pub fn pad_circuit(builder: &mut CircuitBuilder<F, D>, common_data: &CommonCircu
478493
}
479494
}
480495

496+
fn hash_verifier_data_gadget(
497+
builder: &mut CircuitBuilder<F, D>,
498+
verifier_data: &VerifierCircuitTarget,
499+
) -> HashOutTarget {
500+
let f: Vec<Target> = [
501+
verifier_data.circuit_digest.elements.to_vec(),
502+
verifier_data
503+
.constants_sigmas_cap
504+
.0
505+
.iter()
506+
.flat_map(|e| e.elements)
507+
.collect(),
508+
]
509+
.concat();
510+
builder.hash_n_to_hash_no_pad::<PoseidonHash>(f)
511+
}
512+
513+
// compatible with hash_verifier_data_gadget.
514+
pub(crate) fn hash_verifier_data(verifier_only_data: &VerifierOnlyCircuitData<C, D>) -> HashOut<F> {
515+
let f: Vec<F> = [
516+
verifier_only_data.circuit_digest.elements.to_vec(),
517+
verifier_only_data
518+
.constants_sigmas_cap
519+
.0
520+
.iter()
521+
.flat_map(|e| e.elements)
522+
.collect(),
523+
]
524+
.concat();
525+
PoseidonHash::hash_no_pad(&f)
526+
}
527+
481528
#[cfg(test)]
482529
mod tests {
483530
use std::time::Instant;

0 commit comments

Comments
 (0)