pod2/src/backends/plonky2/emptypod.rs
Eduard S. 0e2f7b756e
No Pod IDs (#394)
- middleware:
  - Add `Statement::Intro`
  - Add `SignedBy` native predicate and operation.  The signature is auxiliary data to the operation
  - Rename `PodSigner` to `Signer` with a new API (just for signing `RawValue`)
  - Removed `NewEntry` operation.  Use `ContainsFromEntries` instead
  - Remove `KEY_SIGNER` and `KEY_TYPE` which are no longer used
  - Merge `RecursivePod` and `Pod` traits
  - Change the `Pod::deserialize_data` method to use `Self` instead of `Box<dyn Pod>` 
  - Extend `Pod` trait with these methods:
    - `is_main`: when the pod is Main, in a (recursive) verification its vk will be checked to exist in the vd_set but not if it's intro pod
    - `is_mock`: skip some verifications in the recursive mock MainPod verification
    - `verifier_data_hash`
    - `pod_id` renamed to `statements_hash`
  - AnchoredKeys are now a pair of dictionary root and key
  - Entry statements are now defined as Contains with literal arguments
    - Operations that take Entries now use Contains statements with literal arguments
- frontend:
  - Rename `SignedPod` to `SignedDict` (which now contains the dict, public key and signature, and can still `verify(self)`ed)
  - The `SignedDict` keeps the method `get_statement` for convenience but now it returns a `Contains` statement that proves the existence of the key in the dict
  - The `MainPodBuilder` automatically inserts a `Contains` statement when an operation is added that uses an entry as argument that was not yet "opened".
  - Removed the `literal` methods from the `MainPodBuilder` that were loading literals to anchored keys: that was no longer needed after we introduced literal arguments
- backend
  - Only verify inclusion of the verifying key into the vd_set if the pod is MainPod.  A pod is not MainPod if the first statement is Intro.
  - Reject intro pods that have non-intro statements
  - Empty pod now returns an intro statement
  - Don't insert a type statement automatically in MainPod and MockMainPod.  We get rid of the type entry.
  - Implement `SignedBy` operation, which uses the muxed table to store signature verifications
- Rename `PodId` to `statements_hash` or `sts_hash` for short.  Now this is only used as a hash of the statements for the circuits public inputs.
- Refactor normalization of `self` statements:
  - Before: replace values that contain `SELF` by the given pod_id
  - After: place the verifying key hash into the Intro predicates
2025-08-27 13:19:40 +02:00

266 lines
8.3 KiB
Rust

use itertools::Itertools;
use plonky2::{
hash::hash_types::HashOutTarget,
iop::witness::{PartialWitness, WitnessWrite},
plonk::{
circuit_data::{self, CircuitConfig},
proof::ProofWithPublicInputs,
},
};
use serde::{Deserialize, Serialize};
use crate::{
backends::plonky2::{
basetypes::{CircuitBuilder, Proof, C, D},
cache_get_standard_rec_main_pod_common_circuit_data,
circuits::{
common::{Flattenable, StatementTarget},
mainpod::{calculate_statements_hash_circuit, PI_OFFSET_STATEMENTS_HASH},
},
deserialize_proof, deserialize_verifier_only,
error::{Error, Result},
hash_common_data,
mainpod::{self, calculate_statements_hash},
recursion::pad_circuit,
serialization::{
CircuitDataSerializer, VerifierCircuitDataSerializer, VerifierOnlyCircuitDataSerializer,
},
serialize_proof, serialize_verifier_only,
},
cache::{self, CacheEntry},
middleware::{
self, Hash, IntroPredicateRef, Params, Pod, PodType, Statement, ToFields, VDSet,
VerifierOnlyCircuitData, EMPTY_HASH, F, HASH_SIZE,
},
timed,
};
fn empty_statement() -> Statement {
Statement::Intro(
IntroPredicateRef {
name: "empty".to_string(),
args_len: 0,
verifier_data_hash: EMPTY_HASH,
},
vec![],
)
}
#[derive(Clone, Serialize, Deserialize)]
pub struct EmptyPodVerifyTarget {
vds_root: HashOutTarget,
}
impl EmptyPodVerifyTarget {
pub fn new_virtual(builder: &mut CircuitBuilder) -> Self {
Self {
vds_root: builder.add_virtual_hash(),
}
}
pub fn set_targets(&self, pw: &mut PartialWitness<F>, vds_root: Hash) -> Result<()> {
Ok(pw.set_target_arr(&self.vds_root.elements, &vds_root.0)?)
}
}
fn verify_empty_pod_circuit(
params: &Params,
builder: &mut CircuitBuilder,
empty_pod: &EmptyPodVerifyTarget,
) {
let empty_statement = StatementTarget::from_flattened(
params,
&builder.constants(&empty_statement().to_fields(params)),
);
let sts_hash = calculate_statements_hash_circuit(params, builder, &[empty_statement]);
builder.register_public_inputs(&sts_hash.elements);
builder.register_public_inputs(&empty_pod.vds_root.elements);
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct EmptyPod {
params: Params,
sts_hash: Hash,
verifier_only: VerifierOnlyCircuitDataSerializer,
common_hash: String,
vd_set: VDSet,
proof: Proof,
}
type CircuitData = circuit_data::CircuitData<F, C, D>;
pub fn cache_get_standard_empty_pod_circuit_data(
) -> CacheEntry<(EmptyPodVerifyTarget, CircuitDataSerializer)> {
cache::get("standard_empty_pod_circuit_data", &(), |_| {
let (target, circuit_data) = build().expect("successful build");
(target, CircuitDataSerializer(circuit_data))
})
.expect("cache ok")
}
pub fn cache_get_standard_empty_pod_verifier_circuit_data(
) -> CacheEntry<VerifierCircuitDataSerializer> {
cache::get("standard_empty_pod_verifier_circuit_data", &(), |_| {
let (_, standard_empty_pod_circuit_data) = &*cache_get_standard_empty_pod_circuit_data();
VerifierCircuitDataSerializer(standard_empty_pod_circuit_data.verifier_data().clone())
})
.expect("cache ok")
}
fn build() -> Result<(EmptyPodVerifyTarget, CircuitData)> {
let params = Params::default();
#[cfg(not(feature = "zk"))]
let config = CircuitConfig::standard_recursion_config();
#[cfg(feature = "zk")]
let config = CircuitConfig::standard_recursion_zk_config();
let mut builder = CircuitBuilder::new(config);
let empty_pod = EmptyPodVerifyTarget::new_virtual(&mut builder);
verify_empty_pod_circuit(&params, &mut builder, &empty_pod);
let common_circuit_data = &*cache_get_standard_rec_main_pod_common_circuit_data();
pad_circuit(&mut builder, common_circuit_data);
let data = timed!("EmptyPod build", builder.build::<C>());
assert_eq!(common_circuit_data.0, data.common);
Ok((empty_pod, data))
}
impl EmptyPod {
fn new(params: &Params, vd_set: VDSet) -> Result<EmptyPod> {
let (empty_pod_verify_target, data) = &*cache_get_standard_empty_pod_circuit_data();
let mut pw = PartialWitness::<F>::new();
empty_pod_verify_target.set_targets(&mut pw, vd_set.root())?;
let proof = timed!("EmptyPod prove", data.prove(pw)?);
let sts_hash = {
let v = &proof.public_inputs
[PI_OFFSET_STATEMENTS_HASH..PI_OFFSET_STATEMENTS_HASH + HASH_SIZE];
Hash([v[0], v[1], v[2], v[3]])
};
let common_hash = hash_common_data(&data.common).expect("hash ok");
Ok(EmptyPod {
params: params.clone(),
verifier_only: VerifierOnlyCircuitDataSerializer(data.verifier_only.clone()),
common_hash,
sts_hash,
vd_set,
proof: proof.proof,
})
}
pub fn new_boxed(params: &Params, vd_set: VDSet) -> Box<dyn Pod> {
let default_params = Params::default();
assert_eq!(default_params.id_params(), params.id_params());
let empty_pod = cache::get(
"empty_pod",
&(default_params, vd_set),
|(params, vd_set)| Self::new(params, vd_set.clone()).expect("prove EmptyPod"),
)
.expect("cache ok");
Box::new(empty_pod.clone())
}
}
#[derive(Serialize, Deserialize)]
struct Data {
proof: String,
verifier_only: String,
common_hash: String,
}
impl Pod for EmptyPod {
fn params(&self) -> &Params {
&self.params
}
fn verify(&self) -> Result<()> {
let statements = self
.pub_self_statements()
.into_iter()
.map(mainpod::Statement::from)
.collect_vec();
let sts_hash = calculate_statements_hash(&statements, &self.params);
if sts_hash != self.sts_hash {
return Err(Error::statements_hash_not_equal(self.sts_hash, sts_hash));
}
let public_inputs = sts_hash
.to_fields(&self.params)
.iter()
.chain(self.vd_set.root().0.iter())
.cloned()
.collect_vec();
let standard_empty_pod_verifier_data = cache_get_standard_empty_pod_verifier_circuit_data();
standard_empty_pod_verifier_data
.verify(ProofWithPublicInputs {
proof: self.proof.clone(),
public_inputs,
})
.map_err(|e| Error::plonky2_proof_fail("EmptyPod", e))
}
fn statements_hash(&self) -> Hash {
self.sts_hash
}
fn pod_type(&self) -> (usize, &'static str) {
(PodType::Empty as usize, "Empty")
}
fn pub_self_statements(&self) -> Vec<middleware::Statement> {
vec![empty_statement()]
}
fn verifier_data(&self) -> VerifierOnlyCircuitData {
self.verifier_only.0.clone()
}
fn common_hash(&self) -> String {
self.common_hash.clone()
}
fn proof(&self) -> Proof {
self.proof.clone()
}
fn vd_set(&self) -> &VDSet {
&self.vd_set
}
fn serialize_data(&self) -> serde_json::Value {
serde_json::to_value(Data {
proof: serialize_proof(&self.proof),
verifier_only: serialize_verifier_only(&self.verifier_only),
common_hash: self.common_hash.clone(),
})
.expect("serialization to json")
}
fn deserialize_data(
params: Params,
data: serde_json::Value,
vd_set: VDSet,
sts_hash: Hash,
) -> Result<Self> {
let data: Data = serde_json::from_value(data)?;
let common_circuit_data = cache_get_standard_rec_main_pod_common_circuit_data();
let proof = deserialize_proof(&common_circuit_data, &data.proof)?;
let verifier_only = deserialize_verifier_only(&data.verifier_only)?;
Ok(Self {
params,
sts_hash,
verifier_only: VerifierOnlyCircuitDataSerializer(verifier_only),
common_hash: data.common_hash,
vd_set,
proof,
})
}
}
#[cfg(test)]
pub mod tests {
use super::*;
#[test]
fn test_empty_pod() {
let params = Params::default();
let empty_pod = EmptyPod::new_boxed(&params, VDSet::new(8, &[]).unwrap());
empty_pod.verify().unwrap();
}
}