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

@ -1,73 +1,30 @@
use std::collections::HashMap;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use super::Error;
use crate::{
frontend::{MainPod, SignedPod},
middleware::{
deserialize_pod, deserialize_signed_pod, Key, Params, PodId, Statement, VDSet, Value,
},
frontend::MainPod,
middleware::{deserialize_pod, Hash, Params, Statement, VDSet},
};
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[schemars(rename = "SignedPod")]
pub struct SerializedSignedPod {
pod_type: (usize, String),
id: PodId,
entries: HashMap<Key, Value>,
data: serde_json::Value,
}
impl SerializedSignedPod {
pub fn id(&self) -> PodId {
self.id
}
}
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[schemars(rename = "MainPod")]
pub struct SerializedMainPod {
params: Params,
pod_type: (usize, String),
id: PodId,
id: Hash,
vd_set: VDSet,
public_statements: Vec<Statement>,
data: serde_json::Value,
}
impl SerializedMainPod {
pub fn id(&self) -> PodId {
pub fn id(&self) -> Hash {
self.id
}
}
impl From<SignedPod> for SerializedSignedPod {
fn from(pod: SignedPod) -> Self {
let (pod_type, pod_type_name_str) = pod.pod.pod_type();
let data = pod.pod.serialize_data();
SerializedSignedPod {
pod_type: (pod_type, pod_type_name_str.to_string()),
id: pod.id(),
entries: pod.kvs().clone(),
data,
}
}
}
impl TryFrom<SerializedSignedPod> for SignedPod {
type Error = Error;
fn try_from(serialized: SerializedSignedPod) -> Result<Self, Self::Error> {
let pod = deserialize_signed_pod(serialized.pod_type.0, serialized.id, serialized.data)?;
let kvs = pod.kvs().into_iter().map(|(ak, v)| (ak.key, v)).collect();
Ok(Self { pod, kvs })
}
}
impl From<MainPod> for SerializedMainPod {
fn from(pod: MainPod) -> Self {
let (pod_type, pod_type_name_str) = pod.pod.pod_type();
@ -116,17 +73,17 @@ mod tests {
mainpod::{rec_main_pod_circuit_data, Prover},
mock::mainpod::MockProver,
primitives::ec::schnorr::SecretKey,
signedpod::Signer,
signer::Signer,
},
examples::{
attest_eth_friend, zu_kyc_pod_builder, zu_kyc_sign_pod_builders, EthDosHelper,
attest_eth_friend, zu_kyc_pod_builder, zu_kyc_sign_dict_builders, EthDosHelper,
MOCK_VD_SET,
},
frontend::{Result, SignedPodBuilder},
frontend::{Result, SignedDict, SignedDictBuilder},
middleware::{
self,
containers::{Array, Dictionary, Set},
Params, TypedValue, DEFAULT_VD_LIST,
Params, Signer as _, TypedValue, DEFAULT_VD_LIST,
},
};
@ -182,9 +139,9 @@ mod tests {
}
}
fn signed_pod_builder() -> SignedPodBuilder {
fn signed_dict_builder() -> SignedDictBuilder {
let params = &Params::default();
let mut builder = SignedPodBuilder::new(params);
let mut builder = SignedDictBuilder::new(params);
builder.insert("name", "test");
builder.insert("age", 30);
builder.insert("very_large_int", 1152921504606846976);
@ -228,46 +185,29 @@ mod tests {
}
#[test]
fn test_signed_pod_serialization() {
let builder = signed_pod_builder();
fn test_signed_dict_serialization() {
let builder = signed_dict_builder();
let signer = Signer(SecretKey(1u32.into()));
let pod = builder.sign(&signer).unwrap();
let signed_dict = builder.sign(&signer).unwrap();
let serialized = serde_json::to_string_pretty(&pod).unwrap();
let serialized = serde_json::to_string_pretty(&signed_dict).unwrap();
println!("serialized: {}", serialized);
let deserialized: SignedPod = serde_json::from_str(&serialized).unwrap();
let deserialized: SignedDict = serde_json::from_str(&serialized).unwrap();
println!(
"deserialized: {}",
serde_json::to_string_pretty(&deserialized).unwrap()
);
assert_eq!(pod.kvs, deserialized.kvs);
assert_eq!(pod.verify().is_ok(), deserialized.verify().is_ok());
assert_eq!(pod.id(), deserialized.id())
}
#[test]
fn test_mock_signed_pod_serialization() {
let builder = signed_pod_builder();
let signer = Signer(SecretKey(1u32.into()));
let pod = builder.sign(&signer).unwrap();
let serialized = serde_json::to_string_pretty(&pod).unwrap();
println!("serialized: {}", serialized);
let deserialized: SignedPod = serde_json::from_str(&serialized).unwrap();
println!(
"deserialized: {}",
serde_json::to_string_pretty(&deserialized).unwrap()
);
assert_eq!(pod.kvs, deserialized.kvs);
assert_eq!(pod.verify().is_ok(), deserialized.verify().is_ok());
assert_eq!(pod.id(), deserialized.id())
assert_eq!(signed_dict.dict.kvs(), deserialized.dict.kvs());
assert_eq!(signed_dict.public_key, deserialized.public_key);
assert_eq!(signed_dict.signature, deserialized.signature);
assert_eq!(signed_dict.verify().is_ok(), deserialized.verify().is_ok());
}
fn build_mock_zukyc_pod() -> Result<MainPod> {
let params = middleware::Params::default();
let vd_set = &*MOCK_VD_SET;
let (gov_id_builder, pay_stub_builder) = zu_kyc_sign_pod_builders(&params);
let (gov_id_builder, pay_stub_builder) = zu_kyc_sign_dict_builders(&params);
let signer = Signer(SecretKey(1u32.into()));
let gov_id_pod = gov_id_builder.sign(&signer).unwrap();
let signer = Signer(SecretKey(2u32.into()));
@ -283,18 +223,19 @@ mod tests {
let params = middleware::Params {
// Currently the circuit uses random access that only supports vectors of length 64.
// With max_input_main_pods=3 we need random access to a vector of length 73.
max_input_recursive_pods: 1,
max_input_pods: 1,
..Default::default()
};
let mut vds = DEFAULT_VD_LIST.clone();
vds.push(rec_main_pod_circuit_data(&params).1.verifier_only.clone());
let vd_set = VDSet::new(params.max_depth_mt_vds, &vds).unwrap();
let (gov_id_builder, pay_stub_builder) = zu_kyc_sign_pod_builders(&params);
let (gov_id_builder, pay_stub_builder) = zu_kyc_sign_dict_builders(&params);
let signer = Signer(SecretKey(1u32.into()));
let gov_id_pod = gov_id_builder.sign(&signer)?;
let signer = Signer(SecretKey(2u32.into()));
let pay_stub_pod = pay_stub_builder.sign(&signer)?;
let _signer = Signer(SecretKey(3u32.into()));
let kyc_builder = zu_kyc_pod_builder(&params, &vd_set, &gov_id_pod, &pay_stub_pod)?;
let prover = Prover {};
@ -311,7 +252,10 @@ mod tests {
let deserialized: MainPod = serde_json::from_str(&serialized).unwrap();
assert_eq!(kyc_pod.public_statements, deserialized.public_statements);
assert_eq!(kyc_pod.pod.id(), deserialized.pod.id());
assert_eq!(
kyc_pod.pod.statements_hash(),
deserialized.pod.statements_hash()
);
assert_eq!(kyc_pod.pod.verify()?, deserialized.pod.verify()?);
Ok(())
@ -324,7 +268,10 @@ mod tests {
let deserialized: MainPod = serde_json::from_str(&serialized).unwrap();
assert_eq!(kyc_pod.public_statements, deserialized.public_statements);
assert_eq!(kyc_pod.pod.id(), deserialized.pod.id());
assert_eq!(
kyc_pod.pod.statements_hash(),
deserialized.pod.statements_hash()
);
assert_eq!(kyc_pod.pod.verify()?, deserialized.pod.verify()?);
Ok(())
@ -348,7 +295,7 @@ mod tests {
let alice_attestation = attest_eth_friend(&params, &alice, bob.public_key());
let bob_attestation = attest_eth_friend(&params, &bob, charlie.public_key());
let helper = EthDosHelper::new(&params, vd_set, true, alice.public_key())?;
let helper = EthDosHelper::new(&params, vd_set, alice.public_key())?;
let prover = MockProver {};
let dist_1 = helper.dist_1(&alice_attestation)?.prove(&prover)?;
let dist_2 = helper
@ -360,28 +307,28 @@ mod tests {
#[test]
// This tests that we can generate JSON Schemas for the MainPod and
// SignedPod types, and that we can validate Signed and Main Pods
// SignedDict types, and that we can validate Signed and Main Pods
// against the schemas. Since both Mock and Plonky2 PODs have the same
// public interface, we can assume that the schema works for both.
fn test_schema() {
let mainpod_schema = schema_for!(SerializedMainPod);
let signedpod_schema = schema_for!(SerializedSignedPod);
let signeddict_schema = schema_for!(SignedDict);
let kyc_pod = build_mock_zukyc_pod().unwrap();
let signed_pod = signed_pod_builder()
let signed_dict = signed_dict_builder()
.sign(&Signer(SecretKey(1u32.into())))
.unwrap();
let ethdos_pod = build_ethdos_pod().unwrap();
let mainpod_schema_value = serde_json::to_value(&mainpod_schema).unwrap();
let signedpod_schema_value = serde_json::to_value(&signedpod_schema).unwrap();
let signed_dict_schema_value = serde_json::to_value(&signeddict_schema).unwrap();
let kyc_pod_value = serde_json::to_value(&kyc_pod).unwrap();
let mainpod_valid = jsonschema::validate(&mainpod_schema_value, &kyc_pod_value);
assert!(mainpod_valid.is_ok(), "{:#?}", mainpod_valid);
let signed_pod_value = serde_json::to_value(&signed_pod).unwrap();
let signedpod_valid = jsonschema::validate(&signedpod_schema_value, &signed_pod_value);
assert!(signedpod_valid.is_ok(), "{:#?}", signedpod_valid);
let signed_dict_value = serde_json::to_value(&signed_dict).unwrap();
let signed_dict_valid = jsonschema::validate(&signed_dict_schema_value, &signed_dict_value);
assert!(signed_dict_valid.is_ok(), "{:#?}", signed_dict_valid);
let ethdos_pod_value = serde_json::to_value(&ethdos_pod).unwrap();
let ethdos_pod_valid = jsonschema::validate(&mainpod_schema_value, &ethdos_pod_value);