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

@ -9,14 +9,9 @@ use serde::{Deserialize, Serialize};
use strum_macros::FromRepr;
use crate::middleware::{
AnchoredKey, CustomPredicateRef, Error, Params, Result, ToFields, Value, F, VALUE_SIZE,
self, AnchoredKey, CustomPredicateRef, Error, Params, Result, ToFields, Value, F, VALUE_SIZE,
};
// TODO: Maybe store KEY_SIGNER and KEY_TYPE as Key with lazy_static
// hash(KEY_SIGNER) = [2145458785152392366, 15113074911296146791, 15323228995597834291, 11804480340100333725]
pub const KEY_SIGNER: &str = "_signer";
// hash(KEY_TYPE) = [17948789436443445142, 12513915140657440811, 15878361618879468769, 938231894693848619]
pub const KEY_TYPE: &str = "_type";
pub const STATEMENT_ARG_F_LEN: usize = 8;
#[derive(Clone, Copy, Debug, FromRepr, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
@ -34,9 +29,10 @@ pub enum NativePredicate {
MaxOf = 10,
HashOf = 11,
PublicKeyOf = 12,
ContainerInsert = 13,
ContainerUpdate = 14,
ContainerDelete = 15,
SignedBy = 13,
ContainerInsert = 14,
ContainerUpdate = 15,
ContainerDelete = 16,
// Syntactic sugar predicates. These predicates are not supported by the backend. The
// frontend compiler is responsible of translating these predicates into the predicates above.
@ -73,6 +69,7 @@ impl Display for NativePredicate {
NativePredicate::MaxOf => "MaxOf",
NativePredicate::HashOf => "HashOf",
NativePredicate::PublicKeyOf => "PublicKeyOf",
NativePredicate::SignedBy => "SignedBy",
NativePredicate::ContainerInsert => "ContainerInsert",
NativePredicate::ContainerUpdate => "ContainerUpdate",
NativePredicate::ContainerDelete => "ContainerDelete",
@ -98,12 +95,20 @@ impl ToFields for NativePredicate {
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
pub struct IntroPredicateRef {
pub name: String,
pub args_len: usize,
pub verifier_data_hash: middleware::Hash,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "type", content = "value")]
pub enum Predicate {
Native(NativePredicate),
BatchSelf(usize),
Custom(CustomPredicateRef),
Intro(IntroPredicateRef),
}
impl From<NativePredicate> for Predicate {
@ -117,6 +122,7 @@ pub enum PredicatePrefix {
Native = 1,
BatchSelf = 2,
Custom = 3,
Intro = 4,
}
impl From<PredicatePrefix> for F {
@ -133,6 +139,8 @@ impl ToFields for Predicate {
// CustomPredicateRef(pb, i) as
// (3, [hash of pb], i) -- pb hashes to 4 field elements
// -- i: usize
// IntroPredicateRef(vd_hash) as
// (4, [vd_hash], 0)
// in every case: pad to (hash_size + 2) field elements
let mut fields: Vec<F> = match self {
@ -148,6 +156,11 @@ impl ToFields for Predicate {
.chain(iter::once(F::from_canonical_usize(*index)))
.collect()
}
Self::Intro(IntroPredicateRef {
verifier_data_hash, ..
}) => iter::once(F::from(PredicatePrefix::Intro))
.chain(verifier_data_hash.0)
.collect(),
};
fields.resize_with(Params::predicate_size(), || F::from_canonical_u64(0));
fields
@ -172,6 +185,7 @@ impl fmt::Display for Predicate {
write!(f, "{}", batch.predicates()[*index].name)
}
}
Self::Intro(IntroPredicateRef { name, .. }) => write!(f, "{}", name),
}
}
}
@ -196,6 +210,7 @@ pub enum Statement {
MaxOf(ValueRef, ValueRef, ValueRef),
HashOf(ValueRef, ValueRef, ValueRef),
PublicKeyOf(ValueRef, ValueRef),
SignedBy(ValueRef, ValueRef),
ContainerInsert(
/* new_root */ ValueRef,
/* old_root */ ValueRef,
@ -214,6 +229,7 @@ pub enum Statement {
/* key */ ValueRef,
),
Custom(CustomPredicateRef, Vec<Value>),
Intro(IntroPredicateRef, Vec<Value>),
}
macro_rules! statement_constructor {
@ -258,6 +274,7 @@ impl Statement {
statement_constructor!(max_of, MaxOf, 3);
statement_constructor!(hash_of, HashOf, 3);
statement_constructor!(public_key_of, PublicKeyOf, 2);
statement_constructor!(signed_by, SignedBy, 2);
statement_constructor!(insert, ContainerInsert, 4);
statement_constructor!(update, ContainerUpdate, 4);
statement_constructor!(delete, ContainerDelete, 3);
@ -276,10 +293,12 @@ impl Statement {
Self::MaxOf(_, _, _) => Native(NativePredicate::MaxOf),
Self::HashOf(_, _, _) => Native(NativePredicate::HashOf),
Self::PublicKeyOf(_, _) => Native(NativePredicate::PublicKeyOf),
Self::SignedBy(_, _) => Native(NativePredicate::SignedBy),
Self::ContainerInsert(_, _, _, _) => Native(NativePredicate::ContainerInsert),
Self::ContainerUpdate(_, _, _, _) => Native(NativePredicate::ContainerUpdate),
Self::ContainerDelete(_, _, _) => Native(NativePredicate::ContainerDelete),
Self::Custom(cpr, _) => Custom(cpr.clone()),
Self::Intro(ir, _) => Intro(ir.clone()),
}
}
pub fn args(&self) -> Vec<StatementArg> {
@ -297,6 +316,7 @@ impl Statement {
Self::MaxOf(ak1, ak2, ak3) => vec![ak1.into(), ak2.into(), ak3.into()],
Self::HashOf(ak1, ak2, ak3) => vec![ak1.into(), ak2.into(), ak3.into()],
Self::PublicKeyOf(ak1, ak2) => vec![ak1.into(), ak2.into()],
Self::SignedBy(ak1, ak2) => vec![ak1.into(), ak2.into()],
Self::ContainerInsert(ak1, ak2, ak3, ak4) => {
vec![ak1.into(), ak2.into(), ak3.into(), ak4.into()]
}
@ -305,6 +325,7 @@ impl Statement {
}
Self::ContainerDelete(ak1, ak2, ak3) => vec![ak1.into(), ak2.into(), ak3.into()],
Self::Custom(_, args) => Vec::from_iter(args.into_iter().map(Literal)),
Self::Intro(_, args) => Vec::from_iter(args.into_iter().map(Literal)),
}
}
@ -351,6 +372,9 @@ impl Statement {
(Native(NativePredicate::PublicKeyOf), &[a1, a2]) => {
Self::PublicKeyOf(a1.try_into()?, a2.try_into()?)
}
(Native(NativePredicate::SignedBy), &[a1, a2]) => {
Self::SignedBy(a1.try_into()?, a2.try_into()?)
}
(Native(NativePredicate::ContainerInsert), &[a1, a2, a3, a4]) => Self::ContainerInsert(
a1.try_into()?,
a2.try_into()?,
@ -380,6 +404,16 @@ impl Statement {
.collect();
Self::Custom(cpr, v_args?)
}
(Intro(ir), _) => {
let v_args: Result<Vec<Value>> = args
.iter()
.map(|x| match x {
StatementArg::Literal(v) => Ok(v.clone()),
_ => Err(Error::incorrect_statements_args()),
})
.collect();
Self::Intro(ir, v_args?)
}
};
Ok(st)
}
@ -453,7 +487,7 @@ impl ToFields for StatementArg {
/// Encoding:
/// - None => [0, 0, 0, 0, 0, 0, 0, 0]
/// - Literal(v) => [[v], 0, 0, 0, 0]
/// - Key(pod_id, key) => [[pod_id], [key]]
/// - Key(root, key) => [[root], [key]]
/// - WildcardLiteral(v) => [[v], 0, 0, 0, 0]
fn to_fields(&self, params: &Params) -> Vec<F> {
// NOTE for @ax0: I removed the old comment because may `to_fields` implementations do
@ -467,7 +501,7 @@ impl ToFields for StatementArg {
.chain(iter::repeat(F::ZERO).take(STATEMENT_ARG_F_LEN - VALUE_SIZE))
.collect(),
StatementArg::Key(ak) => {
let mut fields = ak.pod_id.to_fields(params);
let mut fields = ak.root.to_fields(params);
fields.extend(ak.key.to_fields(params));
fields
}
@ -528,17 +562,3 @@ where
Self::Literal(value.into())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::middleware::hash_str;
#[test]
fn test_print_special_keys() {
let key = hash_str(KEY_SIGNER);
println!("hash(KEY_SIGNER) = {:?}", key);
let key = hash_str(KEY_TYPE);
println!("hash(KEY_TYPE) = {:?}", key);
}
}