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
This commit is contained in:
Eduard S. 2025-08-27 13:19:40 +02:00 committed by GitHub
parent 122f9c3cac
commit 0e2f7b756e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 2127 additions and 3064 deletions

View file

@ -3,7 +3,6 @@ use plonky2::{
hash::hash_types::HashOutTarget,
iop::witness::{PartialWitness, WitnessWrite},
plonk::{
circuit_builder::CircuitBuilder,
circuit_data::{self, CircuitConfig},
proof::ProofWithPublicInputs,
},
@ -12,16 +11,16 @@ use serde::{Deserialize, Serialize};
use crate::{
backends::plonky2::{
basetypes::{Proof, C, D},
basetypes::{CircuitBuilder, Proof, C, D},
cache_get_standard_rec_main_pod_common_circuit_data,
circuits::{
common::{Flattenable, StatementTarget},
mainpod::{calculate_id_circuit, PI_OFFSET_ID},
mainpod::{calculate_statements_hash_circuit, PI_OFFSET_STATEMENTS_HASH},
},
deserialize_proof, deserialize_verifier_only,
error::{Error, Result},
hash_common_data,
mainpod::{self, calculate_id},
mainpod::{self, calculate_statements_hash},
recursion::pad_circuit,
serialization::{
CircuitDataSerializer, VerifierCircuitDataSerializer, VerifierOnlyCircuitDataSerializer,
@ -30,52 +29,57 @@ use crate::{
},
cache::{self, CacheEntry},
middleware::{
self, AnchoredKey, Hash, Params, Pod, PodId, PodType, RecursivePod, Statement, ToFields,
VDSet, Value, VerifierOnlyCircuitData, F, HASH_SIZE, KEY_TYPE, SELF,
self, Hash, IntroPredicateRef, Params, Pod, PodType, Statement, ToFields, VDSet,
VerifierOnlyCircuitData, EMPTY_HASH, F, HASH_SIZE,
},
timed,
};
struct EmptyPodVerifyCircuit {
params: Params,
}
fn type_statement() -> Statement {
Statement::equal(
AnchoredKey::from((SELF, KEY_TYPE)),
Value::from(PodType::Empty),
fn empty_statement() -> Statement {
Statement::Intro(
IntroPredicateRef {
name: "empty".to_string(),
args_len: 0,
verifier_data_hash: EMPTY_HASH,
},
vec![],
)
}
impl EmptyPodVerifyCircuit {
fn eval(&self, builder: &mut CircuitBuilder<F, D>) -> Result<EmptyPodVerifyTarget> {
let type_statement = StatementTarget::from_flattened(
&self.params,
&builder.constants(&type_statement().to_fields(&self.params)),
);
let id = calculate_id_circuit(&self.params, builder, &[type_statement]);
let vds_root = builder.add_virtual_hash();
builder.register_public_inputs(&id.elements);
builder.register_public_inputs(&vds_root.elements);
Ok(EmptyPodVerifyTarget { vds_root })
}
}
#[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,
id: PodId,
sts_hash: Hash,
verifier_only: VerifierOnlyCircuitDataSerializer,
common_hash: String,
vd_set: VDSet,
@ -110,17 +114,15 @@ fn build() -> Result<(EmptyPodVerifyTarget, CircuitData)> {
#[cfg(feature = "zk")]
let config = CircuitConfig::standard_recursion_zk_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
let empty_pod_verify_target = EmptyPodVerifyCircuit {
params: params.clone(),
}
.eval(&mut builder)?;
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_verify_target, data))
Ok((empty_pod, data))
}
impl EmptyPod {
@ -130,19 +132,22 @@ impl EmptyPod {
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 id = &proof.public_inputs[PI_OFFSET_ID..PI_OFFSET_ID + HASH_SIZE];
let id = PodId(Hash([id[0], id[1], id[2], id[3]]));
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,
id,
sts_hash,
vd_set,
proof: proof.proof,
})
}
pub fn new_boxed(params: &Params, vd_set: VDSet) -> Box<dyn RecursivePod> {
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());
@ -173,12 +178,12 @@ impl Pod for EmptyPod {
.into_iter()
.map(mainpod::Statement::from)
.collect_vec();
let id = PodId(calculate_id(&statements, &self.params));
if id != self.id {
return Err(Error::id_not_equal(self.id, id));
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 = id
let public_inputs = sts_hash
.to_fields(&self.params)
.iter()
.chain(self.vd_set.root().0.iter())
@ -194,28 +199,17 @@ impl Pod for EmptyPod {
.map_err(|e| Error::plonky2_proof_fail("EmptyPod", e))
}
fn id(&self) -> PodId {
self.id
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![type_statement()]
vec![empty_statement()]
}
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")
}
}
impl RecursivePod for EmptyPod {
fn verifier_data(&self) -> VerifierOnlyCircuitData {
self.verifier_only.0.clone()
}
@ -228,24 +222,33 @@ impl RecursivePod for EmptyPod {
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,
id: PodId,
) -> Result<Box<dyn RecursivePod>> {
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(Box::new(Self {
Ok(Self {
params,
id,
sts_hash,
verifier_only: VerifierOnlyCircuitDataSerializer(verifier_only),
common_hash: data.common_hash,
vd_set,
proof,
}))
})
}
}