diff --git a/Cargo.lock b/Cargo.lock index f2474d29a1..c26573622e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1611,6 +1611,7 @@ name = "snarkvm-algorithms" version = "0.7.5" dependencies = [ "anyhow", + "bincode", "blake2", "blake2s_simd", "criterion", @@ -1643,6 +1644,7 @@ dependencies = [ name = "snarkvm-curves" version = "0.7.5" dependencies = [ + "bincode", "criterion", "derivative", "rand", @@ -1683,6 +1685,7 @@ dependencies = [ "rand_chacha", "rayon", "serde", + "serde_json", "snarkvm-algorithms", "snarkvm-curves", "snarkvm-fields", @@ -1700,10 +1703,8 @@ name = "snarkvm-fields" version = "0.7.5" dependencies = [ "anyhow", - "bincode", "derivative", "rand", - "rand_xorshift", "serde", "snarkvm-utilities", "thiserror", @@ -1861,6 +1862,7 @@ dependencies = [ "num-bigint", "rand", "rand_xorshift", + "serde", "snarkvm-derives", "thiserror", ] diff --git a/algorithms/Cargo.toml b/algorithms/Cargo.toml index 1cabf71fcd..3c00d57a3c 100644 --- a/algorithms/Cargo.toml +++ b/algorithms/Cargo.toml @@ -114,6 +114,9 @@ version = "2" [dependencies.digest] version = "0.9" +[dependencies.hex] +version = "0.4.3" + [dependencies.itertools] version = "0.10.1" @@ -139,6 +142,11 @@ default-features = false [dependencies.rayon] version = "1" +[dependencies.serde] +version = "1.0" +default-features = false +features = ["derive"] + [dependencies.sha2] version = "0.9" default-features = false @@ -146,6 +154,9 @@ default-features = false [dependencies.thiserror] version = "1.0" +[dev-dependencies.bincode] +version = "1" + [dev-dependencies.criterion] version = "0.3.5" diff --git a/algorithms/src/signature/aleo.rs b/algorithms/src/signature/aleo.rs index 35c9670a0f..f2ea41f5f5 100644 --- a/algorithms/src/signature/aleo.rs +++ b/algorithms/src/signature/aleo.rs @@ -31,19 +31,24 @@ use snarkvm_curves::{ }; use snarkvm_fields::{ConstraintFieldError, Field, FieldParameters, PrimeField, ToConstraintField}; use snarkvm_utilities::{ + fmt, io::{Read, Result as IoResult, Write}, ops::Mul, rand::UniformRand, serialize::*, + str::FromStr, FromBits, FromBytes, + FromBytesDeserializer, ToBits, ToBytes, + ToBytesSerializer, }; use anyhow::Result; use itertools::Itertools; use rand::{CryptoRng, Rng}; +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; #[derive(Derivative)] #[derivative( @@ -62,6 +67,11 @@ pub struct AleoSignature { } impl AleoSignature { + #[inline] + pub fn size() -> usize { + 2 * TE::ScalarField::SERIALIZED_SIZE + 2 * TE::BaseField::SERIALIZED_SIZE + } + #[inline] pub fn root_public_key(&self) -> Result> { if let Some(element) = TEAffine::::from_x_coordinate(self.root_public_key, true) { @@ -97,16 +107,6 @@ impl AleoSignature { } } -impl ToBytes for AleoSignature { - #[inline] - fn write_le(&self, mut writer: W) -> IoResult<()> { - self.prover_response.write_le(&mut writer)?; - self.verifier_challenge.write_le(&mut writer)?; - self.root_public_key.write_le(&mut writer)?; - self.root_randomizer.write_le(&mut writer) - } -} - impl FromBytes for AleoSignature { #[inline] fn read_le(mut reader: R) -> IoResult { @@ -124,6 +124,56 @@ impl FromBytes for AleoSignature { } } +impl ToBytes for AleoSignature { + #[inline] + fn write_le(&self, mut writer: W) -> IoResult<()> { + self.prover_response.write_le(&mut writer)?; + self.verifier_challenge.write_le(&mut writer)?; + self.root_public_key.write_le(&mut writer)?; + self.root_randomizer.write_le(&mut writer) + } +} + +impl FromStr for AleoSignature { + type Err = anyhow::Error; + + #[inline] + fn from_str(signature_hex: &str) -> Result { + Self::from_bytes_le(&hex::decode(signature_hex)?) + } +} + +impl fmt::Display for AleoSignature { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let signature_hex = hex::encode(self.to_bytes_le().expect("Failed to convert signature to bytes")); + write!(f, "{}", signature_hex) + } +} + +impl Serialize for AleoSignature { + #[inline] + fn serialize(&self, serializer: S) -> Result { + match serializer.is_human_readable() { + true => serializer.collect_str(self), + false => ToBytesSerializer::serialize(self, serializer), + } + } +} + +impl<'de, TE: TwistedEdwardsParameters> Deserialize<'de> for AleoSignature { + #[inline] + fn deserialize>(deserializer: D) -> Result { + match deserializer.is_human_readable() { + true => { + let s: String = Deserialize::deserialize(deserializer)?; + FromStr::from_str(&s).map_err(de::Error::custom) + } + false => FromBytesDeserializer::::deserialize(deserializer, "signature", Self::size()), + } + } +} + #[derive(Derivative)] #[derivative( Clone(bound = "TE: TwistedEdwardsParameters"), @@ -135,7 +185,7 @@ pub struct AleoSignatureScheme where TE::BaseField: PoseidonDefaultParametersField, { - pub g_bases: Vec>, + g_bases: Vec>, crypto_hash: PoseidonCryptoHash, } @@ -171,8 +221,8 @@ where Self { g_bases, crypto_hash } } - fn parameters(&self) -> Self::Parameters { - self.g_bases.clone() + fn parameters(&self) -> &Self::Parameters { + &self.g_bases } /// diff --git a/algorithms/src/signature/tests.rs b/algorithms/src/signature/tests.rs index acb15adaf9..7ebc187cdc 100644 --- a/algorithms/src/signature/tests.rs +++ b/algorithms/src/signature/tests.rs @@ -17,11 +17,10 @@ use crate::SignatureScheme; use snarkvm_utilities::FromBytes; -use rand::SeedableRng; -use rand_chacha::ChaChaRng; +use rand::thread_rng; fn sign_and_verify(message: &[u8]) { - let rng = &mut ChaChaRng::seed_from_u64(1231275789u64); + let rng = &mut thread_rng(); let signature_scheme = S::setup("sign_and_verify"); let private_key = signature_scheme.generate_private_key(rng); @@ -31,7 +30,7 @@ fn sign_and_verify(message: &[u8]) { } fn failed_verification(message: &[u8], bad_message: &[u8]) { - let rng = &mut ChaChaRng::seed_from_u64(1231275789u64); + let rng = &mut thread_rng(); let signature_scheme = S::setup("failed_verification"); let private_key = signature_scheme.generate_private_key(rng); @@ -48,11 +47,12 @@ fn signature_scheme_serialization() { mod aleo { use super::*; - use crate::signature::AleoSignatureScheme; + use crate::signature::{AleoSignature, AleoSignatureScheme}; use snarkvm_curves::{ edwards_bls12::EdwardsParameters as EdwardsBls12, edwards_bw6::EdwardsParameters as EdwardsBW6, }; + use snarkvm_utilities::{str::FromStr, FromBytes, ToBytes}; #[test] fn test_aleo_signature_on_edwards_bls12_377() { @@ -77,4 +77,57 @@ mod aleo { signature_scheme_serialization::>(); signature_scheme_serialization::>(); } + + #[test] + fn test_serde_json() { + type TestSignature = AleoSignatureScheme; + + let expected_signature = { + let rng = &mut thread_rng(); + let signature_scheme = TestSignature::setup("test_serde_json"); + let private_key = signature_scheme.generate_private_key(rng); + let message = b"Hi, I am an Aleo signature!"; + signature_scheme.sign(&private_key, message, rng).unwrap() + }; + + // Serialize + let expected_string = &expected_signature.to_string(); + let candidate_string = serde_json::to_string(&expected_signature).unwrap(); + assert_eq!(258, candidate_string.len(), "Update me if serialization has changed"); + assert_eq!( + expected_string, + serde_json::Value::from_str(&candidate_string) + .unwrap() + .as_str() + .unwrap() + ); + + // Deserialize + assert_eq!(expected_signature, serde_json::from_str(&candidate_string).unwrap()); + assert_eq!(expected_signature, AleoSignature::from_str(&expected_string).unwrap()); + } + + #[test] + fn test_bincode() { + type TestSignature = AleoSignatureScheme; + + let expected_signature = { + let rng = &mut thread_rng(); + let signature_scheme = TestSignature::setup("test_bincode"); + let private_key = signature_scheme.generate_private_key(rng); + let message = b"Hi, I am an Aleo signature!"; + signature_scheme.sign(&private_key, message, rng).unwrap() + }; + + // Serialize + let expected_bytes = expected_signature.to_bytes_le().unwrap(); + assert_eq!( + &expected_bytes[..], + &bincode::serialize(&expected_signature).unwrap()[..] + ); + + // Deserialize + assert_eq!(expected_signature, bincode::deserialize(&expected_bytes[..]).unwrap()); + assert_eq!(expected_signature, AleoSignature::read_le(&expected_bytes[..]).unwrap()); + } } diff --git a/algorithms/src/snark/groth16/mod.rs b/algorithms/src/snark/groth16/mod.rs index 76e08fdccf..ab7761444f 100644 --- a/algorithms/src/snark/groth16/mod.rs +++ b/algorithms/src/snark/groth16/mod.rs @@ -21,8 +21,19 @@ use snarkvm_curves::traits::{AffineCurve, PairingCurve, PairingEngine}; use snarkvm_fields::{ConstraintFieldError, Field, ToConstraintField}; use snarkvm_r1cs::{Index, LinearCombination}; -use snarkvm_utilities::{errors::SerializationError, serialize::*, FromBytes, ToBytes, ToMinimalBits}; +use snarkvm_utilities::{ + errors::SerializationError, + fmt, + serialize::*, + str::FromStr, + FromBytes, + FromBytesDeserializer, + ToBytes, + ToBytesSerializer, + ToMinimalBits, +}; +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use std::io::{ Read, Result as IoResult, @@ -63,46 +74,16 @@ pub struct Proof { pub(crate) compressed: bool, } -impl ToBytes for Proof { - #[inline] - fn write_le(&self, mut writer: W) -> IoResult<()> { - match self.compressed { - true => self.write_compressed(&mut writer), - false => self.write_uncompressed(&mut writer), - } - } -} - -impl FromBytes for Proof { - #[inline] - fn read_le(mut reader: R) -> IoResult { - Self::read(&mut reader) - } -} - -impl PartialEq for Proof { - fn eq(&self, other: &Self) -> bool { - self.a == other.a && self.b == other.b && self.c == other.c - } -} - -impl Default for Proof { - fn default() -> Self { - Self { - a: E::G1Affine::default(), - b: E::G2Affine::default(), - c: E::G1Affine::default(), - compressed: true, - } +impl Proof { + /// Returns `true` if the proof is in compressed form. + pub fn is_compressed(&self) -> bool { + self.compressed } -} -impl Proof { /// Serialize the proof into bytes in compressed form, for storage /// on disk or transmission over the network. pub fn write_compressed(&self, mut writer: W) -> IoResult<()> { CanonicalSerialize::serialize(self, &mut writer)?; - Ok(()) } @@ -169,6 +150,85 @@ impl Proof { } } +impl FromBytes for Proof { + #[inline] + fn read_le(mut reader: R) -> IoResult { + Self::read(&mut reader) + } +} + +impl ToBytes for Proof { + #[inline] + fn write_le(&self, mut writer: W) -> IoResult<()> { + match self.compressed { + true => self.write_compressed(&mut writer), + false => self.write_uncompressed(&mut writer), + } + } +} + +impl FromStr for Proof { + type Err = anyhow::Error; + + #[inline] + fn from_str(proof_hex: &str) -> Result { + Self::from_bytes_le(&hex::decode(proof_hex)?) + } +} + +impl fmt::Display for Proof { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let proof_hex = hex::encode(self.to_bytes_le().expect("Failed to convert proof to bytes")); + write!(f, "{}", proof_hex) + } +} + +impl Serialize for Proof { + #[inline] + fn serialize(&self, serializer: S) -> Result { + match serializer.is_human_readable() { + true => serializer.collect_str(self), + false => ToBytesSerializer::serialize(self, serializer), + } + } +} + +impl<'de, E: PairingEngine> Deserialize<'de> for Proof { + #[inline] + fn deserialize>(deserializer: D) -> Result { + match deserializer.is_human_readable() { + true => { + let s: String = Deserialize::deserialize(deserializer)?; + FromStr::from_str(&s).map_err(de::Error::custom) + } + false => FromBytesDeserializer::::try_deserialize( + deserializer, + "proof", + Self::compressed_proof_size().map_err(de::Error::custom)?, + Self::uncompressed_proof_size().map_err(de::Error::custom)?, + ), + } + } +} + +impl PartialEq for Proof { + fn eq(&self, other: &Self) -> bool { + self.a == other.a && self.b == other.b && self.c == other.c + } +} + +impl Default for Proof { + fn default() -> Self { + Self { + a: E::G1Affine::default(), + b: E::G2Affine::default(), + c: E::G1Affine::default(), + compressed: true, + } + } +} + /// A verification key in the Groth16 SNARK. #[derive(Clone, Debug, PartialEq, CanonicalSerialize, CanonicalDeserialize)] pub struct VerifyingKey { diff --git a/algorithms/src/snark/groth16/tests.rs b/algorithms/src/snark/groth16/tests.rs index 7fa3500507..cb5be49ce8 100644 --- a/algorithms/src/snark/groth16/tests.rs +++ b/algorithms/src/snark/groth16/tests.rs @@ -50,23 +50,27 @@ impl ConstraintSynthesizer for MySillyCircuit(&MySillyCircuit { a: None, b: None }, rng).unwrap(); for _ in 0..100 { - let a = Fr::rand(rng); - let b = Fr::rand(rng); - let mut c = a; - c.mul_assign(&b); + let (a, b) = (Fr::rand(rng), Fr::rand(rng)); + let c = a * b; let proof = create_random_proof(&MySillyCircuit { a: Some(a), b: Some(b) }, ¶meters, rng).unwrap(); let pvk = prepare_verifying_key::(parameters.vk.clone()); @@ -75,24 +79,97 @@ mod bls12_377 { assert!(!verify_proof(&pvk, &proof, &[a]).unwrap()); } } + + #[test] + fn test_serde_json() { + let expected_proof = { + let rng = &mut thread_rng(); + let parameters = + generate_random_parameters::(&MySillyCircuit { a: None, b: None }, rng).unwrap(); + + let (a, b) = (Fr::rand(rng), Fr::rand(rng)); + create_random_proof(&MySillyCircuit { a: Some(a), b: Some(b) }, ¶meters, rng).unwrap() + }; + + // Serialize + let expected_string = &expected_proof.to_string(); + let candidate_string = serde_json::to_string(&expected_proof).unwrap(); + assert_eq!(388, candidate_string.len(), "Update me if serialization has changed"); + assert_eq!( + expected_string, + serde_json::Value::from_str(&candidate_string) + .unwrap() + .as_str() + .unwrap() + ); + + // Deserialize + assert_eq!(expected_proof, serde_json::from_str(&candidate_string).unwrap()); + assert_eq!(expected_proof, Proof::from_str(&expected_string).unwrap()); + } + + #[test] + fn test_bincode_compressed() { + let expected_proof = { + let rng = &mut thread_rng(); + let parameters = + generate_random_parameters::(&MySillyCircuit { a: None, b: None }, rng).unwrap(); + + let (a, b) = (Fr::rand(rng), Fr::rand(rng)); + create_random_proof(&MySillyCircuit { a: Some(a), b: Some(b) }, ¶meters, rng).unwrap() + }; + + // Serialize + let expected_bytes = expected_proof.to_bytes_le().unwrap(); + assert_eq!(&expected_bytes[..], &bincode::serialize(&expected_proof).unwrap()[..]); + + // Deserialize + assert_eq!(expected_proof, bincode::deserialize(&expected_bytes[..]).unwrap()); + assert_eq!(expected_proof, Proof::read_le(&expected_bytes[..]).unwrap()); + } + + #[test] + fn test_bincode_uncompressed() { + let expected_proof = { + let rng = &mut thread_rng(); + let parameters = + generate_random_parameters::(&MySillyCircuit { a: None, b: None }, rng).unwrap(); + + let (a, b) = (Fr::rand(rng), Fr::rand(rng)); + create_random_proof(&MySillyCircuit { a: Some(a), b: Some(b) }, ¶meters, rng).unwrap() + }; + + // Uncompressed bytes. + let mut expected_bytes = Vec::new(); + expected_proof.write_uncompressed(&mut expected_bytes).unwrap(); + + // Deserialize + assert_eq!(expected_proof, bincode::deserialize(&expected_bytes[..]).unwrap()); + assert_eq!(expected_proof, Proof::read_le(&expected_bytes[..]).unwrap()); + } } mod bw6_761 { use super::*; - use crate::snark::groth16::{create_random_proof, generate_random_parameters, prepare_verifying_key, verify_proof}; - + use crate::snark::groth16::{ + create_random_proof, + generate_random_parameters, + prepare_verifying_key, + verify_proof, + Proof, + }; use snarkvm_curves::bw6_761::{Fr, BW6_761}; - use snarkvm_utilities::rand::{test_rng, UniformRand}; + use snarkvm_utilities::{rand::UniformRand, str::FromStr, FromBytes, ToBytes}; + + use rand::thread_rng; #[test] fn prove_and_verify() { - let rng = &mut test_rng(); - + let rng = &mut thread_rng(); let parameters = generate_random_parameters::(&MySillyCircuit { a: None, b: None }, rng).unwrap(); - let a = Fr::rand(rng); - let b = Fr::rand(rng); + let (a, b) = (Fr::rand(rng), Fr::rand(rng)); let c = a * b; let proof = create_random_proof(&MySillyCircuit { a: Some(a), b: Some(b) }, ¶meters, rng).unwrap(); @@ -101,6 +178,74 @@ mod bw6_761 { assert!(verify_proof(&pvk, &proof, &[c]).unwrap()); assert!(!verify_proof(&pvk, &proof, &[Fr::zero()]).unwrap()); } + + #[test] + fn test_serde_json() { + let expected_proof = { + let rng = &mut thread_rng(); + let parameters = + generate_random_parameters::(&MySillyCircuit { a: None, b: None }, rng).unwrap(); + + let (a, b) = (Fr::rand(rng), Fr::rand(rng)); + create_random_proof(&MySillyCircuit { a: Some(a), b: Some(b) }, ¶meters, rng).unwrap() + }; + + // Serialize + let expected_string = &expected_proof.to_string(); + let candidate_string = serde_json::to_string(&expected_proof).unwrap(); + assert_eq!(580, candidate_string.len(), "Update me if serialization has changed"); + assert_eq!( + expected_string, + serde_json::Value::from_str(&candidate_string) + .unwrap() + .as_str() + .unwrap() + ); + + // Deserialize + assert_eq!(expected_proof, serde_json::from_str(&candidate_string).unwrap()); + assert_eq!(expected_proof, Proof::from_str(&expected_string).unwrap()); + } + + #[test] + fn test_bincode_compressed() { + let expected_proof = { + let rng = &mut thread_rng(); + let parameters = + generate_random_parameters::(&MySillyCircuit { a: None, b: None }, rng).unwrap(); + + let (a, b) = (Fr::rand(rng), Fr::rand(rng)); + create_random_proof(&MySillyCircuit { a: Some(a), b: Some(b) }, ¶meters, rng).unwrap() + }; + + // Serialize + let expected_bytes = expected_proof.to_bytes_le().unwrap(); + assert_eq!(&expected_bytes[..], &bincode::serialize(&expected_proof).unwrap()[..]); + + // Deserialize + assert_eq!(expected_proof, bincode::deserialize(&expected_bytes[..]).unwrap()); + assert_eq!(expected_proof, Proof::read_le(&expected_bytes[..]).unwrap()); + } + + #[test] + fn test_bincode_uncompressed() { + let expected_proof = { + let rng = &mut thread_rng(); + let parameters = + generate_random_parameters::(&MySillyCircuit { a: None, b: None }, rng).unwrap(); + + let (a, b) = (Fr::rand(rng), Fr::rand(rng)); + create_random_proof(&MySillyCircuit { a: Some(a), b: Some(b) }, ¶meters, rng).unwrap() + }; + + // Uncompressed bytes. + let mut expected_bytes = Vec::new(); + expected_proof.write_uncompressed(&mut expected_bytes).unwrap(); + + // Deserialize + assert_eq!(expected_proof, bincode::deserialize(&expected_bytes[..]).unwrap()); + assert_eq!(expected_proof, Proof::read_le(&expected_bytes[..]).unwrap()); + } } mod serialization { @@ -114,50 +259,46 @@ mod serialization { }; #[test] - fn test_compressed_proof_serialization() { + fn test_canonical_compressed() { let rng = &mut test_rng(); - let parameters = generate_random_parameters::(&MySillyCircuit { a: None, b: None }, rng).unwrap(); - let a = Fr::rand(rng); - let b = Fr::rand(rng); - + let (a, b) = (Fr::rand(rng), Fr::rand(rng)); let proof = create_random_proof(&MySillyCircuit { a: Some(a), b: Some(b) }, ¶meters, rng).unwrap(); + // Serialize let compressed_serialization = proof.to_bytes_le().unwrap(); - assert_eq!( Proof::::compressed_proof_size().unwrap(), compressed_serialization.len() ); assert!(Proof::::read_uncompressed(&compressed_serialization[..]).is_err()); + // Deserialize let recovered_proof: Proof = FromBytes::read_le(&compressed_serialization[..]).unwrap(); assert_eq!(recovered_proof.compressed, true); } #[test] - fn test_uncompressed_proof_serialization() { + fn test_canonical_uncompressed() { let rng = &mut test_rng(); - let parameters = generate_random_parameters::(&MySillyCircuit { a: None, b: None }, rng).unwrap(); - let a = Fr::rand(rng); - let b = Fr::rand(rng); - + let (a, b) = (Fr::rand(rng), Fr::rand(rng)); let proof = create_random_proof(&MySillyCircuit { a: Some(a), b: Some(b) }, ¶meters, rng).unwrap(); + // Serialize let mut uncompressed_serialization = Vec::new(); proof.write_uncompressed(&mut uncompressed_serialization).unwrap(); - assert_eq!( Proof::::uncompressed_proof_size().unwrap(), uncompressed_serialization.len() ); assert!(Proof::::read_compressed(&uncompressed_serialization[..]).is_err()); + // Deserialize let recovered_proof: Proof = FromBytes::read_le(&uncompressed_serialization[..]).unwrap(); assert_eq!(recovered_proof.compressed, false); } diff --git a/algorithms/src/traits/signature.rs b/algorithms/src/traits/signature.rs index 6910b90d48..5579b5620d 100644 --- a/algorithms/src/traits/signature.rs +++ b/algorithms/src/traits/signature.rs @@ -30,7 +30,7 @@ pub trait SignatureScheme: fn setup(message: &str) -> Self; - fn parameters(&self) -> Self::Parameters; + fn parameters(&self) -> &Self::Parameters; fn generate_private_key(&self, rng: &mut R) -> Self::PrivateKey; diff --git a/curves/Cargo.toml b/curves/Cargo.toml index 012d9d5d25..13124e2683 100644 --- a/curves/Cargo.toml +++ b/curves/Cargo.toml @@ -39,10 +39,6 @@ version = "2" version = "0.8" default-features = false -[dependencies.rand_xorshift] -version = "0.3" -default-features = false - [dependencies.serde] version = "1.0.130" default-features = false @@ -51,12 +47,19 @@ features = [ "derive" ] [dependencies.thiserror] version = "1.0" +[dev-dependencies.bincode] +version = "1.3.3" + [dev-dependencies.criterion] version = "0.3" [dev-dependencies.rand] version = "0.8" +[dev-dependencies.rand_xorshift] +version = "0.3" +default-features = false + [build-dependencies] rustc_version = "0.4" diff --git a/curves/src/bls12_377/tests.rs b/curves/src/bls12_377/tests.rs index 393f7079fc..79bf3550a0 100644 --- a/curves/src/bls12_377/tests.rs +++ b/curves/src/bls12_377/tests.rs @@ -36,6 +36,7 @@ use crate::{ templates::{short_weierstrass_jacobian::tests::sw_tests, twisted_edwards_extended::tests::edwards_test}, traits::{ tests_curve::curve_tests, + tests_field::{field_serialization_test, field_test, frobenius_test, primefield_test, sqrt_field_test}, tests_group::group_test, AffineCurve, PairingEngine, @@ -45,7 +46,6 @@ use crate::{ }; use snarkvm_fields::{ fp6_3over2::Fp6Parameters, - tests_field::{field_serialization_test, field_test, frobenius_test, primefield_test, sqrt_field_test}, FftField, FftParameters, Field, diff --git a/curves/src/bw6_761/tests.rs b/curves/src/bw6_761/tests.rs index 8168468108..53eb89271d 100644 --- a/curves/src/bw6_761/tests.rs +++ b/curves/src/bw6_761/tests.rs @@ -29,14 +29,15 @@ use crate::{ BW6_761, }, templates::short_weierstrass_jacobian::tests::sw_tests, - traits::{tests_curve::curve_tests, tests_group::group_test, AffineCurve, PairingEngine}, -}; -use snarkvm_fields::{ - tests_field::{field_serialization_test, field_test, frobenius_test, primefield_test, sqrt_field_test}, - Field, - One, - PrimeField, + traits::{ + tests_curve::curve_tests, + tests_field::{field_serialization_test, field_test, frobenius_test, primefield_test, sqrt_field_test}, + tests_group::group_test, + AffineCurve, + PairingEngine, + }, }; +use snarkvm_fields::{Field, One, PrimeField}; #[test] fn test_bw6_761_fr() { diff --git a/curves/src/edwards_bls12/tests.rs b/curves/src/edwards_bls12/tests.rs index 5c77f5ab94..8641e34464 100644 --- a/curves/src/edwards_bls12/tests.rs +++ b/curves/src/edwards_bls12/tests.rs @@ -19,6 +19,7 @@ use crate::{ templates::twisted_edwards_extended::tests::{edwards_test, montgomery_conversion_test}, traits::{ tests_curve::curve_tests, + tests_field::{field_serialization_test, field_test, primefield_test}, tests_group::group_test, AffineCurve, Group, @@ -27,14 +28,7 @@ use crate::{ TwistedEdwardsParameters, }, }; -use snarkvm_fields::{ - tests_field::{field_serialization_test, field_test, primefield_test}, - Field, - LegendreSymbol, - One, - SquareRootField, - Zero, -}; +use snarkvm_fields::{Field, LegendreSymbol, One, SquareRootField, Zero}; use snarkvm_utilities::{rand::UniformRand, to_bytes_le, ToBytes}; use rand::thread_rng; diff --git a/curves/src/edwards_bw6/tests.rs b/curves/src/edwards_bw6/tests.rs index 8b8e91bd95..a48ac57745 100644 --- a/curves/src/edwards_bw6/tests.rs +++ b/curves/src/edwards_bw6/tests.rs @@ -17,9 +17,15 @@ use crate::{ edwards_bw6::*, templates::twisted_edwards_extended::tests::{edwards_test, montgomery_conversion_test}, - traits::{tests_curve::curve_tests, tests_group::group_test, AffineCurve, Group, ProjectiveCurve}, + traits::{ + tests_curve::curve_tests, + tests_field::{field_serialization_test, field_test, primefield_test}, + tests_group::group_test, + AffineCurve, + Group, + ProjectiveCurve, + }, }; -use snarkvm_fields::tests_field::{field_serialization_test, field_test, primefield_test}; #[test] fn test_edwards_bw6_fr() { diff --git a/curves/src/traits/mod.rs b/curves/src/traits/mod.rs index 1c6ee71f0b..792cd5ffc0 100644 --- a/curves/src/traits/mod.rs +++ b/curves/src/traits/mod.rs @@ -20,6 +20,11 @@ pub use group::*; pub mod pairing_engine; pub use pairing_engine::*; +#[cfg(test)] +pub mod tests_field; + +#[cfg(test)] pub mod tests_group; +#[cfg(test)] pub mod tests_curve; diff --git a/curves/src/traits/pairing_engine.rs b/curves/src/traits/pairing_engine.rs index a001484c04..b09af952fa 100644 --- a/curves/src/traits/pairing_engine.rs +++ b/curves/src/traits/pairing_engine.rs @@ -18,6 +18,7 @@ use crate::traits::Group; use snarkvm_fields::{Field, PrimeField, SquareRootField, ToConstraintField}; use snarkvm_utilities::{biginteger::BigInteger, serialize::*, BitIteratorBE, ToBytes, ToMinimalBits}; +use serde::{de::DeserializeOwned, Serialize}; use std::{fmt::Debug, iter}; pub trait PairingEngine: Sized + 'static + Copy + Debug + PartialEq + Eq + Sync + Send { @@ -156,6 +157,8 @@ pub trait ProjectiveCurve: pub trait AffineCurve: Group + Sized + + Serialize + + DeserializeOwned + CanonicalSerialize + ConstantSerializedSize + CanonicalDeserialize diff --git a/fields/src/tests_field.rs b/curves/src/traits/tests_field.rs similarity index 98% rename from fields/src/tests_field.rs rename to curves/src/traits/tests_field.rs index 8317deac5d..ecf3199aec 100644 --- a/fields/src/tests_field.rs +++ b/curves/src/traits/tests_field.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with the snarkVM library. If not, see . -use crate::{traits::FftParameters, FftField, Field, LegendreSymbol, PrimeField, SquareRootField}; +use snarkvm_fields::{traits::FftParameters, FftField, Field, LegendreSymbol, PrimeField, SquareRootField}; use snarkvm_utilities::{ io::Cursor, serialize::{CanonicalDeserialize, CanonicalSerialize, Flags, SWFlags}, @@ -408,11 +408,10 @@ pub fn frobenius_test>(characteristic: C, maxpower: us } } -// Taken from https://github.com/scipr-lab/zexe/blob/master/algebra/src/tests/fields.rs#L381 pub fn field_serialization_test() { let buf_size = F::SERIALIZED_SIZE; - let mut rng = XorShiftRng::seed_from_u64(1231275789u64); + let mut rng = &mut rand::thread_rng(); for _ in 0..ITERATIONS { let a = F::rand(&mut rng); diff --git a/dpc/Cargo.toml b/dpc/Cargo.toml index c05e2dd79d..80969dcf2f 100644 --- a/dpc/Cargo.toml +++ b/dpc/Cargo.toml @@ -91,9 +91,6 @@ version = "0.2" [dependencies.bech32] version = "0.8" -[dependencies.bincode] -version = "1.3" - [dependencies.chrono] version = "0.4" features = [ "serde" ] @@ -118,11 +115,17 @@ version = "1" [dependencies.serde] version = "1.0" -features = [ "derive" ] +features = ["derive"] + +[dependencies.serde_json] +version = "1.0" [dependencies.thiserror] version = "1.0" +[dev-dependencies.bincode] +version = "1.3" + [dev-dependencies.criterion] version = "0.3.5" diff --git a/dpc/src/account/address.rs b/dpc/src/account/address.rs index 1c67fa84c7..0f921b25d2 100644 --- a/dpc/src/account/address.rs +++ b/dpc/src/account/address.rs @@ -17,16 +17,20 @@ use crate::{account_format, AccountError, ComputeKey, Network, PrivateKey, ViewKey}; use snarkvm_algorithms::{EncryptionScheme, SignatureScheme}; use snarkvm_curves::AffineCurve; -use snarkvm_utilities::{FromBytes, ToBytes}; - -use bech32::{self, FromBase32, ToBase32}; -use std::{ +use snarkvm_utilities::{ fmt, io::{Read, Result as IoResult, Write}, ops::Deref, str::FromStr, + FromBytes, + FromBytesDeserializer, + ToBytes, + ToBytesSerializer, }; +use bech32::{self, FromBase32, ToBase32}; +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; + #[derive(Derivative)] #[derivative( Default(bound = "N: Network"), @@ -159,10 +163,8 @@ impl FromStr for Address { impl fmt::Display for Address { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - // Write the encryption key to a buffer. - let mut encryption_key = [0u8; 32]; - self.write_le(&mut encryption_key[0..32]) - .expect("Failed to write encryption key as bytes"); + // Convert the encryption key to bytes. + let encryption_key = self.to_bytes_le().expect("Failed to write encryption key as bytes"); bech32::encode( &account_format::ADDRESS_PREFIX.to_string(), @@ -180,6 +182,24 @@ impl fmt::Debug for Address { } } +impl Serialize for Address { + fn serialize(&self, serializer: S) -> Result { + match serializer.is_human_readable() { + true => serializer.collect_str(self), + false => ToBytesSerializer::serialize(self, serializer), + } + } +} + +impl<'de, N: Network> Deserialize<'de> for Address { + fn deserialize>(deserializer: D) -> Result { + match deserializer.is_human_readable() { + true => FromStr::from_str(&String::deserialize(deserializer)?).map_err(de::Error::custom), + false => FromBytesDeserializer::::deserialize(deserializer, "address", N::ADDRESS_SIZE_IN_BYTES), + } + } +} + impl Deref for Address { type Target = ::PublicKey; @@ -187,3 +207,50 @@ impl Deref for Address { &self.0 } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::testnet2::Testnet2; + + use rand::thread_rng; + + #[test] + fn test_serde_json() { + let rng = &mut thread_rng(); + + let private_key = PrivateKey::new(rng); + let expected_address: Address = private_key.into(); + + // Serialize + let expected_string = &expected_address.to_string(); + let candidate_string = serde_json::to_string(&expected_address).unwrap(); + assert_eq!( + expected_string, + serde_json::Value::from_str(&candidate_string) + .unwrap() + .as_str() + .unwrap() + ); + + // Deserialize + assert_eq!(expected_address, Address::from_str(&expected_string).unwrap()); + assert_eq!(expected_address, serde_json::from_str(&candidate_string).unwrap()); + } + + #[test] + fn test_bincode() { + let rng = &mut thread_rng(); + + let private_key = PrivateKey::new(rng); + let expected_address: Address = private_key.into(); + + // Serialize + let expected_bytes = expected_address.to_bytes_le().unwrap(); + assert_eq!(&expected_bytes[..], &bincode::serialize(&expected_address).unwrap()[..]); + + // Deserialize + assert_eq!(expected_address, Address::read_le(&expected_bytes[..]).unwrap()); + assert_eq!(expected_address, bincode::deserialize(&expected_bytes[..]).unwrap()); + } +} diff --git a/dpc/src/block/transactions.rs b/dpc/src/block/transactions.rs index 059983480f..90d16ef656 100644 --- a/dpc/src/block/transactions.rs +++ b/dpc/src/block/transactions.rs @@ -107,10 +107,7 @@ impl Transactions { pub fn to_transactions_root(&self) -> Result { match self.is_valid() { true => { - let transaction_ids = (*self) - .iter() - .map(Transaction::to_transaction_id) - .collect::>>()?; + let transaction_ids = (*self).iter().map(Transaction::transaction_id).collect::>(); Ok(*MerkleTree::::new( Arc::new(N::transactions_tree_parameters().clone()), diff --git a/dpc/src/circuits/inner_circuit.rs b/dpc/src/circuits/inner_circuit.rs index 3f90f42ac9..40e637cd0d 100644 --- a/dpc/src/circuits/inner_circuit.rs +++ b/dpc/src/circuits/inner_circuit.rs @@ -140,7 +140,7 @@ impl ConstraintSynthesizer for InnerCircuit // Declares a constant for a 0 value in a record. let zero_value = UInt8::constant_vec(&(0u64).to_bytes_le()?); // Declares a constant for an empty payload in a record. - let empty_payload = UInt8::constant_vec(&Payload::default().to_bytes_le()?); + let empty_payload = UInt8::constant_vec(&Payload::::default().to_bytes_le()?); // Declare the noop program ID as bytes. let noop_program_id_bytes = UInt8::constant_vec(&N::noop_program_id().to_bytes_le()?); diff --git a/dpc/src/errors/record.rs b/dpc/src/errors/record.rs index 9abfee88a5..b0e21a19b1 100644 --- a/dpc/src/errors/record.rs +++ b/dpc/src/errors/record.rs @@ -42,6 +42,9 @@ pub enum RecordError { #[error("Given compute key does not correspond to the record owner")] IncorrectComputeKey, + #[error("Invalid commitment. Expected {}, found {}", _0, _1)] + InvalidCommitment(String, String), + #[error("{}", _0)] PRFError(#[from] PRFError), @@ -49,6 +52,12 @@ pub enum RecordError { SignatureError(#[from] SignatureError), } +impl From for RecordError { + fn from(error: serde_json::Error) -> Self { + RecordError::Crate("serde_json::Error", format!("{:?}", error)) + } +} + impl From for RecordError { fn from(error: std::io::Error) -> Self { RecordError::Crate("std::io", format!("{:?}", error)) diff --git a/dpc/src/ledger/memory_pool.rs b/dpc/src/ledger/memory_pool.rs index 0c7c77386d..45db2fef16 100644 --- a/dpc/src/ledger/memory_pool.rs +++ b/dpc/src/ledger/memory_pool.rs @@ -40,13 +40,7 @@ impl MemoryPool { /// Returns `true` if the given transaction exists in the memory pool. pub fn contains_transaction(&self, transaction: &Transaction) -> bool { - match transaction.to_transaction_id() { - Ok(id) => self.transactions.contains_key(&id), - Err(error) => { - eprintln!("Failed to lookup transaction ID: {}", error); - return false; - } - } + self.transactions.contains_key(&transaction.transaction_id()) } /// Returns the transactions in the memory pool. @@ -67,7 +61,7 @@ impl MemoryPool { } // Ensure the transaction does not already exist in the memory pool. - let transaction_id = transaction.to_transaction_id()?; + let transaction_id = transaction.transaction_id(); if self.transactions.contains_key(&transaction_id) { return Err(anyhow!("Transaction already exists in memory pool")); } diff --git a/dpc/src/network/testnet1.rs b/dpc/src/network/testnet1.rs index d265cfb087..dbb06e5200 100644 --- a/dpc/src/network/testnet1.rs +++ b/dpc/src/network/testnet1.rs @@ -80,10 +80,16 @@ impl Network for Testnet1 { const NETWORK_ID: u16 = 1u16; const NETWORK_NAME: &'static str = "testnet1"; - const NUM_EVENTS: usize = 1024; + const NUM_EVENTS: usize = 512; const NUM_INPUT_RECORDS: usize = 2; const NUM_OUTPUT_RECORDS: usize = 2; + const ADDRESS_SIZE_IN_BYTES: usize = 32; + const CIPHERTEXT_SIZE_IN_BYTES: usize = 320; + const RECORD_SIZE_IN_BYTES: usize = 280; + const PAYLOAD_SIZE_IN_BYTES: usize = 128; + const TRANSITION_SIZE_IN_BYTES: usize = 1129; + const POSW_PROOF_SIZE_IN_BYTES: usize = 771; const POSW_NUM_LEAVES: usize = 8; const POSW_TREE_DEPTH: usize = 3; @@ -108,6 +114,7 @@ impl Network for Testnet1 { type InnerSNARKGadget = Groth16VerifierGadget; type OuterSNARK = Groth16>; + type OuterProof = ::Proof; type ProgramSNARK = Groth16>; type ProgramSNARKGadget = Groth16VerifierGadget; @@ -144,6 +151,7 @@ impl Network for Testnet1 { type CommitmentScheme = BHPCommitment; type CommitmentGadget = BHPCommitmentGadget; + type CommitmentRandomness = ::Randomness; type Commitment = ::Output; type CommitmentsTreeCRH = BHPCRH; diff --git a/dpc/src/network/testnet2.rs b/dpc/src/network/testnet2.rs index 2778a62ae9..0247f6fcc9 100644 --- a/dpc/src/network/testnet2.rs +++ b/dpc/src/network/testnet2.rs @@ -84,10 +84,16 @@ impl Network for Testnet2 { const NETWORK_ID: u16 = 2u16; const NETWORK_NAME: &'static str = "testnet2"; - const NUM_EVENTS: usize = 1024; + const NUM_EVENTS: usize = 512; const NUM_INPUT_RECORDS: usize = 2; const NUM_OUTPUT_RECORDS: usize = 2; + const ADDRESS_SIZE_IN_BYTES: usize = 32; + const CIPHERTEXT_SIZE_IN_BYTES: usize = 320; + const RECORD_SIZE_IN_BYTES: usize = 280; + const PAYLOAD_SIZE_IN_BYTES: usize = 128; + const TRANSITION_SIZE_IN_BYTES: usize = 1129; + const POSW_PROOF_SIZE_IN_BYTES: usize = 771; const POSW_NUM_LEAVES: usize = 8; const POSW_TREE_DEPTH: usize = 3; @@ -112,6 +118,7 @@ impl Network for Testnet2 { type InnerSNARKGadget = Groth16VerifierGadget; type OuterSNARK = Groth16>; + type OuterProof = ::Proof; type ProgramSNARK = MarlinSNARK, FiatShamirAlgebraicSpongeRng>, MarlinTestnet2Mode, ProgramPublicVariables>; type ProgramSNARKGadget = MarlinVerificationGadget, SonicKZG10Gadget>; @@ -148,6 +155,7 @@ impl Network for Testnet2 { type CommitmentScheme = BHPCommitment; type CommitmentGadget = BHPCommitmentGadget; + type CommitmentRandomness = ::Randomness; type Commitment = ::Output; type CommitmentsTreeCRH = BHPCRH; diff --git a/dpc/src/record/ciphertext.rs b/dpc/src/record/ciphertext.rs index 679882ea49..32ee63b880 100644 --- a/dpc/src/record/ciphertext.rs +++ b/dpc/src/record/ciphertext.rs @@ -14,20 +14,25 @@ // You should have received a copy of the GNU General Public License // along with the snarkVM library. If not, see . -use crate::{Address, Network, Payload, Record, ViewKey}; -use snarkvm_algorithms::traits::{CommitmentScheme, EncryptionScheme, CRH}; +use crate::{Address, Network, Payload, Record, RecordError, ViewKey}; +use snarkvm_algorithms::traits::{EncryptionScheme, CRH}; use snarkvm_utilities::{ + fmt, io::{Cursor, Result as IoResult}, marker::PhantomData, + str::FromStr, to_bytes_le, FromBytes, + FromBytesDeserializer, Read, ToBytes, + ToBytesSerializer, Write, }; use anyhow::{anyhow, Result}; use rand::{thread_rng, CryptoRng, Rng}; +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; #[derive(Derivative)] #[derivative( @@ -43,7 +48,8 @@ pub struct RecordCiphertext { } impl RecordCiphertext { - pub fn new(ciphertext: Vec) -> Self { + pub fn from_vec(ciphertext: Vec) -> Self { + assert_eq!(N::CIPHERTEXT_SIZE_IN_BYTES, ciphertext.len()); Self { ciphertext, phantom: PhantomData, @@ -78,7 +84,7 @@ impl RecordCiphertext { let randomizer = N::account_encryption_scheme().generate_randomness(rng); let ciphertext = N::account_encryption_scheme().encrypt(&*record.owner(), &randomizer, &buffer)?; - Ok((Self::new(ciphertext), randomizer)) + Ok((Self::from_vec(ciphertext), randomizer)) } /// Decrypt the record ciphertext using the view key of the recipient. @@ -93,7 +99,7 @@ impl RecordCiphertext { let payload = Payload::read_le(&mut cursor)?; let program_id = N::ProgramID::read_le(&mut cursor)?; let serial_number_nonce = N::SerialNumber::read_le(&mut cursor)?; - let commitment_randomness = ::Randomness::read_le(&mut cursor)?; + let commitment_randomness = N::CommitmentRandomness::read_le(&mut cursor)?; // Derive the record owner. let owner = Address::from_view_key(&recipient_view_key); @@ -114,30 +120,126 @@ impl RecordCiphertext { } } -impl Default for RecordCiphertext { - fn default() -> Self { - let (record, _randomness) = Self::encrypt(&Record::default(), &mut thread_rng()).unwrap(); - record +impl FromBytes for RecordCiphertext { + #[inline] + fn read_le(mut reader: R) -> IoResult { + let mut ciphertext = Vec::with_capacity(N::CIPHERTEXT_SIZE_IN_BYTES); + for _ in 0..N::CIPHERTEXT_SIZE_IN_BYTES { + ciphertext.push(u8::read_le(&mut reader)?); + } + + Ok(Self::from_vec(ciphertext)) } } impl ToBytes for RecordCiphertext { #[inline] fn write_le(&self, mut writer: W) -> IoResult<()> { - (self.ciphertext.len() as u16).write_le(&mut writer)?; self.ciphertext.write_le(&mut writer) } } -impl FromBytes for RecordCiphertext { - #[inline] - fn read_le(mut reader: R) -> IoResult { - let ciphertext_len = u16::read_le(&mut reader)?; - let mut ciphertext = Vec::with_capacity(ciphertext_len as usize); - for _ in 0..ciphertext_len { - ciphertext.push(u8::read_le(&mut reader)?); +impl FromStr for RecordCiphertext { + type Err = RecordError; + + fn from_str(payload_hex: &str) -> Result { + Ok(Self::from_bytes_le(&hex::decode(payload_hex)?)?) + } +} + +impl fmt::Display for RecordCiphertext { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let bytes = self.to_bytes_le().expect("Failed to convert ciphertext to bytes"); + write!(f, "{}", hex::encode(bytes)) + } +} + +impl Serialize for RecordCiphertext { + fn serialize(&self, serializer: S) -> Result { + match serializer.is_human_readable() { + true => serializer.collect_str(self), + false => ToBytesSerializer::serialize(self, serializer), } + } +} + +impl<'de, N: Network> Deserialize<'de> for RecordCiphertext { + fn deserialize>(deserializer: D) -> Result { + match deserializer.is_human_readable() { + true => FromStr::from_str(&String::deserialize(deserializer)?).map_err(de::Error::custom), + false => { + FromBytesDeserializer::::deserialize(deserializer, "ciphertext", N::CIPHERTEXT_SIZE_IN_BYTES) + } + } + } +} + +impl Default for RecordCiphertext { + fn default() -> Self { + let (record, _randomness) = Self::encrypt(&Record::default(), &mut thread_rng()).unwrap(); + record + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::testnet2::Testnet2; + use snarkvm_utilities::UniformRand; + + use rand::thread_rng; + + #[test] + fn test_serde_json() { + let rng = &mut thread_rng(); + + let expected_ciphertext = RecordCiphertext::::from_vec( + (0..Testnet2::CIPHERTEXT_SIZE_IN_BYTES) + .map(|_| u8::rand(rng)) + .collect::>(), + ); + + // Serialize + let expected_string = &expected_ciphertext.to_string(); + let candidate_string = serde_json::to_string(&expected_ciphertext).unwrap(); + assert_eq!( + expected_string, + serde_json::Value::from_str(&candidate_string) + .unwrap() + .as_str() + .unwrap() + ); + + // Deserialize + assert_eq!( + expected_ciphertext, + RecordCiphertext::from_str(&expected_string).unwrap() + ); + assert_eq!(expected_ciphertext, serde_json::from_str(&candidate_string).unwrap()); + } - Ok(Self::new(ciphertext)) + #[test] + fn test_bincode() { + let rng = &mut thread_rng(); + + let expected_ciphertext = RecordCiphertext::::from_vec( + (0..Testnet2::CIPHERTEXT_SIZE_IN_BYTES) + .map(|_| u8::rand(rng)) + .collect::>(), + ); + + // Serialize + let expected_bytes = expected_ciphertext.to_bytes_le().unwrap(); + assert_eq!( + &expected_bytes[..], + &bincode::serialize(&expected_ciphertext).unwrap()[..] + ); + + // Deserialize + assert_eq!( + expected_ciphertext, + RecordCiphertext::read_le(&expected_bytes[..]).unwrap() + ); + assert_eq!(expected_ciphertext, bincode::deserialize(&expected_bytes[..]).unwrap()); } } diff --git a/dpc/src/record/payload.rs b/dpc/src/record/payload.rs index 128398d38a..34d398f435 100644 --- a/dpc/src/record/payload.rs +++ b/dpc/src/record/payload.rs @@ -14,78 +14,164 @@ // You should have received a copy of the GNU General Public License // along with the snarkVM library. If not, see . -use snarkvm_utilities::{FromBytes, ToBytes}; +use crate::{Network, RecordError}; +use snarkvm_utilities::{FromBytes, FromBytesDeserializer, ToBytes, ToBytesSerializer}; -use std::io::{Read, Result as IoResult, Write}; - -pub const PAYLOAD_SIZE: usize = 128; +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; +use std::{ + fmt, + io::{Read, Result as IoResult, Write}, + marker::PhantomData, + str::FromStr, +}; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct Payload([u8; PAYLOAD_SIZE]); +pub struct Payload(Vec, PhantomData); -impl Payload { +impl Payload { pub fn from(bytes: &[u8]) -> Self { - assert!(bytes.len() <= PAYLOAD_SIZE); + assert!(bytes.len() <= N::PAYLOAD_SIZE_IN_BYTES); // Pad the bytes up to PAYLOAD_SIZE. let mut buffer = bytes.to_vec(); - buffer.resize(PAYLOAD_SIZE, 0u8); - - // Copy exactly PAYLOAD_SIZE. - let mut payload = [0u8; PAYLOAD_SIZE]; - payload.copy_from_slice(&buffer); + buffer.resize(N::PAYLOAD_SIZE_IN_BYTES, 0u8); - Self(payload) + Self(buffer, PhantomData) } pub fn is_empty(&self) -> bool { - self.0 == [0u8; PAYLOAD_SIZE] + let mut payload = vec![0u8; N::PAYLOAD_SIZE_IN_BYTES]; + payload.copy_from_slice(&self.0); + payload == vec![0u8; N::PAYLOAD_SIZE_IN_BYTES] + } + + pub fn size() -> usize { + N::PAYLOAD_SIZE_IN_BYTES } pub fn as_any(&self) -> &dyn std::any::Any { self } +} - pub const fn size(&self) -> usize { - PAYLOAD_SIZE +impl FromBytes for Payload { + #[inline] + fn read_le(mut reader: R) -> IoResult { + let mut buffer = vec![0u8; N::PAYLOAD_SIZE_IN_BYTES]; + reader.read_exact(&mut buffer)?; + Ok(Self::from(&buffer)) } } -impl ToBytes for Payload { +impl ToBytes for Payload { #[inline] fn write_le(&self, mut writer: W) -> IoResult<()> { self.0.write_le(&mut writer) } } -impl FromBytes for Payload { - #[inline] - fn read_le(mut reader: R) -> IoResult { - Ok(Self(FromBytes::read_le(&mut reader)?)) +impl FromStr for Payload { + type Err = RecordError; + + fn from_str(payload_hex: &str) -> Result { + Ok(Self::read_le(&hex::decode(payload_hex)?[..])?) + } +} + +impl fmt::Display for Payload { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let bytes = self.to_bytes_le().expect("Failed to convert payload to bytes"); + write!(f, "{}", hex::encode(bytes)) + } +} + +impl Serialize for Payload { + fn serialize(&self, serializer: S) -> Result { + match serializer.is_human_readable() { + true => serializer.collect_str(self), + false => ToBytesSerializer::serialize(self, serializer), + } + } +} + +impl<'de, N: Network> Deserialize<'de> for Payload { + fn deserialize>(deserializer: D) -> Result { + match deserializer.is_human_readable() { + true => FromStr::from_str(&String::deserialize(deserializer)?).map_err(de::Error::custom), + false => FromBytesDeserializer::::deserialize(deserializer, "payload", N::PAYLOAD_SIZE_IN_BYTES), + } } } -impl Default for Payload { +impl Default for Payload { fn default() -> Self { - Self([0u8; PAYLOAD_SIZE]) + Self::from(&[]) } } #[cfg(test)] mod tests { use super::*; + use crate::testnet2::Testnet2; use snarkvm_utilities::UniformRand; + use rand::thread_rng; + #[test] fn test_payload_from() { - let rng = &mut rand::thread_rng(); + let rng = &mut thread_rng(); // Create a random byte array, construct a payload from it, and check its byte array matches. - for i in 0..PAYLOAD_SIZE { + for i in 0..Testnet2::PAYLOAD_SIZE_IN_BYTES { let expected_payload = (0..i).map(|_| u8::rand(rng)).collect::>(); - let candidate_payload = Payload::from(&expected_payload).to_bytes_le().unwrap(); + let candidate_payload = Payload::::from(&expected_payload).to_bytes_le().unwrap(); assert_eq!(expected_payload, candidate_payload[0..i]); - assert_eq!(vec![0u8; PAYLOAD_SIZE - i], candidate_payload[i..]); + assert_eq!(vec![0u8; Testnet2::PAYLOAD_SIZE_IN_BYTES - i], candidate_payload[i..]); } } + + #[test] + fn test_serde_json() { + let rng = &mut thread_rng(); + + let expected_payload = Payload::::from( + &(0..Testnet2::PAYLOAD_SIZE_IN_BYTES) + .map(|_| u8::rand(rng)) + .collect::>(), + ); + + // Serialize + let expected_string = &expected_payload.to_string(); + let candidate_string = serde_json::to_string(&expected_payload).unwrap(); + assert_eq!( + expected_string, + serde_json::Value::from_str(&candidate_string) + .unwrap() + .as_str() + .unwrap() + ); + + // Deserialize + assert_eq!(expected_payload, Payload::from_str(&expected_string).unwrap()); + assert_eq!(expected_payload, serde_json::from_str(&candidate_string).unwrap()); + } + + #[test] + fn test_bincode() { + let rng = &mut thread_rng(); + + let expected_payload = Payload::::from( + &(0..Testnet2::PAYLOAD_SIZE_IN_BYTES) + .map(|_| u8::rand(rng)) + .collect::>(), + ); + + // Serialize + let expected_bytes = expected_payload.to_bytes_le().unwrap(); + assert_eq!(&expected_bytes[..], &bincode::serialize(&expected_payload).unwrap()[..]); + + // Deserialize + assert_eq!(expected_payload, Payload::read_le(&expected_bytes[..]).unwrap()); + assert_eq!(expected_payload, bincode::deserialize(&expected_bytes[..]).unwrap()); + } } diff --git a/dpc/src/record/record.rs b/dpc/src/record/record.rs index 9f6f28e93a..1dd63104c7 100644 --- a/dpc/src/record/record.rs +++ b/dpc/src/record/record.rs @@ -16,9 +16,10 @@ use crate::{Address, ComputeKey, Network, Payload, RecordError}; use snarkvm_algorithms::traits::{CommitmentScheme, PRF}; -use snarkvm_utilities::{to_bytes_le, FromBytes, ToBytes, UniformRand}; +use snarkvm_utilities::{to_bytes_le, FromBytes, FromBytesDeserializer, ToBytes, ToBytesSerializer, UniformRand}; use rand::{CryptoRng, Rng}; +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use std::{ fmt, io::{Read, Result as IoResult, Write}, @@ -37,10 +38,10 @@ pub struct Record { owner: Address, // TODO (raychu86) use AleoAmount which will guard the value range value: u64, - payload: Payload, + payload: Payload, program_id: N::ProgramID, serial_number_nonce: N::SerialNumber, - commitment_randomness: ::Randomness, + commitment_randomness: N::CommitmentRandomness, commitment: N::Commitment, } @@ -50,7 +51,7 @@ impl Record { Self::new_input( owner, 0, - Payload::default(), + Payload::::default(), *N::noop_program_id(), UniformRand::rand(rng), UniformRand::rand(rng), @@ -61,10 +62,10 @@ impl Record { pub fn new_input( owner: Address, value: u64, - payload: Payload, + payload: Payload, program_id: N::ProgramID, serial_number_nonce: N::SerialNumber, - commitment_randomness: ::Randomness, + commitment_randomness: N::CommitmentRandomness, ) -> Result { Self::from( owner, @@ -85,7 +86,7 @@ impl Record { Self::new_output( owner, 0, - Payload::default(), + Payload::::default(), *N::noop_program_id(), serial_number_nonce, rng, @@ -96,7 +97,7 @@ impl Record { pub fn new_output( owner: Address, value: u64, - payload: Payload, + payload: Payload, program_id: N::ProgramID, serial_number_nonce: N::SerialNumber, rng: &mut R, @@ -111,14 +112,13 @@ impl Record { ) } - #[allow(clippy::too_many_arguments)] pub fn from( owner: Address, value: u64, - payload: Payload, + payload: Payload, program_id: N::ProgramID, serial_number_nonce: N::SerialNumber, - commitment_randomness: ::Randomness, + commitment_randomness: N::CommitmentRandomness, ) -> Result { // Determine if the record is a dummy. let is_dummy = value == 0 && payload.is_empty() && program_id == *N::noop_program_id(); @@ -163,7 +163,7 @@ impl Record { } /// Returns the record payload. - pub fn payload(&self) -> &Payload { + pub fn payload(&self) -> &Payload { &self.payload } @@ -178,7 +178,7 @@ impl Record { } /// Returns the randomness used for the commitment. - pub fn commitment_randomness(&self) -> ::Randomness { + pub fn commitment_randomness(&self) -> N::CommitmentRandomness { self.commitment_randomness.clone() } @@ -220,11 +220,10 @@ impl FromBytes for Record { fn read_le(mut reader: R) -> IoResult { let owner: Address = FromBytes::read_le(&mut reader)?; let value: u64 = FromBytes::read_le(&mut reader)?; - let payload: Payload = FromBytes::read_le(&mut reader)?; + let payload: Payload = FromBytes::read_le(&mut reader)?; let program_id: N::ProgramID = FromBytes::read_le(&mut reader)?; let serial_number_nonce: N::SerialNumber = FromBytes::read_le(&mut reader)?; - let commitment_randomness: ::Randomness = - FromBytes::read_le(&mut reader)?; + let commitment_randomness: N::CommitmentRandomness = FromBytes::read_le(&mut reader)?; Ok(Self::from( owner, @@ -241,16 +240,170 @@ impl FromStr for Record { type Err = RecordError; fn from_str(record: &str) -> Result { - Ok(Self::read_le(&hex::decode(record)?[..])?) + let record = serde_json::Value::from_str(record)?; + let commitment: N::Commitment = serde_json::from_value(record["commitment"].clone())?; + + // Recover the record. + let record = Self::from( + serde_json::from_value(record["owner"].clone())?, + serde_json::from_value(record["value"].clone())?, + serde_json::from_value(record["payload"].clone())?, + serde_json::from_value(record["program_id"].clone())?, + serde_json::from_value(record["serial_number_nonce"].clone())?, + serde_json::from_value(record["commitment_randomness"].clone())?, + )?; + + // Ensure the commitment matches. + match commitment == record.commitment() { + true => Ok(record), + false => Err(RecordError::InvalidCommitment( + commitment.to_string(), + record.commitment().to_string(), + )), + } } } impl fmt::Display for Record { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "{}", - hex::encode(to_bytes_le![self].expect("serialization to bytes failed")) + let record = serde_json::json!({ + "owner": self.owner, + "value": self.value, + "payload": self.payload, + "program_id": self.program_id, + "serial_number_nonce": self.serial_number_nonce, + "commitment_randomness": self.commitment_randomness, + "commitment": self.commitment + }); + write!(f, "{}", record) + } +} + +impl Serialize for Record { + fn serialize(&self, serializer: S) -> Result { + match serializer.is_human_readable() { + true => serializer.collect_str(self), + false => ToBytesSerializer::serialize(self, serializer), + } + } +} + +impl<'de, N: Network> Deserialize<'de> for Record { + fn deserialize>(deserializer: D) -> Result { + match deserializer.is_human_readable() { + true => FromStr::from_str(&String::deserialize(deserializer)?).map_err(de::Error::custom), + false => FromBytesDeserializer::::deserialize(deserializer, "record", N::RECORD_SIZE_IN_BYTES), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{testnet2::Testnet2, Address, PrivateKey}; + use snarkvm_utilities::UniformRand; + + use rand::thread_rng; + + #[test] + fn test_serde_json_noop() { + let rng = &mut thread_rng(); + let address: Address = PrivateKey::new(rng).into(); + + // Noop output record + let expected_record = Record::new_noop_output(address, UniformRand::rand(rng), rng).unwrap(); + + // Serialize + let expected_string = &expected_record.to_string(); + let candidate_string = serde_json::to_string(&expected_record).unwrap(); + assert_eq!( + expected_string, + serde_json::Value::from_str(&candidate_string) + .unwrap() + .as_str() + .unwrap() + ); + + // Deserialize + assert_eq!(expected_record, Record::from_str(&expected_string).unwrap()); + assert_eq!(expected_record, serde_json::from_str(&candidate_string).unwrap()); + } + + #[test] + fn test_serde_json() { + let rng = &mut thread_rng(); + let address: Address = PrivateKey::new(rng).into(); + + // Output record + let mut payload = [0u8; Testnet2::PAYLOAD_SIZE_IN_BYTES]; + rng.fill(&mut payload); + let expected_record = Record::new_output( + address, + 1234, + Payload::from_bytes_le(&payload).unwrap(), + *Testnet2::noop_program_id(), + UniformRand::rand(rng), + rng, ) + .unwrap(); + + // Serialize + let expected_string = &expected_record.to_string(); + let candidate_string = serde_json::to_string(&expected_record).unwrap(); + assert_eq!( + expected_string, + serde_json::Value::from_str(&candidate_string) + .unwrap() + .as_str() + .unwrap() + ); + + // Deserialize + assert_eq!(expected_record, Record::from_str(&expected_string).unwrap()); + assert_eq!(expected_record, serde_json::from_str(&candidate_string).unwrap()); + } + + #[test] + fn test_bincode_noop() { + let rng = &mut thread_rng(); + let address: Address = PrivateKey::new(rng).into(); + + // Noop output record + let expected_record = Record::new_noop_output(address, UniformRand::rand(rng), rng).unwrap(); + + // Serialize + let expected_bytes = expected_record.to_bytes_le().unwrap(); + assert_eq!(&expected_bytes[..], &bincode::serialize(&expected_record).unwrap()[..]); + + // Deserialize + assert_eq!(expected_record, Record::read_le(&expected_bytes[..]).unwrap()); + assert_eq!(expected_record, bincode::deserialize(&expected_bytes[..]).unwrap()); + } + + #[test] + fn test_bincode() { + let rng = &mut thread_rng(); + let address: Address = PrivateKey::new(rng).into(); + + // Output record + let mut payload = [0u8; Testnet2::PAYLOAD_SIZE_IN_BYTES]; + rng.fill(&mut payload); + let expected_record = Record::new_output( + address, + 1234, + Payload::from_bytes_le(&payload).unwrap(), + *Testnet2::noop_program_id(), + UniformRand::rand(rng), + rng, + ) + .unwrap(); + + // Serialize + let expected_bytes = expected_record.to_bytes_le().unwrap(); + assert_eq!(&expected_bytes[..], &bincode::serialize(&expected_record).unwrap()[..]); + + // Deserialize + assert_eq!(expected_record, Record::read_le(&expected_bytes[..]).unwrap()); + assert_eq!(expected_record, bincode::deserialize(&expected_bytes[..]).unwrap()); } } diff --git a/dpc/src/record/tests.rs b/dpc/src/record/tests.rs index fe4b155289..c68e1c6266 100644 --- a/dpc/src/record/tests.rs +++ b/dpc/src/record/tests.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with the snarkVM library. If not, see . -use crate::{record::*, testnet2::*, Account, AccountScheme, Network, Payload, Record, ViewKey, PAYLOAD_SIZE}; +use crate::{record::*, testnet2::*, Account, AccountScheme, Network, Payload, Record, ViewKey}; use snarkvm_utilities::{FromBytes, UniformRand}; use rand::{Rng, SeedableRng}; @@ -30,7 +30,7 @@ fn test_record_ciphertext() { let account = Account::::new(rng); let value = rng.gen(); - let mut payload = [0u8; PAYLOAD_SIZE]; + let mut payload = [0u8; Testnet2::PAYLOAD_SIZE_IN_BYTES]; rng.fill(&mut payload); let expected_record = Record::new_input( diff --git a/dpc/src/traits/network.rs b/dpc/src/traits/network.rs index c00b1c130c..e705428ea1 100644 --- a/dpc/src/traits/network.rs +++ b/dpc/src/traits/network.rs @@ -35,7 +35,7 @@ use snarkvm_utilities::{ use anyhow::Result; use rand::{CryptoRng, Rng}; -use serde::Serialize; +use serde::{de::DeserializeOwned, Serialize}; use std::{cell::RefCell, rc::Rc}; #[rustfmt::skip] @@ -47,6 +47,12 @@ pub trait Network: 'static + Clone + Debug + PartialEq + Eq + Serialize + Send + const NUM_INPUT_RECORDS: usize; const NUM_OUTPUT_RECORDS: usize; const NUM_TOTAL_RECORDS: usize = Self::NUM_INPUT_RECORDS + Self::NUM_OUTPUT_RECORDS; + + const ADDRESS_SIZE_IN_BYTES: usize; + const CIPHERTEXT_SIZE_IN_BYTES: usize; + const PAYLOAD_SIZE_IN_BYTES: usize; + const RECORD_SIZE_IN_BYTES: usize; + const TRANSITION_SIZE_IN_BYTES: usize; const POSW_PROOF_SIZE_IN_BYTES: usize; const POSW_NUM_LEAVES: usize; @@ -76,7 +82,8 @@ pub trait Network: 'static + Clone + Debug + PartialEq + Eq + Serialize + Send + type InnerSNARKGadget: SNARKVerifierGadget; /// SNARK for proof-verification checks. - type OuterSNARK: SNARK>; + type OuterSNARK: SNARK, Proof = Self::OuterProof>; + type OuterProof: Clone + Debug + ToBytes + FromBytes + PartialEq + Eq + Serialize + DeserializeOwned + Sync + Send; /// SNARK for Aleo programs. type ProgramSNARK: SNARK, ProvingKey = Self::ProgramProvingKey, VerifyingKey = Self::ProgramVerifyingKey, Proof = Self::ProgramProof, UniversalSetupConfig = usize>; @@ -102,55 +109,56 @@ pub trait Network: 'static + Clone + Debug + PartialEq + Eq + Serialize + Send + + SignatureSchemeOperations; type AccountSignatureGadget: SignatureGadget; type AccountSignaturePublicKey: ToConstraintField + Clone + Default + Debug + Display + ToBytes + FromBytes + PartialEq + Eq + Hash + Sync + Send; - type AccountSignature: Clone + Debug + Default + ToBytes + FromBytes + Send + Sync + PartialEq + Eq; + type AccountSignature: Clone + Debug + Default + ToBytes + FromBytes + Serialize + DeserializeOwned + Send + Sync + PartialEq + Eq; /// CRH schemes for the block hash. Invoked only over `Self::InnerScalarField`. type BlockHashCRH: CRH; type BlockHashCRHGadget: CRHGadget; - type BlockHash: ToConstraintField + Copy + Clone + Default + Debug + Display + ToBytes + FromBytes + Serialize + PartialEq + Eq + Hash + Sync + Send; + type BlockHash: ToConstraintField + Copy + Clone + Default + Debug + Display + ToBytes + FromBytes + Serialize + DeserializeOwned + PartialEq + Eq + Hash + Sync + Send; /// Masked Merkle tree for the block header root on Proof of Succinct Work (PoSW). Invoked only over `Self::InnerScalarField`. type BlockHeaderTreeCRH: CRH; type BlockHeaderTreeCRHGadget: MaskedCRHGadget<::H, Self::InnerScalarField, OutputGadget = >::Seed>; type BlockHeaderTreeParameters: MaskedMerkleParameters; - type BlockHeaderRoot: ToConstraintField + Copy + Clone + Default + Debug + Display + ToBytes + FromBytes + PartialEq + Eq + Hash + Sync + Send; + type BlockHeaderRoot: ToConstraintField + Copy + Clone + Default + Debug + Display + ToBytes + FromBytes + Serialize + DeserializeOwned + PartialEq + Eq + Hash + Sync + Send; /// CRH scheme for encrypted record ID. Invoked only over `Self::InnerScalarField`. type CiphertextIDCRH: CRH; type CiphertextIDCRHGadget: CRHGadget; - type CiphertextID: ToConstraintField + Copy + Clone + Default + Debug + Display + ToBytes + FromBytes + PartialEq + Eq + Hash + Sync + Send; + type CiphertextID: ToConstraintField + Copy + Clone + Default + Debug + Display + ToBytes + FromBytes + Serialize + DeserializeOwned + PartialEq + Eq + Hash + Sync + Send; /// Commitment scheme for records. Invoked only over `Self::InnerScalarField`. - type CommitmentScheme: CommitmentScheme; + type CommitmentScheme: CommitmentScheme; type CommitmentGadget: CommitmentGadget; - type Commitment: ToConstraintField + Copy + Clone + Debug + Display + Default + ToBytes + FromBytes + PartialEq + Eq + Hash + Sync + Send; + type CommitmentRandomness: Copy + Clone + Debug + Display + Default + ToBytes + FromBytes + Serialize + DeserializeOwned + PartialEq + Eq + Hash + UniformRand + Sync + Send; + type Commitment: ToConstraintField + Copy + Clone + Debug + Display + Default + ToBytes + FromBytes + Serialize + DeserializeOwned + PartialEq + Eq + Hash + Sync + Send; /// Merkle tree scheme for the commitments root on the ledger. Invoked only over `Self::InnerScalarField`. type CommitmentsTreeCRH: CRH; type CommitmentsTreeCRHGadget: CRHGadget; type CommitmentsTreeParameters: MerkleParameters; - type CommitmentsRoot: ToConstraintField + Copy + Clone + Default + Debug + Display + ToBytes + FromBytes + PartialEq + Eq + Hash + Sync + Send; + type CommitmentsRoot: ToConstraintField + Copy + Clone + Default + Debug + Display + ToBytes + FromBytes + Serialize + DeserializeOwned + PartialEq + Eq + Hash + Sync + Send; /// CRH for deriving function IDs. Invoked only over `Self::OuterScalarField`. type FunctionIDCRH: CRH; type FunctionIDCRHGadget: CRHGadget; - type FunctionID: ToConstraintField + Copy + Clone + Default + Debug + Display + ToBytes + FromBytes + PartialEq + Eq + Hash + Sync + Send; + type FunctionID: ToConstraintField + Copy + Clone + Default + Debug + Display + ToBytes + FromBytes + Serialize + DeserializeOwned + PartialEq + Eq + Hash + Sync + Send; /// Crypto hash for deriving the function inputs digest. Invoked only over `Self::InnerScalarField`. type FunctionInputsCRH: CRH; type FunctionInputsCRHGadget: CRHGadget; - type FunctionInputsDigest: ToConstraintField + Copy + Clone + Default + Debug + Display + ToBytes + FromBytes + PartialEq + Eq + Hash + Sync + Send; + type FunctionInputsDigest: ToConstraintField + Copy + Clone + Default + Debug + Display + ToBytes + FromBytes + Serialize + DeserializeOwned + PartialEq + Eq + Hash + Sync + Send; /// CRH for hash of the `Self::InnerSNARK` verifying keys. Invoked only over `Self::OuterScalarField`. type InnerCircuitIDCRH: CRH; type InnerCircuitIDCRHGadget: CRHGadget; - type InnerCircuitID: ToConstraintField + Copy + Clone + Default + Debug + Display + ToBytes + FromBytes + PartialEq + Eq + Hash + Sync + Send; + type InnerCircuitID: ToConstraintField + Copy + Clone + Default + Debug + Display + ToBytes + FromBytes + Serialize + DeserializeOwned + PartialEq + Eq + Hash + Sync + Send; /// Merkle tree scheme for the local commitments root in transitions. Invoked only over `Self::InnerScalarField`. type LocalCommitmentsTreeCRH: CRH; type LocalCommitmentsTreeCRHGadget: CRHGadget; type LocalCommitmentsTreeParameters: MerkleParameters; - type LocalCommitmentsRoot: ToConstraintField + Copy + Clone + Default + Debug + Display + ToBytes + FromBytes + PartialEq + Eq + Hash + Sync + Send; + type LocalCommitmentsRoot: ToConstraintField + Copy + Clone + Default + Debug + Display + ToBytes + FromBytes + Serialize + DeserializeOwned + PartialEq + Eq + Hash + Sync + Send; /// Schemes for PoSW. Invoked only over `Self::InnerScalarField`. type PoSWMaskPRF: PRF, Seed = Self::BlockHeaderRoot, Output = Self::InnerScalarField>; @@ -161,32 +169,32 @@ pub trait Network: 'static + Clone + Debug + PartialEq + Eq + Serialize + Send + type ProgramFunctionsTreeCRH: CRH; type ProgramFunctionsTreeCRHGadget: CRHGadget; type ProgramFunctionsTreeParameters: MerkleParameters; - type ProgramID: ToConstraintField + Copy + Clone + Default + Debug + Display + ToBytes + FromBytes + PartialEq + Eq + Hash + Sync + Send; + type ProgramID: ToConstraintField + Copy + Clone + Default + Debug + Display + ToBytes + FromBytes + Serialize + DeserializeOwned + PartialEq + Eq + Hash + Sync + Send; /// PRF for computing serial numbers. Invoked only over `Self::InnerScalarField`. // TODO (howardwu): TEMPORARY - Revisit Vec after upgrading serial number construction. type SerialNumberPRF: PRF, Seed = Self::InnerScalarField, Output = Self::SerialNumber>; type SerialNumberPRFGadget: PRFGadget; - type SerialNumber: ToConstraintField + Copy + Clone + Debug + Display + Default + ToBytes + FromBytes + UniformRand + PartialEq + Eq + Hash + Sync + Send; + type SerialNumber: ToConstraintField + Copy + Clone + Debug + Display + Default + ToBytes + FromBytes + Serialize + DeserializeOwned + UniformRand + PartialEq + Eq + Hash + Sync + Send; /// Merkle tree scheme for the serial numbers root. Invoked only over `Self::InnerScalarField`. type SerialNumbersTreeCRH: CRH; type SerialNumbersTreeParameters: MerkleParameters; - type SerialNumbersRoot: ToConstraintField + Copy + Clone + Default + Debug + Display + ToBytes + FromBytes + PartialEq + Eq + Hash + Sync + Send; + type SerialNumbersRoot: ToConstraintField + Copy + Clone + Default + Debug + Display + ToBytes + FromBytes + Serialize + DeserializeOwned + PartialEq + Eq + Hash + Sync + Send; /// CRH scheme for computing the transaction ID. Invoked only over `Self::InnerScalarField`. type TransactionIDCRH: CRH; - type TransactionID: ToConstraintField + Copy + Clone + Default + Debug + Display + ToBytes + FromBytes + PartialEq + Eq + Hash + Sync + Send; + type TransactionID: ToConstraintField + Copy + Clone + Default + Debug + Display + ToBytes + FromBytes + Serialize + DeserializeOwned + PartialEq + Eq + Hash + Sync + Send; /// CRH scheme for computing the block transactions root. Invoked only over `Self::InnerScalarField`. type TransactionsTreeCRH: CRH; type TransactionsTreeParameters: MerkleParameters; - type TransactionsRoot: ToConstraintField + Copy + Clone + Default + Debug + Display + ToBytes + FromBytes + PartialEq + Eq + Hash + Sync + Send; + type TransactionsRoot: ToConstraintField + Copy + Clone + Default + Debug + Display + ToBytes + FromBytes + Serialize + DeserializeOwned + PartialEq + Eq + Hash + Sync + Send; /// CRH scheme for computing the transition ID. Invoked only over `Self::InnerScalarField`. type TransitionIDCRH: CRH; type TransitionIDCRHGadget: CRHGadget; - type TransitionID: ToConstraintField + Copy + Clone + Default + Debug + Display + ToBytes + FromBytes + PartialEq + Eq + Hash + Sync + Send; + type TransitionID: ToConstraintField + Copy + Clone + Default + Debug + Display + ToBytes + FromBytes + Serialize + DeserializeOwned + PartialEq + Eq + Hash + Sync + Send; fn account_encryption_scheme() -> &'static Self::AccountEncryptionScheme; fn account_signature_scheme() -> &'static Self::AccountSignatureScheme; diff --git a/dpc/src/transaction/transaction.rs b/dpc/src/transaction/transaction.rs index 6bf5bb86cc..5fe2581cfd 100644 --- a/dpc/src/transaction/transaction.rs +++ b/dpc/src/transaction/transaction.rs @@ -27,17 +27,18 @@ use crate::{ VirtualMachine, }; use snarkvm_algorithms::CRH; -use snarkvm_utilities::{has_duplicates, FromBytes, ToBytes}; +use snarkvm_utilities::{has_duplicates, to_bytes_le, FromBytes, ToBytes}; use anyhow::{anyhow, Result}; use rand::{CryptoRng, Rng}; +use serde::{Deserialize, Serialize}; use std::{ collections::HashSet, hash::{Hash, Hasher}, io::{Read, Result as IoResult, Write}, }; -#[derive(Derivative)] +#[derive(Derivative, Serialize, Deserialize)] #[derivative( Clone(bound = "N: Network"), Debug(bound = "N: Network"), @@ -45,12 +46,16 @@ use std::{ Eq(bound = "N: Network") )] pub struct Transaction { + /// The ID of this transaction. + transaction_id: N::TransactionID, /// The network ID. network_id: u16, /// The ID of the inner circuit used to execute this transaction. inner_circuit_id: N::InnerCircuitID, /// The state transition. transitions: Vec>, + /// The events emitted from this transaction. + events: Vec>, } impl Transaction { @@ -69,11 +74,20 @@ impl Transaction { /// Initializes an instance of `Transaction` from the given inputs. #[inline] - pub fn from(network_id: u16, inner_circuit_id: N::InnerCircuitID, transitions: Vec>) -> Result { + pub fn from( + network_id: u16, + inner_circuit_id: N::InnerCircuitID, + transitions: Vec>, + events: Vec>, + ) -> Result { + let transaction_id = Self::compute_transaction_id(&transitions)?; + let transaction = Self { + transaction_id, network_id, - transitions, inner_circuit_id, + transitions, + events, }; match transaction.is_valid() { @@ -94,7 +108,7 @@ impl Transaction { } // Ensure the number of events is less than `N::NUM_EVENTS`. - if self.events().len() > N::NUM_EVENTS { + if self.events.len() > N::NUM_EVENTS { eprintln!("Transaction contains an invalid number of events"); return false; } @@ -185,6 +199,12 @@ impl Transaction { true } + /// Returns the transaction ID. + #[inline] + pub fn transaction_id(&self) -> N::TransactionID { + self.transaction_id + } + /// Returns the network ID. #[inline] pub fn network_id(&self) -> u16 { @@ -242,18 +262,24 @@ impl Transaction { .fold(AleoAmount::ZERO, |a, b| a.add(*b)) } - /// Returns the events. - #[inline] - pub fn events(&self) -> Vec> { - self.transitions.iter().flat_map(Transition::events).cloned().collect() - } - /// Returns a reference to the state transitions. #[inline] pub fn transitions(&self) -> &Vec> { &self.transitions } + /// Returns the transition IDs. + #[inline] + pub fn transition_ids(&self) -> Vec { + self.transitions.iter().map(Transition::transition_id).collect() + } + + /// Returns a reference to the events. + #[inline] + pub fn events(&self) -> &Vec> { + &self.events + } + /// Returns the ciphertext IDs. #[inline] pub fn to_ciphertext_ids(&self) -> Result> { @@ -263,40 +289,23 @@ impl Transaction { .collect::>>() } - /// Returns the records that can be decrypted with the given account view key. - pub fn to_decrypted_records(&self, account_view_key: &ViewKey) -> Result>> { + /// Returns records from the transaction belonging to the given account view key. + #[inline] + pub fn to_decrypted_records(&self, account_view_key: &ViewKey) -> Vec> { self.transitions .iter() .flat_map(Transition::ciphertexts) - .map(|c| c.decrypt(account_view_key)) - .filter(|decryption_result| match decryption_result { - Ok(r) => !r.is_dummy(), - Err(_) => true, - }) - .collect::>>>() - } - - /// Returns the transaction ID. - #[inline] - pub fn to_transaction_id(&self) -> Result { - let transition_ids = self - .transitions - .iter() - .map(|transition| Ok(transition.transition_id().to_bytes_le()?)) - .collect::>>()? - .concat(); - - Ok(N::transaction_id_crh().hash(&transition_ids)?) + .filter_map(|c| c.decrypt(account_view_key).ok()) + .filter(|record| !record.is_dummy()) + .collect() } -} -impl ToBytes for Transaction { + /// Transaction ID := Hash(transition IDs) #[inline] - fn write_le(&self, mut writer: W) -> IoResult<()> { - self.network_id.write_le(&mut writer)?; - self.inner_circuit_id.write_le(&mut writer)?; - (self.transitions.len() as u16).write_le(&mut writer)?; - self.transitions.write_le(&mut writer) + pub(crate) fn compute_transaction_id(transitions: &Vec>) -> Result { + Ok(N::transaction_id_crh().hash(&to_bytes_le![ + transitions.iter().map(Transition::transition_id).collect::>() + ]?)?) } } @@ -312,58 +321,68 @@ impl FromBytes for Transaction { transitions.push(FromBytes::read_le(&mut reader)?); } - Ok(Self::from(network_id, inner_circuit_id, transitions).expect("Failed to deserialize a transaction")) + let num_events: u16 = FromBytes::read_le(&mut reader)?; + let mut events = Vec::with_capacity(num_events as usize); + for _ in 0..num_events { + events.push(FromBytes::read_le(&mut reader)?); + } + + Ok(Self::from(network_id, inner_circuit_id, transitions, events).expect("Failed to deserialize a transaction")) + } +} + +impl ToBytes for Transaction { + #[inline] + fn write_le(&self, mut writer: W) -> IoResult<()> { + self.network_id.write_le(&mut writer)?; + self.inner_circuit_id.write_le(&mut writer)?; + (self.transitions.len() as u16).write_le(&mut writer)?; + self.transitions.write_le(&mut writer)?; + (self.events.len() as u16).write_le(&mut writer)?; + self.events.write_le(&mut writer) } } impl Hash for Transaction { #[inline] fn hash(&self, state: &mut H) { - self.to_transaction_id() - .expect("Failed to compute the transaction ID") - .hash(state); + self.transaction_id().hash(state); } } #[cfg(test)] mod tests { use super::*; - use crate::{testnet2::Testnet2, Account, AccountScheme, Payload, PAYLOAD_SIZE}; + use crate::{testnet2::Testnet2, Account, AccountScheme}; use snarkvm_utilities::UniformRand; - use rand::{Rng, SeedableRng}; - use rand_chacha::ChaChaRng; + use rand::thread_rng; - #[ignore] #[test] fn test_decrypt_records() { - let rng = &mut ChaChaRng::seed_from_u64(1231275789u64); - let dummy_account = Account::::new(rng); - - // Construct output records - let mut payload = [0u8; PAYLOAD_SIZE]; - rng.fill(&mut payload); - let record = Record::new_output( - dummy_account.address(), + let rng = &mut thread_rng(); + let account = Account::::new(rng); + + // Craft the expected coinbase record. + let expected_record = Record::new_output( + account.address(), 1234, - Payload::from_bytes_le(&payload).unwrap(), + Default::default(), *Testnet2::noop_program_id(), UniformRand::rand(rng), rng, ) .unwrap(); - let dummy_record = Record::new_noop_output(dummy_account.address(), UniformRand::rand(rng), rng).unwrap(); - - // Encrypt output records - let (_encrypted_record, _) = RecordCiphertext::encrypt(&record, rng).unwrap(); - let (_encrypted_dummy_record, _) = RecordCiphertext::encrypt(&dummy_record, rng).unwrap(); - let account_view_key = ViewKey::from_private_key(&dummy_account.private_key()); - // Construct transaction with 1 output record and 1 dummy output record - let transaction = Transaction::new_coinbase(dummy_account.address(), AleoAmount(1234), rng).unwrap(); + // Craft a transaction with 1 coinbase record. + let transaction = Transaction::new_coinbase(account.address(), AleoAmount(1234), rng).unwrap(); + let decrypted_records = transaction.to_decrypted_records(&account.view_key()); + assert_eq!(decrypted_records.len(), 1); // Excludes dummy records upon decryption. - let decrypted_records = transaction.to_decrypted_records(&account_view_key).unwrap(); - assert_eq!(decrypted_records.len(), 1); // Excludes dummy record upon decryption - assert_eq!(decrypted_records.first().unwrap(), &record); + let candidate_record = decrypted_records.first().unwrap(); + assert_eq!(expected_record.owner(), candidate_record.owner()); + assert_eq!(expected_record.value(), candidate_record.value()); + assert_eq!(expected_record.payload(), candidate_record.payload()); + assert_eq!(expected_record.program_id(), candidate_record.program_id()); } } diff --git a/dpc/src/transition/transition.rs b/dpc/src/transition/transition.rs index 5a13c4683d..612f001b12 100644 --- a/dpc/src/transition/transition.rs +++ b/dpc/src/transition/transition.rs @@ -16,12 +16,15 @@ use crate::{circuits::*, prelude::*}; use snarkvm_algorithms::traits::{CRH, SNARK}; -use snarkvm_utilities::{to_bytes_le, FromBytes, ToBytes}; +use snarkvm_utilities::{to_bytes_le, FromBytes, FromBytesDeserializer, ToBytes, ToBytesSerializer}; -use anyhow::Result; +use anyhow::{anyhow, Result}; +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use std::{ fmt, + hash::{Hash, Hasher}, io::{Read, Result as IoResult, Write}, + str::FromStr, }; #[derive(Derivative)] @@ -46,20 +49,14 @@ pub struct Transition { ciphertexts: Vec>, /// A value balance is the difference between the input and output record values. value_balance: AleoAmount, - /// The events emitted from this transition. - events: Vec>, /// The zero-knowledge proof attesting to the validity of this transition. - proof: ::Proof, + proof: N::OuterProof, } impl Transition { /// Initializes a new instance of a transition. #[inline] - pub(crate) fn from( - request: &Request, - response: &Response, - proof: ::Proof, - ) -> Result { + pub(crate) fn new(request: &Request, response: &Response, proof: N::OuterProof) -> Result { // Fetch the block hash, local commitments root, and serial numbers. let block_hash = request.block_hash(); let local_commitments_root = request.local_commitments_root(); @@ -69,8 +66,29 @@ impl Transition { let commitments = response.commitments(); let ciphertexts = response.ciphertexts().clone(); let value_balance = response.value_balance(); - let events = response.events().clone(); + // Construct the transition. + Self::from( + block_hash, + local_commitments_root, + serial_numbers, + commitments, + ciphertexts, + value_balance, + proof, + ) + } + + /// Constructs an instance of a transition from the given inputs. + pub(crate) fn from( + block_hash: N::BlockHash, + local_commitments_root: N::LocalCommitmentsRoot, + serial_numbers: Vec, + commitments: Vec, + ciphertexts: Vec>, + value_balance: AleoAmount, + proof: N::OuterProof, + ) -> Result { // Compute the transition ID. let transition_id = Self::compute_transition_id( block_hash, @@ -90,7 +108,6 @@ impl Transition { commitments, ciphertexts, value_balance, - events, proof, }) } @@ -193,15 +210,9 @@ impl Transition { &self.value_balance } - /// Returns a reference to the events. - #[inline] - pub fn events(&self) -> &Vec> { - &self.events - } - /// Returns a reference to the transition proof. #[inline] - pub fn proof(&self) -> &::Proof { + pub fn proof(&self) -> &N::OuterProof { &self.proof } @@ -257,36 +268,18 @@ impl FromBytes for Transition { } let value_balance: AleoAmount = FromBytes::read_le(&mut reader)?; + let proof: N::OuterProof = FromBytes::read_le(&mut reader)?; - let num_events: u16 = FromBytes::read_le(&mut reader)?; - let mut events = Vec::with_capacity(num_events as usize); - for _ in 0..num_events { - events.push(FromBytes::read_le(&mut reader)?); - } - - let proof: ::Proof = FromBytes::read_le(&mut reader)?; - - let transition_id = Self::compute_transition_id( - block_hash, - local_commitments_root, - &serial_numbers, - &commitments, - &ciphertexts, - value_balance, - ) - .expect("Failed to compute the transition ID during deserialization"); - - Ok(Self { - transition_id, + Ok(Self::from( block_hash, local_commitments_root, serial_numbers, commitments, ciphertexts, value_balance, - events, proof, - }) + ) + .expect("Failed to deserialize a transition from bytes")) } } @@ -299,8 +292,135 @@ impl ToBytes for Transition { self.commitments.write_le(&mut writer)?; self.ciphertexts.write_le(&mut writer)?; self.value_balance.write_le(&mut writer)?; - (self.events.len() as u16).write_le(&mut writer)?; - self.events.write_le(&mut writer)?; self.proof.write_le(&mut writer) } } + +impl FromStr for Transition { + type Err = anyhow::Error; + + fn from_str(transition: &str) -> Result { + let transition = serde_json::Value::from_str(transition)?; + let transition_id: N::TransitionID = serde_json::from_value(transition["transition_id"].clone())?; + + // Recover the transition. + let transition = Self::from( + serde_json::from_value(transition["block_hash"].clone())?, + serde_json::from_value(transition["local_commitments_root"].clone())?, + serde_json::from_value(transition["serial_numbers"].clone())?, + serde_json::from_value(transition["commitments"].clone())?, + serde_json::from_value(transition["ciphertexts"].clone())?, + serde_json::from_value(transition["value_balance"].clone())?, + serde_json::from_value(transition["proof"].clone())?, + )?; + + // Ensure the transition ID matches. + match transition_id == transition.transition_id() { + true => Ok(transition), + false => Err(anyhow!( + "Incorrect transition ID during deserialization. Expected {}, found {}", + transition_id, + transition.transition_id() + )), + } + } +} + +impl fmt::Display for Transition { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let transition = serde_json::json!({ + "transition_id": self.transition_id, + "block_hash": self.block_hash, + "local_commitments_root": self.local_commitments_root, + "serial_numbers": self.serial_numbers, + "commitments": self.commitments, + "ciphertext_ids": self.to_ciphertext_ids().collect::>>().expect("Failed to format ciphertext IDs"), + "ciphertexts": self.ciphertexts, + "value_balance": self.value_balance, + "proof": self.proof, + }); + write!(f, "{}", transition) + } +} + +impl Serialize for Transition { + fn serialize(&self, serializer: S) -> Result { + match serializer.is_human_readable() { + true => serializer.collect_str(self), + false => ToBytesSerializer::serialize(self, serializer), + } + } +} + +impl<'de, N: Network> Deserialize<'de> for Transition { + fn deserialize>(deserializer: D) -> Result { + match deserializer.is_human_readable() { + true => FromStr::from_str(&String::deserialize(deserializer)?).map_err(de::Error::custom), + false => { + FromBytesDeserializer::::deserialize(deserializer, "transition", N::TRANSITION_SIZE_IN_BYTES) + } + } + } +} + +impl Hash for Transition { + #[inline] + fn hash(&self, state: &mut H) { + self.transition_id().hash(state); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::testnet2::Testnet2; + + #[test] + fn test_size() { + let transaction = Testnet2::genesis_block().to_coinbase_transaction().unwrap(); + let transition = transaction.transitions().first().unwrap().clone(); + assert_eq!( + transition.to_bytes_le().unwrap().len(), + Testnet2::TRANSITION_SIZE_IN_BYTES + ); + } + + #[test] + fn test_serde_json() { + let transaction = Testnet2::genesis_block().to_coinbase_transaction().unwrap(); + let expected_transition = transaction.transitions().first().unwrap().clone(); + + // Serialize + let expected_string = &expected_transition.to_string(); + let candidate_string = serde_json::to_string(&expected_transition).unwrap(); + assert_eq!(2710, candidate_string.len(), "Update me if serialization has changed"); + assert_eq!( + expected_string, + serde_json::Value::from_str(&candidate_string) + .unwrap() + .as_str() + .unwrap() + ); + + // Deserialize + assert_eq!(expected_transition, Transition::from_str(&expected_string).unwrap()); + assert_eq!(expected_transition, serde_json::from_str(&candidate_string).unwrap()); + } + + #[test] + fn test_bincode() { + let transaction = Testnet2::genesis_block().to_coinbase_transaction().unwrap(); + let expected_transition = transaction.transitions().first().unwrap().clone(); + + println!("{}", serde_json::to_string(&expected_transition).unwrap()); + + let expected_bytes = expected_transition.to_bytes_le().unwrap(); + assert_eq!( + &expected_bytes[..], + &bincode::serialize(&expected_transition).unwrap()[..] + ); + + assert_eq!(expected_transition, Transition::read_le(&expected_bytes[..]).unwrap()); + assert_eq!(expected_transition, bincode::deserialize(&expected_bytes[..]).unwrap()); + } +} diff --git a/dpc/src/virtual_machine/amount.rs b/dpc/src/virtual_machine/amount.rs index 2f5bae3253..b8e5aa8339 100644 --- a/dpc/src/virtual_machine/amount.rs +++ b/dpc/src/virtual_machine/amount.rs @@ -16,13 +16,14 @@ use snarkvm_utilities::{FromBytes, ToBytes}; +use serde::{Deserialize, Serialize}; use std::{ fmt, io::{Read, Result as IoResult, Write}, }; /// Represents the amount of ALEOs. -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct AleoAmount(pub i64); pub enum Denomination { diff --git a/dpc/src/virtual_machine/event.rs b/dpc/src/virtual_machine/event.rs index 38fb049372..67c2e29681 100644 --- a/dpc/src/virtual_machine/event.rs +++ b/dpc/src/virtual_machine/event.rs @@ -17,9 +17,10 @@ use crate::{Network, Operation}; use snarkvm_utilities::{FromBytes, ToBytes}; +use serde::{Deserialize, Serialize}; use std::io::{Read, Result as IoResult, Write}; -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub enum Event { /// Emits publicly-visible arbitrary data. Custom(Vec), diff --git a/dpc/src/virtual_machine/function_inputs.rs b/dpc/src/virtual_machine/function_inputs.rs index 371812a6ee..7e9a0881c3 100644 --- a/dpc/src/virtual_machine/function_inputs.rs +++ b/dpc/src/virtual_machine/function_inputs.rs @@ -20,12 +20,13 @@ use snarkvm_fields::{ConstraintFieldError, ToConstraintField}; use snarkvm_utilities::{FromBytes, ToBytes}; use anyhow::Result; +use serde::{Deserialize, Serialize}; use std::{ io::{Read, Result as IoResult, Write}, marker::PhantomData, }; -#[derive(Derivative)] +#[derive(Derivative, Serialize, Deserialize)] #[derivative( Copy(bound = "N: Network"), Clone(bound = "N: Network"), @@ -34,6 +35,7 @@ use std::{ PartialEq(bound = "N: Network") )] pub struct FunctionInputs { + #[serde(skip)] _unused: PhantomData, } diff --git a/dpc/src/virtual_machine/function_type.rs b/dpc/src/virtual_machine/function_type.rs index 711dc883e9..8e312228f5 100644 --- a/dpc/src/virtual_machine/function_type.rs +++ b/dpc/src/virtual_machine/function_type.rs @@ -14,7 +14,9 @@ // You should have received a copy of the GNU General Public License // along with the snarkVM library. If not, see . -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +use serde::{Deserialize, Serialize}; + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum FunctionType { Noop, Add, @@ -49,7 +51,7 @@ impl FunctionType { Self::Update => 1, Self::Remove => 0, Self::DoubleAdd => 2, - Self::DoubleRemove => 2, + Self::DoubleRemove => 0, Self::Join => 1, Self::Split => 2, Self::Full => 2, diff --git a/dpc/src/virtual_machine/operation.rs b/dpc/src/virtual_machine/operation.rs index 0409bd2874..013acdba94 100644 --- a/dpc/src/virtual_machine/operation.rs +++ b/dpc/src/virtual_machine/operation.rs @@ -19,12 +19,13 @@ use snarkvm_fields::{ConstraintFieldError, ToConstraintField}; use snarkvm_utilities::{FromBytes, ToBytes}; use anyhow::Result; +use serde::{Deserialize, Serialize}; use std::io::{Read, Result as IoResult, Write}; type Caller = Address; type Recipient = Address; -#[derive(Derivative)] +#[derive(Derivative, Serialize, Deserialize)] #[derivative( Clone(bound = "N: Network"), Debug(bound = "N: Network"), diff --git a/dpc/src/virtual_machine/output.rs b/dpc/src/virtual_machine/output.rs index 06ab22570c..48329b6c18 100644 --- a/dpc/src/virtual_machine/output.rs +++ b/dpc/src/virtual_machine/output.rs @@ -28,7 +28,7 @@ pub struct Output { /// The balance of the recipient. value: AleoAmount, /// The program data of the recipient. - payload: Payload, + payload: Payload, /// The program that was run. program_id: N::ProgramID, } @@ -46,7 +46,7 @@ impl Output { pub fn new( address: Address, value: AleoAmount, - payload: Payload, + payload: Payload, program_id: Option, ) -> Result { // Retrieve the program ID. If `None` is provided, construct the noop program ID. @@ -95,7 +95,7 @@ impl Output { } /// Returns a reference to the payload. - pub fn payload(&self) -> &Payload { + pub fn payload(&self) -> &Payload { &self.payload } diff --git a/dpc/src/virtual_machine/virtual_machine.rs b/dpc/src/virtual_machine/virtual_machine.rs index a50f97dd1b..19d089b339 100644 --- a/dpc/src/virtual_machine/virtual_machine.rs +++ b/dpc/src/virtual_machine/virtual_machine.rs @@ -25,6 +25,8 @@ pub struct VirtualMachine { local_commitments: LocalCommitments, /// The current list of transitions. transitions: Vec>, + /// The current list of events. + events: Vec>, } impl VirtualMachine { @@ -33,6 +35,7 @@ impl VirtualMachine { Ok(Self { local_commitments: LocalCommitments::new()?, transitions: Default::default(), + events: Default::default(), }) } @@ -88,18 +91,24 @@ impl VirtualMachine { )?); // Construct the transition. - let transition = Transition::::from(request, &response, outer_proof)?; + let transition = Transition::::new(request, &response, outer_proof)?; // Update the state of the virtual machine. self.local_commitments.add(transition.commitments())?; self.transitions.push(transition); + self.events.extend_from_slice(response.events()); Ok(self) } /// Finalizes the virtual machine state and returns a transaction. pub fn finalize(&self) -> Result> { - Transaction::from(N::NETWORK_ID, *N::inner_circuit_id(), self.transitions.clone()) + Transaction::from( + N::NETWORK_ID, + *N::inner_circuit_id(), + self.transitions.clone(), + self.events.clone(), + ) } /// Performs a noop transition. diff --git a/fields/Cargo.toml b/fields/Cargo.toml index 1a784f4069..567f7c9fc8 100644 --- a/fields/Cargo.toml +++ b/fields/Cargo.toml @@ -25,9 +25,6 @@ default-features = false [dependencies.anyhow] version = "1.0" -[dependencies.bincode] -version = "1.3.3" - [dependencies.derivative] version = "2" @@ -35,12 +32,8 @@ version = "2" version = "0.8" default-features = false -[dependencies.rand_xorshift] -version = "0.3" -default-features = false - [dependencies.serde] -version = "1.0.130" +version = "1.0" default-features = false features = [ "derive" ] diff --git a/fields/src/lib.rs b/fields/src/lib.rs index c6d9b7f344..9113ec568d 100644 --- a/fields/src/lib.rs +++ b/fields/src/lib.rs @@ -51,8 +51,6 @@ pub use fp12_2over3over2::*; mod legendre; pub use legendre::*; -pub mod tests_field; - mod to_field_vec; pub use to_field_vec::*; diff --git a/fields/src/macros.rs b/fields/src/macros.rs index 12df8495bf..3bf199d7da 100644 --- a/fields/src/macros.rs +++ b/fields/src/macros.rs @@ -238,58 +238,54 @@ macro_rules! impl_primefield_serializer { } impl serde::Serialize for $field

{ - fn serialize(&self, s: S) -> Result - where - S: serde::ser::Serializer, - { - use serde::ser::SerializeTuple; - - let len = self.serialized_size(); - let mut bytes = Vec::with_capacity(len); + fn serialize(&self, serializer: S) -> Result { + let mut bytes = Vec::with_capacity(Self::SERIALIZED_SIZE); CanonicalSerialize::serialize(self, &mut bytes).map_err(serde::ser::Error::custom)?; - let mut tup = s.serialize_tuple(len)?; - for byte in &bytes { - tup.serialize_element(byte)?; + match serializer.is_human_readable() { + true => serializer.collect_str(self), + false => snarkvm_utilities::ToBytesSerializer::serialize(&bytes, serializer), } - tup.end() } } impl<'de, P: $params> serde::Deserialize<'de> for $field

{ - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - struct SerVisitor

(std::marker::PhantomData

); - - impl<'de, P: $params> serde::de::Visitor<'de> for SerVisitor

{ - type Value = $field

; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a valid field element") + fn deserialize>(deserializer: D) -> Result { + match deserializer.is_human_readable() { + true => { + let s: String = serde::Deserialize::deserialize(deserializer)?; + core::str::FromStr::from_str(&s).map_err(serde::de::Error::custom) } - - fn visit_seq(self, mut seq: S) -> Result - where - S: serde::de::SeqAccess<'de>, - { - let len = ::SERIALIZED_SIZE; - let bytes: Vec = (0..len) - .map(|_| { - seq.next_element()? - .ok_or_else(|| serde::de::Error::custom("could not read bytes")) - }) - .collect::, _>>()?; - - let res = - CanonicalDeserialize::deserialize(&mut &bytes[..]).map_err(serde::de::Error::custom)?; - Ok(res) + false => { + struct SerVisitor

(std::marker::PhantomData

); + + impl<'de, P: $params> serde::de::Visitor<'de> for SerVisitor

{ + type Value = $field

; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a valid field element") + } + + fn visit_seq(self, mut seq: S) -> Result + where + S: serde::de::SeqAccess<'de>, + { + let len = ::SERIALIZED_SIZE; + let bytes: Vec = (0..len) + .map(|_| { + seq.next_element()? + .ok_or_else(|| serde::de::Error::custom("could not read bytes")) + }) + .collect::, _>>()?; + + CanonicalDeserialize::deserialize(&mut &bytes[..]).map_err(serde::de::Error::custom) + } + } + + let visitor = SerVisitor(std::marker::PhantomData); + deserializer.deserialize_tuple(Self::SERIALIZED_SIZE, visitor) } } - - let visitor = SerVisitor(std::marker::PhantomData); - deserializer.deserialize_tuple(Self::SERIALIZED_SIZE, visitor) } } }; diff --git a/gadgets/src/algorithms/signature/aleo.rs b/gadgets/src/algorithms/signature/aleo.rs index d015f248d5..5d8b8490fd 100644 --- a/gadgets/src/algorithms/signature/aleo.rs +++ b/gadgets/src/algorithms/signature/aleo.rs @@ -33,6 +33,7 @@ use crate::{ use snarkvm_algorithms::{ crypto_hash::PoseidonDefaultParametersField, signature::{AleoSignature, AleoSignatureScheme}, + SignatureScheme, }; use snarkvm_curves::{templates::twisted_edwards_extended::Affine as TEAffine, TwistedEdwardsParameters}; use snarkvm_fields::{FieldParameters, PrimeField}; @@ -411,7 +412,7 @@ impl, F: PrimeField + PoseidonDefaul // Compute G^s. let g_s = { let mut g_s = zero_affine.clone(); - for (i, (base, bit)) in self.signature.g_bases.iter().zip_eq(s).enumerate() { + for (i, (base, bit)) in self.signature.parameters().iter().zip_eq(s).enumerate() { let added = g_s.add_constant(cs.ns(|| format!("add_g_s_{}", i)), base)?; g_s = TEAffineGadget::::conditionally_select( @@ -513,7 +514,7 @@ impl, F: PrimeField + PoseidonDefaul // Compute G^sk_prf. let g_sk_prf = { let mut g_sk_prf = zero_affine.clone(); - for (i, (base, bit)) in self.signature.g_bases.iter().zip_eq(sk_prf).enumerate() { + for (i, (base, bit)) in self.signature.parameters().iter().zip_eq(sk_prf).enumerate() { let added = g_sk_prf.add_constant(cs.ns(|| format!("add_g_sk_prf_{}", i)), base)?; g_sk_prf = TEAffineGadget::::conditionally_select( diff --git a/parameters/src/testnet1/genesis/block.rs b/parameters/src/testnet1/genesis/block.rs index e573f67204..b6c42b16b9 100644 --- a/parameters/src/testnet1/genesis/block.rs +++ b/parameters/src/testnet1/genesis/block.rs @@ -23,7 +23,7 @@ pub struct GenesisBlock; impl Genesis for GenesisBlock { const CHECKSUM: &'static str = ""; - const SIZE: u64 = 2140; + const SIZE: u64 = 2136; fn load_bytes() -> Vec { let block_header_bytes = GenesisBlockHeader::load_bytes(); diff --git a/parameters/src/testnet1/genesis/block_header.genesis b/parameters/src/testnet1/genesis/block_header.genesis index 4c68ef548b..634551c492 100644 Binary files a/parameters/src/testnet1/genesis/block_header.genesis and b/parameters/src/testnet1/genesis/block_header.genesis differ diff --git a/parameters/src/testnet1/genesis/transaction_1.genesis b/parameters/src/testnet1/genesis/transaction_1.genesis index 4a85422c5a..d4e7636265 100644 Binary files a/parameters/src/testnet1/genesis/transaction_1.genesis and b/parameters/src/testnet1/genesis/transaction_1.genesis differ diff --git a/parameters/src/testnet1/genesis/transaction_1.rs b/parameters/src/testnet1/genesis/transaction_1.rs index b2da1490c3..95b9871324 100644 --- a/parameters/src/testnet1/genesis/transaction_1.rs +++ b/parameters/src/testnet1/genesis/transaction_1.rs @@ -20,7 +20,7 @@ pub struct Transaction1; impl Genesis for Transaction1 { const CHECKSUM: &'static str = ""; - const SIZE: u64 = 1187; + const SIZE: u64 = 1183; fn load_bytes() -> Vec { let buffer = include_bytes!("transaction_1.genesis"); diff --git a/parameters/src/testnet2/genesis/block.rs b/parameters/src/testnet2/genesis/block.rs index 0100abd5e1..4ee8307282 100644 --- a/parameters/src/testnet2/genesis/block.rs +++ b/parameters/src/testnet2/genesis/block.rs @@ -23,7 +23,7 @@ pub struct GenesisBlock; impl Genesis for GenesisBlock { const CHECKSUM: &'static str = ""; - const SIZE: u64 = 2140; + const SIZE: u64 = 2136; fn load_bytes() -> Vec { let block_header_bytes = GenesisBlockHeader::load_bytes(); diff --git a/parameters/src/testnet2/genesis/block_header.genesis b/parameters/src/testnet2/genesis/block_header.genesis index 8656d4a51f..ce0333608c 100644 Binary files a/parameters/src/testnet2/genesis/block_header.genesis and b/parameters/src/testnet2/genesis/block_header.genesis differ diff --git a/parameters/src/testnet2/genesis/transaction_1.genesis b/parameters/src/testnet2/genesis/transaction_1.genesis index 2a16104518..aca8db5b0c 100644 Binary files a/parameters/src/testnet2/genesis/transaction_1.genesis and b/parameters/src/testnet2/genesis/transaction_1.genesis differ diff --git a/parameters/src/testnet2/genesis/transaction_1.rs b/parameters/src/testnet2/genesis/transaction_1.rs index b2da1490c3..95b9871324 100644 --- a/parameters/src/testnet2/genesis/transaction_1.rs +++ b/parameters/src/testnet2/genesis/transaction_1.rs @@ -20,7 +20,7 @@ pub struct Transaction1; impl Genesis for Transaction1 { const CHECKSUM: &'static str = ""; - const SIZE: u64 = 1187; + const SIZE: u64 = 1183; fn load_bytes() -> Vec { let buffer = include_bytes!("transaction_1.genesis"); diff --git a/utilities/Cargo.toml b/utilities/Cargo.toml index a1f208e303..0384be0508 100644 --- a/utilities/Cargo.toml +++ b/utilities/Cargo.toml @@ -36,6 +36,10 @@ version = "0.8" default-features = false features = [ "std_rng" ] +[dependencies.serde] +version = "1.0" +default-features = false + [dependencies.thiserror] version = "1.0" diff --git a/utilities/src/bytes.rs b/utilities/src/bytes.rs index 1bceb7db70..b02dc879f5 100644 --- a/utilities/src/bytes.rs +++ b/utilities/src/bytes.rs @@ -16,46 +16,17 @@ use crate::{ error, + fmt, io::{Read, Result as IoResult, Write}, + marker::PhantomData, Vec, }; - -#[inline] -pub fn from_bytes_le_to_bits_le(bytes: &[u8]) -> impl Iterator + DoubleEndedIterator + '_ { - bytes - .iter() - .map(|byte| (0..8).map(move |i| (*byte >> i) & 1 == 1)) - .flatten() -} - -#[inline] -pub fn from_bits_le_to_bytes_le(bits: &[bool]) -> Vec { - let desired_size = if bits.len() % 8 == 0 { - bits.len() / 8 - } else { - bits.len() / 8 + 1 - }; - - let mut bytes = Vec::with_capacity(desired_size); - for bits in bits.chunks(8) { - let mut result = 0u8; - for (i, bit) in bits.iter().enumerate() { - let bit_value = *bit as u8; - result += bit_value << i as u8; - } - - // Pad the bits if their number doesn't correspond to full bytes - if bits.len() < 8 { - for i in bits.len()..8 { - let bit_value = false as u8; - result += bit_value << i as u8; - } - } - bytes.push(result); - } - - bytes -} +use serde::{ + de::{self, SeqAccess, Visitor}, + ser::{self, SerializeTuple}, + Deserializer, + Serializer, +}; /// Takes as input a sequence of structs, and converts them to a series of little-endian bytes. /// All traits that implement `ToBytes` can be automatically converted to bytes in this manner. @@ -108,72 +79,125 @@ pub trait FromBytes { } } -macro_rules! to_bytes_for_int_array { - ($int:ty) => { - impl ToBytes for [$int; N] { - #[inline] - fn write_le(&self, mut writer: W) -> IoResult<()> { - for num in self { - writer.write_all(&num.to_le_bytes())?; - } - Ok(()) - } +pub struct ToBytesSerializer(String, Option, PhantomData); + +impl ToBytesSerializer { + /// Serializes a static-sized byte array (without length encoding). + pub fn serialize(object: &T, serializer: S) -> Result { + let bytes = object.to_bytes_le().map_err(ser::Error::custom)?; + let mut tuple = serializer.serialize_tuple(bytes.len())?; + for byte in &bytes { + tuple.serialize_element(byte)?; } + tuple.end() + } +} - impl FromBytes for [$int; N] { - #[inline] - fn read_le(mut reader: R) -> IoResult { - let mut res: [$int; N] = [0; N]; - for num in res.iter_mut() { - let mut bytes = [0u8; core::mem::size_of::<$int>()]; - reader.read_exact(&mut bytes)?; - *num = <$int>::from_le_bytes(bytes); - } - Ok(res) - } +pub struct FromBytesDeserializer(String, Option, PhantomData); + +impl<'de, T: FromBytes> FromBytesDeserializer { + /// + /// Deserializes a static-sized byte array (without length encoding). + /// + /// This method fails if `deserializer` is given an insufficient `size`. + /// + pub fn deserialize>(deserializer: D, name: &str, size: usize) -> Result { + let mut buffer = Vec::with_capacity(size); + deserializer.deserialize_tuple(size, FromBytesVisitor::new(&mut buffer, name))?; + FromBytes::read_le(&buffer[..]).map_err(de::Error::custom) + } + + /// + /// Attempts to deserialize a byte array (without length encoding). + /// + /// This method does *not* fail if `deserializer` is given an insufficient `size`, + /// however this method fails if `FromBytes` fails to read the value of `T`. + /// + pub fn try_deserialize>( + deserializer: D, + name: &str, + size_a: usize, + size_b: usize, + ) -> Result { + // Order the given sizes from smallest to largest. + let (size_a, size_b) = match size_a < size_b { + true => (size_a, size_b), + false => (size_b, size_a), + }; + + // Reserve a new `Vec` with the larger size capacity. + let mut buffer = Vec::with_capacity(size_b); + + // Attempt to deserialize on the larger size, to load up to the maximum buffer size. + match deserializer.deserialize_tuple(size_b, FromBytesVisitor::new(&mut buffer, name)) { + // Deserialized a full buffer, attempt to read up to `size_b`. + Ok(()) => FromBytes::read_le(&buffer[..size_b]).map_err(de::Error::custom), + // Deserialized a partial buffer, attempt to read up to `size_a`, if exactly `size_a` was read. + Err(error) => match buffer.len() == size_a { + true => FromBytes::read_le(&buffer[..size_a]).map_err(de::Error::custom), + false => Err(error), + }, } - }; + } } -// u8 has a dedicated, faster implementation -to_bytes_for_int_array!(u16); -to_bytes_for_int_array!(u32); -to_bytes_for_int_array!(u64); +pub struct FromBytesVisitor<'a>(&'a mut Vec, String, Option); -impl ToBytes for [u8; N] { - #[inline] - fn write_le(&self, mut writer: W) -> IoResult<()> { - writer.write_all(self) +impl<'a, 'de> FromBytesVisitor<'a> { + pub fn new(buffer: &'a mut Vec, name: &str) -> Self { + Self(buffer, name.to_string(), None) } } -impl FromBytes for [u8; N] { +impl<'a, 'de> Visitor<'de> for FromBytesVisitor<'a> { + type Value = (); + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str(&format!("a valid {} ", self.1)) + } + + fn visit_seq>(self, mut seq: S) -> Result { + while let Some(byte) = seq.next_element()? { + self.0.push(byte); + } + Ok(()) + } +} + +impl ToBytes for () { #[inline] - fn read_le(mut reader: R) -> IoResult { - let mut arr = [0u8; N]; - reader.read_exact(&mut arr)?; - Ok(arr) + fn write_le(&self, _writer: W) -> IoResult<()> { + Ok(()) } } -impl ToBytes for (L, R) { - fn write_le(&self, mut writer: W) -> IoResult<()> { - self.0.write_le(&mut writer)?; - self.1.write_le(&mut writer)?; +impl FromBytes for () { + #[inline] + fn read_le(_bytes: R) -> IoResult { Ok(()) } } -impl FromBytes for (L, R) { +impl ToBytes for bool { #[inline] - fn read_le(mut reader: Reader) -> IoResult { - let left: L = FromBytes::read_le(&mut reader)?; - let right: R = FromBytes::read_le(&mut reader)?; - Ok((left, right)) + fn write_le(&self, writer: W) -> IoResult<()> { + u8::write_le(&(*self as u8), writer) + } +} + +impl FromBytes for bool { + #[inline] + fn read_le(reader: R) -> IoResult { + match u8::read_le(reader) { + Ok(0) => Ok(false), + Ok(1) => Ok(true), + Ok(_) => Err(error("FromBytes::read failed")), + Err(err) => Err(err), + } } } -macro_rules! to_bytes_for_integer { +macro_rules! impl_bytes_for_integer { ($int:ty) => { impl ToBytes for $int { #[inline] @@ -193,48 +217,80 @@ macro_rules! to_bytes_for_integer { }; } -to_bytes_for_integer!(u8); -to_bytes_for_integer!(u16); -to_bytes_for_integer!(u32); -to_bytes_for_integer!(u64); -to_bytes_for_integer!(u128); +impl_bytes_for_integer!(u8); +impl_bytes_for_integer!(u16); +impl_bytes_for_integer!(u32); +impl_bytes_for_integer!(u64); +impl_bytes_for_integer!(u128); -to_bytes_for_integer!(i8); -to_bytes_for_integer!(i16); -to_bytes_for_integer!(i32); -to_bytes_for_integer!(i64); -to_bytes_for_integer!(i128); +impl_bytes_for_integer!(i8); +impl_bytes_for_integer!(i16); +impl_bytes_for_integer!(i32); +impl_bytes_for_integer!(i64); +impl_bytes_for_integer!(i128); -impl ToBytes for () { +impl ToBytes for [u8; N] { #[inline] - fn write_le(&self, _writer: W) -> IoResult<()> { - Ok(()) + fn write_le(&self, mut writer: W) -> IoResult<()> { + writer.write_all(self) } } -impl FromBytes for () { +impl FromBytes for [u8; N] { #[inline] - fn read_le(_bytes: R) -> IoResult { - Ok(()) + fn read_le(mut reader: R) -> IoResult { + let mut arr = [0u8; N]; + reader.read_exact(&mut arr)?; + Ok(arr) } } -impl ToBytes for bool { - #[inline] - fn write_le(&self, writer: W) -> IoResult<()> { - u8::write_le(&(*self as u8), writer) +macro_rules! impl_bytes_for_integer_array { + ($int:ty) => { + impl ToBytes for [$int; N] { + #[inline] + fn write_le(&self, mut writer: W) -> IoResult<()> { + for num in self { + writer.write_all(&num.to_le_bytes())?; + } + Ok(()) + } + } + + impl FromBytes for [$int; N] { + #[inline] + fn read_le(mut reader: R) -> IoResult { + let mut res: [$int; N] = [0; N]; + for num in res.iter_mut() { + let mut bytes = [0u8; core::mem::size_of::<$int>()]; + reader.read_exact(&mut bytes)?; + *num = <$int>::from_le_bytes(bytes); + } + Ok(res) + } + } + }; +} + +// u8 has a dedicated, faster implementation above +impl_bytes_for_integer_array!(u16); +impl_bytes_for_integer_array!(u32); +impl_bytes_for_integer_array!(u64); + +impl ToBytes for (L, R) { + fn write_le(&self, mut writer: W) -> IoResult<()> { + self.0.write_le(&mut writer)?; + self.1.write_le(&mut writer)?; + Ok(()) } } -impl FromBytes for bool { +impl FromBytes for (L, R) { #[inline] - fn read_le(reader: R) -> IoResult { - match u8::read_le(reader) { - Ok(0) => Ok(false), - Ok(1) => Ok(true), - Ok(_) => Err(error("FromBytes::read failed")), - Err(err) => Err(err), - } + fn read_le(mut reader: Reader) -> IoResult { + let left: L = FromBytes::read_le(&mut reader)?; + let right: R = FromBytes::read_le(&mut reader)?; + Ok((left, right)) } } @@ -265,6 +321,45 @@ impl<'a, T: 'a + ToBytes> ToBytes for &'a T { } } +#[deprecated] +#[inline] +pub fn from_bytes_le_to_bits_le(bytes: &[u8]) -> impl Iterator + DoubleEndedIterator + '_ { + bytes + .iter() + .map(|byte| (0..8).map(move |i| (*byte >> i) & 1 == 1)) + .flatten() +} + +#[deprecated] +#[inline] +pub fn from_bits_le_to_bytes_le(bits: &[bool]) -> Vec { + let desired_size = if bits.len() % 8 == 0 { + bits.len() / 8 + } else { + bits.len() / 8 + 1 + }; + + let mut bytes = Vec::with_capacity(desired_size); + for bits in bits.chunks(8) { + let mut result = 0u8; + for (i, bit) in bits.iter().enumerate() { + let bit_value = *bit as u8; + result += bit_value << i as u8; + } + + // Pad the bits if their number doesn't correspond to full bytes + if bits.len() < 8 { + for i in bits.len()..8 { + let bit_value = false as u8; + result += bit_value << i as u8; + } + } + bytes.push(result); + } + + bytes +} + #[cfg(test)] mod test { use super::{from_bits_le_to_bytes_le, from_bytes_le_to_bits_le, ToBytes}; diff --git a/utilities/src/errors/serialization.rs b/utilities/src/errors/serialization.rs index f0dd3234a3..f60ff08988 100644 --- a/utilities/src/errors/serialization.rs +++ b/utilities/src/errors/serialization.rs @@ -16,22 +16,22 @@ #[derive(Error, Debug)] pub enum SerializationError { - /// During serialization, we didn't have enough space to write extra info. - #[error("the last byte does not have enough space to encode the extra info bits")] - NotEnoughSpace, + /// During serialization with bincode, we encountered a serialization issue + #[error(transparent)] + BincodeError(#[from] bincode::Error), /// During serialization, the data was invalid. #[error("the input buffer contained invalid data")] InvalidData, + /// During serialization, we countered an I/O error. + #[error("IoError: {0}")] + IoError(#[from] crate::io::Error), + /// During serialization, we didn't have enough space to write extra info. + #[error("the last byte does not have enough space to encode the extra info bits")] + NotEnoughSpace, /// During serialization, non-empty flags were given where none were /// expected. #[error("the call expects empty flags")] UnexpectedFlags, - /// During serialization, we countered an I/O error. - #[error("IoError: {0}")] - IoError(#[from] crate::io::Error), - /// During serialization with bincode, we encountered a serialization issue - #[error(transparent)] - BincodeError(#[from] bincode::Error), } impl From for crate::io::Error {