use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use super::Error; use crate::{ frontend::MainPod, middleware::{deserialize_pod, Hash, Params, Statement, VDSet}, }; #[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[schemars(rename = "MainPod")] pub struct SerializedMainPod { params: Params, pod_type: (usize, String), sts_hash: Hash, vd_set: VDSet, public_statements: Vec, data: serde_json::Value, } impl SerializedMainPod { pub fn statements_hash(&self) -> Hash { self.sts_hash } } impl From for SerializedMainPod { fn from(pod: MainPod) -> Self { let (pod_type, pod_type_name_str) = pod.pod.pod_type(); let data = pod.pod.serialize_data(); SerializedMainPod { pod_type: (pod_type, pod_type_name_str.to_string()), sts_hash: pod.statements_hash(), vd_set: pod.pod.vd_set().clone(), params: pod.params.clone(), public_statements: pod.pod.pub_statements(), data, } } } impl TryFrom for MainPod { type Error = Error; fn try_from(serialized: SerializedMainPod) -> Result { let pod = deserialize_pod( serialized.pod_type.0, serialized.params.clone(), serialized.sts_hash, serialized.vd_set, serialized.data, )?; let public_statements = pod.pub_statements(); Ok(Self { pod, public_statements, params: serialized.params, }) } } #[cfg(test)] mod tests { use std::collections::{HashMap, HashSet}; use pretty_assertions::assert_eq; use schemars::schema_for; use super::*; use crate::{ backends::plonky2::{ mainpod::{rec_main_pod_circuit_data, Prover}, mock::mainpod::MockProver, primitives::ec::schnorr::SecretKey, signer::Signer, }, examples::{ attest_eth_friend, zu_kyc_pod_builder, zu_kyc_sign_dict_builders, EthDosHelper, MOCK_VD_SET, }, frontend::{Result, SignedDict, SignedDictBuilder}, middleware::{ self, containers::{Array, Dictionary, Set}, Params, Signer as _, TypedValue, DEFAULT_VD_LIST, }, }; #[test] fn test_value_serialization() { let params = &Params::default(); // Pairs of values and their expected serialized representations let values = vec![ (TypedValue::String("hello".to_string()), "\"hello\""), (TypedValue::Int(42), "{\"Int\":\"42\"}"), (TypedValue::Bool(true), "true"), ( TypedValue::Array(Array::new(params.max_depth_mt_containers, vec!["foo".into(), false.into()]).unwrap()), "{\"max_depth\":32,\"array\":[\"foo\",false]}", ), ( TypedValue::Dictionary( Dictionary::new(params.max_depth_mt_containers, HashMap::from([ // 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(), ), "{\"max_depth\":32,\"kvs\":{\"\":\"baz\",\"\\u0000\":\"\",\" hi\":false,\"!@£$%^&&*()\":\"\",\"foo\":{\"Int\":\"123\"},\"🥳\":\"party time!\"}}", ), ( TypedValue::Set(Set::new(params.max_depth_mt_containers, HashSet::from(["foo".into(), "bar".into()])).unwrap()), "{\"max_depth\":32,\"set\":[\"bar\",\"foo\"]}", ), ]; for (value, expected) in values { 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, "value {:#?} should equal deserialized {:#?}", value, deserialized ); let expected_deserialized: TypedValue = serde_json::from_str(expected).unwrap(); assert_eq!(value, expected_deserialized); } } fn signed_dict_builder() -> SignedDictBuilder { let params = &Params::default(); let mut builder = SignedDictBuilder::new(params); builder.insert("name", "test"); builder.insert("age", 30); builder.insert("very_large_int", 1152921504606846976); builder.insert( "a_dict_containing_one_key", Dictionary::new( params.max_depth_mt_containers, HashMap::from([ ("foo".into(), 123.into()), ( "an_array_containing_three_ints".into(), Array::new( params.max_depth_mt_containers, vec![1.into(), 2.into(), 3.into()], ) .unwrap() .into(), ), ( "a_set_containing_two_strings".into(), Set::new( params.max_depth_mt_containers, HashSet::from([ Array::new( params.max_depth_mt_containers, vec!["foo".into(), "bar".into()], ) .unwrap() .into(), "baz".into(), ]), ) .unwrap() .into(), ), ]), ) .unwrap(), ); builder } #[test] fn test_signed_dict_serialization() { let builder = signed_dict_builder(); let signer = Signer(SecretKey(1u32.into())); let signed_dict = builder.sign(&signer).unwrap(); let serialized = serde_json::to_string_pretty(&signed_dict).unwrap(); println!("serialized: {}", serialized); let deserialized: SignedDict = serde_json::from_str(&serialized).unwrap(); println!( "deserialized: {}", serde_json::to_string_pretty(&deserialized).unwrap() ); assert_eq!(signed_dict.dict.kvs(), deserialized.dict.kvs()); assert_eq!(signed_dict.public_key, deserialized.public_key); assert_eq!(signed_dict.signature, deserialized.signature); assert_eq!(signed_dict.verify().is_ok(), deserialized.verify().is_ok()); } fn build_mock_zukyc_pod() -> Result { let params = middleware::Params::default(); let vd_set = &*MOCK_VD_SET; let (gov_id_builder, pay_stub_builder) = zu_kyc_sign_dict_builders(¶ms); let signer = Signer(SecretKey(1u32.into())); let gov_id_pod = gov_id_builder.sign(&signer).unwrap(); let signer = Signer(SecretKey(2u32.into())); let pay_stub_pod = pay_stub_builder.sign(&signer).unwrap(); let kyc_builder = zu_kyc_pod_builder(¶ms, vd_set, &gov_id_pod, &pay_stub_pod).unwrap(); let prover = MockProver {}; let kyc_pod = kyc_builder.prove(&prover).unwrap(); Ok(kyc_pod) } fn build_plonky2_zukyc_pod() -> Result { let params = middleware::Params { // Currently the circuit uses random access that only supports vectors of length 64. // With max_input_main_pods=3 we need random access to a vector of length 73. max_input_pods: 1, ..Default::default() }; let mut vds = DEFAULT_VD_LIST.clone(); vds.push(rec_main_pod_circuit_data(¶ms).1.verifier_only.clone()); let vd_set = VDSet::new(params.max_depth_mt_vds, &vds).unwrap(); let (gov_id_builder, pay_stub_builder) = zu_kyc_sign_dict_builders(¶ms); let signer = Signer(SecretKey(1u32.into())); let gov_id_pod = gov_id_builder.sign(&signer)?; let signer = Signer(SecretKey(2u32.into())); let pay_stub_pod = pay_stub_builder.sign(&signer)?; let _signer = Signer(SecretKey(3u32.into())); let kyc_builder = zu_kyc_pod_builder(¶ms, &vd_set, &gov_id_pod, &pay_stub_pod)?; let prover = Prover {}; let kyc_pod = kyc_builder.prove(&prover)?; Ok(kyc_pod) } #[test] fn test_mock_main_pod_serialization() -> Result<()> { let kyc_pod = build_mock_zukyc_pod()?; let serialized = serde_json::to_string_pretty(&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.statements_hash(), deserialized.pod.statements_hash() ); assert_eq!(kyc_pod.pod.verify()?, deserialized.pod.verify()?); Ok(()) } #[test] fn test_plonky2_main_pod_serialization() -> Result<()> { let kyc_pod = build_plonky2_zukyc_pod()?; let serialized = serde_json::to_string_pretty(&kyc_pod).unwrap(); let deserialized: MainPod = serde_json::from_str(&serialized).unwrap(); assert_eq!(kyc_pod.public_statements, deserialized.public_statements); assert_eq!( kyc_pod.pod.statements_hash(), deserialized.pod.statements_hash() ); assert_eq!(kyc_pod.pod.verify()?, deserialized.pod.verify()?); Ok(()) } fn build_ethdos_pod() -> Result { let params = Params { max_input_pods_public_statements: 8, max_statements: 24, max_public_statements: 8, ..Default::default() }; let vd_set = &*MOCK_VD_SET; let alice = Signer(SecretKey(1u32.into())); let bob = Signer(SecretKey(2u32.into())); let charlie = Signer(SecretKey(3u32.into())); // Alice attests that she is ETH friends with Bob and Bob // attests that he is ETH friends with Charlie. let alice_attestation = attest_eth_friend(¶ms, &alice, bob.public_key()); let bob_attestation = attest_eth_friend(¶ms, &bob, charlie.public_key()); let helper = EthDosHelper::new(¶ms, vd_set, alice.public_key())?; let prover = MockProver {}; let dist_1 = helper.dist_1(&alice_attestation)?.prove(&prover)?; let dist_2 = helper .dist_n_plus_1(&dist_1, &bob_attestation)? .prove(&prover)?; Ok(dist_2) } #[test] // This tests that we can generate JSON Schemas for the MainPod and // SignedDict types, and that we can validate Signed and Main Pods // against the schemas. Since both Mock and Plonky2 PODs have the same // public interface, we can assume that the schema works for both. fn test_schema() { let mainpod_schema = schema_for!(SerializedMainPod); let signeddict_schema = schema_for!(SignedDict); let kyc_pod = build_mock_zukyc_pod().unwrap(); let signed_dict = signed_dict_builder() .sign(&Signer(SecretKey(1u32.into()))) .unwrap(); let ethdos_pod = build_ethdos_pod().unwrap(); let mainpod_schema_value = serde_json::to_value(&mainpod_schema).unwrap(); let signed_dict_schema_value = serde_json::to_value(&signeddict_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_dict_value = serde_json::to_value(&signed_dict).unwrap(); let signed_dict_valid = jsonschema::validate(&signed_dict_schema_value, &signed_dict_value); assert!(signed_dict_valid.is_ok(), "{:#?}", signed_dict_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); } }