chore(frontend): make Merkle proofs optional (#198)

* Make frontend Merkle proofs optional

* Code review

* Clippy
This commit is contained in:
Ahmad Afuni 2025-04-17 21:51:02 +10:00 committed by GitHub
parent 281f57f0a0
commit 17e6c2a092
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 98 additions and 48 deletions

View file

@ -548,7 +548,7 @@ pub mod tests {
let key = RawValue::from(hash_value(&RawValue::from(5)));
let (value, proof) = tree.prove(&key)?;
assert_eq!(value, RawValue::from(5));
assert_eq!(proof.existence, true);
assert!(proof.existence);
MerkleTree::verify(max_depth, tree.root(), &proof, &key, &value)?;

View file

@ -2,14 +2,14 @@ pub mod custom;
use std::collections::HashSet;
use anyhow::{anyhow, Result};
use anyhow::Result;
use custom::{eth_dos_batch, eth_friend_batch};
use crate::{
backends::plonky2::mock::signedpod::MockSigner,
frontend::{MainPodBuilder, SignedPod, SignedPodBuilder},
middleware::{
containers::Set, CustomPredicateRef, Key, Params, PodType, Statement, TypedValue, Value,
containers::Set, CustomPredicateRef, Params, PodType, Statement, TypedValue, Value,
KEY_SIGNER, KEY_TYPE,
},
op,
@ -45,16 +45,9 @@ pub fn zu_kyc_pod_builder(
pay_stub: &SignedPod,
sanction_list: &SignedPod,
) -> Result<MainPodBuilder> {
let sanction_set = match sanction_list.get("sanctionList").map(|v| v.typed()) {
Some(TypedValue::Set(s)) => Ok(s),
_ => Err(anyhow!("Missing sanction list!")),
}?;
let now_minus_18y: i64 = 1169909388;
let now_minus_1y: i64 = 1706367566;
let gov_id_kvs = gov_id.kvs();
let id_number_value = gov_id_kvs.get(&Key::from("idNumber")).unwrap();
let mut kyc = MainPodBuilder::new(params);
kyc.add_signed_pod(gov_id);
kyc.add_signed_pod(pay_stub);
@ -62,8 +55,7 @@ pub fn zu_kyc_pod_builder(
kyc.pub_op(op!(
set_not_contains,
(sanction_list, "sanctionList"),
(gov_id, "idNumber"),
sanction_set.prove_nonexistence(id_number_value)?
(gov_id, "idNumber")
))?;
kyc.pub_op(op!(lt, (gov_id, "dateOfBirth"), now_minus_18y))?;
kyc.pub_op(op!(
@ -269,8 +261,6 @@ pub fn great_boy_pod_builder(
PodType::MockSigned as i64
))?;
for issuer_idx in 0..2 {
let pod_kvs = good_boy_pods[good_boy_idx * 2 + issuer_idx].kvs();
// Type check
great_boy.pub_op(op!(
eq,
@ -278,16 +268,10 @@ pub fn great_boy_pod_builder(
PodType::MockSigned as i64
))?;
// Each good boy POD comes from a valid issuer
let good_boy_proof = match good_boy_issuers.typed() {
TypedValue::Set(set) => Ok(set),
_ => Err(anyhow!("Invalid good boy issuers!")),
}?
.prove(pod_kvs.get(&Key::from(KEY_SIGNER)).unwrap())?;
great_boy.pub_op(op!(
set_contains,
good_boy_issuers,
(good_boy_pods[good_boy_idx * 2 + issuer_idx], KEY_SIGNER),
good_boy_proof
(good_boy_pods[good_boy_idx * 2 + issuer_idx], KEY_SIGNER)
))?;
// Each good boy has 2 good boy pods
great_boy.pub_op(op!(
@ -403,12 +387,6 @@ pub fn tickets_pod_builder(
expect_consumed: bool,
blacklisted_emails: &Set,
) -> Result<MainPodBuilder> {
let attendee_email_value = signed_pod
.kvs()
.get(&Key::from("attendeeEmail"))
.unwrap()
.clone();
let attendee_nin_blacklist_pf = blacklisted_emails.prove_nonexistence(&attendee_email_value)?;
let blacklisted_email_set_value = Value::from(TypedValue::Set(blacklisted_emails.clone()));
// Create a main pod referencing this signed pod with some statements
let mut builder = MainPodBuilder::new(params);
@ -419,8 +397,7 @@ pub fn tickets_pod_builder(
builder.pub_op(op!(
dict_not_contains,
blacklisted_email_set_value,
(signed_pod, "attendeeEmail"),
attendee_nin_blacklist_pf
(signed_pod, "attendeeEmail")
))?;
Ok(builder)
}

View file

@ -233,7 +233,7 @@ mod tests {
let eth_dos_batch = eth_dos_batch(&params)?;
let eth_dos_batch_mw: middleware::CustomPredicateBatch =
Arc::unwrap_or_clone(eth_dos_batch).into();
Arc::unwrap_or_clone(eth_dos_batch);
let fields = eth_dos_batch_mw.to_fields(&params);
println!("Batch b, serialized: {:?}", fields);

View file

@ -262,9 +262,39 @@ impl MainPodBuilder {
}
}
/// Fills in auxiliary data if necessary/possible.
fn fill_in_aux(op: Operation) -> Result<Operation> {
use NativeOperation::{ContainsFromEntries, NotContainsFromEntries};
use OperationAux as OpAux;
use OperationType::Native;
let op_type = &op.0;
match (op_type, &op.2) {
(Native(ContainsFromEntries), OpAux::None)
| (Native(NotContainsFromEntries), OpAux::None) => {
let container =
op.1.get(0)
.and_then(|arg| arg.value())
.ok_or(anyhow!("Invalid container argument for op {}.", op))?;
let key =
op.1.get(1)
.and_then(|arg| arg.value())
.ok_or(anyhow!("Invalid key argument for op {}.", op))?;
let proof = if op_type == &Native(ContainsFromEntries) {
container.prove_existence(key)?.1
} else {
container.prove_nonexistence(key)?
};
Ok(Operation(op_type.clone(), op.1, OpAux::MerkleProof(proof)))
}
_ => Ok(op),
}
}
fn op(&mut self, public: bool, op: Operation) -> Result<Statement, anyhow::Error> {
use NativeOperation::*;
let mut op = Self::lower_op(op);
let mut op = Self::fill_in_aux(Self::lower_op(op))?;
let Operation(op_type, ref mut args, _) = &mut op;
// TODO: argument type checking
let pred = op_type.output_predicate().map(Ok).unwrap_or_else(|| {
@ -736,21 +766,21 @@ pub mod build_utils {
(custom, $op:expr, $($arg:expr),+) => { $crate::frontend::Operation(
$crate::middleware::OperationType::Custom($op),
$crate::op_args!($($arg),*), $crate::middleware::OperationAux::None) };
(dict_contains, $dict:expr, $key:expr, $value:expr, $aux:expr) => { $crate::frontend::Operation(
(dict_contains, $dict:expr, $key:expr, $value:expr) => { $crate::frontend::Operation(
$crate::middleware::OperationType::Native($crate::middleware::NativeOperation::DictContainsFromEntries),
$crate::op_args!($dict, $key, $value), $crate::middleware::OperationAux::MerkleProof($aux)) };
(dict_not_contains, $dict:expr, $key:expr, $aux:expr) => { $crate::frontend::Operation(
$crate::op_args!($dict, $key, $value), $crate::middleware::OperationAux::None) };
(dict_not_contains, $dict:expr, $key:expr) => { $crate::frontend::Operation(
$crate::middleware::OperationType::Native($crate::middleware::NativeOperation::DictNotContainsFromEntries),
$crate::op_args!($dict, $key), $crate::middleware::OperationAux::MerkleProof($aux)) };
(set_contains, $set:expr, $value:expr, $aux:expr) => { $crate::frontend::Operation(
$crate::op_args!($dict, $key), $crate::middleware::OperationAux::None) };
(set_contains, $set:expr, $value:expr) => { $crate::frontend::Operation(
$crate::middleware::OperationType::Native($crate::middleware::NativeOperation::SetContainsFromEntries),
$crate::op_args!($set, $value), $crate::middleware::OperationAux::MerkleProof($aux)) };
(set_not_contains, $set:expr, $value:expr, $aux:expr) => { $crate::frontend::Operation(
$crate::op_args!($set, $value), $crate::middleware::OperationAux::None) };
(set_not_contains, $set:expr, $value:expr) => { $crate::frontend::Operation(
$crate::middleware::OperationType::Native($crate::middleware::NativeOperation::SetNotContainsFromEntries),
$crate::op_args!($set, $value), $crate::middleware::OperationAux::MerkleProof($aux)) };
(array_contains, $array:expr, $index:expr, $value:expr, $aux:expr) => { $crate::frontend::Operation(
$crate::op_args!($set, $value), $crate::middleware::OperationAux::None) };
(array_contains, $array:expr, $index:expr, $value:expr) => { $crate::frontend::Operation(
$crate::middleware::OperationType::Native($crate::middleware::NativeOperation::ArrayContainsFromEntries),
$crate::op_args!($array, $index, $value), $crate::middleware::OperationAux::MerkleProof($aux)) };
$crate::op_args!($array, $index, $value), $crate::middleware::OperationAux::None) };
}
}
@ -781,12 +811,7 @@ pub mod tests {
// Check that frontend key-values agree with those embedded in a
// SignedPod.
fn check_kvs(pod: &SignedPod) -> Result<()> {
let kvs = pod
.kvs
.clone()
.into_iter()
.map(|(k, v)| (k, v))
.collect::<HashMap<_, _>>();
let kvs = pod.kvs.clone().into_iter().collect::<HashMap<_, _>>();
let embedded_kvs = pod
.pod
.kvs()

View file

@ -13,6 +13,18 @@ pub enum OperationArg {
Entry(String, Value),
}
impl OperationArg {
/// Extracts the value underlying literal and `ValueOf` statement
/// operation args.
pub(crate) fn value(&self) -> Option<&Value> {
match self {
Self::Literal(v) => Some(v),
Self::Statement(Statement::ValueOf(_, v)) => Some(v),
_ => None,
}
}
}
impl fmt::Display for OperationArg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {

View file

@ -26,6 +26,8 @@ pub use operation::*;
// use serde::{Deserialize, Serialize};
pub use statement::*;
use crate::backends::plonky2::primitives::merkletree::MerkleProof;
pub const SELF: PodId = PodId(SELF_ID_HASH);
// TODO: Move all value-related types to to `value.rs`
@ -136,6 +138,16 @@ impl TryFrom<&TypedValue> for i64 {
}
}
impl TryFrom<TypedValue> for Key {
type Error = anyhow::Error;
fn try_from(tv: TypedValue) -> Result<Self> {
match tv {
TypedValue::String(s) => Ok(Key::new(s)),
_ => Err(anyhow!("Value {} cannot be converted to a key.", tv)),
}
}
}
impl fmt::Display for TypedValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
@ -218,6 +230,30 @@ impl Value {
pub fn raw(&self) -> RawValue {
self.raw
}
/// Determines Merkle existence proof for `key` in `self` (if applicable).
pub(crate) fn prove_existence<'a>(
&'a self,
key: &'a Value,
) -> Result<(&'a Value, MerkleProof)> {
match &self.typed() {
TypedValue::Array(a) => match key.typed() {
TypedValue::Int(i) if i >= &0 => a.prove((*i) as usize),
_ => Err(anyhow!("Invalid key {} for container {}.", key, self))?,
},
TypedValue::Dictionary(d) => d.prove(&key.typed().clone().try_into()?),
TypedValue::Set(s) => Ok((key, s.prove(key)?)),
_ => Err(anyhow!("Invalid container value {}", self.typed())),
}
}
/// Determines Merkle non-existence proof for `key` in `self` (if applicable).
pub(crate) fn prove_nonexistence<'a>(&'a self, key: &'a Value) -> Result<MerkleProof> {
match &self.typed() {
TypedValue::Array(_) => Err(anyhow!("Arrays do not support `NotContains` operation.")),
TypedValue::Dictionary(d) => d.prove_nonexistence(&key.typed().clone().try_into()?),
TypedValue::Set(s) => s.prove_nonexistence(key),
_ => Err(anyhow!("Invalid container value {}", self.typed())),
}
}
}
// A Value can be created from any type Into<TypedValue> type: bool, string-like, i64, ...

View file

@ -6,7 +6,7 @@ use plonky2::field::types::Field;
// use serde::{Deserialize, Serialize};
use crate::{
backends::plonky2::primitives::merkletree::{MerkleProof, MerkleTree},
backends::plonky2::primitives::merkletree::MerkleProof,
middleware::{
custom::KeyOrWildcard, AnchoredKey, CustomPredicateBatch, CustomPredicateRef,
NativePredicate, Params, Predicate, Statement, StatementArg, StatementTmplArg, ToFields,