Add versioning features (#387)
- Add a function to calculate the hash of the `CommonCircuitData`. The hash uniquely identify the `CommonCircuitData` used for a circuit/proof. Serializing the struct is not enough because the polynomial identities of the custom gates are not serialized (only their parameters are); so I made a function to extract "fingerprints" of the custom gates by evaluating them over a predefined list of uniform values, and then doing a random linear combination over the results.
- Store the full verifier only circuit data of a proof in the MainPod so that we can verify pods from old circuits in new circuits and code
- Store the hash of the `CommonCircuitData` in the MainPod so that we can reject verifying old pods that use a different `CommonCircuitData` than the current one. This has two goals
- If the `CommonCircuitData` changes it's very likely that the verification will fail, but it will be hard to debug. Doing this early check helps identify the origin of the verification failure as early as possible
- There's a chance that the verification could succeed when the `CommonCircuitData` changes, and that could be dangerous because the verification will be doing different checks than the ones intended for the original proof, so we may be skipping some constraints that could lead to exploiting the system. For this reason, whenever the common circuit data hash changes, all previous verifying keys should be discarded (that is, not included in the VDSet)
- The fingerprint only has ~64 bits and the "random evaluation point" is fixed. The assumption is that the pod developers are not malicious and are not changing the gates such that different gates give the same fingerprint. With this assumption, I find it reasonable to assume that with high probability if a gate changes, its fingerprint changes as well.
- Add a github action that updates a wiki page with a table that contains: date, commit, params hash (with a link to the actual params), verifier data only circuit data hash and common circuit data hash. This will make it easy to track when the common circuit data changes as well as track the verifier data corresponding to various versions (identified by commit)
- The edited page is this one https://github.com/0xPARC/pod2/wiki/MainPod-circuit-info
Resolve https://github.com/0xPARC/pod2/issues/386
Summary of breaking changes:
- The `RecursivePod` trait has a new method `common_hash` that needs to return the result of `hash_common_data` on the `CommonCircuitData` that the circuit uses.
This commit is contained in:
parent
594c4d2e63
commit
656cae77e0
12 changed files with 357 additions and 27 deletions
|
|
@ -6,19 +6,33 @@ pub mod mainpod;
|
|||
pub mod mock;
|
||||
pub mod primitives;
|
||||
pub mod recursion;
|
||||
mod serialization;
|
||||
pub mod serialization;
|
||||
pub mod signedpod;
|
||||
|
||||
use std::iter;
|
||||
|
||||
use base64::{prelude::BASE64_STANDARD, Engine};
|
||||
pub use error::*;
|
||||
use plonky2::util::serialization::{Buffer, Read};
|
||||
use plonky2::{
|
||||
field::{
|
||||
extension::quadratic::QuadraticExtension,
|
||||
types::{Field, Field64},
|
||||
},
|
||||
hash::hash_types::HashOut,
|
||||
plonk::vars::EvaluationVars,
|
||||
util::serialization::{Buffer, Read},
|
||||
};
|
||||
use rand::prelude::*;
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
use serde::{ser, Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
use crate::{
|
||||
backends::plonky2::{
|
||||
basetypes::{CommonCircuitData, Proof},
|
||||
basetypes::{CommonCircuitData, Proof, VerifierOnlyCircuitData, F},
|
||||
circuits::mainpod::{MainPodVerifyTarget, NUM_PUBLIC_INPUTS},
|
||||
recursion::RecursiveCircuit,
|
||||
serialization::CommonCircuitDataSerializer,
|
||||
serialization::{CommonCircuitDataSerializer, Pod2GateSerializer},
|
||||
},
|
||||
cache::{self, CacheEntry},
|
||||
middleware::Params,
|
||||
|
|
@ -73,9 +87,99 @@ pub fn deserialize_proof(common: &CommonCircuitData, proof: &str) -> Result<Proo
|
|||
Ok(proof)
|
||||
}
|
||||
|
||||
pub fn serialize_verifier_only(verifier_only: &VerifierOnlyCircuitData) -> String {
|
||||
let bytes = verifier_only.to_bytes().expect("write to Vec");
|
||||
serialize_bytes(&bytes)
|
||||
}
|
||||
|
||||
pub fn deserialize_verifier_only(verifier_only: &str) -> Result<VerifierOnlyCircuitData> {
|
||||
let decoded = deserialize_bytes(verifier_only)?;
|
||||
let verifier_only = VerifierOnlyCircuitData::from_bytes(&decoded).map_err(|e| {
|
||||
Error::custom(format!(
|
||||
"Failed to read VerifierOnlyCircuitData from buffer: {}. Value: {}",
|
||||
e, verifier_only
|
||||
))
|
||||
})?;
|
||||
|
||||
Ok(verifier_only)
|
||||
}
|
||||
|
||||
pub fn serialize_proof(proof: &Proof) -> String {
|
||||
let mut buffer = Vec::new();
|
||||
use plonky2::util::serialization::Write;
|
||||
buffer.write_proof(proof).unwrap();
|
||||
serialize_bytes(&buffer)
|
||||
}
|
||||
|
||||
fn rand_vec(rng: &mut impl RngCore, len: usize) -> Vec<F> {
|
||||
iter::repeat_with(|| rng.next_u64())
|
||||
.filter(|v| *v < F::ORDER)
|
||||
.map(F::from_canonical_u64)
|
||||
.take(len)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn base(r: F, xs: &[F]) -> F {
|
||||
let mut res = F::ZERO;
|
||||
for x in xs.iter().rev() {
|
||||
res *= r;
|
||||
res += *x;
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
fn gate_fingerprints(common: &CommonCircuitData) -> Vec<(String, F)> {
|
||||
type Ext = QuadraticExtension<F>;
|
||||
let config = &common.config;
|
||||
let mut rng = ChaCha20Rng::seed_from_u64(42);
|
||||
let r = rand_vec(&mut rng, 1)[0];
|
||||
let local_constants: Vec<Ext> = rand_vec(&mut rng, config.num_constants)
|
||||
.into_iter()
|
||||
.map(Ext::from)
|
||||
.collect();
|
||||
let local_wires: Vec<Ext> = rand_vec(&mut rng, config.num_wires)
|
||||
.into_iter()
|
||||
.map(Ext::from)
|
||||
.collect();
|
||||
let public_inputs_hash = HashOut::from_vec(rand_vec(&mut rng, 4));
|
||||
let vars = EvaluationVars {
|
||||
local_constants: &local_constants,
|
||||
local_wires: &local_wires,
|
||||
public_inputs_hash: &public_inputs_hash,
|
||||
};
|
||||
let mut fingerprints = Vec::new();
|
||||
for gate in &common.gates {
|
||||
let eval: Vec<F> = gate
|
||||
.0
|
||||
.eval_unfiltered(vars)
|
||||
.into_iter()
|
||||
.map(|e| e.0[0])
|
||||
.collect();
|
||||
fingerprints.push((gate.0.id(), base(r, &eval)));
|
||||
}
|
||||
fingerprints
|
||||
}
|
||||
|
||||
pub fn hash_common_data(common: &CommonCircuitData) -> serde_json::Result<String> {
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CommonFingerprintData {
|
||||
common: String,
|
||||
gate_fingerprints: Vec<(String, F)>,
|
||||
}
|
||||
|
||||
let gate_serializer = Pod2GateSerializer {};
|
||||
let bytes = common
|
||||
.to_bytes(&gate_serializer)
|
||||
.map_err(ser::Error::custom)?;
|
||||
let gate_fingerprints = gate_fingerprints(common);
|
||||
let data = CommonFingerprintData {
|
||||
common: serialize_bytes(&bytes),
|
||||
gate_fingerprints,
|
||||
};
|
||||
|
||||
let json = serde_json::to_string(&data)?;
|
||||
let json_hash = Sha256::digest(&json);
|
||||
let json_hash_str_long = format!("{:x}", json_hash);
|
||||
let json_hash_str = json_hash_str_long[..32].to_string();
|
||||
Ok(json_hash_str)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue