From 9afc43675d47a21bc9b0361a0619e4c934faf6f7 Mon Sep 17 00:00:00 2001 From: Rob Knight Date: Fri, 21 Mar 2025 14:42:16 +0100 Subject: [PATCH] Serialization of Signed and Main Pods (#128) --- Cargo.toml | 4 + src/backends/plonky2/basetypes.rs | 33 ++- src/backends/plonky2/mock_main/mod.rs | 31 +- src/backends/plonky2/mock_main/operation.rs | 10 +- src/backends/plonky2/mock_main/statement.rs | 3 +- src/backends/plonky2/mock_signed.rs | 14 + src/examples.rs | 17 +- src/frontend/containers.rs | 110 +++++++ src/frontend/mod.rs | 309 ++++++++++++-------- src/frontend/serialization.rs | 294 +++++++++++++++++++ src/frontend/statement.rs | 49 ++-- src/middleware/custom.rs | 22 +- src/middleware/mod.rs | 21 +- src/middleware/operation.rs | 14 +- src/middleware/serialization.rs | 69 +++++ src/middleware/statement.rs | 6 +- 16 files changed, 817 insertions(+), 189 deletions(-) create mode 100644 src/frontend/containers.rs create mode 100644 src/frontend/serialization.rs create mode 100644 src/middleware/serialization.rs diff --git a/Cargo.toml b/Cargo.toml index 7e96497..d3a3d2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,10 @@ log = "0.4" env_logger = "0.11" # enabled by features: plonky2 = { git = "https://github.com/0xPolygonZero/plonky2", optional = true } +serde = "1.0.219" +serde_json = "1.0.140" +base64 = "0.22.1" +schemars = "1.0.0-alpha.17" [features] default = ["backend_plonky2"] diff --git a/src/backends/plonky2/basetypes.rs b/src/backends/plonky2/basetypes.rs index 65368d8..aedf970 100644 --- a/src/backends/plonky2/basetypes.rs +++ b/src/backends/plonky2/basetypes.rs @@ -2,6 +2,10 @@ //! `backend_plonky2` feature is enabled. //! See src/middleware/basetypes.rs for more details. +use crate::middleware::serialization::{ + deserialize_hash_tuple, deserialize_value_tuple, serialize_hash_tuple, serialize_value_tuple, +}; +use crate::middleware::{Params, ToFields}; use anyhow::{anyhow, Error, Result}; use hex::{FromHex, FromHexError}; use plonky2::field::goldilocks_field::GoldilocksField; @@ -10,11 +14,11 @@ use plonky2::hash::poseidon::PoseidonHash; use plonky2::plonk::config::Hasher; use plonky2::plonk::config::PoseidonGoldilocksConfig; use plonky2::plonk::proof::Proof as Plonky2Proof; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; use std::cmp::{Ord, Ordering}; use std::fmt; -use crate::middleware::{Params, ToFields}; - use crate::backends::counter; /// F is the native field we use everywhere. Currently it's Goldilocks from plonky2 @@ -34,8 +38,18 @@ pub const EMPTY_VALUE: Value = Value([F::ZERO, F::ZERO, F::ZERO, F::ZERO]); pub const SELF_ID_HASH: Hash = Hash([F::ONE, F::ZERO, F::ZERO, F::ZERO]); pub const EMPTY_HASH: Hash = Hash([F::ZERO, F::ZERO, F::ZERO, F::ZERO]); -#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq)] -pub struct Value(pub [F; VALUE_SIZE]); +#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[schemars(rename = "MiddlewareValue")] +pub struct Value( + #[serde( + serialize_with = "serialize_value_tuple", + deserialize_with = "deserialize_value_tuple" + )] + // We know that Serde will serialize and deserialize this as a string, so we can + // use the JsonSchema to validate the format. + #[schemars(with = "String", regex(pattern = r"^[0-9a-fA-F]{64}$"))] + pub [F; VALUE_SIZE], +); impl ToFields for Value { fn to_fields(&self, _params: &Params) -> Vec { @@ -117,8 +131,15 @@ impl fmt::Display for Value { } } -#[derive(Clone, Copy, Debug, Default, Hash, Eq, PartialEq)] -pub struct Hash(pub [F; HASH_SIZE]); +#[derive(Clone, Copy, Debug, Default, Hash, Eq, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct Hash( + #[serde( + serialize_with = "serialize_hash_tuple", + deserialize_with = "deserialize_hash_tuple" + )] + #[schemars(with = "String", regex(pattern = r"^[0-9a-fA-F]{64}$"))] + pub [F; HASH_SIZE], +); pub fn hash_value(input: &Value) -> Hash { hash_fields(&input.0) diff --git a/src/backends/plonky2/mock_main/mod.rs b/src/backends/plonky2/mock_main/mod.rs index aa08d53..030ebe4 100644 --- a/src/backends/plonky2/mock_main/mod.rs +++ b/src/backends/plonky2/mock_main/mod.rs @@ -1,8 +1,10 @@ use anyhow::{anyhow, Result}; +use base64::prelude::*; use itertools::Itertools; use log::error; use plonky2::hash::poseidon::PoseidonHash; use plonky2::plonk::config::Hasher; +use serde::{Deserialize, Serialize}; use std::any::Any; use std::fmt; @@ -27,14 +29,14 @@ impl PodProver for MockProver { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct MockMainPod { params: Params, id: PodId, - input_signed_pods: Vec>, - input_main_pods: Vec>, + // input_signed_pods: Vec>, + // input_main_pods: Vec>, // New statements introduced by this pod - input_statements: Vec, + // input_statements: Vec, public_statements: Vec, operations: Vec, // All statements (inherited + new) @@ -258,7 +260,7 @@ impl MockMainPod { .map(|mid_arg| Self::find_op_arg(statements, mid_arg)) .collect::>>()?; Self::pad_operation_args(params, &mut args); - operations.push(Operation(op.code(), args)); + operations.push(Operation(op.predicate(), args)); } Ok(operations) } @@ -332,9 +334,9 @@ impl MockMainPod { Ok(Self { params: params.clone(), id, - input_signed_pods, - input_main_pods, - input_statements, + // input_signed_pods, + // input_main_pods, + // input_statements, public_statements, statements, operations, @@ -360,6 +362,15 @@ impl MockMainPod { fn pad_operation_args(params: &Params, args: &mut Vec) { fill_pad(args, OperationArg::None, params.max_operation_args) } + + pub fn deserialize(serialized: String) -> Result { + let proof = String::from_utf8(BASE64_STANDARD.decode(&serialized)?) + .map_err(|e| anyhow::anyhow!("Invalid base64 encoding: {}", e))?; + let pod: MockMainPod = serde_json::from_str(&proof) + .map_err(|e| anyhow::anyhow!("Failed to parse proof: {}", e))?; + + Ok(pod) + } } pub fn hash_statements(statements: &[Statement], _params: &Params) -> middleware::Hash { @@ -485,6 +496,10 @@ impl Pod for MockMainPod { fn into_any(self: Box) -> Box { self } + + fn serialized_proof(&self) -> String { + BASE64_STANDARD.encode(serde_json::to_string(self).unwrap()) + } } #[cfg(test)] diff --git a/src/backends/plonky2/mock_main/operation.rs b/src/backends/plonky2/mock_main/operation.rs index 9e2bd96..460f739 100644 --- a/src/backends/plonky2/mock_main/operation.rs +++ b/src/backends/plonky2/mock_main/operation.rs @@ -1,10 +1,10 @@ -use anyhow::Result; -use std::fmt; - use super::Statement; use crate::middleware::{self, OperationType}; +use anyhow::Result; +use serde::{Deserialize, Serialize}; +use std::fmt; -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum OperationArg { None, Index(usize), @@ -16,7 +16,7 @@ impl OperationArg { } } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct Operation(pub OperationType, pub Vec); impl Operation { diff --git a/src/backends/plonky2/mock_main/statement.rs b/src/backends/plonky2/mock_main/statement.rs index 553b3d2..0bbcf9e 100644 --- a/src/backends/plonky2/mock_main/statement.rs +++ b/src/backends/plonky2/mock_main/statement.rs @@ -1,11 +1,12 @@ use anyhow::{anyhow, Result}; +use serde::{Deserialize, Serialize}; use std::fmt; use crate::middleware::{ self, AnchoredKey, NativePredicate, Params, Predicate, StatementArg, ToFields, }; -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct Statement(pub Predicate, pub Vec); impl Statement { diff --git a/src/backends/plonky2/mock_signed.rs b/src/backends/plonky2/mock_signed.rs index 36cbeae..e9c3b7a 100644 --- a/src/backends/plonky2/mock_signed.rs +++ b/src/backends/plonky2/mock_signed.rs @@ -44,6 +44,16 @@ pub struct MockSignedPod { dict: Dictionary, } +impl MockSignedPod { + pub fn deserialize(id: PodId, signature: String, dict: Dictionary) -> Self { + Self { + id, + signature, + dict, + } + } +} + impl Pod for MockSignedPod { fn verify(&self) -> bool { // 1. Verify type @@ -100,6 +110,10 @@ impl Pod for MockSignedPod { fn into_any(self: Box) -> Box { self } + + fn serialized_proof(&self) -> String { + self.signature.to_string() + } } #[cfg(test)] diff --git a/src/examples.rs b/src/examples.rs index 59c971a..a973132 100644 --- a/src/examples.rs +++ b/src/examples.rs @@ -6,11 +6,11 @@ use std::collections::HashMap; use crate::backends::plonky2::mock_signed::MockSigner; use crate::frontend::{ - MainPodBuilder, Operation, OperationArg, SignedPod, SignedPodBuilder, Statement, Value, + containers::{Dictionary, Set}, + MainPodBuilder, SignedPod, SignedPodBuilder, Statement, Value, }; -use crate::middleware::containers::Set; -use crate::middleware::{containers::Dictionary, Params, PodType, KEY_SIGNER, KEY_TYPE}; -use crate::middleware::{hash_str, CustomPredicateRef, NativeOperation, OperationType}; +use crate::middleware::CustomPredicateRef; +use crate::middleware::{Params, PodType, KEY_SIGNER, KEY_TYPE}; use crate::op; // ZuKYC @@ -28,10 +28,11 @@ pub fn zu_kyc_sign_pod_builders( pay_stub.insert("startDate", 1706367566); let mut sanction_list = SignedPodBuilder::new(params); - let sanctions_values = ["A343434340"].map(|s| crate::middleware::Value::from(hash_str(s))); + let sanctions_values = ["A343434340"].map(Value::from); + sanction_list.insert( "sanctionList", - Value::Set(Set::new(&sanctions_values.to_vec()).unwrap()), + Value::Set(Set::new(sanctions_values.into()).unwrap()), ); (gov_id, pay_stub, sanction_list) @@ -337,7 +338,7 @@ pub fn great_boy_pod_full_flow() -> Result { alice_friend_pods.push(friend.sign(&mut bob_signer).unwrap()); alice_friend_pods.push(friend.sign(&mut charlie_signer).unwrap()); - let good_boy_issuers_dict = Value::Dictionary(Dictionary::new(&HashMap::new())?); // empty + let good_boy_issuers_dict = Value::Dictionary(Dictionary::new(HashMap::new()).unwrap()); // empty great_boy_pod_builder( ¶ms, [ @@ -396,6 +397,6 @@ pub fn tickets_pod_full_flow() -> Result { &signed_pod, 123, true, - &Value::Dictionary(Dictionary::new(&HashMap::new())?), + &Value::Dictionary(Dictionary::new(HashMap::new()).unwrap()), ) } diff --git a/src/frontend/containers.rs b/src/frontend/containers.rs new file mode 100644 index 0000000..956f213 --- /dev/null +++ b/src/frontend/containers.rs @@ -0,0 +1,110 @@ +use std::collections::HashMap; + +use anyhow::Result; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use super::Value; +use crate::frontend::serialization::ordered_map; +use crate::middleware::{ + containers::{ + Array as MiddlewareArray, Dictionary as MiddlewareDictionary, Set as MiddlewareSet, + }, + hash_str, Value as MiddlewareValue, +}; + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, JsonSchema)] +#[serde(transparent)] +pub struct Set(Vec, #[serde(skip)] MiddlewareSet); + +impl Set { + pub fn new(values: Vec) -> Result { + let set = MiddlewareSet::new(&values.iter().map(|v| MiddlewareValue::from(v)).collect())?; + Ok(Self(values, set)) + } + + pub fn middleware_set(&self) -> &MiddlewareSet { + &self.1 + } + + pub fn values(&self) -> &Vec { + &self.0 + } +} + +impl<'de> Deserialize<'de> for Set { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let values: Vec = Vec::deserialize(deserializer)?; + Set::new(values).map_err(serde::de::Error::custom) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, JsonSchema)] +#[serde(transparent)] +pub struct Dictionary( + #[serde(serialize_with = "ordered_map")] HashMap, + #[serde(skip)] MiddlewareDictionary, +); + +impl Dictionary { + pub fn new(values: HashMap) -> Result { + let dict = MiddlewareDictionary::new( + &values + .iter() + .map(|(k, v)| (hash_str(k), MiddlewareValue::from(v))) + .collect::>(), + )?; + Ok(Self(values, dict)) + } + + pub fn middleware_dict(&self) -> &MiddlewareDictionary { + &self.1 + } + + pub fn values(&self) -> &HashMap { + &self.0 + } +} + +impl<'de> Deserialize<'de> for Dictionary { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let values: HashMap = HashMap::deserialize(deserializer)?; + Dictionary::new(values).map_err(serde::de::Error::custom) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, JsonSchema)] +#[serde(transparent)] +pub struct Array(Vec, #[serde(skip)] MiddlewareArray); + +impl Array { + pub fn new(values: Vec) -> Result { + let array = + MiddlewareArray::new(&values.iter().map(|v| MiddlewareValue::from(v)).collect())?; + Ok(Self(values, array)) + } + + pub fn middleware_array(&self) -> &MiddlewareArray { + &self.1 + } + + pub fn values(&self) -> &Vec { + &self.0 + } +} + +impl<'de> Deserialize<'de> for Array { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let values: Vec = Vec::deserialize(deserializer)?; + Array::new(values).map_err(serde::de::Error::custom) + } +} diff --git a/src/frontend/mod.rs b/src/frontend/mod.rs index df1b752..5f92a36 100644 --- a/src/frontend/mod.rs +++ b/src/frontend/mod.rs @@ -1,31 +1,34 @@ //! 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, NativeOperation, NativePredicate, Params, PodId, + PodProver, PodSigner, SELF, +}; +use crate::middleware::{OperationType, Predicate, 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::{ - self, - containers::{Array, Dictionary, Set}, - hash_str, Hash, MainPodInputs, NativeOperation, NativePredicate, Params, PodId, PodProver, - PodSigner, SELF, -}; -use crate::middleware::{OperationType, Predicate, KEY_SIGNER, KEY_TYPE}; - +pub mod containers; mod custom; mod operation; +mod serialization; mod statement; pub use custom::*; pub use operation::*; pub use statement::*; /// This type is just for presentation purposes. -#[derive(Clone, Debug, Default, h::Hash, PartialEq, Eq)] +#[derive(Clone, Debug, Default, h::Hash, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] pub enum PodClass { #[default] Signed, @@ -33,18 +36,52 @@ pub enum PodClass { } // An Origin, which represents a reference to an ancestor POD. -#[derive(Clone, Debug, PartialEq, Eq, h::Hash, Default)] -pub struct Origin(pub PodClass, pub PodId); +#[derive(Clone, Debug, PartialEq, Eq, h::Hash, Default, Serialize, Deserialize, JsonSchema)] +pub struct Origin { + pub pod_class: PodClass, + pub pod_id: PodId, +} -#[derive(Clone, Debug, PartialEq, Eq)] +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 { - String(String), - Int(i64), - Bool(bool), - Dictionary(Dictionary), + // 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), - Array(Array), + 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 { @@ -71,9 +108,9 @@ impl From<&Value> for middleware::Value { 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.commitment().value(), - Value::Set(s) => s.commitment().value(), - Value::Array(a) => a.commitment().value(), + 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, } } @@ -102,9 +139,9 @@ impl fmt::Display for Value { 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.commitment()), - Value::Set(s) => write!(f, "set:{}", s.commitment()), - Value::Array(a) => write!(f, "arr:{}", a.commitment()), + 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), } } @@ -174,12 +211,14 @@ impl SignedPodBuilder { /// SignedPod is a wrapper on top of backend::SignedPod, which additionally stores the /// string<-->hash relation of the keys. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(try_from = "SignedPodHelper", into = "SignedPodHelper")] pub struct SignedPod { pub pod: Box, /// 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, } @@ -208,7 +247,7 @@ impl SignedPod { self.pod.id() } pub fn origin(&self) -> Origin { - Origin(PodClass::Signed, self.id()) + Origin::new(PodClass::Signed, self.id()) } pub fn verify(&self) -> bool { self.pod.verify() @@ -222,12 +261,21 @@ impl SignedPod { } } -#[derive(Clone, Debug, PartialEq, Eq, h::Hash)] -pub struct AnchoredKey(pub Origin, pub String); +#[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 for middleware::AnchoredKey { fn from(ak: AnchoredKey) -> Self { - middleware::AnchoredKey(ak.0 .1, hash_str(&ak.1)) + middleware::AnchoredKey(ak.origin.pod_id, hash_str(&ak.key)) } } @@ -295,11 +343,12 @@ impl MainPodBuilder { // Add key-hash and POD ID-class correspondences to tables. pod.public_statements .iter() - .flat_map(|s| &s.1) + .flat_map(|s| &s.args) .flat_map(|arg| match arg { - StatementArg::Key(AnchoredKey(Origin(pod_class, pod_id), key)) => { - Some((*pod_id, pod_class.clone(), hash_str(key), key.clone())) - } + 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)| { @@ -324,8 +373,8 @@ impl MainPodBuilder { for arg in args.iter_mut() { match arg { OperationArg::Statement(s) => { - if s.0 == Predicate::Native(NativePredicate::ValueOf) { - st_args.push(s.1[0].clone()) + if s.predicate == Predicate::Native(NativePredicate::ValueOf) { + st_args.push(s.args[0].clone()) } else { panic!("Invalid statement argument."); } @@ -334,11 +383,11 @@ impl MainPodBuilder { OperationArg::Literal(v) => { let value_of_st = self.literal(public, v)?; *arg = OperationArg::Statement(value_of_st.clone()); - st_args.push(value_of_st.1[0].clone()) + st_args.push(value_of_st.args[0].clone()) } OperationArg::Entry(k, v) => { - st_args.push(StatementArg::Key(AnchoredKey( - Origin(PodClass::Main, SELF), + st_args.push(StatementArg::Key(AnchoredKey::new( + Origin::new(PodClass::Main, SELF), k.clone(), ))); st_args.push(StatementArg::Literal(v.clone())) @@ -366,7 +415,7 @@ impl MainPodBuilder { .unwrap_or_else(|| { // We are dealing with a copy here. match (&args).get(0) { - Some(OperationArg::Statement(s)) if args.len() == 1 => Ok(s.0.clone()), + Some(OperationArg::Statement(s)) if args.len() == 1 => Ok(s.predicate.clone()), _ => Err(anyhow!("Invalid arguments to copy operation: {:?}", args)), } })?; @@ -376,7 +425,7 @@ impl MainPodBuilder { None => vec![], NewEntry => self.op_args_entries(public, args)?, CopyStatement => match &args[0] { - OperationArg::Statement(s) => s.1.clone(), + OperationArg::Statement(s) => s.args.clone(), _ => { return Err(anyhow!("Invalid arguments to copy operation: {}", op)); } @@ -388,14 +437,14 @@ impl MainPodBuilder { TransitiveEqualFromStatements => { match (args[0].clone(), args[1].clone()) { ( - OperationArg::Statement(Statement( - Predicate::Native(NativePredicate::Equal), - st0_args, - )), - OperationArg::Statement(Statement( - Predicate::Native(NativePredicate::Equal), - st1_args, - )), + 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] @@ -416,10 +465,10 @@ impl MainPodBuilder { } } GtToNotEqual => match args[0].clone() { - OperationArg::Statement(Statement( - Predicate::Native(NativePredicate::Gt), - st_args, - )) => { + OperationArg::Statement(Statement { + predicate: Predicate::Native(NativePredicate::Gt), + args: st_args, + }) => { vec![st_args[0].clone()] } _ => { @@ -427,10 +476,10 @@ impl MainPodBuilder { } }, LtToNotEqual => match args[0].clone() { - OperationArg::Statement(Statement( - Predicate::Native(NativePredicate::Lt), - st_args, - )) => { + OperationArg::Statement(Statement { + predicate: Predicate::Native(NativePredicate::Lt), + args: st_args, + }) => { vec![st_args[0].clone()] } _ => { @@ -441,18 +490,18 @@ impl MainPodBuilder { NotContainsFromEntries => self.op_args_entries(public, args)?, SumOf => match (args[0].clone(), args[1].clone(), args[2].clone()) { ( - OperationArg::Statement(Statement( - Predicate::Native(NativePredicate::ValueOf), - st0_args, - )), - OperationArg::Statement(Statement( - Predicate::Native(NativePredicate::ValueOf), - st1_args, - )), - OperationArg::Statement(Statement( - Predicate::Native(NativePredicate::ValueOf), - st2_args, - )), + 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 = match ( st0_args[1].clone(), @@ -489,18 +538,18 @@ impl MainPodBuilder { }, ProductOf => match (args[0].clone(), args[1].clone(), args[2].clone()) { ( - OperationArg::Statement(Statement( - Predicate::Native(NativePredicate::ValueOf), - st0_args, - )), - OperationArg::Statement(Statement( - Predicate::Native(NativePredicate::ValueOf), - st1_args, - )), - OperationArg::Statement(Statement( - Predicate::Native(NativePredicate::ValueOf), - st2_args, - )), + 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 = match ( st0_args[1].clone(), @@ -539,18 +588,18 @@ impl MainPodBuilder { }, MaxOf => match (args[0].clone(), args[1].clone(), args[2].clone()) { ( - OperationArg::Statement(Statement( - Predicate::Native(NativePredicate::ValueOf), - st0_args, - )), - OperationArg::Statement(Statement( - Predicate::Native(NativePredicate::ValueOf), - st1_args, - )), - OperationArg::Statement(Statement( - Predicate::Native(NativePredicate::ValueOf), - st2_args, - )), + 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 = match ( st0_args[1].clone(), @@ -610,8 +659,8 @@ impl MainPodBuilder { output_arg_values .chunks(2) .map(|chunk| { - Ok(StatementArg::Key(AnchoredKey( - Origin( + Ok(StatementArg::Key(AnchoredKey::new( + Origin::new( self.pod_class_table .get(&PodId(chunk[0].into())) .cloned() @@ -627,15 +676,15 @@ impl MainPodBuilder { .collect::>>()? } }; - let st = Statement(pred, st_args); + 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.1.iter().for_each(|arg| { - if let StatementArg::Key(AnchoredKey(_, key)) = arg { + st.args.iter().for_each(|arg| { + if let StatementArg::Key(AnchoredKey { origin: _, key }) = arg { self.key_table.insert(hash_str(key), key.clone()); } }); @@ -702,16 +751,16 @@ impl MainPodBuilder { crate::middleware::Statement::ValueOf( crate::middleware::AnchoredKey(id, key), value, - ) if id == pod_id && key == type_key_hash => Some(Statement( - Predicate::Native(NativePredicate::ValueOf), - vec![ - StatementArg::Key(AnchoredKey( - Origin(PodClass::Main, pod_id), + ) 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))?; @@ -720,18 +769,25 @@ impl MainPodBuilder { let public_statements = [type_statement] .into_iter() .chain(self.public_statements.clone().into_iter().map(|s| { - let s_type = s.0; + let s_type = s.predicate; let s_args = s - .1 + .args .into_iter() .map(|arg| match arg { - StatementArg::Key(AnchoredKey(Origin(class, id), key)) if id == SELF => { - StatementArg::Key(AnchoredKey(Origin(class, pod_id), key)) + 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(s_type, s_args) + Statement::new(s_type, s_args) })) .collect(); @@ -742,7 +798,8 @@ impl MainPodBuilder { } } -#[derive(Debug)] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(try_from = "MainPodHelper", into = "MainPodHelper")] pub struct MainPod { pub pod: Box, pub public_statements: Vec, @@ -769,7 +826,7 @@ impl MainPod { self.pod.id() } pub fn origin(&self) -> Origin { - Origin(PodClass::Main, self.id()) + Origin::new(PodClass::Main, self.id()) } } @@ -1166,20 +1223,26 @@ pub mod tests { let params = Params::default(); let mut builder = MainPodBuilder::new(¶ms); builder.insert(( - Statement( + Statement::new( Predicate::Native(NativePredicate::ValueOf), vec![ - StatementArg::Key(AnchoredKey(Origin(PodClass::Main, SELF), "a".into())), + StatementArg::Key(AnchoredKey::new( + Origin::new(PodClass::Main, SELF), + "a".into(), + )), StatementArg::Literal(Value::Int(3)), ], ), Operation(OperationType::Native(NativeOperation::NewEntry), vec![]), )); builder.insert(( - Statement( + Statement::new( Predicate::Native(NativePredicate::ValueOf), vec![ - StatementArg::Key(AnchoredKey(Origin(PodClass::Main, SELF), "a".into())), + StatementArg::Key(AnchoredKey::new( + Origin::new(PodClass::Main, SELF), + "a".into(), + )), StatementArg::Literal(Value::Int(28)), ], ), @@ -1194,19 +1257,25 @@ pub mod tests { // 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(Origin(PodClass::Main, SELF), "a".into()); - let self_b = AnchoredKey(Origin(PodClass::Main, SELF), "b".into()); - let value_of_a = Statement( + 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(Origin(PodClass::Main, SELF), "a".into())), + StatementArg::Key(AnchoredKey::new( + Origin::new(PodClass::Main, SELF), + "a".into(), + )), StatementArg::Literal(Value::Int(3)), ], ); - let value_of_b = Statement( + let value_of_b = Statement::new( Predicate::Native(NativePredicate::ValueOf), vec![ - StatementArg::Key(AnchoredKey(Origin(PodClass::Main, SELF), "b".into())), + StatementArg::Key(AnchoredKey::new( + Origin::new(PodClass::Main, SELF), + "b".into(), + )), StatementArg::Literal(Value::Int(27)), ], ); @@ -1220,7 +1289,7 @@ pub mod tests { Operation(OperationType::Native(NativeOperation::NewEntry), vec![]), )); builder.insert(( - Statement( + Statement::new( Predicate::Native(NativePredicate::Equal), vec![StatementArg::Key(self_a), StatementArg::Key(self_b)], ), diff --git a/src/frontend/serialization.rs b/src/frontend/serialization.rs new file mode 100644 index 0000000..f30341d --- /dev/null +++ b/src/frontend/serialization.rs @@ -0,0 +1,294 @@ +use std::collections::{BTreeMap, HashMap}; + +use schemars::{JsonSchema, Schema}; +use serde::{Deserialize, Serialize, Serializer}; + +use crate::backends::plonky2::mock_main::MockMainPod; +use crate::backends::plonky2::mock_signed::MockSignedPod; +use crate::frontend::containers::Dictionary; +use crate::frontend::Statement; +use crate::middleware::PodId; + +use super::{MainPod, SignedPod, Value}; + +#[derive(Serialize, Deserialize, JsonSchema)] +pub struct SignedPodHelper { + entries: HashMap, + proof: String, + pod_class: String, + pod_type: String, +} + +impl TryFrom for SignedPod { + type Error = anyhow::Error; + + fn try_from(helper: SignedPodHelper) -> Result { + if helper.pod_class != "Signed" { + return Err(anyhow::anyhow!("pod_class is not Signed")); + } + if helper.pod_type != "Mock" { + return Err(anyhow::anyhow!("pod_type is not Mock")); + } + + let dict = Dictionary::new(helper.entries.clone())? + .middleware_dict() + .clone(); + let pod = MockSignedPod::deserialize(PodId(dict.commitment()), helper.proof, dict); + + Ok(SignedPod { + pod: Box::new(pod), + kvs: helper.entries, + }) + } +} + +impl From for SignedPodHelper { + fn from(pod: SignedPod) -> Self { + SignedPodHelper { + entries: pod.kvs, + proof: pod.pod.serialized_proof(), + pod_class: "Signed".to_string(), + pod_type: "Mock".to_string(), + } + } +} + +#[derive(Serialize, Deserialize, JsonSchema)] +pub struct MainPodHelper { + public_statements: Vec, + proof: String, + pod_class: String, + pod_type: String, +} + +impl TryFrom for MainPod { + type Error = anyhow::Error; // or you can create a custom error type + + fn try_from(helper: MainPodHelper) -> Result { + if helper.pod_class != "Main" { + return Err(anyhow::anyhow!("pod_class is not Main")); + } + if helper.pod_type != "Mock" { + return Err(anyhow::anyhow!("pod_type is not Mock")); + } + + let pod = MockMainPod::deserialize(helper.proof) + .map_err(|e| anyhow::anyhow!("Failed to deserialize proof: {}", e))?; + + Ok(MainPod { + pod: Box::new(pod), + public_statements: helper.public_statements, + }) + } +} + +impl From for MainPodHelper { + fn from(pod: MainPod) -> Self { + MainPodHelper { + public_statements: pod.public_statements, + proof: pod.pod.serialized_proof(), + pod_class: "Main".to_string(), + pod_type: "Mock".to_string(), + } + } +} + +pub fn serialize_i64(value: &i64, serializer: S) -> Result +where + S: serde::Serializer, +{ + serializer.serialize_str(&value.to_string()) +} + +pub fn deserialize_i64<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + String::deserialize(deserializer)? + .parse() + .map_err(serde::de::Error::custom) +} + +// HashMap is not ordered, but we want our dictionaries to be ordered +// by key for serialization, so we turn HashMaps into BTreeMaps. +pub fn ordered_map( + value: &HashMap, + serializer: S, +) -> Result +where + S: Serializer, +{ + let ordered: BTreeMap<_, _> = value.iter().collect(); + ordered.serialize(serializer) +} + +pub fn transform_value_schema(schema: &mut Schema) { + let obj = schema.as_object_mut().unwrap(); + + // Get the oneOf array which contains our variant schemas + if let Some(one_of_container) = obj.get_mut("oneOf") { + if let Some(variants) = one_of_container.as_array_mut() { + // Add String variant (untagged) + variants.push(serde_json::json!({ + "type": "string" + })); + + // Add Boolean variant (untagged) + variants.push(serde_json::json!({ + "type": "boolean" + })); + + // Add Array variant (untagged) + variants.push(serde_json::json!({ + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + })); + } + } +} + +#[cfg(test)] +mod tests { + use crate::{ + backends::plonky2::{mock_main::MockProver, mock_signed::MockSigner}, + examples::{zu_kyc_pod_builder, zu_kyc_sign_pod_builders}, + frontend::{ + containers::{Array, Dictionary, Set}, + SignedPodBuilder, + }, + middleware::{self, Params}, + }; + + use super::*; + + #[test] + fn test_value_serialization() { + // Pairs of values and their expected serialized representations + let values = vec![ + (Value::String("hello".to_string()), "\"hello\""), + (Value::Int(42), "{\"Int\":\"42\"}"), + (Value::Bool(true), "true"), + ( + Value::Array( + Array::new(vec![Value::String("foo".to_string()), Value::Bool(false)]).unwrap(), + ), + "[\"foo\",false]", + ), + ( + Value::Dictionary( + Dictionary::new(HashMap::from([ + ("foo".to_string(), Value::Int(123)), + ("bar".to_string(), Value::String("baz".to_string())), + ])) + .unwrap(), + ), + "{\"Dictionary\":{\"bar\":\"baz\",\"foo\":{\"Int\":\"123\"}}}", + ), + ( + Value::Set( + Set::new(vec![ + Value::String("foo".to_string()), + Value::String("bar".to_string()), + ]) + .unwrap(), + ), + "{\"Set\":[\"foo\",\"bar\"]}", + ), + ]; + + for (value, expected) in values { + let serialized = serde_json::to_string(&value).unwrap(); + assert_eq!(serialized, expected); + let deserialized: Value = serde_json::from_str(&serialized).unwrap(); + assert_eq!(value, deserialized); + let expected_deserialized: Value = serde_json::from_str(&expected).unwrap(); + assert_eq!(value, expected_deserialized); + } + } + + #[test] + fn test_signed_pod_serialization() { + let mut signer = MockSigner { pk: "test".into() }; + let mut builder = SignedPodBuilder::new(&Params::default()); + builder.insert("name", "test"); + builder.insert("age", 30); + builder.insert("very_large_int", 1152921504606846976); + builder.insert( + "a_dict_containing_one_key", + Value::Dictionary( + Dictionary::new(HashMap::from([ + ("foo".to_string(), Value::Int(123)), + ( + "an_array_containing_three_ints".to_string(), + Value::Array( + Array::new(vec![Value::Int(1), Value::Int(2), Value::Int(3)]).unwrap(), + ), + ), + ( + "a_set_containing_two_strings".to_string(), + Value::Set( + Set::new(vec![ + Value::Array( + Array::new(vec![ + Value::String("foo".to_string()), + Value::String("bar".to_string()), + ]) + .unwrap(), + ), + Value::String("baz".to_string()), + ]) + .unwrap(), + ), + ), + ])) + .unwrap(), + ), + ); + + let pod = builder.sign(&mut signer).unwrap(); + + let serialized = serde_json::to_string(&pod).unwrap(); + println!("serialized: {}", serialized); + let deserialized: SignedPod = serde_json::from_str(&serialized).unwrap(); + + assert_eq!(pod.kvs, deserialized.kvs); + assert_eq!(pod.origin(), deserialized.origin()); + assert_eq!(pod.verify(), deserialized.verify()); + assert_eq!(pod.id(), deserialized.id()) + } + + #[test] + fn test_main_pod_serialization() { + let params = middleware::Params::default(); + + let (gov_id_builder, pay_stub_builder, sanction_list_builder) = + zu_kyc_sign_pod_builders(¶ms); + let mut signer = MockSigner { + pk: "ZooGov".into(), + }; + let gov_id_pod = gov_id_builder.sign(&mut signer).unwrap(); + let mut signer = MockSigner { + pk: "ZooDeel".into(), + }; + let pay_stub_pod = pay_stub_builder.sign(&mut signer).unwrap(); + let mut signer = MockSigner { + pk: "ZooOFAC".into(), + }; + let sanction_list_pod = sanction_list_builder.sign(&mut signer).unwrap(); + let kyc_builder = + zu_kyc_pod_builder(¶ms, &gov_id_pod, &pay_stub_pod, &sanction_list_pod).unwrap(); + + let mut prover = MockProver {}; + let kyc_pod = kyc_builder.prove(&mut prover, ¶ms).unwrap(); + + let serialized = serde_json::to_string(&kyc_pod).unwrap(); + println!("serialized: {}", serialized); + 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.verify(), deserialized.pod.verify()); + } +} diff --git a/src/frontend/statement.rs b/src/frontend/statement.rs index 5b4b396..dcdf848 100644 --- a/src/frontend/statement.rs +++ b/src/frontend/statement.rs @@ -1,10 +1,11 @@ -use anyhow::{anyhow, Result}; -use std::fmt; - use super::{AnchoredKey, SignedPod, Value}; use crate::middleware::{self, NativePredicate, Predicate}; +use anyhow::{anyhow, Result}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::fmt; -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] pub enum StatementArg { Literal(Value), Key(AnchoredKey), @@ -14,13 +15,22 @@ impl fmt::Display for StatementArg { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Literal(v) => write!(f, "{}", v), - Self::Key(r) => write!(f, "{}.{}", r.0 .1, r.1), + Self::Key(r) => write!(f, "{}.{}", r.origin.pod_id, r.key), } } } -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Statement(pub Predicate, pub Vec); +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +pub struct Statement { + pub predicate: Predicate, + pub args: Vec, +} + +impl Statement { + pub fn new(predicate: Predicate, args: Vec) -> Self { + Self { predicate, args } + } +} impl From<(&SignedPod, &str)> for Statement { fn from((pod, key): (&SignedPod, &str)) -> Self { @@ -30,13 +40,13 @@ impl From<(&SignedPod, &str)> for Statement { .get(key) .cloned() .unwrap_or_else(|| panic!("Key {} is not present in POD: {}", key, pod)); - Statement( - Predicate::Native(NativePredicate::ValueOf), - vec![ - StatementArg::Key(AnchoredKey(pod.origin(), key.to_string())), + Statement { + predicate: Predicate::Native(NativePredicate::ValueOf), + args: vec![ + StatementArg::Key(AnchoredKey::new(pod.origin(), key.to_string())), StatementArg::Literal(value), ], - ) + } } } @@ -47,11 +57,11 @@ impl TryFrom for middleware::Statement { type NP = NativePredicate; type SA = StatementArg; let args = ( - s.1.first().cloned(), - s.1.get(1).cloned(), - s.1.get(2).cloned(), + s.args.first().cloned(), + s.args.get(1).cloned(), + s.args.get(2).cloned(), ); - Ok(match &s.0 { + Ok(match &s.predicate { Predicate::Native(np) => match (np, args) { (NP::None, (None, None, None)) => MS::None, (NP::ValueOf, (Some(SA::Key(ak)), Some(StatementArg::Literal(v)), None)) => { @@ -88,7 +98,8 @@ impl TryFrom for middleware::Statement { }, Predicate::Custom(cpr) => MS::Custom( cpr.clone(), - s.1.iter() + s.args + .iter() .map(|arg| match arg { StatementArg::Key(ak) => Ok(ak.clone().into()), _ => Err(anyhow!("Invalid statement arg: {}", arg)), @@ -102,8 +113,8 @@ impl TryFrom for middleware::Statement { impl fmt::Display for Statement { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?} ", self.0)?; - for (i, arg) in self.1.iter().enumerate() { + write!(f, "{:?} ", self.predicate)?; + for (i, arg) in self.args.iter().enumerate() { if i != 0 { write!(f, " ")?; } diff --git a/src/middleware/custom.rs b/src/middleware/custom.rs index d4d0caa..6f8d5cb 100644 --- a/src/middleware/custom.rs +++ b/src/middleware/custom.rs @@ -4,18 +4,19 @@ use std::{fmt, hash as h, iter, iter::zip}; use anyhow::{anyhow, Result}; use plonky2::field::types::Field; - -use crate::backends::plonky2::basetypes::HASH_SIZE; -use crate::util::hashmap_insert_no_dupe; +use schemars::JsonSchema; use super::{ hash_fields, AnchoredKey, Hash, NativePredicate, Params, PodId, Statement, StatementArg, ToFields, Value, F, }; +use crate::backends::plonky2::basetypes::HASH_SIZE; +use crate::util::hashmap_insert_no_dupe; +use serde::{Deserialize, Serialize}; // BEGIN Custom 1b -#[derive(Clone, Debug, PartialEq, Eq, h::Hash)] +#[derive(Clone, Debug, PartialEq, Eq, h::Hash, Serialize, Deserialize, JsonSchema)] pub enum HashOrWildcard { Hash(Hash), Wildcard(usize), @@ -59,7 +60,7 @@ impl ToFields for HashOrWildcard { } } -#[derive(Clone, Debug, PartialEq, Eq, h::Hash)] +#[derive(Clone, Debug, PartialEq, Eq, h::Hash, Serialize, Deserialize, JsonSchema)] pub enum StatementTmplArg { None, Literal(Value), @@ -145,7 +146,7 @@ impl fmt::Display for StatementTmplArg { // END /// Statement Template for a Custom Predicate -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] pub struct StatementTmpl(pub Predicate, pub Vec); impl StatementTmpl { @@ -199,7 +200,7 @@ impl ToFields for StatementTmpl { } } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] pub struct CustomPredicate { /// NOTE: fields are not public (outside of crate) to enforce the struct instantiation through /// the `::and/or` methods, which performs checks on the values. @@ -287,7 +288,7 @@ impl fmt::Display for CustomPredicate { } } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] pub struct CustomPredicateBatch { pub name: String, pub predicates: Vec, @@ -324,7 +325,7 @@ impl CustomPredicateBatch { } } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] pub struct CustomPredicateRef(pub Arc, pub usize); impl CustomPredicateRef { @@ -383,7 +384,8 @@ impl CustomPredicateRef { } } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[serde(tag = "type", content = "value")] pub enum Predicate { Native(NativePredicate), BatchSelf(usize), diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 1032f1a..6d9881c 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -5,14 +5,17 @@ mod basetypes; pub mod containers; mod custom; mod operation; +pub mod serialization; mod statement; pub use basetypes::*; pub use custom::*; pub use operation::*; +use schemars::JsonSchema; pub use statement::*; use anyhow::Result; use dyn_clone::DynClone; +use serde::{Deserialize, Serialize}; use std::any::Any; use std::collections::HashMap; use std::fmt; @@ -32,7 +35,7 @@ impl fmt::Display for PodId { } /// AnchoredKey is a tuple containing (OriginId: PodId, key: Hash) -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct AnchoredKey(pub PodId, pub Hash); impl AnchoredKey { @@ -54,7 +57,7 @@ impl fmt::Display for AnchoredKey { /// An entry consists of a key-value pair. pub type Entry = (String, Value); -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default, Serialize, Deserialize, JsonSchema)] pub struct PodId(pub Hash); impl ToFields for PodId { @@ -77,7 +80,7 @@ impl From for Value { } } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct Params { pub max_input_signed_pods: usize, pub max_input_main_pods: usize, @@ -166,6 +169,15 @@ pub trait Pod: fmt::Debug + DynClone { } // Used for downcasting fn into_any(self: Box) -> Box; + // Front-end Pods keep references to middleware Pods. Most of the + // middleware data can be derived directly from front-end data, but the + // "proof" data is only created at the point of proving/signing, and + // cannot be reconstructed. As such, we need to serialize it whenever + // we serialize a front-end Pod. Since the front-end does not understand + // the implementation details of the middleware, this method allows the + // middleware to provide some serialized data that can be used to + // reconstruct the proof. + fn serialized_proof(&self) -> String; } // impl Clone for Box @@ -193,6 +205,9 @@ impl Pod for NonePod { fn into_any(self: Box) -> Box { self } + fn serialized_proof(&self) -> String { + "".to_string() + } } #[derive(Debug)] diff --git a/src/middleware/operation.rs b/src/middleware/operation.rs index 743e01e..da54613 100644 --- a/src/middleware/operation.rs +++ b/src/middleware/operation.rs @@ -1,18 +1,18 @@ -use std::fmt; - use anyhow::{anyhow, Result}; use log::error; +use serde::{Deserialize, Serialize}; +use std::fmt; use super::{CustomPredicateRef, NativePredicate, Statement, StatementArg}; use crate::middleware::{AnchoredKey, Params, Predicate, Value, SELF}; -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum OperationType { Native(NativeOperation), Custom(CustomPredicateRef), } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum NativeOperation { None = 0, NewEntry = 1, @@ -91,7 +91,7 @@ pub enum Operation { } impl Operation { - pub fn code(&self) -> OperationType { + pub fn predicate(&self) -> OperationType { type OT = OperationType; use NativeOperation::*; match self { @@ -178,7 +178,7 @@ impl Operation { /// The outer Result is error handling pub fn output_statement(&self) -> Result> { use Statement::*; - let pred: Option = self.code().output_predicate(); + let pred: Option = self.predicate().output_predicate(); let st_args: Option> = match self { Self::None => Some(vec![]), @@ -404,7 +404,7 @@ impl Operation { impl fmt::Display for Operation { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { writeln!(f, "middleware::Operation:")?; - writeln!(f, " {:?} ", self.code())?; + writeln!(f, " {:?} ", self.predicate())?; for arg in self.args().iter() { writeln!(f, " {}", arg)?; } diff --git a/src/middleware/serialization.rs b/src/middleware/serialization.rs new file mode 100644 index 0000000..a93f508 --- /dev/null +++ b/src/middleware/serialization.rs @@ -0,0 +1,69 @@ +use plonky2::field::types::Field; +use serde::Deserialize; + +use super::{F, HASH_SIZE, VALUE_SIZE}; + +fn serialize_field_tuple( + value: &[F; N], + serializer: S, +) -> Result +where + S: serde::Serializer, +{ + serializer.serialize_str(&format!( + "{:016x}{:016x}{:016x}{:016x}", + value[0].0, value[1].0, value[2].0, value[3].0 + )) +} + +fn deserialize_field_tuple<'de, D, const N: usize>(deserializer: D) -> Result<[F; N], D::Error> +where + D: serde::Deserializer<'de>, +{ + let hex_str = String::deserialize(deserializer)?; + + if !hex_str.chars().count() == 64 || !hex_str.chars().all(|c| c.is_ascii_hexdigit()) { + return Err(serde::de::Error::custom( + "Invalid hex string format - expected 64 hexadecimal characters", + )); + } + + let mut v = [F::ZERO; N]; + for i in 0..N { + let start = i * 16; + let end = start + 16; + let hex_part = &hex_str[start..end]; + v[i] = F::from_canonical_u64( + u64::from_str_radix(hex_part, 16).map_err(serde::de::Error::custom)?, + ); + } + Ok(v) +} + +pub fn serialize_hash_tuple(value: &[F; HASH_SIZE], serializer: S) -> Result +where + S: serde::Serializer, +{ + serialize_field_tuple::(value, serializer) +} + +pub fn deserialize_hash_tuple<'de, D>(deserializer: D) -> Result<[F; HASH_SIZE], D::Error> +where + D: serde::Deserializer<'de>, +{ + deserialize_field_tuple::(deserializer) +} + +pub fn serialize_value_tuple(value: &[F; VALUE_SIZE], serializer: S) -> Result +where + S: serde::Serializer, +{ + serialize_field_tuple::(value, serializer) +} + +pub fn deserialize_value_tuple<'de, D>(deserializer: D) -> Result<[F; VALUE_SIZE], D::Error> +where + D: serde::Deserializer<'de>, +{ + deserialize_field_tuple::(deserializer) +} diff --git a/src/middleware/statement.rs b/src/middleware/statement.rs index a6bd627..ba879ec 100644 --- a/src/middleware/statement.rs +++ b/src/middleware/statement.rs @@ -1,5 +1,7 @@ use anyhow::{anyhow, Result}; use plonky2::field::types::Field; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; use std::{fmt, iter}; use strum_macros::FromRepr; @@ -9,7 +11,7 @@ pub const KEY_SIGNER: &str = "_signer"; pub const KEY_TYPE: &str = "_type"; pub const STATEMENT_ARG_F_LEN: usize = 8; -#[derive(Clone, Copy, Debug, FromRepr, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, FromRepr, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)] pub enum NativePredicate { None = 0, ValueOf = 1, @@ -203,7 +205,7 @@ impl fmt::Display for Statement { } /// Statement argument type. Useful for statement decompositions. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum StatementArg { None, Literal(Value),