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

@ -8,14 +8,14 @@ use crate::{
backends::plonky2::primitives::{
ec::{
curve::{Point as PublicKey, GROUP_ORDER},
schnorr::SecretKey,
schnorr::{SecretKey, Signature},
},
merkletree::{MerkleProof, MerkleTree, MerkleTreeOp, MerkleTreeStateTransitionProof},
},
middleware::{
hash_values, AnchoredKey, CustomPredicate, CustomPredicateRef, Error, NativePredicate,
Params, Predicate, Result, Statement, StatementArg, StatementTmpl, StatementTmplArg,
ToFields, TypedValue, Value, ValueRef, Wildcard, F, SELF,
hash_values, AnchoredKey, CustomPredicate, CustomPredicateRef, Error, Hash, Key,
NativePredicate, Params, Predicate, Result, Statement, StatementArg, StatementTmpl,
StatementTmplArg, ToFields, TypedValue, Value, ValueRef, Wildcard, F,
},
};
@ -30,6 +30,7 @@ pub enum OperationAux {
None,
MerkleProof(MerkleProof),
MerkleTreeStateTransitionProof(MerkleTreeStateTransitionProof),
Signature(Signature),
}
impl fmt::Display for OperationAux {
@ -41,6 +42,7 @@ impl fmt::Display for OperationAux {
Self::MerkleTreeStateTransitionProof(pf) => {
write!(f, "merkle_tree_state_transition_proof({:?})", pf)?
}
Self::Signature(sig) => write!(f, "signature({:?})", sig)?,
}
Ok(())
}
@ -67,24 +69,24 @@ impl ToFields for OperationType {
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, std::hash::Hash, Serialize, Deserialize)]
pub enum NativeOperation {
None = 0,
NewEntry = 1,
CopyStatement = 2,
EqualFromEntries = 3,
NotEqualFromEntries = 4,
LtEqFromEntries = 5,
LtFromEntries = 6,
TransitiveEqualFromStatements = 7,
LtToNotEqual = 8,
ContainsFromEntries = 9,
NotContainsFromEntries = 10,
SumOf = 11,
ProductOf = 12,
MaxOf = 13,
HashOf = 14,
PublicKeyOf = 15,
CopyStatement = 1,
EqualFromEntries = 2,
NotEqualFromEntries = 3,
LtEqFromEntries = 4,
LtFromEntries = 5,
TransitiveEqualFromStatements = 6,
LtToNotEqual = 7,
ContainsFromEntries = 8,
NotContainsFromEntries = 9,
SumOf = 10,
ProductOf = 11,
MaxOf = 12,
HashOf = 13,
PublicKeyOf = 14,
SignedBy = 15,
ContainerInsertFromEntries = 16,
ContainerUpdateFromEntries = 17,
ContainerDeleteFromEntries = 18,
@ -127,7 +129,6 @@ impl OperationType {
match self {
OperationType::Native(native_op) => match native_op {
NativeOperation::None => Some(Predicate::Native(NativePredicate::None)),
NativeOperation::NewEntry => Some(Predicate::Native(NativePredicate::Equal)),
NativeOperation::CopyStatement => None,
NativeOperation::EqualFromEntries => {
Some(Predicate::Native(NativePredicate::Equal))
@ -154,6 +155,7 @@ impl OperationType {
NativeOperation::PublicKeyOf => {
Some(Predicate::Native(NativePredicate::PublicKeyOf))
}
NativeOperation::SignedBy => Some(Predicate::Native(NativePredicate::SignedBy)),
NativeOperation::ContainerInsertFromEntries => {
Some(Predicate::Native(NativePredicate::ContainerInsert))
}
@ -174,7 +176,6 @@ impl OperationType {
#[derive(Clone, Debug, PartialEq)]
pub enum Operation {
None,
NewEntry,
CopyStatement(Statement),
EqualFromEntries(Statement, Statement),
NotEqualFromEntries(Statement, Statement),
@ -198,6 +199,7 @@ pub enum Operation {
MaxOf(Statement, Statement, Statement),
HashOf(Statement, Statement, Statement),
PublicKeyOf(Statement, Statement),
SignedBy(Statement, Statement, Signature),
ContainerInsertFromEntries(
/* new_root */ Statement,
/* old_root */ Statement,
@ -243,7 +245,6 @@ impl Operation {
use NativeOperation::*;
match self {
Self::None => OT::Native(None),
Self::NewEntry => OT::Native(NewEntry),
Self::CopyStatement(_) => OT::Native(CopyStatement),
Self::EqualFromEntries(_, _) => OT::Native(EqualFromEntries),
Self::NotEqualFromEntries(_, _) => OT::Native(NotEqualFromEntries),
@ -258,6 +259,7 @@ impl Operation {
Self::MaxOf(_, _, _) => OT::Native(MaxOf),
Self::HashOf(_, _, _) => OT::Native(HashOf),
Self::PublicKeyOf(_, _) => OT::Native(PublicKeyOf),
Self::SignedBy(_, _, _) => OT::Native(SignedBy),
Self::ContainerInsertFromEntries(_, _, _, _, _) => {
OT::Native(ContainerInsertFromEntries)
}
@ -272,7 +274,6 @@ impl Operation {
pub fn args(&self) -> Vec<Statement> {
match self.clone() {
Self::None => vec![],
Self::NewEntry => vec![],
Self::CopyStatement(s) => vec![s],
Self::EqualFromEntries(s1, s2) => vec![s1, s2],
Self::NotEqualFromEntries(s1, s2) => vec![s1, s2],
@ -287,6 +288,7 @@ impl Operation {
Self::MaxOf(s1, s2, s3) => vec![s1, s2, s3],
Self::HashOf(s1, s2, s3) => vec![s1, s2, s3],
Self::PublicKeyOf(s1, s2) => vec![s1, s2],
Self::SignedBy(s1, s2, _sig) => vec![s1, s2],
Self::ContainerInsertFromEntries(s1, s2, s3, s4, _pf) => vec![s1, s2, s3, s4],
Self::ContainerUpdateFromEntries(s1, s2, s3, s4, _pf) => vec![s1, s2, s3, s4],
Self::ContainerDeleteFromEntries(s1, s2, s3, _pf) => vec![s1, s2, s3],
@ -310,7 +312,6 @@ impl Operation {
Ok(match op_code {
OperationType::Native(o) => match (o, &args, aux.clone()) {
(NO::None, &[], OA::None) => Self::None,
(NO::NewEntry, &[], OA::None) => Self::NewEntry,
(NO::CopyStatement, &[s], OA::None) => Self::CopyStatement(s.clone()),
(NO::EqualFromEntries, &[s1, s2], OA::None) => {
Self::EqualFromEntries(s1.clone(), s2.clone())
@ -343,6 +344,9 @@ impl Operation {
Self::HashOf(s1.clone(), s2.clone(), s3.clone())
}
(NO::PublicKeyOf, &[s1, s2], OA::None) => Self::PublicKeyOf(s1.clone(), s2.clone()),
(NO::SignedBy, &[s1, s2], OA::Signature(sig)) => {
Self::SignedBy(s1.clone(), s2.clone(), sig)
}
(
NO::ContainerInsertFromEntries,
&[s1, s2, s3, s4],
@ -410,6 +414,11 @@ impl Operation {
Ok(sk.0 < *GROUP_ORDER && pk == sk.public_key())
}
pub(crate) fn check_signed_by(msg: &Value, pk: &Value, sig: &Signature) -> Result<bool> {
let pk: PublicKey = pk.typed().try_into()?;
Ok(sig.verify(pk, msg.raw()))
}
/// Checks the given operation against a statement.
pub fn check(&self, params: &Params, output_statement: &Statement) -> Result<bool> {
use Statement::*;
@ -424,9 +433,6 @@ impl Operation {
};
let b = match (self, output_statement) {
(Self::None, None) => true,
(Self::NewEntry, Equal(ValueRef::Key(AnchoredKey { pod_id, .. }), _)) => {
pod_id == &SELF
}
(Self::CopyStatement(s1), s2) => s1 == s2,
(Self::EqualFromEntries(s1, s2), Equal(v3, v4)) => val(v3, s1)? == val(v4, s2)?,
(Self::NotEqualFromEntries(s1, s2), NotEqual(v3, v4)) => val(v3, s1)? != val(v4, s2)?,
@ -479,6 +485,9 @@ impl Operation {
(Self::PublicKeyOf(s1, s2), PublicKeyOf(v3, v4)) => {
Self::check_public_key(&val(v3, s1)?, &val(v4, s2)?)?
}
(Self::SignedBy(msg_s, pk_s, sig), SignedBy(msg_v, pk_v)) => {
Self::check_signed_by(&val(msg_v, msg_s)?, &val(pk_v, pk_s)?, sig)?
}
(
Self::ContainerInsertFromEntries(new_root_s, old_root_s, key_s, val_s, pf),
ContainerInsert(new_root_v, old_root_v, key_v, val_v),
@ -579,15 +588,15 @@ pub fn check_st_tmpl(
(StatementTmplArg::None, StatementArg::None) => Ok(()),
(StatementTmplArg::Literal(lhs), StatementArg::Literal(rhs)) if lhs == rhs => Ok(()),
(
StatementTmplArg::AnchoredKey(pod_id_wc, key_tmpl),
StatementArg::Key(AnchoredKey { pod_id, key }),
StatementTmplArg::AnchoredKey(root_wc, key_tmpl),
StatementArg::Key(AnchoredKey { root, key }),
) => {
let pod_id_ok = check_or_set(Value::from(*pod_id), pod_id_wc, wildcard_map);
pod_id_ok.and_then(|_| {
let root_ok = check_or_set(Value::from(*root), root_wc, wildcard_map);
root_ok.and_then(|_| {
(key_tmpl == key).then_some(()).ok_or(
Error::mismatched_anchored_key_in_statement_tmpl_arg(
pod_id_wc.clone(),
*pod_id,
root_wc.clone(),
*root,
key_tmpl.clone(),
key.clone(),
),
@ -742,14 +751,28 @@ impl fmt::Display for Operation {
}
}
pub(crate) fn root_key_to_ak(root: &Value, key: &Value) -> Option<AnchoredKey> {
let root_hash = Hash::from(root.raw());
Key::try_from(key.typed())
.map(|key| AnchoredKey::new(root_hash, key))
.ok()
}
/// Returns the value associated with `output_ref`.
/// If `output_ref` is a concrete value, returns that value.
/// Otherwise, `output_ref` was constructed using an `Equal` statement, and `input_st`
/// Otherwise, `output_ref` was constructed using a `Contains` statement, and `input_st`
/// must be that statement.
pub(crate) fn value_from_op(input_st: &Statement, output_ref: &ValueRef) -> Option<Value> {
match (input_st, output_ref) {
(Statement::None, ValueRef::Literal(v)) => Some(v.clone()),
(Statement::Equal(r1, ValueRef::Literal(v)), r2) if r1 == r2 => Some(v.clone()),
(
Statement::Contains(
ValueRef::Literal(root),
ValueRef::Literal(key),
ValueRef::Literal(v),
),
ValueRef::Key(out_ak),
) => root_key_to_ak(root, key).and_then(|ak| (*out_ak == ak).then(|| v.clone())),
_ => None,
}
}
@ -761,45 +784,39 @@ mod tests {
use num::BigUint;
use crate::{
backends::plonky2::primitives::{
ec::{curve::GROUP_ORDER, schnorr::SecretKey},
merkletree::MerkleTree,
},
middleware::{
hash_value, AnchoredKey, Error, Key, Operation, Params, PodId, Result, Statement,
backends::plonky2::{
primitives::{
ec::{curve::GROUP_ORDER, schnorr::SecretKey},
merkletree::MerkleTree,
},
signer::Signer,
},
middleware::{hash_value, Error, Operation, Params, Result, Signer as _, Statement, Value},
};
#[test]
fn check_container_ops() -> Result<()> {
let params = Params::default();
let pod_id = PodId::default();
let root_ak = AnchoredKey::new(pod_id, Key::new("root".into()));
let key_ak = AnchoredKey::new(pod_id, Key::new("key".into()));
let val_ak = AnchoredKey::new(pod_id, Key::new("value".into()));
// Form Merkle tree
let kvs = (0..10)
.map(|i| (hash_value(&i.into()).into(), i.into()))
.collect::<HashMap<_, _>>();
let mt = MerkleTree::new(params.max_depth_mt_containers, &kvs)?;
let root_s = Statement::Equal(root_ak.clone().into(), mt.root().into());
let root = mt.root();
// Check existence proofs
kvs.iter().try_for_each(|(k, v)| {
// Form op args
let key_s = Statement::Equal(key_ak.clone().into(), (*k).into());
let value_s = Statement::Equal(val_ak.clone().into(), (*v).into());
let (_, pf) = mt.prove(k)?;
// Form op
let op = Operation::ContainsFromEntries(root_s.clone(), key_s, value_s, pf);
// Form output statement
let st = Statement::Contains(
root_ak.clone().into(),
key_ak.clone().into(),
val_ak.clone().into(),
let op = Operation::ContainsFromEntries(
Statement::None,
Statement::None,
Statement::None,
pf,
);
// Form output statement
let st = Statement::Contains(root.into(), (*k).into(), (*v).into());
// Check op against output statement
op.check(&params, &st).and_then(|ind| {
@ -816,11 +833,10 @@ mod tests {
// Check non-existence proofs similarly
(50..60).try_for_each(|k| {
let key_s = Statement::Equal(key_ak.clone().into(), k.into());
let pf = mt.prove_nonexistence(&k.into())?;
let op = Operation::NotContainsFromEntries(root_s.clone(), key_s, pf);
let st = Statement::NotContains(root_ak.clone().into(), key_ak.clone().into());
let op = Operation::NotContainsFromEntries(Statement::None, Statement::None, pf);
let st = Statement::NotContains(root.into(), k.into());
op.check(&params, &st).and_then(|ind| {
if ind {
@ -838,11 +854,6 @@ mod tests {
#[test]
fn check_container_update_ops() -> Result<()> {
let params = Params::default();
let pod_id = PodId::default();
let new_root_ak = AnchoredKey::new(pod_id, Key::new("new_root".into()));
let old_root_ak = AnchoredKey::new(pod_id, Key::new("new_root".into()));
let key_ak = AnchoredKey::new(pod_id, Key::new("key".into()));
let val_ak = AnchoredKey::new(pod_id, Key::new("value".into()));
// Form Merkle tree
let kvs = (0..10)
@ -854,23 +865,24 @@ mod tests {
(11..20)
.map(|i| (hash_value(&i.into()).into(), i.into()))
.try_for_each(|(k, v)| {
let old_root_s = Statement::Equal(old_root_ak.clone().into(), mt.root().into());
let old_root = mt.root();
let mtp = mt.insert(&k, &v)?;
// Form op args
let new_root_s = Statement::Equal(new_root_ak.clone().into(), mt.root().into());
let key_s = Statement::Equal(key_ak.clone().into(), k.into());
let value_s = Statement::Equal(val_ak.clone().into(), v.into());
let new_root = mt.root();
// Form op
let op = Operation::ContainerInsertFromEntries(
new_root_s, old_root_s, key_s, value_s, mtp,
Statement::None,
Statement::None,
Statement::None,
Statement::None,
mtp,
);
// Form output statement
let st = Statement::ContainerInsert(
new_root_ak.clone().into(),
old_root_ak.clone().into(),
key_ak.clone().into(),
val_ak.clone().into(),
new_root.into(),
old_root.into(),
k.into(),
v.into(),
);
// Check op against output statement
@ -890,23 +902,24 @@ mod tests {
(11..20)
.map(|i| (hash_value(&i.into()).into(), (i + 1).into()))
.try_for_each(|(k, v)| {
let old_root_s = Statement::Equal(old_root_ak.clone().into(), mt.root().into());
let old_root = mt.root();
let mtp = mt.update(&k, &v)?;
// Form op args
let new_root_s = Statement::Equal(new_root_ak.clone().into(), mt.root().into());
let key_s = Statement::Equal(key_ak.clone().into(), k.into());
let value_s = Statement::Equal(val_ak.clone().into(), v.into());
let new_root = mt.root();
// Form op
let op = Operation::ContainerUpdateFromEntries(
new_root_s, old_root_s, key_s, value_s, mtp,
Statement::None,
Statement::None,
Statement::None,
Statement::None,
mtp,
);
// Form output statement
let st = Statement::ContainerUpdate(
new_root_ak.clone().into(),
old_root_ak.clone().into(),
key_ak.clone().into(),
val_ak.clone().into(),
new_root.into(),
old_root.into(),
k.into(),
v.into(),
);
// Check op against output statement
@ -926,20 +939,19 @@ mod tests {
(11..20)
.map(|i| hash_value(&i.into()).into())
.try_for_each(|k| {
let old_root_s = Statement::Equal(old_root_ak.clone().into(), mt.root().into());
let old_root = mt.root();
let mtp = mt.delete(&k)?;
// Form op args
let new_root_s = Statement::Equal(new_root_ak.clone().into(), mt.root().into());
let key_s = Statement::Equal(key_ak.clone().into(), k.into());
let new_root = mt.root();
// Form op
let op = Operation::ContainerDeleteFromEntries(new_root_s, old_root_s, key_s, mtp);
// Form output statement
let st = Statement::ContainerDelete(
new_root_ak.clone().into(),
old_root_ak.clone().into(),
key_ak.clone().into(),
let op = Operation::ContainerDeleteFromEntries(
Statement::None,
Statement::None,
Statement::None,
mtp,
);
// Form output statement
let st = Statement::ContainerDelete(new_root.into(), old_root.into(), k.into());
// Check op against output statement
op.check(&params, &st).and_then(|ind| {
@ -979,20 +991,13 @@ mod tests {
];
let params = Params::default();
let pod_id = PodId::default();
let pk_ak = AnchoredKey::new(pod_id, Key::new("pubkey".into()));
let sk_ak = AnchoredKey::new(pod_id, Key::new("secret".into()));
test_cases.iter().try_for_each(|(pk, sk, expect_good)| {
// Form op args
let pk_s = Statement::Equal(pk_ak.clone().into(), (*pk).into());
let sk_s = Statement::Equal(sk_ak.clone().into(), sk.clone().into());
// Form op
let op = Operation::PublicKeyOf(pk_s.clone(), sk_s.clone());
let op = Operation::PublicKeyOf(Statement::None, Statement::None);
// Form output statement
let st = Statement::PublicKeyOf(pk_ak.clone().into(), sk_ak.clone().into());
let st = Statement::PublicKeyOf((*pk).into(), sk.clone().into());
// Check
op.check(&params, &st).map(|is_good| {
@ -1011,28 +1016,37 @@ mod tests {
let fixed_pk = fixed_sk.public_key();
let params = Params::default();
let pod_id = PodId::default();
let pk_ak = AnchoredKey::new(pod_id, Key::new("pubkey".into()));
let sk_ak = AnchoredKey::new(pod_id, Key::new("secret".into()));
// Form op args
let pk_s = Statement::Equal(pk_ak.clone().into(), fixed_pk.into());
let sk_s = Statement::Equal(sk_ak.clone().into(), fixed_sk.clone().into());
// Bad op and statement with bad first args
let op = Operation::PublicKeyOf(pk_s.clone(), pk_s.clone());
let st = Statement::PublicKeyOf(pk_ak.clone().into(), pk_ak.clone().into());
let op = Operation::PublicKeyOf(Statement::None, Statement::None);
let st = Statement::PublicKeyOf(fixed_pk.into(), fixed_pk.into());
// Check
assert!(op.check(&params, &st).is_err());
// Bad op and statement with bad second args
let op = Operation::PublicKeyOf(sk_s.clone(), sk_s.clone());
let st = Statement::PublicKeyOf(sk_ak.clone().into(), sk_ak.clone().into());
let op = Operation::PublicKeyOf(Statement::None, Statement::None);
let st = Statement::PublicKeyOf(fixed_sk.clone().into(), fixed_sk.clone().into());
// Check
assert!(op.check(&params, &st).is_err());
Ok(())
}
#[test]
fn check_signed_by_op() -> Result<()> {
let params = Params::default();
let sk = SecretKey(BigUint::from(0x1234567890abcdefu64));
let pk = sk.public_key();
let msg = Value::from("hello");
let sig = Signer(sk).sign(msg.raw());
let op = Operation::SignedBy(Statement::None, Statement::None, sig);
let st = Statement::SignedBy(msg.into(), pk.into());
op.check(&params, &st)?;
Ok(())
}
}