Serialization of Signed and Main Pods (#128)
This commit is contained in:
parent
fee70af12b
commit
9afc43675d
16 changed files with 817 additions and 189 deletions
|
|
@ -18,6 +18,10 @@ log = "0.4"
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
# enabled by features:
|
# enabled by features:
|
||||||
plonky2 = { git = "https://github.com/0xPolygonZero/plonky2", optional = true }
|
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]
|
[features]
|
||||||
default = ["backend_plonky2"]
|
default = ["backend_plonky2"]
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,10 @@
|
||||||
//! `backend_plonky2` feature is enabled.
|
//! `backend_plonky2` feature is enabled.
|
||||||
//! See src/middleware/basetypes.rs for more details.
|
//! 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 anyhow::{anyhow, Error, Result};
|
||||||
use hex::{FromHex, FromHexError};
|
use hex::{FromHex, FromHexError};
|
||||||
use plonky2::field::goldilocks_field::GoldilocksField;
|
use plonky2::field::goldilocks_field::GoldilocksField;
|
||||||
|
|
@ -10,11 +14,11 @@ use plonky2::hash::poseidon::PoseidonHash;
|
||||||
use plonky2::plonk::config::Hasher;
|
use plonky2::plonk::config::Hasher;
|
||||||
use plonky2::plonk::config::PoseidonGoldilocksConfig;
|
use plonky2::plonk::config::PoseidonGoldilocksConfig;
|
||||||
use plonky2::plonk::proof::Proof as Plonky2Proof;
|
use plonky2::plonk::proof::Proof as Plonky2Proof;
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::cmp::{Ord, Ordering};
|
use std::cmp::{Ord, Ordering};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use crate::middleware::{Params, ToFields};
|
|
||||||
|
|
||||||
use crate::backends::counter;
|
use crate::backends::counter;
|
||||||
|
|
||||||
/// F is the native field we use everywhere. Currently it's Goldilocks from plonky2
|
/// 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 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]);
|
pub const EMPTY_HASH: Hash = Hash([F::ZERO, F::ZERO, F::ZERO, F::ZERO]);
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct Value(pub [F; VALUE_SIZE]);
|
#[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 {
|
impl ToFields for Value {
|
||||||
fn to_fields(&self, _params: &Params) -> Vec<F> {
|
fn to_fields(&self, _params: &Params) -> Vec<F> {
|
||||||
|
|
@ -117,8 +131,15 @@ impl fmt::Display for Value {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, Hash, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Default, Hash, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct Hash(pub [F; HASH_SIZE]);
|
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 {
|
pub fn hash_value(input: &Value) -> Hash {
|
||||||
hash_fields(&input.0)
|
hash_fields(&input.0)
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
|
use base64::prelude::*;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use log::error;
|
use log::error;
|
||||||
use plonky2::hash::poseidon::PoseidonHash;
|
use plonky2::hash::poseidon::PoseidonHash;
|
||||||
use plonky2::plonk::config::Hasher;
|
use plonky2::plonk::config::Hasher;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
|
@ -27,14 +29,14 @@ impl PodProver for MockProver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct MockMainPod {
|
pub struct MockMainPod {
|
||||||
params: Params,
|
params: Params,
|
||||||
id: PodId,
|
id: PodId,
|
||||||
input_signed_pods: Vec<Box<dyn Pod>>,
|
// input_signed_pods: Vec<Box<dyn Pod>>,
|
||||||
input_main_pods: Vec<Box<dyn Pod>>,
|
// input_main_pods: Vec<Box<dyn Pod>>,
|
||||||
// New statements introduced by this pod
|
// New statements introduced by this pod
|
||||||
input_statements: Vec<Statement>,
|
// input_statements: Vec<Statement>,
|
||||||
public_statements: Vec<Statement>,
|
public_statements: Vec<Statement>,
|
||||||
operations: Vec<Operation>,
|
operations: Vec<Operation>,
|
||||||
// All statements (inherited + new)
|
// All statements (inherited + new)
|
||||||
|
|
@ -258,7 +260,7 @@ impl MockMainPod {
|
||||||
.map(|mid_arg| Self::find_op_arg(statements, mid_arg))
|
.map(|mid_arg| Self::find_op_arg(statements, mid_arg))
|
||||||
.collect::<Result<Vec<_>>>()?;
|
.collect::<Result<Vec<_>>>()?;
|
||||||
Self::pad_operation_args(params, &mut args);
|
Self::pad_operation_args(params, &mut args);
|
||||||
operations.push(Operation(op.code(), args));
|
operations.push(Operation(op.predicate(), args));
|
||||||
}
|
}
|
||||||
Ok(operations)
|
Ok(operations)
|
||||||
}
|
}
|
||||||
|
|
@ -332,9 +334,9 @@ impl MockMainPod {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
params: params.clone(),
|
params: params.clone(),
|
||||||
id,
|
id,
|
||||||
input_signed_pods,
|
// input_signed_pods,
|
||||||
input_main_pods,
|
// input_main_pods,
|
||||||
input_statements,
|
// input_statements,
|
||||||
public_statements,
|
public_statements,
|
||||||
statements,
|
statements,
|
||||||
operations,
|
operations,
|
||||||
|
|
@ -360,6 +362,15 @@ impl MockMainPod {
|
||||||
fn pad_operation_args(params: &Params, args: &mut Vec<OperationArg>) {
|
fn pad_operation_args(params: &Params, args: &mut Vec<OperationArg>) {
|
||||||
fill_pad(args, OperationArg::None, params.max_operation_args)
|
fill_pad(args, OperationArg::None, params.max_operation_args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn deserialize(serialized: String) -> Result<Self> {
|
||||||
|
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 {
|
pub fn hash_statements(statements: &[Statement], _params: &Params) -> middleware::Hash {
|
||||||
|
|
@ -485,6 +496,10 @@ impl Pod for MockMainPod {
|
||||||
fn into_any(self: Box<Self>) -> Box<dyn Any> {
|
fn into_any(self: Box<Self>) -> Box<dyn Any> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn serialized_proof(&self) -> String {
|
||||||
|
BASE64_STANDARD.encode(serde_json::to_string(self).unwrap())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
use anyhow::Result;
|
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
use super::Statement;
|
use super::Statement;
|
||||||
use crate::middleware::{self, OperationType};
|
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 {
|
pub enum OperationArg {
|
||||||
None,
|
None,
|
||||||
Index(usize),
|
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<OperationArg>);
|
pub struct Operation(pub OperationType, pub Vec<OperationArg>);
|
||||||
|
|
||||||
impl Operation {
|
impl Operation {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use crate::middleware::{
|
use crate::middleware::{
|
||||||
self, AnchoredKey, NativePredicate, Params, Predicate, StatementArg, ToFields,
|
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<StatementArg>);
|
pub struct Statement(pub Predicate, pub Vec<StatementArg>);
|
||||||
|
|
||||||
impl Statement {
|
impl Statement {
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,16 @@ pub struct MockSignedPod {
|
||||||
dict: Dictionary,
|
dict: Dictionary,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl MockSignedPod {
|
||||||
|
pub fn deserialize(id: PodId, signature: String, dict: Dictionary) -> Self {
|
||||||
|
Self {
|
||||||
|
id,
|
||||||
|
signature,
|
||||||
|
dict,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Pod for MockSignedPod {
|
impl Pod for MockSignedPod {
|
||||||
fn verify(&self) -> bool {
|
fn verify(&self) -> bool {
|
||||||
// 1. Verify type
|
// 1. Verify type
|
||||||
|
|
@ -100,6 +110,10 @@ impl Pod for MockSignedPod {
|
||||||
fn into_any(self: Box<Self>) -> Box<dyn Any> {
|
fn into_any(self: Box<Self>) -> Box<dyn Any> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn serialized_proof(&self) -> String {
|
||||||
|
self.signature.to_string()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,11 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::backends::plonky2::mock_signed::MockSigner;
|
use crate::backends::plonky2::mock_signed::MockSigner;
|
||||||
use crate::frontend::{
|
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::CustomPredicateRef;
|
||||||
use crate::middleware::{containers::Dictionary, Params, PodType, KEY_SIGNER, KEY_TYPE};
|
use crate::middleware::{Params, PodType, KEY_SIGNER, KEY_TYPE};
|
||||||
use crate::middleware::{hash_str, CustomPredicateRef, NativeOperation, OperationType};
|
|
||||||
use crate::op;
|
use crate::op;
|
||||||
|
|
||||||
// ZuKYC
|
// ZuKYC
|
||||||
|
|
@ -28,10 +28,11 @@ pub fn zu_kyc_sign_pod_builders(
|
||||||
pay_stub.insert("startDate", 1706367566);
|
pay_stub.insert("startDate", 1706367566);
|
||||||
|
|
||||||
let mut sanction_list = SignedPodBuilder::new(params);
|
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(
|
sanction_list.insert(
|
||||||
"sanctionList",
|
"sanctionList",
|
||||||
Value::Set(Set::new(&sanctions_values.to_vec()).unwrap()),
|
Value::Set(Set::new(sanctions_values.into()).unwrap()),
|
||||||
);
|
);
|
||||||
|
|
||||||
(gov_id, pay_stub, sanction_list)
|
(gov_id, pay_stub, sanction_list)
|
||||||
|
|
@ -337,7 +338,7 @@ pub fn great_boy_pod_full_flow() -> Result<MainPodBuilder> {
|
||||||
alice_friend_pods.push(friend.sign(&mut bob_signer).unwrap());
|
alice_friend_pods.push(friend.sign(&mut bob_signer).unwrap());
|
||||||
alice_friend_pods.push(friend.sign(&mut charlie_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(
|
great_boy_pod_builder(
|
||||||
¶ms,
|
¶ms,
|
||||||
[
|
[
|
||||||
|
|
@ -396,6 +397,6 @@ pub fn tickets_pod_full_flow() -> Result<MainPodBuilder> {
|
||||||
&signed_pod,
|
&signed_pod,
|
||||||
123,
|
123,
|
||||||
true,
|
true,
|
||||||
&Value::Dictionary(Dictionary::new(&HashMap::new())?),
|
&Value::Dictionary(Dictionary::new(HashMap::new()).unwrap()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
110
src/frontend/containers.rs
Normal file
110
src/frontend/containers.rs
Normal file
|
|
@ -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<Value>, #[serde(skip)] MiddlewareSet);
|
||||||
|
|
||||||
|
impl Set {
|
||||||
|
pub fn new(values: Vec<Value>) -> Result<Self> {
|
||||||
|
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<Value> {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Set {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let values: Vec<Value> = 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<String, Value>,
|
||||||
|
#[serde(skip)] MiddlewareDictionary,
|
||||||
|
);
|
||||||
|
|
||||||
|
impl Dictionary {
|
||||||
|
pub fn new(values: HashMap<String, Value>) -> Result<Self> {
|
||||||
|
let dict = MiddlewareDictionary::new(
|
||||||
|
&values
|
||||||
|
.iter()
|
||||||
|
.map(|(k, v)| (hash_str(k), MiddlewareValue::from(v)))
|
||||||
|
.collect::<HashMap<_, _>>(),
|
||||||
|
)?;
|
||||||
|
Ok(Self(values, dict))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn middleware_dict(&self) -> &MiddlewareDictionary {
|
||||||
|
&self.1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn values(&self) -> &HashMap<String, Value> {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Dictionary {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let values: HashMap<String, Value> = 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<Value>, #[serde(skip)] MiddlewareArray);
|
||||||
|
|
||||||
|
impl Array {
|
||||||
|
pub fn new(values: Vec<Value>) -> Result<Self> {
|
||||||
|
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<Value> {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Array {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let values: Vec<Value> = Vec::deserialize(deserializer)?;
|
||||||
|
Array::new(values).map_err(serde::de::Error::custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,31 +1,34 @@
|
||||||
//! The frontend includes the user-level abstractions and user-friendly types to define and work
|
//! The frontend includes the user-level abstractions and user-friendly types to define and work
|
||||||
//! with Pods.
|
//! 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 anyhow::{anyhow, Error, Result};
|
||||||
|
use containers::{Array, Dictionary, Set};
|
||||||
use env_logger;
|
use env_logger;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use log::error;
|
use log::error;
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::convert::From;
|
use std::convert::From;
|
||||||
use std::{fmt, hash as h};
|
use std::{fmt, hash as h};
|
||||||
|
|
||||||
use crate::middleware::{
|
pub mod containers;
|
||||||
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};
|
|
||||||
|
|
||||||
mod custom;
|
mod custom;
|
||||||
mod operation;
|
mod operation;
|
||||||
|
mod serialization;
|
||||||
mod statement;
|
mod statement;
|
||||||
pub use custom::*;
|
pub use custom::*;
|
||||||
pub use operation::*;
|
pub use operation::*;
|
||||||
pub use statement::*;
|
pub use statement::*;
|
||||||
|
|
||||||
/// This type is just for presentation purposes.
|
/// 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 {
|
pub enum PodClass {
|
||||||
#[default]
|
#[default]
|
||||||
Signed,
|
Signed,
|
||||||
|
|
@ -33,18 +36,52 @@ pub enum PodClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
// An Origin, which represents a reference to an ancestor POD.
|
// An Origin, which represents a reference to an ancestor POD.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, h::Hash, Default)]
|
#[derive(Clone, Debug, PartialEq, Eq, h::Hash, Default, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct Origin(pub PodClass, pub PodId);
|
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 {
|
pub enum Value {
|
||||||
String(String),
|
// Serde cares about the order of the enum variants, with untagged variants
|
||||||
Int(i64),
|
// appearing at the end.
|
||||||
Bool(bool),
|
// Variants without "untagged" will be serialized as "tagged" values by
|
||||||
Dictionary(Dictionary),
|
// 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),
|
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),
|
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 {
|
impl From<&str> for Value {
|
||||||
|
|
@ -71,9 +108,9 @@ impl From<&Value> for middleware::Value {
|
||||||
Value::String(s) => hash_str(s).value(),
|
Value::String(s) => hash_str(s).value(),
|
||||||
Value::Int(v) => middleware::Value::from(*v),
|
Value::Int(v) => middleware::Value::from(*v),
|
||||||
Value::Bool(b) => middleware::Value::from(*b as i64),
|
Value::Bool(b) => middleware::Value::from(*b as i64),
|
||||||
Value::Dictionary(d) => d.commitment().value(),
|
Value::Dictionary(d) => d.middleware_dict().commitment().value(),
|
||||||
Value::Set(s) => s.commitment().value(),
|
Value::Set(s) => s.middleware_set().commitment().value(),
|
||||||
Value::Array(a) => a.commitment().value(),
|
Value::Array(a) => a.middleware_array().commitment().value(),
|
||||||
Value::Raw(v) => *v,
|
Value::Raw(v) => *v,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -102,9 +139,9 @@ impl fmt::Display for Value {
|
||||||
Value::String(s) => write!(f, "\"{}\"", s),
|
Value::String(s) => write!(f, "\"{}\"", s),
|
||||||
Value::Int(v) => write!(f, "{}", v),
|
Value::Int(v) => write!(f, "{}", v),
|
||||||
Value::Bool(b) => write!(f, "{}", b),
|
Value::Bool(b) => write!(f, "{}", b),
|
||||||
Value::Dictionary(d) => write!(f, "dict:{}", d.commitment()),
|
Value::Dictionary(d) => write!(f, "dict:{}", d.middleware_dict().commitment()),
|
||||||
Value::Set(s) => write!(f, "set:{}", s.commitment()),
|
Value::Set(s) => write!(f, "set:{}", s.middleware_set().commitment()),
|
||||||
Value::Array(a) => write!(f, "arr:{}", a.commitment()),
|
Value::Array(a) => write!(f, "arr:{}", a.middleware_array().commitment()),
|
||||||
Value::Raw(v) => write!(f, "{}", v),
|
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
|
/// SignedPod is a wrapper on top of backend::SignedPod, which additionally stores the
|
||||||
/// string<-->hash relation of the keys.
|
/// string<-->hash relation of the keys.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(try_from = "SignedPodHelper", into = "SignedPodHelper")]
|
||||||
pub struct SignedPod {
|
pub struct SignedPod {
|
||||||
pub pod: Box<dyn middleware::Pod>,
|
pub pod: Box<dyn middleware::Pod>,
|
||||||
/// Key-value pairs as represented in the frontend. These should
|
/// Key-value pairs as represented in the frontend. These should
|
||||||
/// correspond to the entries of `pod.kvs()` after hashing and
|
/// correspond to the entries of `pod.kvs()` after hashing and
|
||||||
/// replacing each key with its corresponding anchored key.
|
/// replacing each key with its corresponding anchored key.
|
||||||
|
#[serde(serialize_with = "ordered_map")]
|
||||||
pub kvs: HashMap<String, Value>,
|
pub kvs: HashMap<String, Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -208,7 +247,7 @@ impl SignedPod {
|
||||||
self.pod.id()
|
self.pod.id()
|
||||||
}
|
}
|
||||||
pub fn origin(&self) -> Origin {
|
pub fn origin(&self) -> Origin {
|
||||||
Origin(PodClass::Signed, self.id())
|
Origin::new(PodClass::Signed, self.id())
|
||||||
}
|
}
|
||||||
pub fn verify(&self) -> bool {
|
pub fn verify(&self) -> bool {
|
||||||
self.pod.verify()
|
self.pod.verify()
|
||||||
|
|
@ -222,12 +261,21 @@ impl SignedPod {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, h::Hash)]
|
#[derive(Clone, Debug, PartialEq, Eq, h::Hash, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct AnchoredKey(pub Origin, pub String);
|
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 {
|
impl From<AnchoredKey> for middleware::AnchoredKey {
|
||||||
fn from(ak: AnchoredKey) -> Self {
|
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.
|
// Add key-hash and POD ID-class correspondences to tables.
|
||||||
pod.public_statements
|
pod.public_statements
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|s| &s.1)
|
.flat_map(|s| &s.args)
|
||||||
.flat_map(|arg| match arg {
|
.flat_map(|arg| match arg {
|
||||||
StatementArg::Key(AnchoredKey(Origin(pod_class, pod_id), key)) => {
|
StatementArg::Key(AnchoredKey {
|
||||||
Some((*pod_id, pod_class.clone(), hash_str(key), key.clone()))
|
origin: Origin { pod_class, pod_id },
|
||||||
}
|
key,
|
||||||
|
}) => Some((*pod_id, pod_class.clone(), hash_str(key), key.clone())),
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
.for_each(|(pod_id, pod_class, hash, key)| {
|
.for_each(|(pod_id, pod_class, hash, key)| {
|
||||||
|
|
@ -324,8 +373,8 @@ impl MainPodBuilder {
|
||||||
for arg in args.iter_mut() {
|
for arg in args.iter_mut() {
|
||||||
match arg {
|
match arg {
|
||||||
OperationArg::Statement(s) => {
|
OperationArg::Statement(s) => {
|
||||||
if s.0 == Predicate::Native(NativePredicate::ValueOf) {
|
if s.predicate == Predicate::Native(NativePredicate::ValueOf) {
|
||||||
st_args.push(s.1[0].clone())
|
st_args.push(s.args[0].clone())
|
||||||
} else {
|
} else {
|
||||||
panic!("Invalid statement argument.");
|
panic!("Invalid statement argument.");
|
||||||
}
|
}
|
||||||
|
|
@ -334,11 +383,11 @@ impl MainPodBuilder {
|
||||||
OperationArg::Literal(v) => {
|
OperationArg::Literal(v) => {
|
||||||
let value_of_st = self.literal(public, v)?;
|
let value_of_st = self.literal(public, v)?;
|
||||||
*arg = OperationArg::Statement(value_of_st.clone());
|
*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) => {
|
OperationArg::Entry(k, v) => {
|
||||||
st_args.push(StatementArg::Key(AnchoredKey(
|
st_args.push(StatementArg::Key(AnchoredKey::new(
|
||||||
Origin(PodClass::Main, SELF),
|
Origin::new(PodClass::Main, SELF),
|
||||||
k.clone(),
|
k.clone(),
|
||||||
)));
|
)));
|
||||||
st_args.push(StatementArg::Literal(v.clone()))
|
st_args.push(StatementArg::Literal(v.clone()))
|
||||||
|
|
@ -366,7 +415,7 @@ impl MainPodBuilder {
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
// We are dealing with a copy here.
|
// We are dealing with a copy here.
|
||||||
match (&args).get(0) {
|
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)),
|
_ => Err(anyhow!("Invalid arguments to copy operation: {:?}", args)),
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
@ -376,7 +425,7 @@ impl MainPodBuilder {
|
||||||
None => vec![],
|
None => vec![],
|
||||||
NewEntry => self.op_args_entries(public, args)?,
|
NewEntry => self.op_args_entries(public, args)?,
|
||||||
CopyStatement => match &args[0] {
|
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));
|
return Err(anyhow!("Invalid arguments to copy operation: {}", op));
|
||||||
}
|
}
|
||||||
|
|
@ -388,14 +437,14 @@ impl MainPodBuilder {
|
||||||
TransitiveEqualFromStatements => {
|
TransitiveEqualFromStatements => {
|
||||||
match (args[0].clone(), args[1].clone()) {
|
match (args[0].clone(), args[1].clone()) {
|
||||||
(
|
(
|
||||||
OperationArg::Statement(Statement(
|
OperationArg::Statement(Statement {
|
||||||
Predicate::Native(NativePredicate::Equal),
|
predicate: Predicate::Native(NativePredicate::Equal),
|
||||||
st0_args,
|
args: st0_args,
|
||||||
)),
|
}),
|
||||||
OperationArg::Statement(Statement(
|
OperationArg::Statement(Statement {
|
||||||
Predicate::Native(NativePredicate::Equal),
|
predicate: Predicate::Native(NativePredicate::Equal),
|
||||||
st1_args,
|
args: st1_args,
|
||||||
)),
|
}),
|
||||||
) => {
|
) => {
|
||||||
// st_args0 == vec![ak0, ak1]
|
// st_args0 == vec![ak0, ak1]
|
||||||
// st_args1 == vec![ak1, ak2]
|
// st_args1 == vec![ak1, ak2]
|
||||||
|
|
@ -416,10 +465,10 @@ impl MainPodBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GtToNotEqual => match args[0].clone() {
|
GtToNotEqual => match args[0].clone() {
|
||||||
OperationArg::Statement(Statement(
|
OperationArg::Statement(Statement {
|
||||||
Predicate::Native(NativePredicate::Gt),
|
predicate: Predicate::Native(NativePredicate::Gt),
|
||||||
st_args,
|
args: st_args,
|
||||||
)) => {
|
}) => {
|
||||||
vec![st_args[0].clone()]
|
vec![st_args[0].clone()]
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
|
@ -427,10 +476,10 @@ impl MainPodBuilder {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
LtToNotEqual => match args[0].clone() {
|
LtToNotEqual => match args[0].clone() {
|
||||||
OperationArg::Statement(Statement(
|
OperationArg::Statement(Statement {
|
||||||
Predicate::Native(NativePredicate::Lt),
|
predicate: Predicate::Native(NativePredicate::Lt),
|
||||||
st_args,
|
args: st_args,
|
||||||
)) => {
|
}) => {
|
||||||
vec![st_args[0].clone()]
|
vec![st_args[0].clone()]
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
|
@ -441,18 +490,18 @@ impl MainPodBuilder {
|
||||||
NotContainsFromEntries => self.op_args_entries(public, args)?,
|
NotContainsFromEntries => self.op_args_entries(public, args)?,
|
||||||
SumOf => match (args[0].clone(), args[1].clone(), args[2].clone()) {
|
SumOf => match (args[0].clone(), args[1].clone(), args[2].clone()) {
|
||||||
(
|
(
|
||||||
OperationArg::Statement(Statement(
|
OperationArg::Statement(Statement {
|
||||||
Predicate::Native(NativePredicate::ValueOf),
|
predicate: Predicate::Native(NativePredicate::ValueOf),
|
||||||
st0_args,
|
args: st0_args,
|
||||||
)),
|
}),
|
||||||
OperationArg::Statement(Statement(
|
OperationArg::Statement(Statement {
|
||||||
Predicate::Native(NativePredicate::ValueOf),
|
predicate: Predicate::Native(NativePredicate::ValueOf),
|
||||||
st1_args,
|
args: st1_args,
|
||||||
)),
|
}),
|
||||||
OperationArg::Statement(Statement(
|
OperationArg::Statement(Statement {
|
||||||
Predicate::Native(NativePredicate::ValueOf),
|
predicate: Predicate::Native(NativePredicate::ValueOf),
|
||||||
st2_args,
|
args: st2_args,
|
||||||
)),
|
}),
|
||||||
) => {
|
) => {
|
||||||
let st_args: Vec<StatementArg> = match (
|
let st_args: Vec<StatementArg> = match (
|
||||||
st0_args[1].clone(),
|
st0_args[1].clone(),
|
||||||
|
|
@ -489,18 +538,18 @@ impl MainPodBuilder {
|
||||||
},
|
},
|
||||||
ProductOf => match (args[0].clone(), args[1].clone(), args[2].clone()) {
|
ProductOf => match (args[0].clone(), args[1].clone(), args[2].clone()) {
|
||||||
(
|
(
|
||||||
OperationArg::Statement(Statement(
|
OperationArg::Statement(Statement {
|
||||||
Predicate::Native(NativePredicate::ValueOf),
|
predicate: Predicate::Native(NativePredicate::ValueOf),
|
||||||
st0_args,
|
args: st0_args,
|
||||||
)),
|
}),
|
||||||
OperationArg::Statement(Statement(
|
OperationArg::Statement(Statement {
|
||||||
Predicate::Native(NativePredicate::ValueOf),
|
predicate: Predicate::Native(NativePredicate::ValueOf),
|
||||||
st1_args,
|
args: st1_args,
|
||||||
)),
|
}),
|
||||||
OperationArg::Statement(Statement(
|
OperationArg::Statement(Statement {
|
||||||
Predicate::Native(NativePredicate::ValueOf),
|
predicate: Predicate::Native(NativePredicate::ValueOf),
|
||||||
st2_args,
|
args: st2_args,
|
||||||
)),
|
}),
|
||||||
) => {
|
) => {
|
||||||
let st_args: Vec<StatementArg> = match (
|
let st_args: Vec<StatementArg> = match (
|
||||||
st0_args[1].clone(),
|
st0_args[1].clone(),
|
||||||
|
|
@ -539,18 +588,18 @@ impl MainPodBuilder {
|
||||||
},
|
},
|
||||||
MaxOf => match (args[0].clone(), args[1].clone(), args[2].clone()) {
|
MaxOf => match (args[0].clone(), args[1].clone(), args[2].clone()) {
|
||||||
(
|
(
|
||||||
OperationArg::Statement(Statement(
|
OperationArg::Statement(Statement {
|
||||||
Predicate::Native(NativePredicate::ValueOf),
|
predicate: Predicate::Native(NativePredicate::ValueOf),
|
||||||
st0_args,
|
args: st0_args,
|
||||||
)),
|
}),
|
||||||
OperationArg::Statement(Statement(
|
OperationArg::Statement(Statement {
|
||||||
Predicate::Native(NativePredicate::ValueOf),
|
predicate: Predicate::Native(NativePredicate::ValueOf),
|
||||||
st1_args,
|
args: st1_args,
|
||||||
)),
|
}),
|
||||||
OperationArg::Statement(Statement(
|
OperationArg::Statement(Statement {
|
||||||
Predicate::Native(NativePredicate::ValueOf),
|
predicate: Predicate::Native(NativePredicate::ValueOf),
|
||||||
st2_args,
|
args: st2_args,
|
||||||
)),
|
}),
|
||||||
) => {
|
) => {
|
||||||
let st_args: Vec<StatementArg> = match (
|
let st_args: Vec<StatementArg> = match (
|
||||||
st0_args[1].clone(),
|
st0_args[1].clone(),
|
||||||
|
|
@ -610,8 +659,8 @@ impl MainPodBuilder {
|
||||||
output_arg_values
|
output_arg_values
|
||||||
.chunks(2)
|
.chunks(2)
|
||||||
.map(|chunk| {
|
.map(|chunk| {
|
||||||
Ok(StatementArg::Key(AnchoredKey(
|
Ok(StatementArg::Key(AnchoredKey::new(
|
||||||
Origin(
|
Origin::new(
|
||||||
self.pod_class_table
|
self.pod_class_table
|
||||||
.get(&PodId(chunk[0].into()))
|
.get(&PodId(chunk[0].into()))
|
||||||
.cloned()
|
.cloned()
|
||||||
|
|
@ -627,15 +676,15 @@ impl MainPodBuilder {
|
||||||
.collect::<Result<Vec<_>>>()?
|
.collect::<Result<Vec<_>>>()?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let st = Statement(pred, st_args);
|
let st = Statement::new(pred, st_args);
|
||||||
self.operations.push(op);
|
self.operations.push(op);
|
||||||
if public {
|
if public {
|
||||||
self.public_statements.push(st.clone());
|
self.public_statements.push(st.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add key-hash pairs in statement to table.
|
// Add key-hash pairs in statement to table.
|
||||||
st.1.iter().for_each(|arg| {
|
st.args.iter().for_each(|arg| {
|
||||||
if let StatementArg::Key(AnchoredKey(_, key)) = arg {
|
if let StatementArg::Key(AnchoredKey { origin: _, key }) = arg {
|
||||||
self.key_table.insert(hash_str(key), key.clone());
|
self.key_table.insert(hash_str(key), key.clone());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -702,16 +751,16 @@ impl MainPodBuilder {
|
||||||
crate::middleware::Statement::ValueOf(
|
crate::middleware::Statement::ValueOf(
|
||||||
crate::middleware::AnchoredKey(id, key),
|
crate::middleware::AnchoredKey(id, key),
|
||||||
value,
|
value,
|
||||||
) if id == pod_id && key == type_key_hash => Some(Statement(
|
) if id == pod_id && key == type_key_hash => Some(Statement {
|
||||||
Predicate::Native(NativePredicate::ValueOf),
|
predicate: Predicate::Native(NativePredicate::ValueOf),
|
||||||
vec![
|
args: vec![
|
||||||
StatementArg::Key(AnchoredKey(
|
StatementArg::Key(AnchoredKey::new(
|
||||||
Origin(PodClass::Main, pod_id),
|
Origin::new(PodClass::Main, pod_id),
|
||||||
KEY_TYPE.to_string(),
|
KEY_TYPE.to_string(),
|
||||||
)),
|
)),
|
||||||
StatementArg::Literal(value.into()),
|
StatementArg::Literal(value.into()),
|
||||||
],
|
],
|
||||||
)),
|
}),
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
.ok_or(anyhow!("Missing POD type information in POD: {:?}", pod))?;
|
.ok_or(anyhow!("Missing POD type information in POD: {:?}", pod))?;
|
||||||
|
|
@ -720,18 +769,25 @@ impl MainPodBuilder {
|
||||||
let public_statements = [type_statement]
|
let public_statements = [type_statement]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(self.public_statements.clone().into_iter().map(|s| {
|
.chain(self.public_statements.clone().into_iter().map(|s| {
|
||||||
let s_type = s.0;
|
let s_type = s.predicate;
|
||||||
let s_args = s
|
let s_args = s
|
||||||
.1
|
.args
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|arg| match arg {
|
.map(|arg| match arg {
|
||||||
StatementArg::Key(AnchoredKey(Origin(class, id), key)) if id == SELF => {
|
StatementArg::Key(AnchoredKey {
|
||||||
StatementArg::Key(AnchoredKey(Origin(class, pod_id), key))
|
origin:
|
||||||
|
Origin {
|
||||||
|
pod_class: class,
|
||||||
|
pod_id: id,
|
||||||
|
},
|
||||||
|
key,
|
||||||
|
}) if id == SELF => {
|
||||||
|
StatementArg::Key(AnchoredKey::new(Origin::new(class, pod_id), key))
|
||||||
}
|
}
|
||||||
_ => arg,
|
_ => arg,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
Statement(s_type, s_args)
|
Statement::new(s_type, s_args)
|
||||||
}))
|
}))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
|
@ -742,7 +798,8 @@ impl MainPodBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(try_from = "MainPodHelper", into = "MainPodHelper")]
|
||||||
pub struct MainPod {
|
pub struct MainPod {
|
||||||
pub pod: Box<dyn middleware::Pod>,
|
pub pod: Box<dyn middleware::Pod>,
|
||||||
pub public_statements: Vec<Statement>,
|
pub public_statements: Vec<Statement>,
|
||||||
|
|
@ -769,7 +826,7 @@ impl MainPod {
|
||||||
self.pod.id()
|
self.pod.id()
|
||||||
}
|
}
|
||||||
pub fn origin(&self) -> Origin {
|
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 params = Params::default();
|
||||||
let mut builder = MainPodBuilder::new(¶ms);
|
let mut builder = MainPodBuilder::new(¶ms);
|
||||||
builder.insert((
|
builder.insert((
|
||||||
Statement(
|
Statement::new(
|
||||||
Predicate::Native(NativePredicate::ValueOf),
|
Predicate::Native(NativePredicate::ValueOf),
|
||||||
vec![
|
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)),
|
StatementArg::Literal(Value::Int(3)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Operation(OperationType::Native(NativeOperation::NewEntry), vec![]),
|
Operation(OperationType::Native(NativeOperation::NewEntry), vec![]),
|
||||||
));
|
));
|
||||||
builder.insert((
|
builder.insert((
|
||||||
Statement(
|
Statement::new(
|
||||||
Predicate::Native(NativePredicate::ValueOf),
|
Predicate::Native(NativePredicate::ValueOf),
|
||||||
vec![
|
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)),
|
StatementArg::Literal(Value::Int(28)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -1194,19 +1257,25 @@ pub mod tests {
|
||||||
// right now the mock prover catches this when it calls compile()
|
// right now the mock prover catches this when it calls compile()
|
||||||
let params = Params::default();
|
let params = Params::default();
|
||||||
let mut builder = MainPodBuilder::new(¶ms);
|
let mut builder = MainPodBuilder::new(¶ms);
|
||||||
let self_a = AnchoredKey(Origin(PodClass::Main, SELF), "a".into());
|
let self_a = AnchoredKey::new(Origin::new(PodClass::Main, SELF), "a".into());
|
||||||
let self_b = AnchoredKey(Origin(PodClass::Main, SELF), "b".into());
|
let self_b = AnchoredKey::new(Origin::new(PodClass::Main, SELF), "b".into());
|
||||||
let value_of_a = Statement(
|
let value_of_a = Statement::new(
|
||||||
Predicate::Native(NativePredicate::ValueOf),
|
Predicate::Native(NativePredicate::ValueOf),
|
||||||
vec![
|
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)),
|
StatementArg::Literal(Value::Int(3)),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
let value_of_b = Statement(
|
let value_of_b = Statement::new(
|
||||||
Predicate::Native(NativePredicate::ValueOf),
|
Predicate::Native(NativePredicate::ValueOf),
|
||||||
vec![
|
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)),
|
StatementArg::Literal(Value::Int(27)),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
@ -1220,7 +1289,7 @@ pub mod tests {
|
||||||
Operation(OperationType::Native(NativeOperation::NewEntry), vec![]),
|
Operation(OperationType::Native(NativeOperation::NewEntry), vec![]),
|
||||||
));
|
));
|
||||||
builder.insert((
|
builder.insert((
|
||||||
Statement(
|
Statement::new(
|
||||||
Predicate::Native(NativePredicate::Equal),
|
Predicate::Native(NativePredicate::Equal),
|
||||||
vec![StatementArg::Key(self_a), StatementArg::Key(self_b)],
|
vec![StatementArg::Key(self_a), StatementArg::Key(self_b)],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
294
src/frontend/serialization.rs
Normal file
294
src/frontend/serialization.rs
Normal file
|
|
@ -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<String, Value>,
|
||||||
|
proof: String,
|
||||||
|
pod_class: String,
|
||||||
|
pod_type: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<SignedPodHelper> for SignedPod {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_from(helper: SignedPodHelper) -> Result<SignedPod, Self::Error> {
|
||||||
|
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<SignedPod> 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<Statement>,
|
||||||
|
proof: String,
|
||||||
|
pod_class: String,
|
||||||
|
pod_type: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<MainPodHelper> for MainPod {
|
||||||
|
type Error = anyhow::Error; // or you can create a custom error type
|
||||||
|
|
||||||
|
fn try_from(helper: MainPodHelper) -> Result<Self, Self::Error> {
|
||||||
|
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<MainPod> 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<S>(value: &i64, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_str(&value.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize_i64<'de, D>(deserializer: D) -> Result<i64, D::Error>
|
||||||
|
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<S, K: Ord + Serialize, V: Serialize>(
|
||||||
|
value: &HashMap<K, V>,
|
||||||
|
serializer: S,
|
||||||
|
) -> Result<S::Ok, S::Error>
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
use anyhow::{anyhow, Result};
|
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
use super::{AnchoredKey, SignedPod, Value};
|
use super::{AnchoredKey, SignedPod, Value};
|
||||||
use crate::middleware::{self, NativePredicate, Predicate};
|
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 {
|
pub enum StatementArg {
|
||||||
Literal(Value),
|
Literal(Value),
|
||||||
Key(AnchoredKey),
|
Key(AnchoredKey),
|
||||||
|
|
@ -14,13 +15,22 @@ impl fmt::Display for StatementArg {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Literal(v) => write!(f, "{}", v),
|
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)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct Statement(pub Predicate, pub Vec<StatementArg>);
|
pub struct Statement {
|
||||||
|
pub predicate: Predicate,
|
||||||
|
pub args: Vec<StatementArg>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Statement {
|
||||||
|
pub fn new(predicate: Predicate, args: Vec<StatementArg>) -> Self {
|
||||||
|
Self { predicate, args }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<(&SignedPod, &str)> for Statement {
|
impl From<(&SignedPod, &str)> for Statement {
|
||||||
fn from((pod, key): (&SignedPod, &str)) -> Self {
|
fn from((pod, key): (&SignedPod, &str)) -> Self {
|
||||||
|
|
@ -30,13 +40,13 @@ impl From<(&SignedPod, &str)> for Statement {
|
||||||
.get(key)
|
.get(key)
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or_else(|| panic!("Key {} is not present in POD: {}", key, pod));
|
.unwrap_or_else(|| panic!("Key {} is not present in POD: {}", key, pod));
|
||||||
Statement(
|
Statement {
|
||||||
Predicate::Native(NativePredicate::ValueOf),
|
predicate: Predicate::Native(NativePredicate::ValueOf),
|
||||||
vec![
|
args: vec![
|
||||||
StatementArg::Key(AnchoredKey(pod.origin(), key.to_string())),
|
StatementArg::Key(AnchoredKey::new(pod.origin(), key.to_string())),
|
||||||
StatementArg::Literal(value),
|
StatementArg::Literal(value),
|
||||||
],
|
],
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -47,11 +57,11 @@ impl TryFrom<Statement> for middleware::Statement {
|
||||||
type NP = NativePredicate;
|
type NP = NativePredicate;
|
||||||
type SA = StatementArg;
|
type SA = StatementArg;
|
||||||
let args = (
|
let args = (
|
||||||
s.1.first().cloned(),
|
s.args.first().cloned(),
|
||||||
s.1.get(1).cloned(),
|
s.args.get(1).cloned(),
|
||||||
s.1.get(2).cloned(),
|
s.args.get(2).cloned(),
|
||||||
);
|
);
|
||||||
Ok(match &s.0 {
|
Ok(match &s.predicate {
|
||||||
Predicate::Native(np) => match (np, args) {
|
Predicate::Native(np) => match (np, args) {
|
||||||
(NP::None, (None, None, None)) => MS::None,
|
(NP::None, (None, None, None)) => MS::None,
|
||||||
(NP::ValueOf, (Some(SA::Key(ak)), Some(StatementArg::Literal(v)), None)) => {
|
(NP::ValueOf, (Some(SA::Key(ak)), Some(StatementArg::Literal(v)), None)) => {
|
||||||
|
|
@ -88,7 +98,8 @@ impl TryFrom<Statement> for middleware::Statement {
|
||||||
},
|
},
|
||||||
Predicate::Custom(cpr) => MS::Custom(
|
Predicate::Custom(cpr) => MS::Custom(
|
||||||
cpr.clone(),
|
cpr.clone(),
|
||||||
s.1.iter()
|
s.args
|
||||||
|
.iter()
|
||||||
.map(|arg| match arg {
|
.map(|arg| match arg {
|
||||||
StatementArg::Key(ak) => Ok(ak.clone().into()),
|
StatementArg::Key(ak) => Ok(ak.clone().into()),
|
||||||
_ => Err(anyhow!("Invalid statement arg: {}", arg)),
|
_ => Err(anyhow!("Invalid statement arg: {}", arg)),
|
||||||
|
|
@ -102,8 +113,8 @@ impl TryFrom<Statement> for middleware::Statement {
|
||||||
|
|
||||||
impl fmt::Display for Statement {
|
impl fmt::Display for Statement {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "{:?} ", self.0)?;
|
write!(f, "{:?} ", self.predicate)?;
|
||||||
for (i, arg) in self.1.iter().enumerate() {
|
for (i, arg) in self.args.iter().enumerate() {
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
write!(f, " ")?;
|
write!(f, " ")?;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,19 @@ use std::{fmt, hash as h, iter, iter::zip};
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use plonky2::field::types::Field;
|
use plonky2::field::types::Field;
|
||||||
|
use schemars::JsonSchema;
|
||||||
use crate::backends::plonky2::basetypes::HASH_SIZE;
|
|
||||||
use crate::util::hashmap_insert_no_dupe;
|
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
hash_fields, AnchoredKey, Hash, NativePredicate, Params, PodId, Statement, StatementArg,
|
hash_fields, AnchoredKey, Hash, NativePredicate, Params, PodId, Statement, StatementArg,
|
||||||
ToFields, Value, F,
|
ToFields, Value, F,
|
||||||
};
|
};
|
||||||
|
use crate::backends::plonky2::basetypes::HASH_SIZE;
|
||||||
|
use crate::util::hashmap_insert_no_dupe;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
// BEGIN Custom 1b
|
// BEGIN Custom 1b
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, h::Hash)]
|
#[derive(Clone, Debug, PartialEq, Eq, h::Hash, Serialize, Deserialize, JsonSchema)]
|
||||||
pub enum HashOrWildcard {
|
pub enum HashOrWildcard {
|
||||||
Hash(Hash),
|
Hash(Hash),
|
||||||
Wildcard(usize),
|
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 {
|
pub enum StatementTmplArg {
|
||||||
None,
|
None,
|
||||||
Literal(Value),
|
Literal(Value),
|
||||||
|
|
@ -145,7 +146,7 @@ impl fmt::Display for StatementTmplArg {
|
||||||
// END
|
// END
|
||||||
|
|
||||||
/// Statement Template for a Custom Predicate
|
/// 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<StatementTmplArg>);
|
pub struct StatementTmpl(pub Predicate, pub Vec<StatementTmplArg>);
|
||||||
|
|
||||||
impl StatementTmpl {
|
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 {
|
pub struct CustomPredicate {
|
||||||
/// NOTE: fields are not public (outside of crate) to enforce the struct instantiation through
|
/// NOTE: fields are not public (outside of crate) to enforce the struct instantiation through
|
||||||
/// the `::and/or` methods, which performs checks on the values.
|
/// 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 struct CustomPredicateBatch {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub predicates: Vec<CustomPredicate>,
|
pub predicates: Vec<CustomPredicate>,
|
||||||
|
|
@ -324,7 +325,7 @@ impl CustomPredicateBatch {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct CustomPredicateRef(pub Arc<CustomPredicateBatch>, pub usize);
|
pub struct CustomPredicateRef(pub Arc<CustomPredicateBatch>, pub usize);
|
||||||
|
|
||||||
impl CustomPredicateRef {
|
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 {
|
pub enum Predicate {
|
||||||
Native(NativePredicate),
|
Native(NativePredicate),
|
||||||
BatchSelf(usize),
|
BatchSelf(usize),
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,17 @@ mod basetypes;
|
||||||
pub mod containers;
|
pub mod containers;
|
||||||
mod custom;
|
mod custom;
|
||||||
mod operation;
|
mod operation;
|
||||||
|
pub mod serialization;
|
||||||
mod statement;
|
mod statement;
|
||||||
pub use basetypes::*;
|
pub use basetypes::*;
|
||||||
pub use custom::*;
|
pub use custom::*;
|
||||||
pub use operation::*;
|
pub use operation::*;
|
||||||
|
use schemars::JsonSchema;
|
||||||
pub use statement::*;
|
pub use statement::*;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use dyn_clone::DynClone;
|
use dyn_clone::DynClone;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
@ -32,7 +35,7 @@ impl fmt::Display for PodId {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// AnchoredKey is a tuple containing (OriginId: PodId, key: Hash)
|
/// 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);
|
pub struct AnchoredKey(pub PodId, pub Hash);
|
||||||
|
|
||||||
impl AnchoredKey {
|
impl AnchoredKey {
|
||||||
|
|
@ -54,7 +57,7 @@ impl fmt::Display for AnchoredKey {
|
||||||
/// An entry consists of a key-value pair.
|
/// An entry consists of a key-value pair.
|
||||||
pub type Entry = (String, Value);
|
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);
|
pub struct PodId(pub Hash);
|
||||||
|
|
||||||
impl ToFields for PodId {
|
impl ToFields for PodId {
|
||||||
|
|
@ -77,7 +80,7 @@ impl From<PodType> for Value {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct Params {
|
pub struct Params {
|
||||||
pub max_input_signed_pods: usize,
|
pub max_input_signed_pods: usize,
|
||||||
pub max_input_main_pods: usize,
|
pub max_input_main_pods: usize,
|
||||||
|
|
@ -166,6 +169,15 @@ pub trait Pod: fmt::Debug + DynClone {
|
||||||
}
|
}
|
||||||
// Used for downcasting
|
// Used for downcasting
|
||||||
fn into_any(self: Box<Self>) -> Box<dyn Any>;
|
fn into_any(self: Box<Self>) -> Box<dyn Any>;
|
||||||
|
// 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<dyn SignedPod>
|
// impl Clone for Box<dyn SignedPod>
|
||||||
|
|
@ -193,6 +205,9 @@ impl Pod for NonePod {
|
||||||
fn into_any(self: Box<Self>) -> Box<dyn Any> {
|
fn into_any(self: Box<Self>) -> Box<dyn Any> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
fn serialized_proof(&self) -> String {
|
||||||
|
"".to_string()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use log::error;
|
use log::error;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
use super::{CustomPredicateRef, NativePredicate, Statement, StatementArg};
|
use super::{CustomPredicateRef, NativePredicate, Statement, StatementArg};
|
||||||
use crate::middleware::{AnchoredKey, Params, Predicate, Value, SELF};
|
use crate::middleware::{AnchoredKey, Params, Predicate, Value, SELF};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum OperationType {
|
pub enum OperationType {
|
||||||
Native(NativeOperation),
|
Native(NativeOperation),
|
||||||
Custom(CustomPredicateRef),
|
Custom(CustomPredicateRef),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum NativeOperation {
|
pub enum NativeOperation {
|
||||||
None = 0,
|
None = 0,
|
||||||
NewEntry = 1,
|
NewEntry = 1,
|
||||||
|
|
@ -91,7 +91,7 @@ pub enum Operation {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Operation {
|
impl Operation {
|
||||||
pub fn code(&self) -> OperationType {
|
pub fn predicate(&self) -> OperationType {
|
||||||
type OT = OperationType;
|
type OT = OperationType;
|
||||||
use NativeOperation::*;
|
use NativeOperation::*;
|
||||||
match self {
|
match self {
|
||||||
|
|
@ -178,7 +178,7 @@ impl Operation {
|
||||||
/// The outer Result is error handling
|
/// The outer Result is error handling
|
||||||
pub fn output_statement(&self) -> Result<Option<Statement>> {
|
pub fn output_statement(&self) -> Result<Option<Statement>> {
|
||||||
use Statement::*;
|
use Statement::*;
|
||||||
let pred: Option<Predicate> = self.code().output_predicate();
|
let pred: Option<Predicate> = self.predicate().output_predicate();
|
||||||
|
|
||||||
let st_args: Option<Vec<StatementArg>> = match self {
|
let st_args: Option<Vec<StatementArg>> = match self {
|
||||||
Self::None => Some(vec![]),
|
Self::None => Some(vec![]),
|
||||||
|
|
@ -404,7 +404,7 @@ impl Operation {
|
||||||
impl fmt::Display for Operation {
|
impl fmt::Display for Operation {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
writeln!(f, "middleware::Operation:")?;
|
writeln!(f, "middleware::Operation:")?;
|
||||||
writeln!(f, " {:?} ", self.code())?;
|
writeln!(f, " {:?} ", self.predicate())?;
|
||||||
for arg in self.args().iter() {
|
for arg in self.args().iter() {
|
||||||
writeln!(f, " {}", arg)?;
|
writeln!(f, " {}", arg)?;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
69
src/middleware/serialization.rs
Normal file
69
src/middleware/serialization.rs
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
use plonky2::field::types::Field;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use super::{F, HASH_SIZE, VALUE_SIZE};
|
||||||
|
|
||||||
|
fn serialize_field_tuple<S, const N: usize>(
|
||||||
|
value: &[F; N],
|
||||||
|
serializer: S,
|
||||||
|
) -> Result<S::Ok, S::Error>
|
||||||
|
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<S>(value: &[F; HASH_SIZE], serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
serialize_field_tuple::<S, HASH_SIZE>(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::<D, HASH_SIZE>(deserializer)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serialize_value_tuple<S>(value: &[F; VALUE_SIZE], serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
serialize_field_tuple::<S, VALUE_SIZE>(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::<D, VALUE_SIZE>(deserializer)
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use plonky2::field::types::Field;
|
use plonky2::field::types::Field;
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{fmt, iter};
|
use std::{fmt, iter};
|
||||||
use strum_macros::FromRepr;
|
use strum_macros::FromRepr;
|
||||||
|
|
||||||
|
|
@ -9,7 +11,7 @@ pub const KEY_SIGNER: &str = "_signer";
|
||||||
pub const KEY_TYPE: &str = "_type";
|
pub const KEY_TYPE: &str = "_type";
|
||||||
pub const STATEMENT_ARG_F_LEN: usize = 8;
|
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 {
|
pub enum NativePredicate {
|
||||||
None = 0,
|
None = 0,
|
||||||
ValueOf = 1,
|
ValueOf = 1,
|
||||||
|
|
@ -203,7 +205,7 @@ impl fmt::Display for Statement {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Statement argument type. Useful for statement decompositions.
|
/// Statement argument type. Useful for statement decompositions.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum StatementArg {
|
pub enum StatementArg {
|
||||||
None,
|
None,
|
||||||
Literal(Value),
|
Literal(Value),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue