Re-implement serialization (#201)
* Serialization tests now pass again * Tidy up and test more edge-cases * Use attributes rather than custom serializer for arrays * Add JSON Schema support * Tests for JSON Schema generation and validation * Add comments * Support custom predicates * Clippy fixes * Make deserialization/constructor functions pub(crate)
This commit is contained in:
parent
26a6b2d143
commit
bf6d8aee8b
17 changed files with 554 additions and 255 deletions
|
|
@ -4,7 +4,6 @@ use std::{collections::HashMap, fmt, hash as h, iter, iter::zip, sync::Arc};
|
|||
use anyhow::{anyhow, Result};
|
||||
use schemars::JsonSchema;
|
||||
|
||||
// use serde::{Deserialize, Serialize};
|
||||
use crate::{
|
||||
frontend::{AnchoredKey, Statement, StatementArg},
|
||||
middleware::{
|
||||
|
|
|
|||
|
|
@ -5,10 +5,8 @@ use std::{collections::HashMap, convert::From, fmt};
|
|||
|
||||
use anyhow::{anyhow, Result};
|
||||
use itertools::Itertools;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// use schemars::JsonSchema;
|
||||
|
||||
// use serde::{Deserialize, Serialize};
|
||||
use crate::middleware::{
|
||||
self, check_st_tmpl, hash_str, AnchoredKey, Key, MainPodInputs, NativeOperation,
|
||||
NativePredicate, OperationAux, OperationType, Params, PodId, PodProver, PodSigner, Predicate,
|
||||
|
|
@ -17,8 +15,10 @@ use crate::middleware::{
|
|||
|
||||
mod custom;
|
||||
mod operation;
|
||||
mod serialization;
|
||||
pub use custom::*;
|
||||
pub use operation::*;
|
||||
use serialization::*;
|
||||
|
||||
/// This type is just for presentation purposes.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
|
|
@ -66,8 +66,8 @@ impl SignedPodBuilder {
|
|||
|
||||
/// SignedPod is a wrapper on top of backend::SignedPod, which additionally stores the
|
||||
/// string<-->hash relation of the keys.
|
||||
#[derive(Debug, Clone)]
|
||||
// #[serde(try_from = "SignedPodHelper", into = "SignedPodHelper")]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(try_from = "SignedPodHelper", into = "SignedPodHelper")]
|
||||
pub struct SignedPod {
|
||||
pub pod: Box<dyn middleware::Pod>,
|
||||
// We store a copy of the key values for quick access
|
||||
|
|
@ -591,8 +591,8 @@ impl MainPodBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
// #[serde(try_from = "MainPodHelper", into = "MainPodHelper")]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(try_from = "MainPodHelper", into = "MainPodHelper")]
|
||||
pub struct MainPod {
|
||||
pub pod: Box<dyn middleware::Pod>,
|
||||
pub public_statements: Vec<Statement>,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
use std::fmt;
|
||||
|
||||
// use serde::{Deserialize, Serialize};
|
||||
use crate::{
|
||||
frontend::SignedPod,
|
||||
middleware::{AnchoredKey, OperationAux, OperationType, Statement, Value},
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
/*
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use schemars::{JsonSchema, Schema};
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
backends::plonky2::mock::{mainpod::MockMainPod, signedpod::MockSignedPod},
|
||||
frontend::{containers::Dictionary, MainPod, SignedPod, Statement, TypedValue},
|
||||
middleware::PodId,
|
||||
frontend::{MainPod, SignedPod, Statement},
|
||||
middleware::{containers::Dictionary, Key, PodId, Value},
|
||||
};
|
||||
|
||||
#[derive(Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[schemars(title = "SignedPod")]
|
||||
pub struct SignedPodHelper {
|
||||
entries: HashMap<String, TypedValue>,
|
||||
entries: HashMap<Key, Value>,
|
||||
proof: String,
|
||||
pod_class: String,
|
||||
pod_type: String,
|
||||
|
|
@ -30,10 +30,8 @@ impl TryFrom<SignedPodHelper> for SignedPod {
|
|||
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);
|
||||
let dict = Dictionary::new(helper.entries.clone())?.clone();
|
||||
let pod = MockSignedPod::new(PodId(dict.commitment()), helper.proof, dict.kvs().clone());
|
||||
|
||||
Ok(SignedPod {
|
||||
pod: Box::new(pod),
|
||||
|
|
@ -55,6 +53,7 @@ impl From<SignedPod> for SignedPodHelper {
|
|||
|
||||
#[derive(Serialize, Deserialize, JsonSchema)]
|
||||
#[schemars(title = "MainPod")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MainPodHelper {
|
||||
public_statements: Vec<Statement>,
|
||||
proof: String,
|
||||
|
|
@ -94,76 +93,29 @@ impl From<MainPod> for MainPodHelper {
|
|||
}
|
||||
}
|
||||
|
||||
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 anyhow::Result;
|
||||
use schemars::generate::SchemaSettings;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use anyhow::Result;
|
||||
// Pretty assertions give nicer diffs between expected and actual values
|
||||
use pretty_assertions::assert_eq;
|
||||
use schemars::schema_for;
|
||||
|
||||
// use schemars::generate::SchemaSettings;
|
||||
use super::*;
|
||||
use crate::{
|
||||
backends::plonky2::mock::{mainpod::MockProver, signedpod::MockSigner},
|
||||
examples::{zu_kyc_pod_builder, zu_kyc_sign_pod_builders},
|
||||
frontend::{
|
||||
containers::{Array, Dictionary, Set},
|
||||
SignedPodBuilder,
|
||||
examples::{
|
||||
eth_dos_pod_builder, eth_friend_signed_pod_builder, zu_kyc_pod_builder,
|
||||
zu_kyc_sign_pod_builders,
|
||||
},
|
||||
frontend::SignedPodBuilder,
|
||||
middleware::{
|
||||
self,
|
||||
containers::{Array, Set},
|
||||
Params, TypedValue,
|
||||
},
|
||||
middleware::{self, Params},
|
||||
};
|
||||
|
||||
#[test]
|
||||
|
|
@ -174,34 +126,32 @@ mod tests {
|
|||
(TypedValue::Int(42), "{\"Int\":\"42\"}"),
|
||||
(TypedValue::Bool(true), "true"),
|
||||
(
|
||||
TypedValue::Array(
|
||||
Array::new(vec![
|
||||
TypedValue::String("foo".to_string()),
|
||||
TypedValue::Bool(false),
|
||||
])
|
||||
.unwrap(),
|
||||
),
|
||||
TypedValue::Array(Array::new(vec!["foo".into(), false.into()]).unwrap()),
|
||||
"[\"foo\",false]",
|
||||
),
|
||||
(
|
||||
TypedValue::Dictionary(
|
||||
Dictionary::new(HashMap::from([
|
||||
("foo".to_string(), TypedValue::Int(123)),
|
||||
("bar".to_string(), TypedValue::String("baz".to_string())),
|
||||
// The set of valid keys is equal to the set of valid JSON keys
|
||||
("foo".into(), 123.into()),
|
||||
// Empty strings are valid JSON keys
|
||||
(("".into()), "baz".into()),
|
||||
// Keys can contain whitespace
|
||||
((" hi".into()), false.into()),
|
||||
// Keys can contain special characters
|
||||
(("!@£$%^&&*()".into()), "".into()),
|
||||
// Keys can contain _very_ special characters
|
||||
(("\0".into()), "".into()),
|
||||
// Keys can contain emojis
|
||||
(("🥳".into()), "party time!".into()),
|
||||
]))
|
||||
.unwrap(),
|
||||
),
|
||||
"{\"Dictionary\":{\"bar\":\"baz\",\"foo\":{\"Int\":\"123\"}}}",
|
||||
"{\"Dictionary\":{\"\":\"baz\",\"\\u0000\":\"\",\" hi\":false,\"!@£$%^&&*()\":\"\",\"foo\":{\"Int\":\"123\"},\"🥳\":\"party time!\"}}",
|
||||
),
|
||||
(
|
||||
TypedValue::Set(
|
||||
Set::new(vec![
|
||||
TypedValue::String("foo".to_string()),
|
||||
TypedValue::String("bar".to_string()),
|
||||
])
|
||||
.unwrap(),
|
||||
),
|
||||
"{\"Set\":[\"foo\",\"bar\"]}",
|
||||
TypedValue::Set(Set::new(HashSet::from(["foo".into(), "bar".into()])).unwrap()),
|
||||
"{\"Set\":[\"bar\",\"foo\"]}",
|
||||
),
|
||||
];
|
||||
|
||||
|
|
@ -209,14 +159,17 @@ mod tests {
|
|||
let serialized = serde_json::to_string(&value).unwrap();
|
||||
assert_eq!(serialized, expected);
|
||||
let deserialized: TypedValue = serde_json::from_str(&serialized).unwrap();
|
||||
assert_eq!(value, deserialized);
|
||||
let expected_deserialized: TypedValue = serde_json::from_str(&expected).unwrap();
|
||||
assert_eq!(
|
||||
value, deserialized,
|
||||
"value {:#?} should equal deserialized {:#?}",
|
||||
value, deserialized
|
||||
);
|
||||
let expected_deserialized: TypedValue = serde_json::from_str(expected).unwrap();
|
||||
assert_eq!(value, expected_deserialized);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_signed_pod_serialization() {
|
||||
fn build_signed_pod() -> Result<SignedPod> {
|
||||
let mut signer = MockSigner { pk: "test".into() };
|
||||
let mut builder = SignedPodBuilder::new(&Params::default());
|
||||
builder.insert("name", "test");
|
||||
|
|
@ -224,44 +177,36 @@ mod tests {
|
|||
builder.insert("very_large_int", 1152921504606846976);
|
||||
builder.insert(
|
||||
"a_dict_containing_one_key",
|
||||
TypedValue::Dictionary(
|
||||
Dictionary::new(HashMap::from([
|
||||
("foo".to_string(), TypedValue::Int(123)),
|
||||
(
|
||||
"an_array_containing_three_ints".to_string(),
|
||||
TypedValue::Array(
|
||||
Array::new(vec![
|
||||
TypedValue::Int(1),
|
||||
TypedValue::Int(2),
|
||||
TypedValue::Int(3),
|
||||
])
|
||||
.unwrap(),
|
||||
),
|
||||
),
|
||||
(
|
||||
"a_set_containing_two_strings".to_string(),
|
||||
TypedValue::Set(
|
||||
Set::new(vec![
|
||||
TypedValue::Array(
|
||||
Array::new(vec![
|
||||
TypedValue::String("foo".to_string()),
|
||||
TypedValue::String("bar".to_string()),
|
||||
])
|
||||
.unwrap(),
|
||||
),
|
||||
TypedValue::String("baz".to_string()),
|
||||
])
|
||||
.unwrap(),
|
||||
),
|
||||
),
|
||||
]))
|
||||
.unwrap(),
|
||||
),
|
||||
Dictionary::new(HashMap::from([
|
||||
("foo".into(), 123.into()),
|
||||
(
|
||||
"an_array_containing_three_ints".into(),
|
||||
Array::new(vec![1.into(), 2.into(), 3.into()])
|
||||
.unwrap()
|
||||
.into(),
|
||||
),
|
||||
(
|
||||
"a_set_containing_two_strings".into(),
|
||||
Set::new(HashSet::from([
|
||||
Array::new(vec!["foo".into(), "bar".into()]).unwrap().into(),
|
||||
"baz".into(),
|
||||
]))
|
||||
.unwrap()
|
||||
.into(),
|
||||
),
|
||||
]))
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let pod = builder.sign(&mut signer).unwrap();
|
||||
Ok(pod)
|
||||
}
|
||||
|
||||
let serialized = serde_json::to_string(&pod).unwrap();
|
||||
#[test]
|
||||
fn test_signed_pod_serialization() {
|
||||
let pod = build_signed_pod().unwrap();
|
||||
|
||||
let serialized = serde_json::to_string_pretty(&pod).unwrap();
|
||||
println!("serialized: {}", serialized);
|
||||
let deserialized: SignedPod = serde_json::from_str(&serialized).unwrap();
|
||||
|
||||
|
|
@ -270,14 +215,11 @@ mod tests {
|
|||
assert_eq!(pod.id(), deserialized.id())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_main_pod_serialization() -> Result<()> {
|
||||
fn build_zukyc_pod() -> Result<MainPod> {
|
||||
let params = middleware::Params::default();
|
||||
let sanctions_values = vec!["A343434340".into()];
|
||||
let sanction_set = TypedValue::Set(Set::new(sanctions_values)?);
|
||||
|
||||
let (gov_id_builder, pay_stub_builder, sanction_list_builder) =
|
||||
zu_kyc_sign_pod_builders(¶ms, &sanction_set);
|
||||
zu_kyc_sign_pod_builders(¶ms);
|
||||
let mut signer = MockSigner {
|
||||
pk: "ZooGov".into(),
|
||||
};
|
||||
|
|
@ -295,8 +237,13 @@ mod tests {
|
|||
|
||||
let mut prover = MockProver {};
|
||||
let kyc_pod = kyc_builder.prove(&mut prover, ¶ms).unwrap();
|
||||
Ok(kyc_pod)
|
||||
}
|
||||
|
||||
let serialized = serde_json::to_string(&kyc_pod).unwrap();
|
||||
#[test]
|
||||
fn test_main_pod_serialization() -> Result<()> {
|
||||
let kyc_pod = build_zukyc_pod()?;
|
||||
let serialized = serde_json::to_string_pretty(&kyc_pod).unwrap();
|
||||
println!("serialized: {}", serialized);
|
||||
let deserialized: MainPod = serde_json::from_str(&serialized).unwrap();
|
||||
|
||||
|
|
@ -307,17 +254,70 @@ mod tests {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_schema() {
|
||||
let generator = SchemaSettings::draft07().into_generator();
|
||||
let mainpod_schema = generator.clone().into_root_schema_for::<MainPodHelper>();
|
||||
let signedpod_schema = generator.into_root_schema_for::<SignedPodHelper>();
|
||||
fn build_ethdos_pod() -> Result<MainPod> {
|
||||
let params = Params {
|
||||
max_input_signed_pods: 3,
|
||||
max_input_main_pods: 3,
|
||||
max_statements: 31,
|
||||
max_signed_pod_values: 8,
|
||||
max_public_statements: 10,
|
||||
max_statement_args: 6,
|
||||
max_operation_args: 5,
|
||||
max_custom_predicate_arity: 5,
|
||||
max_custom_batch_size: 5,
|
||||
max_custom_predicate_wildcards: 12,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
println!("{}", serde_json::to_string_pretty(&mainpod_schema).unwrap());
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&signedpod_schema).unwrap()
|
||||
);
|
||||
let mut alice = MockSigner { pk: "Alice".into() };
|
||||
let bob = MockSigner { pk: "Bob".into() };
|
||||
let mut charlie = MockSigner {
|
||||
pk: "Charlie".into(),
|
||||
};
|
||||
|
||||
// Alice attests that she is ETH friends with Charlie and Charlie
|
||||
// attests that he is ETH friends with Bob.
|
||||
let alice_attestation =
|
||||
eth_friend_signed_pod_builder(¶ms, charlie.pubkey().into()).sign(&mut alice)?;
|
||||
let charlie_attestation =
|
||||
eth_friend_signed_pod_builder(¶ms, bob.pubkey().into()).sign(&mut charlie)?;
|
||||
|
||||
let mut prover = MockProver {};
|
||||
let alice_bob_ethdos = eth_dos_pod_builder(
|
||||
¶ms,
|
||||
&alice_attestation,
|
||||
&charlie_attestation,
|
||||
&bob.pubkey().into(),
|
||||
)?
|
||||
.prove(&mut prover, ¶ms)?;
|
||||
|
||||
Ok(alice_bob_ethdos)
|
||||
}
|
||||
|
||||
#[test]
|
||||
// This tests that we can generate JSON Schemas for the MainPod and
|
||||
// SignedPod types, and that we can validate real Signed and Main Pods
|
||||
// against the schemas.
|
||||
fn test_schema() {
|
||||
let mainpod_schema = schema_for!(MainPodHelper);
|
||||
let signedpod_schema = schema_for!(SignedPodHelper);
|
||||
|
||||
let kyc_pod = build_zukyc_pod().unwrap();
|
||||
let signed_pod = build_signed_pod().unwrap();
|
||||
let ethdos_pod = build_ethdos_pod().unwrap();
|
||||
let mainpod_schema_value = serde_json::to_value(&mainpod_schema).unwrap();
|
||||
let signedpod_schema_value = serde_json::to_value(&signedpod_schema).unwrap();
|
||||
|
||||
let kyc_pod_value = serde_json::to_value(&kyc_pod).unwrap();
|
||||
let mainpod_valid = jsonschema::validate(&mainpod_schema_value, &kyc_pod_value);
|
||||
assert!(mainpod_valid.is_ok(), "{:#?}", mainpod_valid);
|
||||
|
||||
let signed_pod_value = serde_json::to_value(&signed_pod).unwrap();
|
||||
let signedpod_valid = jsonschema::validate(&signedpod_schema_value, &signed_pod_value);
|
||||
assert!(signedpod_valid.is_ok(), "{:#?}", signedpod_valid);
|
||||
|
||||
let ethdos_pod_value = serde_json::to_value(ðdos_pod).unwrap();
|
||||
let ethdos_pod_valid = jsonschema::validate(&mainpod_schema_value, ðdos_pod_value);
|
||||
assert!(ethdos_pod_valid.is_ok(), "{:#?}", ethdos_pod_valid);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue