* Contains should take three arguments (root, key, value) * Add a test for frontend Dictionaries * Separate frontend and middleware operations * Make tests pass: add arg to contains * Cargo fmt * Merkleproof verify circuit (#143) * merkletree: add keypath circuit * merkletree-circuit: implement proof of existence verification in-circuit * parametrize max_depth at the tree circuit * Constrain selectors in-circuit * implement merketree nonexistence proof circuit, and add edgecase tests * add non-existence proofs documentation in the mdbook, mv EMPTY->EMPTY_VALUE & NULL->EMPTY_HASH, dependency clean and public exposure methods * review comments, some extra polishing and add a test that expects wrong proofs to fail * Add circuit to check only merkleproofs-of-existence With this, the merkletree_circuit module offers two different circuits: - `MerkleProofCircuit`: allows to verify both proofs of existence and proofs non-existence with the same circuit. - `MerkleProofExistenceCircuit`: allows to verify proofs of existence only. In this way, if only proofs of existence are needed, `MerkleProofExistenceCircuit` should be used, which requires less amount of constraints than `MerkleProofCircuit`. * Code review --------- Co-authored-by: Ahmad <root@ahmadafuni.com> * Towards Contains/NotContains in middleware and backend * Fix build * Adding error handling to deal with op compile introduce extra ops * Incorporate Merkle proofs into MockMainPod * Merkleproof verify circuit (#143) * merkletree: add keypath circuit * merkletree-circuit: implement proof of existence verification in-circuit * parametrize max_depth at the tree circuit * Constrain selectors in-circuit * implement merketree nonexistence proof circuit, and add edgecase tests * add non-existence proofs documentation in the mdbook, mv EMPTY->EMPTY_VALUE & NULL->EMPTY_HASH, dependency clean and public exposure methods * review comments, some extra polishing and add a test that expects wrong proofs to fail * Add circuit to check only merkleproofs-of-existence With this, the merkletree_circuit module offers two different circuits: - `MerkleProofCircuit`: allows to verify both proofs of existence and proofs non-existence with the same circuit. - `MerkleProofExistenceCircuit`: allows to verify proofs of existence only. In this way, if only proofs of existence are needed, `MerkleProofExistenceCircuit` should be used, which requires less amount of constraints than `MerkleProofCircuit`. * Code review --------- Co-authored-by: Ahmad <root@ahmadafuni.com> * Towards Contains/NotContains in middleware and backend * Frontend compound types -- allow one frontend operation to produce multiple middleware statements (in progress) * Incorporate Merkle proofs into MockMainPod * Incorporate Merkle proof op arg into frontend * Compile one statement to many, in progress * Fix remaining tests * Minor clean-up * Oops I did a bunch of work in the middle of a rebase, committing * Incorporate Merkle proof op arg into frontend * still working on frontend compound types, refactor compile() to output multiple statements * Contains statements for frontend types: code compiles * Tests pass * Examples use front-end compound types * Remove old Contains and NotContains from frontend * Add nin to typos * Code review --------- Co-authored-by: arnaucube <git@arnaucube.com> Co-authored-by: Ahmad <root@ahmadafuni.com>
1540 lines
59 KiB
Rust
1540 lines
59 KiB
Rust
//! The frontend includes the user-level abstractions and user-friendly types to define and work
|
|
//! with Pods.
|
|
|
|
use crate::frontend::serialization::*;
|
|
use crate::middleware::{
|
|
self, hash_str, Hash, MainPodInputs, Params, PodId, PodProver, PodSigner, SELF,
|
|
};
|
|
use crate::middleware::{KEY_SIGNER, KEY_TYPE};
|
|
use anyhow::{anyhow, Error, Result};
|
|
use containers::{Array, Dictionary, Set};
|
|
use env_logger;
|
|
use itertools::Itertools;
|
|
use log::error;
|
|
use schemars::JsonSchema;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::collections::HashMap;
|
|
use std::convert::From;
|
|
use std::{fmt, hash as h};
|
|
|
|
use crate::middleware::{hash_value, OperationAux, EMPTY_VALUE};
|
|
|
|
pub mod containers;
|
|
mod custom;
|
|
mod operation;
|
|
mod predicate;
|
|
mod serialization;
|
|
mod statement;
|
|
pub use custom::*;
|
|
pub use operation::*;
|
|
pub use predicate::*;
|
|
pub use statement::*;
|
|
|
|
/// This type is just for presentation purposes.
|
|
#[derive(Clone, Debug, Default, h::Hash, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
|
pub enum PodClass {
|
|
#[default]
|
|
Signed,
|
|
Main,
|
|
}
|
|
|
|
// An Origin, which represents a reference to an ancestor POD.
|
|
#[derive(Clone, Debug, PartialEq, Eq, h::Hash, Default, Serialize, Deserialize, JsonSchema)]
|
|
pub struct Origin {
|
|
pub pod_class: PodClass,
|
|
pub pod_id: PodId,
|
|
}
|
|
|
|
impl Origin {
|
|
pub fn new(pod_class: PodClass, pod_id: PodId) -> Self {
|
|
Self { pod_class, pod_id }
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
|
#[schemars(transform = serialization::transform_value_schema)]
|
|
pub enum Value {
|
|
// Serde cares about the order of the enum variants, with untagged variants
|
|
// appearing at the end.
|
|
// Variants without "untagged" will be serialized as "tagged" values by
|
|
// default, meaning that a Set appears in JSON as {"Set":[...]}
|
|
// and not as [...]
|
|
// Arrays, Strings and Booleans are untagged, as there is a natural JSON
|
|
// representation for them that is unambiguous to deserialize and is fully
|
|
// compatible with the semantics of the POD types.
|
|
// As JSON integers do not specify precision, and JavaScript is limited to
|
|
// 53-bit precision for integers, integers are represented as tagged
|
|
// strings, with a custom serializer and deserializer.
|
|
// TAGGED TYPES:
|
|
Set(Set),
|
|
Dictionary(Dictionary),
|
|
Int(
|
|
#[serde(serialize_with = "serialize_i64", deserialize_with = "deserialize_i64")]
|
|
#[schemars(with = "String", regex(pattern = r"^\d+$"))]
|
|
i64,
|
|
),
|
|
// Uses the serialization for middleware::Value:
|
|
Raw(middleware::Value),
|
|
// UNTAGGED TYPES:
|
|
#[serde(untagged)]
|
|
#[schemars(skip)]
|
|
Array(Array),
|
|
#[serde(untagged)]
|
|
#[schemars(skip)]
|
|
String(String),
|
|
#[serde(untagged)]
|
|
#[schemars(skip)]
|
|
Bool(bool),
|
|
}
|
|
|
|
impl From<&str> for Value {
|
|
fn from(s: &str) -> Self {
|
|
Value::String(s.to_string())
|
|
}
|
|
}
|
|
|
|
impl From<i64> for Value {
|
|
fn from(v: i64) -> Self {
|
|
Value::Int(v)
|
|
}
|
|
}
|
|
|
|
impl From<bool> for Value {
|
|
fn from(b: bool) -> Self {
|
|
Value::Bool(b)
|
|
}
|
|
}
|
|
|
|
impl From<&Value> for middleware::Value {
|
|
fn from(v: &Value) -> Self {
|
|
match v {
|
|
Value::String(s) => hash_str(s).value(),
|
|
Value::Int(v) => middleware::Value::from(*v),
|
|
Value::Bool(b) => middleware::Value::from(*b as i64),
|
|
Value::Dictionary(d) => d.middleware_dict().commitment().value(),
|
|
Value::Set(s) => s.middleware_set().commitment().value(),
|
|
Value::Array(a) => a.middleware_array().commitment().value(),
|
|
Value::Raw(v) => *v,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<middleware::Value> for Value {
|
|
fn from(v: middleware::Value) -> Self {
|
|
Self::Raw(v)
|
|
}
|
|
}
|
|
|
|
impl From<middleware::Hash> for Value {
|
|
fn from(v: middleware::Hash) -> Self {
|
|
Self::Raw(v.into())
|
|
}
|
|
}
|
|
|
|
impl TryInto<i64> for Value {
|
|
type Error = Error;
|
|
fn try_into(self) -> std::result::Result<i64, Self::Error> {
|
|
if let Value::Int(n) = self {
|
|
Ok(n)
|
|
} else {
|
|
Err(anyhow!("Value not an int"))
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for Value {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
Value::String(s) => write!(f, "\"{}\"", s),
|
|
Value::Int(v) => write!(f, "{}", v),
|
|
Value::Bool(b) => write!(f, "{}", b),
|
|
Value::Dictionary(d) => write!(f, "dict:{}", d.middleware_dict().commitment()),
|
|
Value::Set(s) => write!(f, "set:{}", s.middleware_set().commitment()),
|
|
Value::Array(a) => write!(f, "arr:{}", a.middleware_array().commitment()),
|
|
Value::Raw(v) => write!(f, "{}", v),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct SignedPodBuilder {
|
|
pub params: Params,
|
|
pub kvs: HashMap<String, Value>,
|
|
}
|
|
|
|
impl fmt::Display for SignedPodBuilder {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
writeln!(f, "SignedPodBuilder:")?;
|
|
for (k, v) in self.kvs.iter().sorted_by_key(|kv| kv.0) {
|
|
writeln!(f, " - {}: {}", k, v)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl SignedPodBuilder {
|
|
pub fn new(params: &Params) -> Self {
|
|
Self {
|
|
params: params.clone(),
|
|
kvs: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
pub fn insert(&mut self, key: impl Into<String>, value: impl Into<Value>) {
|
|
self.kvs.insert(key.into(), value.into());
|
|
}
|
|
|
|
pub fn sign<S: PodSigner>(&self, signer: &mut S) -> Result<SignedPod> {
|
|
// Sign POD with committed KV store.
|
|
let committed_kvs = self
|
|
.kvs
|
|
.iter()
|
|
.map(|(k, v)| (hash_str(k), v.into()))
|
|
.collect::<HashMap<_, _>>();
|
|
let pod = signer.sign(&self.params, &committed_kvs)?;
|
|
|
|
let mut kvs = self.kvs.clone();
|
|
|
|
// Type and signer information are passed in by the
|
|
// backend. Include these in the frontend representation.
|
|
let mid_kvs = pod.kvs();
|
|
let pod_type = mid_kvs
|
|
.get(&crate::middleware::AnchoredKey(
|
|
pod.id(),
|
|
hash_str(KEY_TYPE),
|
|
))
|
|
.cloned()
|
|
.ok_or(anyhow!("Missing POD type information in POD: {:?}", pod))?;
|
|
let pod_signer = mid_kvs
|
|
.get(&crate::middleware::AnchoredKey(
|
|
pod.id(),
|
|
hash_str(KEY_SIGNER),
|
|
))
|
|
.cloned()
|
|
.ok_or(anyhow!("Missing POD signer in POD: {:?}", pod))?;
|
|
kvs.insert(KEY_TYPE.to_string(), pod_type.into());
|
|
kvs.insert(KEY_SIGNER.to_string(), pod_signer.into());
|
|
Ok(SignedPod { pod, kvs })
|
|
}
|
|
}
|
|
|
|
/// SignedPod is a wrapper on top of backend::SignedPod, which additionally stores the
|
|
/// string<-->hash relation of the keys.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(try_from = "SignedPodHelper", into = "SignedPodHelper")]
|
|
pub struct SignedPod {
|
|
pub pod: Box<dyn middleware::Pod>,
|
|
/// Key-value pairs as represented in the frontend. These should
|
|
/// correspond to the entries of `pod.kvs()` after hashing and
|
|
/// replacing each key with its corresponding anchored key.
|
|
#[serde(serialize_with = "ordered_map")]
|
|
pub kvs: HashMap<String, Value>,
|
|
}
|
|
|
|
impl fmt::Display for SignedPod {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
writeln!(f, "SignedPod (id:{}):", self.id())?;
|
|
// Note: current version iterates sorting by keys of the kvs, but the merkletree defined at
|
|
// https://0xparc.github.io/pod2/merkletree.html will not need it since it will be
|
|
// deterministic based on the keys values not on the order of the keys when added into the
|
|
// tree.
|
|
for (k, v) in self.kvs.iter().sorted_by_key(|kv| kv.0) {
|
|
writeln!(
|
|
f,
|
|
" - {} = {}: {}",
|
|
hash_str(k),
|
|
k,
|
|
crate::middleware::Value::from(v)
|
|
)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl SignedPod {
|
|
pub fn id(&self) -> PodId {
|
|
self.pod.id()
|
|
}
|
|
pub fn origin(&self) -> Origin {
|
|
Origin::new(PodClass::Signed, self.id())
|
|
}
|
|
pub fn verify(&self) -> Result<()> {
|
|
self.pod.verify()
|
|
}
|
|
pub fn kvs(&self) -> HashMap<Hash, middleware::Value> {
|
|
self.pod
|
|
.kvs()
|
|
.into_iter()
|
|
.map(|(middleware::AnchoredKey(_, k), v)| (k, v))
|
|
.collect()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, h::Hash, Serialize, Deserialize, JsonSchema)]
|
|
pub struct AnchoredKey {
|
|
pub origin: Origin,
|
|
pub key: String,
|
|
}
|
|
|
|
impl AnchoredKey {
|
|
pub fn new(origin: Origin, key: String) -> Self {
|
|
Self { origin, key }
|
|
}
|
|
}
|
|
|
|
impl From<AnchoredKey> for middleware::AnchoredKey {
|
|
fn from(ak: AnchoredKey) -> Self {
|
|
middleware::AnchoredKey(ak.origin.pod_id, hash_str(&ak.key))
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct MainPodBuilder {
|
|
pub params: Params,
|
|
pub input_signed_pods: Vec<SignedPod>,
|
|
pub input_main_pods: Vec<MainPod>,
|
|
pub statements: Vec<Statement>,
|
|
pub operations: Vec<Operation>,
|
|
pub public_statements: Vec<Statement>,
|
|
// Internal state
|
|
const_cnt: usize,
|
|
key_table: HashMap<Hash, String>,
|
|
pod_class_table: HashMap<PodId, PodClass>,
|
|
}
|
|
|
|
impl fmt::Display for MainPodBuilder {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
writeln!(f, "MainPod:")?;
|
|
writeln!(f, " input_signed_pods:")?;
|
|
for in_pod in &self.input_signed_pods {
|
|
writeln!(f, " - {}", in_pod.id())?;
|
|
}
|
|
writeln!(f, " input_main_pods:")?;
|
|
for in_pod in &self.input_main_pods {
|
|
writeln!(f, " - {}", in_pod.id())?;
|
|
}
|
|
writeln!(f, " statements:")?;
|
|
for (st, op) in self.statements.iter().zip_eq(self.operations.iter()) {
|
|
write!(f, " - {} <- ", st)?;
|
|
write!(f, "{}", op)?;
|
|
writeln!(f)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl MainPodBuilder {
|
|
pub fn new(params: &Params) -> Self {
|
|
Self {
|
|
params: params.clone(),
|
|
input_signed_pods: Vec::new(),
|
|
input_main_pods: Vec::new(),
|
|
statements: Vec::new(),
|
|
operations: Vec::new(),
|
|
public_statements: Vec::new(),
|
|
const_cnt: 0,
|
|
key_table: HashMap::new(),
|
|
pod_class_table: HashMap::from_iter([(SELF, PodClass::Main)]),
|
|
}
|
|
}
|
|
pub fn add_signed_pod(&mut self, pod: &SignedPod) {
|
|
self.input_signed_pods.push(pod.clone());
|
|
// Add key-hash correspondences to key table.
|
|
pod.kvs.iter().for_each(|(key, _)| {
|
|
self.key_table.insert(hash_str(key), key.clone());
|
|
});
|
|
// Add POD class to POD class table.
|
|
self.pod_class_table.insert(pod.id(), PodClass::Signed);
|
|
}
|
|
pub fn add_main_pod(&mut self, pod: MainPod) {
|
|
// Add POD class to POD class table.
|
|
self.pod_class_table.insert(pod.id(), PodClass::Main);
|
|
// Add key-hash and POD ID-class correspondences to tables.
|
|
pod.public_statements
|
|
.iter()
|
|
.flat_map(|s| &s.args)
|
|
.flat_map(|arg| match arg {
|
|
StatementArg::Key(AnchoredKey {
|
|
origin: Origin { pod_class, pod_id },
|
|
key,
|
|
}) => Some((*pod_id, pod_class.clone(), hash_str(key), key.clone())),
|
|
_ => None,
|
|
})
|
|
.for_each(|(pod_id, pod_class, hash, key)| {
|
|
self.pod_class_table.insert(pod_id, pod_class);
|
|
self.key_table.insert(hash, key);
|
|
});
|
|
self.input_main_pods.push(pod);
|
|
}
|
|
pub fn insert(&mut self, st_op: (Statement, Operation)) {
|
|
let (st, op) = st_op;
|
|
self.statements.push(st);
|
|
self.operations.push(op);
|
|
}
|
|
|
|
/// Convert [OperationArg]s to [StatementArg]s for the operations that work with entries
|
|
fn op_args_entries(
|
|
&mut self,
|
|
public: bool,
|
|
args: &mut [OperationArg],
|
|
) -> Result<Vec<StatementArg>> {
|
|
let mut st_args = Vec::new();
|
|
for arg in args.iter_mut() {
|
|
match arg {
|
|
OperationArg::Statement(s) => {
|
|
if s.predicate == Predicate::Native(NativePredicate::ValueOf) {
|
|
st_args.push(s.args[0].clone())
|
|
} else {
|
|
panic!("Invalid statement argument.");
|
|
}
|
|
}
|
|
// todo: better error handling
|
|
OperationArg::Literal(v) => {
|
|
let value_of_st = self.literal(public, v)?;
|
|
*arg = OperationArg::Statement(value_of_st.clone());
|
|
st_args.push(value_of_st.args[0].clone())
|
|
}
|
|
OperationArg::Entry(k, v) => {
|
|
st_args.push(StatementArg::Key(AnchoredKey::new(
|
|
Origin::new(PodClass::Main, SELF),
|
|
k.clone(),
|
|
)));
|
|
st_args.push(StatementArg::Literal(v.clone()))
|
|
}
|
|
};
|
|
}
|
|
Ok(st_args)
|
|
}
|
|
|
|
pub fn pub_op(&mut self, op: Operation) -> Result<Statement> {
|
|
self.op(true, op)
|
|
}
|
|
|
|
pub fn priv_op(&mut self, op: Operation) -> Result<Statement> {
|
|
self.op(false, op)
|
|
}
|
|
|
|
fn op(&mut self, public: bool, mut op: Operation) -> Result<Statement, anyhow::Error> {
|
|
use NativeOperation::*;
|
|
let Operation(op_type, ref mut args, _) = &mut op;
|
|
// TODO: argument type checking
|
|
let pred = op_type
|
|
.output_predicate()
|
|
.map(|p| Ok(p))
|
|
.unwrap_or_else(|| {
|
|
// We are dealing with a copy here.
|
|
match (&args).get(0) {
|
|
Some(OperationArg::Statement(s)) if args.len() == 1 => Ok(s.predicate.clone()),
|
|
_ => Err(anyhow!("Invalid arguments to copy operation: {:?}", args)),
|
|
}
|
|
})?;
|
|
|
|
let st_args: Vec<StatementArg> = match op_type {
|
|
OperationType::Native(o) => match o {
|
|
None => vec![],
|
|
NewEntry => self.op_args_entries(public, args)?,
|
|
CopyStatement => match &args[0] {
|
|
OperationArg::Statement(s) => s.args.clone(),
|
|
_ => {
|
|
return Err(anyhow!("Invalid arguments to copy operation: {}", op));
|
|
}
|
|
},
|
|
EqualFromEntries => self.op_args_entries(public, args)?,
|
|
NotEqualFromEntries => self.op_args_entries(public, args)?,
|
|
GtFromEntries => self.op_args_entries(public, args)?,
|
|
LtFromEntries => self.op_args_entries(public, args)?,
|
|
TransitiveEqualFromStatements => {
|
|
match (args[0].clone(), args[1].clone()) {
|
|
(
|
|
OperationArg::Statement(Statement {
|
|
predicate: Predicate::Native(NativePredicate::Equal),
|
|
args: st0_args,
|
|
}),
|
|
OperationArg::Statement(Statement {
|
|
predicate: Predicate::Native(NativePredicate::Equal),
|
|
args: st1_args,
|
|
}),
|
|
) => {
|
|
// st_args0 == vec![ak0, ak1]
|
|
// st_args1 == vec![ak1, ak2]
|
|
// output statement Equals(ak0, ak2)
|
|
if st0_args[1] == st1_args[0] {
|
|
vec![st0_args[0].clone(), st1_args[1].clone()]
|
|
} else {
|
|
return Err(anyhow!(
|
|
"Invalid arguments to transitive equality operation"
|
|
));
|
|
}
|
|
}
|
|
_ => {
|
|
return Err(anyhow!(
|
|
"Invalid arguments to transitive equality operation"
|
|
));
|
|
}
|
|
}
|
|
}
|
|
GtToNotEqual => match args[0].clone() {
|
|
OperationArg::Statement(Statement {
|
|
predicate: Predicate::Native(NativePredicate::Gt),
|
|
args: st_args,
|
|
}) => {
|
|
vec![st_args[0].clone()]
|
|
}
|
|
_ => {
|
|
return Err(anyhow!("Invalid arguments to gt-to-neq operation"));
|
|
}
|
|
},
|
|
LtToNotEqual => match args[0].clone() {
|
|
OperationArg::Statement(Statement {
|
|
predicate: Predicate::Native(NativePredicate::Lt),
|
|
args: st_args,
|
|
}) => {
|
|
vec![st_args[0].clone()]
|
|
}
|
|
_ => {
|
|
return Err(anyhow!("Invalid arguments to lt-to-neq operation"));
|
|
}
|
|
},
|
|
SumOf => match (args[0].clone(), args[1].clone(), args[2].clone()) {
|
|
(
|
|
OperationArg::Statement(Statement {
|
|
predicate: Predicate::Native(NativePredicate::ValueOf),
|
|
args: st0_args,
|
|
}),
|
|
OperationArg::Statement(Statement {
|
|
predicate: Predicate::Native(NativePredicate::ValueOf),
|
|
args: st1_args,
|
|
}),
|
|
OperationArg::Statement(Statement {
|
|
predicate: Predicate::Native(NativePredicate::ValueOf),
|
|
args: st2_args,
|
|
}),
|
|
) => {
|
|
let st_args: Vec<StatementArg> = match (
|
|
st0_args[1].clone(),
|
|
st1_args[1].clone(),
|
|
st2_args[1].clone(),
|
|
) {
|
|
(
|
|
StatementArg::Literal(v0),
|
|
StatementArg::Literal(v1),
|
|
StatementArg::Literal(v2),
|
|
) => {
|
|
let v0: i64 = v0.clone().try_into()?;
|
|
let v1: i64 = v1.clone().try_into()?;
|
|
let v2: i64 = v2.clone().try_into()?;
|
|
if v0 == v1 + v2 {
|
|
vec![
|
|
st0_args[0].clone(),
|
|
st1_args[0].clone(),
|
|
st2_args[0].clone(),
|
|
]
|
|
} else {
|
|
return Err(anyhow!("Invalid arguments to sum-of operation"));
|
|
}
|
|
}
|
|
_ => {
|
|
return Err(anyhow!("Invalid arguments to sum-of operation"));
|
|
}
|
|
};
|
|
st_args
|
|
}
|
|
_ => {
|
|
return Err(anyhow!("Invalid arguments to sum-of operation"));
|
|
}
|
|
},
|
|
ProductOf => match (args[0].clone(), args[1].clone(), args[2].clone()) {
|
|
(
|
|
OperationArg::Statement(Statement {
|
|
predicate: Predicate::Native(NativePredicate::ValueOf),
|
|
args: st0_args,
|
|
}),
|
|
OperationArg::Statement(Statement {
|
|
predicate: Predicate::Native(NativePredicate::ValueOf),
|
|
args: st1_args,
|
|
}),
|
|
OperationArg::Statement(Statement {
|
|
predicate: Predicate::Native(NativePredicate::ValueOf),
|
|
args: st2_args,
|
|
}),
|
|
) => {
|
|
let st_args: Vec<StatementArg> = match (
|
|
st0_args[1].clone(),
|
|
st1_args[1].clone(),
|
|
st2_args[1].clone(),
|
|
) {
|
|
(
|
|
StatementArg::Literal(v0),
|
|
StatementArg::Literal(v1),
|
|
StatementArg::Literal(v2),
|
|
) => {
|
|
let v0: i64 = v0.clone().try_into()?;
|
|
let v1: i64 = v1.clone().try_into()?;
|
|
let v2: i64 = v2.clone().try_into()?;
|
|
if v0 == v1 * v2 {
|
|
vec![
|
|
st0_args[0].clone(),
|
|
st1_args[0].clone(),
|
|
st2_args[0].clone(),
|
|
]
|
|
} else {
|
|
return Err(anyhow!(
|
|
"Invalid arguments to product-of operation"
|
|
));
|
|
}
|
|
}
|
|
_ => {
|
|
return Err(anyhow!("Invalid arguments to product-of operation"));
|
|
}
|
|
};
|
|
st_args
|
|
}
|
|
_ => {
|
|
return Err(anyhow!("Invalid arguments to product-of operation"));
|
|
}
|
|
},
|
|
MaxOf => match (args[0].clone(), args[1].clone(), args[2].clone()) {
|
|
(
|
|
OperationArg::Statement(Statement {
|
|
predicate: Predicate::Native(NativePredicate::ValueOf),
|
|
args: st0_args,
|
|
}),
|
|
OperationArg::Statement(Statement {
|
|
predicate: Predicate::Native(NativePredicate::ValueOf),
|
|
args: st1_args,
|
|
}),
|
|
OperationArg::Statement(Statement {
|
|
predicate: Predicate::Native(NativePredicate::ValueOf),
|
|
args: st2_args,
|
|
}),
|
|
) => {
|
|
let st_args: Vec<StatementArg> = match (
|
|
st0_args[1].clone(),
|
|
st1_args[1].clone(),
|
|
st2_args[1].clone(),
|
|
) {
|
|
(
|
|
StatementArg::Literal(v0),
|
|
StatementArg::Literal(v1),
|
|
StatementArg::Literal(v2),
|
|
) => {
|
|
let v0: i64 = v0.clone().try_into()?;
|
|
let v1: i64 = v1.clone().try_into()?;
|
|
let v2: i64 = v2.clone().try_into()?;
|
|
if v0 == std::cmp::max(v1, v2) {
|
|
vec![
|
|
st0_args[0].clone(),
|
|
st1_args[0].clone(),
|
|
st2_args[0].clone(),
|
|
]
|
|
} else {
|
|
return Err(anyhow!("Invalid arguments to max-of operation"));
|
|
}
|
|
}
|
|
_ => {
|
|
return Err(anyhow!("Invalid arguments to max-of operation"));
|
|
}
|
|
};
|
|
st_args
|
|
}
|
|
RenameContainedBy => todo!(),
|
|
_ => {
|
|
return Err(anyhow!("Invalid arguments to operation"));
|
|
}
|
|
},
|
|
DictContainsFromEntries => self.op_args_entries(public, args)?,
|
|
DictNotContainsFromEntries => self.op_args_entries(public, args)?,
|
|
SetContainsFromEntries => self.op_args_entries(public, args)?,
|
|
SetNotContainsFromEntries => self.op_args_entries(public, args)?,
|
|
ArrayContainsFromEntries => self.op_args_entries(public, args)?,
|
|
},
|
|
OperationType::Custom(cpr) => {
|
|
// All args should be statements to be pattern matched against statement templates.
|
|
let args = args.iter().map(
|
|
|a| match a {
|
|
OperationArg::Statement(s) => Ok(middleware::Statement::try_from(s.clone())?),
|
|
_ => Err(anyhow!("Invalid argument {} to operation corresponding to custom predicate {:?}.", a, cpr))
|
|
}
|
|
).collect::<Result<Vec<_>>>()?;
|
|
// Match these statements against the custom predicate definition
|
|
let bindings = cpr.match_against(&args)?;
|
|
let output_arg_values = (0..cpr.arg_len())
|
|
.map(|i| {
|
|
bindings.get(&i).cloned().ok_or(anyhow!(
|
|
"Wildcard {} of custom predicate {:?} is unbound.",
|
|
i,
|
|
cpr
|
|
))
|
|
})
|
|
.collect::<Result<Vec<_>>>()?;
|
|
|
|
output_arg_values
|
|
.chunks(2)
|
|
.map(|chunk| {
|
|
Ok(StatementArg::Key(AnchoredKey::new(
|
|
Origin::new(
|
|
self.pod_class_table
|
|
.get(&PodId(chunk[0].into()))
|
|
.cloned()
|
|
.ok_or(anyhow!("Missing POD class value."))?,
|
|
PodId(chunk[0].into()),
|
|
),
|
|
self.key_table
|
|
.get(&chunk[1].into())
|
|
.cloned()
|
|
.ok_or(anyhow!("Missing key corresponding to hash."))?,
|
|
)))
|
|
})
|
|
.collect::<Result<Vec<_>>>()?
|
|
}
|
|
};
|
|
let st = Statement::new(pred, st_args);
|
|
self.operations.push(op);
|
|
if public {
|
|
self.public_statements.push(st.clone());
|
|
}
|
|
|
|
// Add key-hash pairs in statement to table.
|
|
st.args.iter().for_each(|arg| {
|
|
if let StatementArg::Key(AnchoredKey { origin: _, key }) = arg {
|
|
self.key_table.insert(hash_str(key), key.clone());
|
|
}
|
|
});
|
|
|
|
self.statements.push(st);
|
|
Ok(self.statements[self.statements.len() - 1].clone())
|
|
}
|
|
|
|
/// Convenience method for introducing public constants.
|
|
pub fn pub_literal<V: Clone + Into<Value>>(&mut self, v: &V) -> Result<Statement> {
|
|
self.literal(true, v)
|
|
}
|
|
|
|
/// Convenience method for introducing private constants.
|
|
pub fn priv_literal<V: Clone + Into<Value>>(&mut self, v: &V) -> Result<Statement> {
|
|
self.literal(false, v)
|
|
}
|
|
|
|
fn literal<V: Clone + Into<Value>>(&mut self, public: bool, v: &V) -> Result<Statement> {
|
|
let v: Value = v.clone().into();
|
|
let k = format!("c{}", self.const_cnt);
|
|
self.const_cnt += 1;
|
|
self.op(
|
|
public,
|
|
Operation(
|
|
OperationType::Native(NativeOperation::NewEntry),
|
|
vec![OperationArg::Entry(k.clone(), v)],
|
|
OperationAux::None,
|
|
),
|
|
)
|
|
}
|
|
|
|
pub fn reveal(&mut self, st: &Statement) {
|
|
self.public_statements.push(st.clone());
|
|
}
|
|
|
|
pub fn prove<P: PodProver>(&self, prover: &mut P, params: &Params) -> Result<MainPod> {
|
|
let compiler = MainPodCompiler::new(&self.params);
|
|
let inputs = MainPodCompilerInputs {
|
|
// signed_pods: &self.input_signed_pods,
|
|
// main_pods: &self.input_main_pods,
|
|
statements: &self.statements,
|
|
operations: &self.operations,
|
|
public_statements: &self.public_statements,
|
|
};
|
|
|
|
let (statements, operations, public_statements) = compiler.compile(inputs, params)?;
|
|
let inputs = MainPodInputs {
|
|
signed_pods: &self.input_signed_pods.iter().map(|p| &p.pod).collect_vec(),
|
|
main_pods: &self.input_main_pods.iter().map(|p| &p.pod).collect_vec(),
|
|
statements: &statements,
|
|
operations: &operations,
|
|
public_statements: &public_statements,
|
|
};
|
|
let pod = prover.prove(&self.params, inputs)?;
|
|
|
|
// Gather public statements, making sure to inject the type
|
|
// information specified by the backend.
|
|
let pod_id = pod.id();
|
|
let type_key_hash = hash_str(KEY_TYPE);
|
|
let type_statement = pod
|
|
.pub_statements()
|
|
.into_iter()
|
|
.find_map(|s| match s {
|
|
crate::middleware::Statement::ValueOf(
|
|
crate::middleware::AnchoredKey(id, key),
|
|
value,
|
|
) if id == pod_id && key == type_key_hash => Some(Statement {
|
|
predicate: Predicate::Native(NativePredicate::ValueOf),
|
|
args: vec![
|
|
StatementArg::Key(AnchoredKey::new(
|
|
Origin::new(PodClass::Main, pod_id),
|
|
KEY_TYPE.to_string(),
|
|
)),
|
|
StatementArg::Literal(value.into()),
|
|
],
|
|
}),
|
|
_ => None,
|
|
})
|
|
.ok_or(anyhow!("Missing POD type information in POD: {:?}", pod))?;
|
|
// Replace instances of `SELF` with the POD ID for consistency
|
|
// with `pub_statements` method.
|
|
let public_statements = [type_statement]
|
|
.into_iter()
|
|
.chain(self.public_statements.clone().into_iter().map(|s| {
|
|
let s_type = s.predicate;
|
|
let s_args = s
|
|
.args
|
|
.into_iter()
|
|
.map(|arg| match arg {
|
|
StatementArg::Key(AnchoredKey {
|
|
origin:
|
|
Origin {
|
|
pod_class: class,
|
|
pod_id: id,
|
|
},
|
|
key,
|
|
}) if id == SELF => {
|
|
StatementArg::Key(AnchoredKey::new(Origin::new(class, pod_id), key))
|
|
}
|
|
_ => arg,
|
|
})
|
|
.collect();
|
|
Statement::new(s_type, s_args)
|
|
}))
|
|
.collect();
|
|
|
|
Ok(MainPod {
|
|
pod,
|
|
public_statements,
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(try_from = "MainPodHelper", into = "MainPodHelper")]
|
|
pub struct MainPod {
|
|
pub pod: Box<dyn middleware::Pod>,
|
|
pub public_statements: Vec<Statement>,
|
|
}
|
|
|
|
impl fmt::Display for MainPod {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
writeln!(f, "MainPod: {}", self.pod.id())?;
|
|
writeln!(f, " valid? {}", self.pod.verify().is_ok())?;
|
|
writeln!(f, " statements:")?;
|
|
for st in &self.pod.pub_statements() {
|
|
writeln!(f, " - {}", st)?;
|
|
}
|
|
writeln!(f, " kvs:")?;
|
|
for (k, v) in &self.pod.kvs() {
|
|
writeln!(f, " - {}: {}", k, v)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl MainPod {
|
|
pub fn id(&self) -> PodId {
|
|
self.pod.id()
|
|
}
|
|
pub fn origin(&self) -> Origin {
|
|
Origin::new(PodClass::Main, self.id())
|
|
}
|
|
}
|
|
|
|
struct MainPodCompilerInputs<'a> {
|
|
// pub signed_pods: &'a [Box<dyn middleware::SignedPod>],
|
|
// pub main_pods: &'a [Box<dyn middleware::MainPod>],
|
|
pub statements: &'a [Statement],
|
|
pub operations: &'a [Operation],
|
|
pub public_statements: &'a [Statement],
|
|
}
|
|
|
|
struct MainPodCompiler {
|
|
params: Params,
|
|
// Output
|
|
statements: Vec<middleware::Statement>,
|
|
operations: Vec<middleware::Operation>,
|
|
// Internal state
|
|
// Tracks literal constants assigned to ValueOf statements by self.literal()
|
|
// If `val` has been added as a literal,
|
|
// then `self.literals.get(&val)` returns `Some(idx)`, and
|
|
// then `self.statements[idx]` is the ValueOf statement
|
|
// where it was introduced.
|
|
literals: HashMap<middleware::Value, usize>,
|
|
}
|
|
|
|
impl MainPodCompiler {
|
|
fn new(params: &Params) -> Self {
|
|
Self {
|
|
params: params.clone(),
|
|
statements: Vec::new(),
|
|
operations: Vec::new(),
|
|
literals: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
fn push_st_op(&mut self, st: middleware::Statement, op: middleware::Operation) {
|
|
self.statements.push(st);
|
|
self.operations.push(op);
|
|
}
|
|
|
|
fn compile_op_arg(&self, op_arg: &OperationArg) -> Option<middleware::Statement> {
|
|
match op_arg {
|
|
OperationArg::Statement(s) => self.compile_st(s).ok(),
|
|
OperationArg::Literal(_v) => {
|
|
// OperationArg::Literal is a syntax sugar for the frontend. This is translated to
|
|
// a new ValueOf statement and it's key used instead.
|
|
unreachable!()
|
|
}
|
|
OperationArg::Entry(_k, _v) => {
|
|
// OperationArg::Entry is only used in the frontend. The (key, value) will only
|
|
// appear in the ValueOf statement in the backend. This is because a new ValueOf
|
|
// statement doesn't have any requirement on the key and value.
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
// Introduces a literal value if it hasn't been introduced,
|
|
// or else returns the existing ValueOf statement where it was first introduced.
|
|
// TODO: this might produce duplicate keys, fix
|
|
fn literal<V: Clone + Into<middleware::Value>>(&mut self, val: V) -> &middleware::Statement {
|
|
let val: middleware::Value = val.into();
|
|
match self.literals.get(&val) {
|
|
Some(idx) => &self.statements[*idx],
|
|
None => {
|
|
let ak = middleware::AnchoredKey(SELF, hash_value(&val));
|
|
let st = middleware::Statement::ValueOf(ak, val);
|
|
let op = middleware::Operation::NewEntry;
|
|
self.statements.push(st);
|
|
self.operations.push(op);
|
|
self.statements.last().unwrap()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Returns the existing ValueOf statement where it was first introduced,
|
|
// or None if it does not exist.
|
|
fn get_literal<V: Clone + Into<middleware::Value>>(
|
|
&self,
|
|
val: V,
|
|
) -> Option<&middleware::Statement> {
|
|
let val: middleware::Value = val.into();
|
|
match self.literals.get(&val) {
|
|
Some(idx) => Some(&self.statements[*idx]),
|
|
None => None,
|
|
}
|
|
}
|
|
|
|
// This function handles cases where one frontend statement
|
|
// compiles to multiple middleware statements.
|
|
// For example: DictContains(x, y) on the frontend compiles to:
|
|
// ValueOf(empty, EMPTY_VALUE)
|
|
// Contains(x, y, empty)
|
|
fn manual_compile_st_op(&mut self, st: &Statement, op: &Operation) -> Result<()> {
|
|
match st.predicate {
|
|
Predicate::Native(NativePredicate::DictContains) => {
|
|
let empty_st = self.literal(EMPTY_VALUE).clone();
|
|
let empty_ak = match empty_st {
|
|
middleware::Statement::ValueOf(ak, _) => ak,
|
|
_ => unreachable!(),
|
|
};
|
|
let (ak1, ak2) = match (st.args.get(0).cloned(), st.args.get(1).cloned()) {
|
|
(Some(StatementArg::Key(ak1)), Some(StatementArg::Key(ak2))) => (ak1, ak2),
|
|
_ => Err(anyhow!("Ill-formed statement: {}", st))?,
|
|
};
|
|
let middle_st =
|
|
middleware::Statement::Contains(ak1.into(), ak2.into(), empty_ak.clone());
|
|
let middle_op = middleware::Operation::ContainsFromEntries(
|
|
match &op.1[0] {
|
|
OperationArg::Statement(s) => self.compile_st(&s)?,
|
|
_ => Err(anyhow!("Statement compile failed in manual compile"))?,
|
|
},
|
|
match &op.1[1] {
|
|
OperationArg::Statement(s) => self.compile_st(&s)?,
|
|
_ => Err(anyhow!("Statement compile failed in manual compile"))?,
|
|
},
|
|
empty_st,
|
|
match &op.2 {
|
|
OperationAux::MerkleProof(mp) => mp.clone(),
|
|
_ => {
|
|
return Err(anyhow!(
|
|
"Auxiliary argument to DictContainsFromEntries must be Merkle proof"
|
|
));
|
|
}
|
|
},
|
|
);
|
|
self.statements.push(middle_st);
|
|
self.operations.push(middle_op);
|
|
assert_eq!(self.statements.len(), self.operations.len());
|
|
Ok(())
|
|
}
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
// If the frontend statement `st` compiles to a single middleware statement,
|
|
// returns that middleware statement.
|
|
// If it compiles to multiple middlewarestatements, returns StatementConversionError.
|
|
// This is only a helper method within compile_st_op().
|
|
// If you want to compile a statement in general, run compile_st().
|
|
fn compile_st_try_simple(
|
|
&self,
|
|
st: &Statement,
|
|
) -> Result<middleware::Statement, StatementConversionError> {
|
|
st.clone().try_into()
|
|
}
|
|
|
|
// Compiles the frontend statement `st` to a middleware statement.
|
|
// This function assumes the middleware statement already exists --
|
|
// it should not be called from compile_st_op.
|
|
fn compile_st(&self, st: &Statement) -> Result<middleware::Statement> {
|
|
match self.compile_st_try_simple(st) {
|
|
Ok(s) => Ok(s),
|
|
Err(StatementConversionError::Error(e)) => Err(e),
|
|
Err(StatementConversionError::MCR(_)) => {
|
|
let empty_st = self
|
|
.get_literal(EMPTY_VALUE)
|
|
.clone()
|
|
.ok_or(anyhow!("Literal value not found for empty literal."))?;
|
|
let empty_ak = match empty_st {
|
|
middleware::Statement::ValueOf(ak, _) => ak,
|
|
_ => unreachable!(),
|
|
};
|
|
let (ak1, ak2) = match (st.args.get(0).cloned(), st.args.get(1).cloned()) {
|
|
(Some(StatementArg::Key(ak1)), Some(StatementArg::Key(ak2))) => (ak1, ak2),
|
|
_ => Err(anyhow!("Ill-formed statement: {}", st))?,
|
|
};
|
|
let middle_st =
|
|
middleware::Statement::Contains(ak1.into(), ak2.into(), empty_ak.clone());
|
|
Ok(middle_st)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn compile_op(&self, op: &Operation) -> Result<middleware::Operation> {
|
|
let mop_code: middleware::OperationType = op.0.clone().try_into()?;
|
|
|
|
// TODO: Take Merkle proof into account.
|
|
let mop_args =
|
|
op.1.iter()
|
|
.flat_map(|arg| self.compile_op_arg(arg).map(|op_arg| Ok(op_arg)))
|
|
.collect::<Result<Vec<_>>>()?;
|
|
middleware::Operation::op(mop_code, &mop_args, &op.2)
|
|
}
|
|
|
|
fn compile_st_op(&mut self, st: &Statement, op: &Operation, params: &Params) -> Result<()> {
|
|
let middle_st_res = self.compile_st_try_simple(st);
|
|
match middle_st_res {
|
|
Ok(middle_st) => {
|
|
let middle_op = self.compile_op(op)?;
|
|
let is_correct = middle_op.check(params, &middle_st)?;
|
|
if !is_correct {
|
|
// todo: improve error handling
|
|
Err(anyhow!(
|
|
"Compile failed due to invalid deduction:\n {} ⇏ {}",
|
|
middle_op,
|
|
middle_st
|
|
))
|
|
} else {
|
|
self.push_st_op(middle_st, middle_op);
|
|
Ok(())
|
|
}
|
|
}
|
|
Err(StatementConversionError::Error(e)) => Err(e),
|
|
Err(StatementConversionError::MCR(_)) => self.manual_compile_st_op(st, op),
|
|
}
|
|
}
|
|
|
|
pub fn compile(
|
|
mut self,
|
|
inputs: MainPodCompilerInputs<'_>,
|
|
params: &Params,
|
|
) -> Result<(
|
|
Vec<middleware::Statement>, // input statements
|
|
Vec<middleware::Operation>,
|
|
Vec<middleware::Statement>, // public statements
|
|
)> {
|
|
let MainPodCompilerInputs {
|
|
// signed_pods: _,
|
|
// main_pods: _,
|
|
statements,
|
|
operations,
|
|
public_statements,
|
|
} = inputs;
|
|
for (st, op) in statements.iter().zip_eq(operations.iter()) {
|
|
self.compile_st_op(st, op, params)?;
|
|
if self.statements.len() > self.params.max_statements {
|
|
panic!("too many statements");
|
|
}
|
|
}
|
|
let public_statements = public_statements
|
|
.iter()
|
|
.map(|st| self.compile_st(st))
|
|
.collect::<Result<Vec<_>>>()?;
|
|
Ok((self.statements, self.operations, public_statements))
|
|
}
|
|
}
|
|
|
|
// TODO fn fmt_signed_pod_builder
|
|
// TODO fn fmt_main_pod
|
|
|
|
#[macro_use]
|
|
pub mod build_utils {
|
|
#[macro_export]
|
|
macro_rules! op_args {
|
|
($($arg:expr),+) => {vec![$($crate::frontend::OperationArg::from($arg)),*]}
|
|
}
|
|
|
|
#[macro_export]
|
|
macro_rules! op {
|
|
(new_entry, ($key:expr, $value:expr)) => { $crate::frontend::Operation(
|
|
$crate::frontend::OperationType::Native($crate::frontend::NativeOperation::NewEntry),
|
|
$crate::op_args!(($key, $value)), crate::middleware::OperationAux::None) };
|
|
(eq, $($arg:expr),+) => { $crate::frontend::Operation(
|
|
$crate::frontend::OperationType::Native($crate::frontend::NativeOperation::EqualFromEntries),
|
|
$crate::op_args!($($arg),*), crate::middleware::OperationAux::None) };
|
|
(ne, $($arg:expr),+) => { $crate::frontend::Operation(
|
|
$crate::frontend::OperationType::Native($crate::frontend::NativeOperation::NotEqualFromEntries),
|
|
$crate::op_args!($($arg),*), crate::middleware::OperationAux::None) };
|
|
(gt, $($arg:expr),+) => { crate::frontend::Operation(
|
|
crate::frontend::OperationType::Native(crate::frontend::NativeOperation::GtFromEntries),
|
|
crate::op_args!($($arg),*), crate::middleware::OperationAux::None) };
|
|
(lt, $($arg:expr),+) => { crate::frontend::Operation(
|
|
crate::frontend::OperationType::Native(crate::frontend::NativeOperation::LtFromEntries),
|
|
crate::op_args!($($arg),*), crate::middleware::OperationAux::None) };
|
|
(transitive_eq, $($arg:expr),+) => { crate::frontend::Operation(
|
|
crate::frontend::OperationType::Native(crate::frontend::NativeOperation::TransitiveEqualFromStatements),
|
|
crate::op_args!($($arg),*), crate::middleware::OperationAux::None) };
|
|
(gt_to_ne, $($arg:expr),+) => { crate::frontend::Operation(
|
|
crate::frontend::OperationType::Native(crate::frontend::NativeOperation::GtToNotEqual),
|
|
crate::op_args!($($arg),*), crate::middleware::OperationAux::None) };
|
|
(lt_to_ne, $($arg:expr),+) => { crate::frontend::Operation(
|
|
crate::frontend::OperationType::Native(crate::frontend::NativeOperation::LtToNotEqual),
|
|
crate::op_args!($($arg),*), crate::middleware::OperationAux::None) };
|
|
(sum_of, $($arg:expr),+) => { crate::frontend::Operation(
|
|
crate::frontend::OperationType::Native(crate::frontend::NativeOperation::SumOf),
|
|
crate::op_args!($($arg),*), crate::middleware::OperationAux::None) };
|
|
(product_of, $($arg:expr),+) => { crate::frontend::Operation(
|
|
crate::frontend::OperationType::Native(crate::frontend::NativeOperation::ProductOf),
|
|
crate::op_args!($($arg),*), crate::middleware::OperationAux::None) };
|
|
(max_of, $($arg:expr),+) => { crate::frontend::Operation(
|
|
crate::frontend::OperationType::Native(crate::frontend::NativeOperation::MaxOf),
|
|
crate::op_args!($($arg),*), crate::middleware::OperationAux::None) };
|
|
(custom, $op:expr, $($arg:expr),+) => { $crate::frontend::Operation(
|
|
$crate::frontend::OperationType::Custom($op),
|
|
$crate::op_args!($($arg),*), crate::middleware::OperationAux::None) };
|
|
(dict_contains, $dict:expr, $key:expr, $value:expr, $aux:expr) => { crate::frontend::Operation(
|
|
crate::frontend::OperationType::Native(crate::frontend::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::frontend::OperationType::Native(crate::frontend::NativeOperation::DictNotContainsFromEntries),
|
|
crate::op_args!($dict, $key), crate::middleware::OperationAux::MerkleProof($aux)) };
|
|
(set_contains, $set:expr, $value:expr, $aux:expr) => { crate::frontend::Operation(
|
|
crate::frontend::OperationType::Native(crate::frontend::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::frontend::OperationType::Native(crate::frontend::NativeOperation::SetNotContainsFromEntries),
|
|
crate::op_args!($set, $value), crate::middleware::OperationAux::MerkleProof($aux)) };
|
|
(array_contains, $array:expr, $value:expr, $aux:expr) => { crate::frontend::Operation(
|
|
crate::frontend::OperationType::Native(crate::frontend::NativeOperation::ArrayContainsFromEntries),
|
|
crate::op_args!($array, $value), crate::middleware::OperationAux::MerkleProof($aux)) };
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub mod tests {
|
|
use super::*;
|
|
use crate::backends::plonky2::basetypes;
|
|
use crate::backends::plonky2::mock::mainpod::MockProver;
|
|
use crate::backends::plonky2::mock::signedpod::MockSigner;
|
|
use crate::backends::plonky2::primitives::merkletree::MerkleTree;
|
|
use crate::examples::{
|
|
eth_dos_pod_builder, eth_friend_signed_pod_builder, great_boy_pod_full_flow,
|
|
tickets_pod_full_flow, zu_kyc_pod_builder, zu_kyc_sign_pod_builders,
|
|
};
|
|
|
|
// Check that frontend public statements agree with those
|
|
// embedded in a MainPod.
|
|
fn check_public_statements(pod: &MainPod) -> Result<()> {
|
|
Ok(
|
|
std::iter::zip(pod.public_statements.clone(), pod.pod.pub_statements()).try_for_each(
|
|
|(fes, s)| {
|
|
crate::middleware::Statement::try_from(fes).map(|fes| assert_eq!(fes, s))
|
|
},
|
|
)?,
|
|
)
|
|
}
|
|
|
|
// Check that frontend key-values agree with those embedded in a
|
|
// SignedPod.
|
|
fn check_kvs(pod: &SignedPod) -> Result<()> {
|
|
let kvs = pod
|
|
.kvs
|
|
.iter()
|
|
.map(|(k, v)| (hash_str(k), middleware::Value::from(v)))
|
|
.collect::<HashMap<_, _>>();
|
|
let embedded_kvs = pod
|
|
.pod
|
|
.kvs()
|
|
.into_iter()
|
|
.map(|(middleware::AnchoredKey(_, k), v)| (k, v))
|
|
.collect::<HashMap<_, _>>();
|
|
|
|
if kvs == embedded_kvs {
|
|
Ok(())
|
|
} else {
|
|
Err(anyhow!(
|
|
"KVs {:?} do not agree with those embedded in the POD: {:?}",
|
|
kvs,
|
|
embedded_kvs
|
|
))
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_front_zu_kyc() -> Result<()> {
|
|
let params = Params::default();
|
|
let sanctions_values = vec!["A343434340".into()];
|
|
let sanction_set = Value::Set(Set::new(sanctions_values)?);
|
|
let (gov_id, pay_stub, sanction_list) = zu_kyc_sign_pod_builders(¶ms, &sanction_set);
|
|
|
|
println!("{}", gov_id);
|
|
println!("{}", pay_stub);
|
|
|
|
let mut signer = MockSigner {
|
|
pk: "ZooGov".into(),
|
|
};
|
|
let gov_id = gov_id.sign(&mut signer)?;
|
|
check_kvs(&gov_id)?;
|
|
println!("{}", gov_id);
|
|
|
|
let mut signer = MockSigner {
|
|
pk: "ZooDeel".into(),
|
|
};
|
|
let pay_stub = pay_stub.sign(&mut signer)?;
|
|
check_kvs(&pay_stub)?;
|
|
println!("{}", pay_stub);
|
|
|
|
let mut signer = MockSigner {
|
|
pk: "ZooOFAC".into(),
|
|
};
|
|
let sanction_list = sanction_list.sign(&mut signer)?;
|
|
check_kvs(&sanction_list)?;
|
|
println!("{}", sanction_list);
|
|
|
|
let kyc_builder = zu_kyc_pod_builder(¶ms, &gov_id, &pay_stub, &sanction_list)?;
|
|
println!("{}", kyc_builder);
|
|
|
|
// prove kyc with MockProver and print it
|
|
let mut prover = MockProver {};
|
|
let kyc = kyc_builder.prove(&mut prover, ¶ms)?;
|
|
|
|
println!("{}", kyc);
|
|
|
|
check_public_statements(&kyc)
|
|
}
|
|
|
|
#[test]
|
|
fn test_ethdos() -> Result<()> {
|
|
let params = Params {
|
|
max_input_signed_pods: 3,
|
|
max_input_main_pods: 3,
|
|
max_statements: 31,
|
|
max_signed_pod_values: 8,
|
|
max_public_statements: 10,
|
|
max_statement_args: 5,
|
|
max_operation_args: 5,
|
|
max_custom_predicate_arity: 5,
|
|
max_custom_batch_size: 5,
|
|
..Default::default()
|
|
};
|
|
|
|
let mut alice = MockSigner { pk: "Alice".into() };
|
|
let bob = MockSigner { pk: "Bob".into() };
|
|
let mut charlie = MockSigner {
|
|
pk: "Charlie".into(),
|
|
};
|
|
|
|
// Alice attests that she is ETH friends with Charlie and Charlie
|
|
// attests that he is ETH friends with Bob.
|
|
let alice_attestation =
|
|
eth_friend_signed_pod_builder(¶ms, charlie.pubkey().into()).sign(&mut alice)?;
|
|
check_kvs(&alice_attestation)?;
|
|
let charlie_attestation =
|
|
eth_friend_signed_pod_builder(¶ms, bob.pubkey().into()).sign(&mut charlie)?;
|
|
check_kvs(&charlie_attestation)?;
|
|
|
|
let mut prover = MockProver {};
|
|
let alice_bob_ethdos = eth_dos_pod_builder(
|
|
¶ms,
|
|
&alice_attestation,
|
|
&charlie_attestation,
|
|
&bob.pubkey().into(),
|
|
)?
|
|
.prove(&mut prover, ¶ms)?;
|
|
|
|
check_public_statements(&alice_bob_ethdos)
|
|
}
|
|
|
|
#[test]
|
|
fn test_front_great_boy() -> Result<()> {
|
|
let great_boy = great_boy_pod_full_flow()?;
|
|
println!("{}", great_boy);
|
|
|
|
// TODO: prove great_boy with MockProver and print it
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_front_tickets() -> Result<()> {
|
|
let builder = tickets_pod_full_flow()?;
|
|
println!("{}", builder);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
// Transitive equality not implemented yet
|
|
#[should_panic]
|
|
fn test_equal() {
|
|
let params = Params::default();
|
|
let mut signed_builder = SignedPodBuilder::new(¶ms);
|
|
signed_builder.insert("a", 1);
|
|
signed_builder.insert("b", 1);
|
|
let mut signer = MockSigner { pk: "key".into() };
|
|
let signed_pod = signed_builder.sign(&mut signer).unwrap();
|
|
|
|
let mut builder = MainPodBuilder::new(¶ms);
|
|
builder.add_signed_pod(&signed_pod);
|
|
|
|
//let op_val1 = Operation{
|
|
// OperationType::Native(NativeOperation::CopyStatement),
|
|
// signed_pod.
|
|
//}
|
|
|
|
let op_eq1 = Operation(
|
|
OperationType::Native(NativeOperation::EqualFromEntries),
|
|
vec![
|
|
OperationArg::from((&signed_pod, "a")),
|
|
OperationArg::from((&signed_pod, "b")),
|
|
],
|
|
OperationAux::None,
|
|
);
|
|
let st1 = builder.op(true, op_eq1).unwrap();
|
|
let op_eq2 = Operation(
|
|
OperationType::Native(NativeOperation::EqualFromEntries),
|
|
vec![
|
|
OperationArg::from((&signed_pod, "b")),
|
|
OperationArg::from((&signed_pod, "a")),
|
|
],
|
|
OperationAux::None,
|
|
);
|
|
let st2 = builder.op(true, op_eq2).unwrap();
|
|
|
|
let op_eq3 = Operation(
|
|
OperationType::Native(NativeOperation::TransitiveEqualFromStatements),
|
|
vec![OperationArg::Statement(st1), OperationArg::Statement(st2)],
|
|
OperationAux::None,
|
|
);
|
|
let st3 = builder.op(true, op_eq3);
|
|
|
|
let mut prover = MockProver {};
|
|
let pod = builder.prove(&mut prover, ¶ms).unwrap();
|
|
|
|
println!("{}", pod);
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic]
|
|
fn test_false_st() {
|
|
let params = Params::default();
|
|
let mut builder = SignedPodBuilder::new(¶ms);
|
|
|
|
builder.insert("num", 2);
|
|
|
|
let mut signer = MockSigner {
|
|
pk: "signer".into(),
|
|
};
|
|
let pod = builder.sign(&mut signer).unwrap();
|
|
|
|
println!("{}", pod);
|
|
|
|
let mut builder = MainPodBuilder::new(¶ms);
|
|
builder.add_signed_pod(&pod);
|
|
builder.pub_op(op!(gt, (&pod, "num"), 5)).unwrap();
|
|
|
|
let mut prover = MockProver {};
|
|
let false_pod = builder.prove(&mut prover, ¶ms).unwrap();
|
|
|
|
println!("{}", builder);
|
|
println!("{}", false_pod);
|
|
}
|
|
|
|
#[test]
|
|
fn test_dictionaries() -> Result<()> {
|
|
let params = Params::default();
|
|
let mut builder = SignedPodBuilder::new(¶ms);
|
|
|
|
type BeValue = basetypes::Value;
|
|
let mut my_dict_kvs: HashMap<String, Value> = HashMap::new();
|
|
my_dict_kvs.insert("a".to_string(), Value::from(1));
|
|
my_dict_kvs.insert("b".to_string(), Value::from(2));
|
|
my_dict_kvs.insert("c".to_string(), Value::from(3));
|
|
// let my_dict_as_mt = MerkleTree::new(5, &my_dict_kvs).unwrap();
|
|
// let dict = Dictionary { mt: my_dict_as_mt };
|
|
let dict = Dictionary::new(my_dict_kvs)?;
|
|
let dict_root = Value::Dictionary(dict.clone());
|
|
builder.insert("dict", dict_root);
|
|
|
|
let mut signer = MockSigner {
|
|
pk: "signer".into(),
|
|
};
|
|
let pod = builder.sign(&mut signer).unwrap();
|
|
|
|
let mut builder = MainPodBuilder::new(¶ms);
|
|
builder.add_signed_pod(&pod);
|
|
let st0 = Statement::from((&pod, "dict"));
|
|
let st1 = builder.op(true, op!(new_entry, ("key", "a"))).unwrap();
|
|
let st2 = builder.literal(false, &Value::Int(1)).unwrap();
|
|
|
|
builder
|
|
.pub_op(Operation(
|
|
// OperationType
|
|
OperationType::Native(NativeOperation::DictContainsFromEntries),
|
|
// Vec<OperationArg>
|
|
vec![
|
|
OperationArg::Statement(st0),
|
|
OperationArg::Statement(st1),
|
|
OperationArg::Statement(st2),
|
|
],
|
|
OperationAux::MerkleProof(
|
|
dict.middleware_dict()
|
|
.prove(&Hash::from("a").into())
|
|
.unwrap()
|
|
.1,
|
|
),
|
|
))
|
|
.unwrap();
|
|
let mut main_prover = MockProver {};
|
|
let main_pod = builder.prove(&mut main_prover, ¶ms).unwrap();
|
|
|
|
println!("{}", main_pod);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[should_panic]
|
|
fn test_incorrect_pod() {
|
|
// try to insert the same key multiple times
|
|
// right now this is not caught when you build the pod,
|
|
// but it is caught on verify
|
|
env_logger::init();
|
|
|
|
let params = Params::default();
|
|
let mut builder = MainPodBuilder::new(¶ms);
|
|
builder.insert((
|
|
Statement::new(
|
|
Predicate::Native(NativePredicate::ValueOf),
|
|
vec![
|
|
StatementArg::Key(AnchoredKey::new(
|
|
Origin::new(PodClass::Main, SELF),
|
|
"a".into(),
|
|
)),
|
|
StatementArg::Literal(Value::Int(3)),
|
|
],
|
|
),
|
|
Operation(
|
|
OperationType::Native(NativeOperation::NewEntry),
|
|
vec![],
|
|
OperationAux::None,
|
|
),
|
|
));
|
|
builder.insert((
|
|
Statement::new(
|
|
Predicate::Native(NativePredicate::ValueOf),
|
|
vec![
|
|
StatementArg::Key(AnchoredKey::new(
|
|
Origin::new(PodClass::Main, SELF),
|
|
"a".into(),
|
|
)),
|
|
StatementArg::Literal(Value::Int(28)),
|
|
],
|
|
),
|
|
Operation(
|
|
OperationType::Native(NativeOperation::NewEntry),
|
|
vec![],
|
|
OperationAux::None,
|
|
),
|
|
));
|
|
|
|
let mut prover = MockProver {};
|
|
let pod = builder.prove(&mut prover, ¶ms).unwrap();
|
|
pod.pod.verify();
|
|
|
|
// try to insert a statement that doesn't follow from the operation
|
|
// right now the mock prover catches this when it calls compile()
|
|
let params = Params::default();
|
|
let mut builder = MainPodBuilder::new(¶ms);
|
|
let self_a = AnchoredKey::new(Origin::new(PodClass::Main, SELF), "a".into());
|
|
let self_b = AnchoredKey::new(Origin::new(PodClass::Main, SELF), "b".into());
|
|
let value_of_a = Statement::new(
|
|
Predicate::Native(NativePredicate::ValueOf),
|
|
vec![
|
|
StatementArg::Key(AnchoredKey::new(
|
|
Origin::new(PodClass::Main, SELF),
|
|
"a".into(),
|
|
)),
|
|
StatementArg::Literal(Value::Int(3)),
|
|
],
|
|
);
|
|
let value_of_b = Statement::new(
|
|
Predicate::Native(NativePredicate::ValueOf),
|
|
vec![
|
|
StatementArg::Key(AnchoredKey::new(
|
|
Origin::new(PodClass::Main, SELF),
|
|
"b".into(),
|
|
)),
|
|
StatementArg::Literal(Value::Int(27)),
|
|
],
|
|
);
|
|
|
|
builder.insert((
|
|
value_of_a.clone(),
|
|
Operation(
|
|
OperationType::Native(NativeOperation::NewEntry),
|
|
vec![],
|
|
OperationAux::None,
|
|
),
|
|
));
|
|
builder.insert((
|
|
value_of_b.clone(),
|
|
Operation(
|
|
OperationType::Native(NativeOperation::NewEntry),
|
|
vec![],
|
|
OperationAux::None,
|
|
),
|
|
));
|
|
builder.insert((
|
|
Statement::new(
|
|
Predicate::Native(NativePredicate::Equal),
|
|
vec![StatementArg::Key(self_a), StatementArg::Key(self_b)],
|
|
),
|
|
Operation(
|
|
OperationType::Native(NativeOperation::EqualFromEntries),
|
|
vec![
|
|
OperationArg::Statement(value_of_a),
|
|
OperationArg::Statement(value_of_b),
|
|
],
|
|
OperationAux::None,
|
|
),
|
|
));
|
|
|
|
let mut prover = MockProver {};
|
|
let pod = builder.prove(&mut prover, ¶ms).unwrap();
|
|
pod.pod.verify();
|
|
}
|
|
}
|