recursion circuit's verifier_data_hash include constant_sigmas_cap in the hash, and add explanation (#288)

This commit is contained in:
arnaucube 2025-06-17 10:49:04 +02:00 committed by GitHub
parent 462aaee061
commit 6ab0bc52fc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 71 additions and 17 deletions

View file

@ -68,30 +68,37 @@ pub static DEFAULT_VD_SET: LazyLock<VDSet> = LazyLock::new(|| {
#[derive(Clone, Debug)]
pub struct VDSet {
root: Hash,
// (verifier_data, merkleproof)
// (verifier_data's hash, merkleproof)
proofs_map: HashMap<HashOut<F>, MerkleClaimAndProof>,
}
impl VDSet {
/// builds the verifier_datas tree, and returns the root and the proofs
pub fn new(tree_depth: usize, vds: &[VerifierOnlyCircuitData]) -> Result<Self> {
// first of all, sort the vds, so that each set of verifier_datas gets
// the same root
let vds: Vec<&VerifierOnlyCircuitData> = vds
// compute the verifier_data's hashes
let vds_hashes: Vec<HashOut<F>> = vds
.iter()
.sorted_by_key(|vd| RawValue(vd.circuit_digest.elements))
.map(crate::backends::plonky2::recursion::circuit::hash_verifier_data)
.collect::<Vec<_>>();
// before using the hash values, sort them, so that each set of
// verifier_datas gets the same VDSet root
let vds_hashes: Vec<&HashOut<F>> = vds_hashes
.iter()
.sorted_by_key(|vd| RawValue(vd.elements))
.collect::<Vec<_>>();
let array = Array::new(
tree_depth,
vds.iter()
.map(|vd| Value::from(RawValue(vd.circuit_digest.elements)))
vds_hashes
.iter()
.map(|vd| Value::from(RawValue(vd.elements)))
.collect(),
)?;
let root = array.commitment();
let mut proofs_map = HashMap::<HashOut<F>, MerkleClaimAndProof>::new();
for (i, vd) in vds.iter().enumerate() {
for (i, vd) in vds_hashes.iter().enumerate() {
let (value, proof) = array.prove(i)?;
let p = MerkleClaimAndProof {
root,
@ -99,7 +106,7 @@ impl VDSet {
value: value.raw(),
proof,
};
proofs_map.insert(vd.circuit_digest, p);
proofs_map.insert(**vd, p);
}
Ok(Self { root, proofs_map })
}
@ -113,9 +120,9 @@ impl VDSet {
) -> Result<Vec<MerkleClaimAndProof>> {
let mut proofs: Vec<MerkleClaimAndProof> = vec![];
for vd in vds {
let p =
self.proofs_map
.get(&vd.circuit_digest)
let p = self
.proofs_map
.get(&crate::backends::plonky2::recursion::circuit::hash_verifier_data(vd))
.ok_or(crate::middleware::Error::custom(
"verifier_data not found in VDSet".to_string(),
))?;

View file

@ -13,7 +13,10 @@ use plonky2::{
self,
field::{extension::quintic::QuinticExtension, types::Field},
gates::{gate::GateRef, noop::NoopGate},
hash::hash_types::HashOutTarget,
hash::{
hash_types::{HashOut, HashOutTarget},
poseidon::PoseidonHash,
},
iop::{
target::Target,
witness::{PartialWitness, WitnessWrite},
@ -24,6 +27,7 @@ use plonky2::{
CircuitConfig, CircuitData, CommonCircuitData, ProverCircuitData, VerifierCircuitData,
VerifierCircuitTarget, VerifierOnlyCircuitData,
},
config::Hasher,
proof::{ProofWithPublicInputs, ProofWithPublicInputsTarget},
},
util::log2_ceil,
@ -201,7 +205,18 @@ impl<I: InnerCircuit> RecursiveCircuit<I> {
let verified_proofs = (0..arity)
.map(|i| VerifiedProofTarget {
public_inputs: proofs_targ[i].public_inputs.clone(),
verifier_data_hash: verifier_datas_targ[i].circuit_digest,
// note: here we're hashing the verifier_data as Hash(vd.circuit_digest,
// vd.constant_sigmas_cap), despite the circuit_digest is already a hash containing
// the constant_sigmas_cap. Conceptually we would use the circuit_digest as the hash
// of the verifier_data, but unfortunately, the recursion verification circuit does
// not ensure this link. Alternatively we could calculate an modified
// circuit_digest, hashing as in the original plonky2's circuit_digest but
// additionally checking it in-circuit. But since in terms of circuit costs would
// require a hash (with similar amount of elements), the approach that we do is take
// the already computed circuit_digest and hash it together with the
// constant_sigmas_cap, doing the same computation in-circuit, obtaining a new hash
// that we use to represent the verifier_data.
verifier_data_hash: hash_verifier_data_gadget(builder, &verifier_datas_targ[i]),
})
.collect_vec();
@ -478,6 +493,38 @@ pub fn pad_circuit(builder: &mut CircuitBuilder<F, D>, common_data: &CommonCircu
}
}
fn hash_verifier_data_gadget(
builder: &mut CircuitBuilder<F, D>,
verifier_data: &VerifierCircuitTarget,
) -> HashOutTarget {
let f: Vec<Target> = [
verifier_data.circuit_digest.elements.to_vec(),
verifier_data
.constants_sigmas_cap
.0
.iter()
.flat_map(|e| e.elements)
.collect(),
]
.concat();
builder.hash_n_to_hash_no_pad::<PoseidonHash>(f)
}
// compatible with hash_verifier_data_gadget.
pub(crate) fn hash_verifier_data(verifier_only_data: &VerifierOnlyCircuitData<C, D>) -> HashOut<F> {
let f: Vec<F> = [
verifier_only_data.circuit_digest.elements.to_vec(),
verifier_only_data
.constants_sigmas_cap
.0
.iter()
.flat_map(|e| e.elements)
.collect(),
]
.concat();
PoseidonHash::hash_no_pad(&f)
}
#[cfg(test)]
mod tests {
use std::time::Instant;