diff --git a/.github/workflows/dusk_ci.yml b/.github/workflows/dusk_ci.yml index 7d06814..3511a1a 100644 --- a/.github/workflows/dusk_ci.yml +++ b/.github/workflows/dusk_ci.yml @@ -22,13 +22,13 @@ jobs: name: test core uses: dusk-network/.github/.github/workflows/run-tests.yml@main with: - test_flags: -p phoenix-core --features=alloc + test_flags: -p phoenix-core --features=alloc,serde test_core_rkyv: name: test core rkyv compiles uses: dusk-network/.github/.github/workflows/run-tests.yml@main with: - test_flags: -p phoenix-core --features=rkyv-impl,alloc --no-run + test_flags: -p phoenix-core --features=rkyv-impl,alloc,serde --no-run test_circuits: name: test cirucits diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index ab54f10..2d75cf3 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -10,6 +10,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Add jubjub-elgamal dependency [#255] +- Add serde `Serialize` and `Deserialize` implementations for `PublicKey`, `SecretKey`, `ViewKey`, +`NoteType`, `StealthAddress`, `Sender`, `Note` and `TxSkeleton` [#258] +- Add `serde`, `bs58`, `base64`, `hex` and `serde_json` optional dependencies [#258] +- Add `serde` feature [#258] ### Removed @@ -396,6 +400,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Canonical implementation shielded by feature. +[#258]: https://github.com/dusk-network/phoenix/issues/258 [#255]: https://github.com/dusk-network/phoenix/issues/255 [#240]: https://github.com/dusk-network/phoenix/issues/240 [#222]: https://github.com/dusk-network/phoenix/issues/222 diff --git a/core/Cargo.toml b/core/Cargo.toml index 9812ecc..0aec6c5 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -22,6 +22,11 @@ aes-gcm = { version = "0.10", default-features = false, features = ["aes", "allo zeroize = { version = "1", default-features = false, features = ["derive"] } rkyv = { version = "0.7", optional = true, default-features = false } bytecheck = { version = "0.6", optional = true, default-features = false } +serde = { version = "1.0", optional = true } +bs58 = { version = "0.4" , optional = true } +base64 = { version = "0.22", optional = true } +serde_json = { version = "1.0", optional = true } +hex = { version = "0.4" , optional = true } [dev-dependencies] assert_matches = "1.3" @@ -39,3 +44,4 @@ rkyv-impl = [ "rkyv", "bytecheck" ] +serde = ["dep:serde", "dusk-bls12_381/serde", "bs58", "base64", "hex", "serde_json", "dusk-jubjub/serde"] diff --git a/core/src/lib.rs b/core/src/lib.rs index 8597f25..47c246b 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -19,6 +19,9 @@ mod stealth_address; #[cfg(feature = "alloc")] mod transaction; +#[cfg(feature = "serde")] +mod serde_support; + /// The number of output notes in a transaction pub const OUTPUT_NOTES: usize = 2; diff --git a/core/src/serde_support.rs b/core/src/serde_support.rs new file mode 100644 index 0000000..eab6e2b --- /dev/null +++ b/core/src/serde_support.rs @@ -0,0 +1,589 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +extern crate alloc; + +use alloc::format; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; + +use dusk_bls12_381::BlsScalar; +use dusk_bytes::Serializable; +use dusk_jubjub::JubJubAffine; +use serde::de::{ + self, Error as SerdeError, MapAccess, Unexpected, VariantAccess, Visitor, +}; +use serde::ser::SerializeStruct; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use crate::{ + Note, NoteType, PublicKey, SecretKey, Sender, StealthAddress, TxSkeleton, + ViewKey, NOTE_VAL_ENC_SIZE, +}; + +impl Serialize for PublicKey { + fn serialize( + &self, + serializer: S, + ) -> Result { + let s = bs58::encode(self.to_bytes()).into_string(); + serializer.serialize_str(&s) + } +} + +impl<'de> Deserialize<'de> for PublicKey { + fn deserialize>( + deserializer: D, + ) -> Result { + let s = String::deserialize(deserializer)?; + let decoded = + bs58::decode(&s).into_vec().map_err(SerdeError::custom)?; + let decoded_len = decoded.len(); + let byte_length_str = Self::SIZE.to_string(); + let bytes: [u8; Self::SIZE] = decoded.try_into().map_err(|_| { + SerdeError::invalid_length(decoded_len, &byte_length_str.as_str()) + })?; + PublicKey::from_bytes(&bytes) + .map_err(|err| SerdeError::custom(format!("{err:?}"))) + } +} + +impl Serialize for SecretKey { + fn serialize( + &self, + serializer: S, + ) -> Result { + let s = bs58::encode(self.to_bytes()).into_string(); + serializer.serialize_str(&s) + } +} + +impl<'de> Deserialize<'de> for SecretKey { + fn deserialize>( + deserializer: D, + ) -> Result { + let s = String::deserialize(deserializer)?; + let decoded = + bs58::decode(&s).into_vec().map_err(SerdeError::custom)?; + let decoded_len = decoded.len(); + let byte_length_str = Self::SIZE.to_string(); + let bytes: [u8; Self::SIZE] = decoded.try_into().map_err(|_| { + SerdeError::invalid_length(decoded_len, &byte_length_str.as_str()) + })?; + SecretKey::from_bytes(&bytes) + .map_err(|err| SerdeError::custom(format!("{err:?}"))) + } +} + +impl Serialize for ViewKey { + fn serialize( + &self, + serializer: S, + ) -> Result { + let s = bs58::encode(self.to_bytes()).into_string(); + serializer.serialize_str(&s) + } +} + +impl<'de> Deserialize<'de> for ViewKey { + fn deserialize>( + deserializer: D, + ) -> Result { + let s = String::deserialize(deserializer)?; + let decoded = + bs58::decode(&s).into_vec().map_err(SerdeError::custom)?; + let decoded_len = decoded.len(); + let byte_length_str = Self::SIZE.to_string(); + let bytes: [u8; Self::SIZE] = decoded.try_into().map_err(|_| { + SerdeError::invalid_length(decoded_len, &byte_length_str.as_str()) + })?; + ViewKey::from_bytes(&bytes) + .map_err(|err| SerdeError::custom(format!("{err:?}"))) + } +} + +impl Serialize for NoteType { + fn serialize( + &self, + serializer: S, + ) -> Result { + match self { + Self::Transparent => { + serializer.serialize_unit_variant("NoteType", 0, "Transparent") + } + Self::Obfuscated => { + serializer.serialize_unit_variant("NoteType", 1, "Obfuscated") + } + } + } +} + +impl<'de> Deserialize<'de> for NoteType { + fn deserialize>( + deserializer: D, + ) -> Result { + let value = String::deserialize(deserializer)?; + match value.as_str() { + "Obfuscated" => Ok(NoteType::Obfuscated), + "Transparent" => Ok(NoteType::Transparent), + v => Err(SerdeError::unknown_variant( + v, + &["Transparent", "Obfuscated"], + )), + } + } +} + +impl Serialize for StealthAddress { + fn serialize( + &self, + serializer: S, + ) -> Result { + let s = bs58::encode(self.to_bytes()).into_string(); + serializer.serialize_str(&s) + } +} + +impl<'de> Deserialize<'de> for StealthAddress { + fn deserialize>( + deserializer: D, + ) -> Result { + let s = String::deserialize(deserializer)?; + let decoded = + bs58::decode(&s).into_vec().map_err(SerdeError::custom)?; + let decoded_len = decoded.len(); + let byte_length_str = Self::SIZE.to_string(); + let bytes: [u8; Self::SIZE] = decoded.try_into().map_err(|_| { + SerdeError::invalid_length(decoded_len, &byte_length_str.as_str()) + })?; + StealthAddress::from_bytes(&bytes) + .map_err(|err| SerdeError::custom(format!("{err:?}"))) + } +} + +// To serialize and deserialize u64s as big ints: +// https://github.com/dusk-network/rusk/issues/2773#issuecomment-2519791322. +struct Bigint(u64); + +impl Serialize for Bigint { + fn serialize( + &self, + serializer: S, + ) -> Result { + let s: String = format!("{}n", self.0); + s.serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for Bigint { + fn deserialize>( + deserializer: D, + ) -> Result { + let mut s = String::deserialize(deserializer)?; + let last_char = s.pop().ok_or_else(|| { + SerdeError::invalid_value( + Unexpected::Str(&s), + &"a non-empty string", + ) + })?; + if last_char != 'n' { + return Err(SerdeError::invalid_value( + Unexpected::Str(&s), + &"a bigint ending with character 'n'", + )); + } + let parsed_number = u64::from_str_radix(&s, 10).map_err(|e| { + SerdeError::custom(format!("failed to deserialize u64: {e}")) + })?; + Ok(Self(parsed_number)) + } +} + +impl Serialize for Sender { + fn serialize( + &self, + serializer: S, + ) -> Result { + match self { + Sender::Encryption(pk) => serializer.serialize_newtype_variant( + "Sender", + 0, + "Encryption", + pk, + ), + Sender::ContractInfo(info) => serializer.serialize_newtype_variant( + "Sender", + 1, + "ContractInfo", + &hex::encode(info), + ), + } + } +} + +impl<'de> Deserialize<'de> for Sender { + fn deserialize>( + deserializer: D, + ) -> Result { + struct SenderVisitor; + + static VARIANTS: [&'static str; 2] = ["Encryption", "ContractInfo"]; + + impl<'de> Visitor<'de> for SenderVisitor { + type Value = Sender; + + fn expecting( + &self, + formatter: &mut alloc::fmt::Formatter, + ) -> alloc::fmt::Result { + formatter.write_str( + "an enum with variants Enrcyption and ContractInfo", + ) + } + + fn visit_enum>( + self, + data: A, + ) -> Result { + match data.variant()? { + ("Encryption", variant) => { + Ok(Sender::Encryption(variant.newtype_variant()?)) + } + ("ContractInfo", variant) => { + let variant_data: String = variant.newtype_variant()?; + let decoded = hex::decode(&variant_data) + .map_err(SerdeError::custom)?; + let decoded_len = decoded.len(); + let byte_length_str = + format!("{}", 4 * JubJubAffine::SIZE); + let bytes: [u8; 4 * JubJubAffine::SIZE] = + decoded.try_into().map_err(|_| { + SerdeError::invalid_length( + decoded_len, + &byte_length_str.as_str(), + ) + })?; + Ok(Sender::ContractInfo(bytes)) + } + (variant_name, _) => Err(SerdeError::unknown_variant( + variant_name, + &VARIANTS, + )), + } + } + } + + deserializer.deserialize_enum("Sender", &VARIANTS, SenderVisitor) + } +} + +impl Serialize for Note { + fn serialize( + &self, + serializer: S, + ) -> Result { + let mut struct_ser = serializer.serialize_struct("Note", 6)?; + struct_ser.serialize_field("note_type", &self.note_type)?; + struct_ser + .serialize_field("value_commitment", &self.value_commitment)?; + struct_ser.serialize_field("stealth_address", &self.stealth_address)?; + struct_ser.serialize_field("pos", &Bigint(self.pos))?; + struct_ser + .serialize_field("value_enc", &hex::encode(&self.value_enc))?; + struct_ser.serialize_field("sender", &self.sender)?; + struct_ser.end() + } +} + +impl<'de> Deserialize<'de> for Note { + fn deserialize>( + deserializer: D, + ) -> Result { + struct NoteVisitor; + + static FIELDS: [&'static str; 6] = [ + "note_type", + "value_commitment", + "stealth_address", + "pos", + "value_enc", + "sender", + ]; + + impl<'de> Visitor<'de> for NoteVisitor { + type Value = Note; + + fn expecting( + &self, + formatter: &mut alloc::fmt::Formatter, + ) -> alloc::fmt::Result { + formatter.write_str("expecting a struct with fields note_type, value_commitment, stealth_address, pos, value_enc, and sender") + } + + fn visit_map>( + self, + mut map: A, + ) -> Result { + let mut note_type = None; + let mut value_commitment = None; + let mut stealth_address = None; + let mut pos: Option = None; + let mut value_enc = None; + let mut sender = None; + + while let Some(key) = map.next_key()? { + match key { + "note_type" => { + if note_type.is_some() { + return Err(SerdeError::duplicate_field( + "note_type", + )); + } + note_type = Some(map.next_value()?); + } + "value_commitment" => { + if value_commitment.is_some() { + return Err(SerdeError::duplicate_field( + "value_commitment", + )); + } + value_commitment = Some(map.next_value()?); + } + "stealth_address" => { + if stealth_address.is_some() { + return Err(SerdeError::duplicate_field( + "stealth_address", + )); + } + stealth_address = Some(map.next_value()?); + } + "pos" => { + if pos.is_some() { + return Err(SerdeError::duplicate_field("pos")); + } + pos = Some(map.next_value()?); + } + "value_enc" => { + if sender.is_some() { + return Err(SerdeError::duplicate_field( + "value_enc", + )); + } + value_enc = Some(map.next_value()?); + } + "sender" => { + if sender.is_some() { + return Err(SerdeError::duplicate_field( + "sender", + )); + } + sender = Some(map.next_value()?); + } + field => { + return Err(SerdeError::unknown_field( + field, &FIELDS, + )) + } + } + } + + let value_enc: String = value_enc + .ok_or_else(|| SerdeError::missing_field("value_enc"))?; + let value_enc_len = value_enc.len(); + let decoded_value_enc = + hex::decode(value_enc).map_err(SerdeError::custom)?; + let value_enc: [u8; NOTE_VAL_ENC_SIZE] = + decoded_value_enc.try_into().map_err(|_| { + SerdeError::invalid_length( + value_enc_len, + &NOTE_VAL_ENC_SIZE.to_string().as_str(), + ) + })?; + + Ok(Note { + note_type: note_type.ok_or_else(|| { + SerdeError::missing_field("note_type") + })?, + stealth_address: stealth_address.ok_or_else(|| { + SerdeError::missing_field("stealth_address") + })?, + value_commitment: value_commitment.ok_or_else(|| { + SerdeError::missing_field("value_commitment") + })?, + pos: pos.ok_or_else(|| SerdeError::missing_field("pos"))?.0, + value_enc, + sender: sender + .ok_or_else(|| SerdeError::missing_field("sender"))?, + }) + } + } + + deserializer.deserialize_struct("Note", &FIELDS, NoteVisitor) + } +} + +// The current serde implementation for `BlsScalar` is not what it's expected to +// be, so this is needed at the moment: https://github.com/dusk-network/bls12_381/issues/145. +struct BlsScalarSerde(BlsScalar); + +impl Serialize for BlsScalarSerde { + fn serialize( + &self, + serializer: S, + ) -> Result { + let s = hex::encode(self.0.to_bytes()); + s.serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for BlsScalarSerde { + fn deserialize>( + deserializer: D, + ) -> Result { + let s = String::deserialize(deserializer)?; + let decoded = hex::decode(&s).map_err(SerdeError::custom)?; + let decoded_len = decoded.len(); + let bytes: [u8; BlsScalar::SIZE] = + decoded.try_into().map_err(|_| { + SerdeError::invalid_length( + decoded_len, + &BlsScalar::SIZE.to_string().as_str(), + ) + })?; + let bls_scalar = BlsScalar::from_bytes(&bytes).into_option().ok_or( + SerdeError::custom( + "Failed to deserialize BlsScalar: invalid BlsScalar", + ), + )?; + Ok(BlsScalarSerde(bls_scalar)) + } +} + +impl Serialize for TxSkeleton { + fn serialize( + &self, + serializer: S, + ) -> Result { + let mut struct_ser = serializer.serialize_struct("TxSkeleton", 5)?; + let serde_nullifiers: Vec = self + .nullifiers + .iter() + .map(|nullifier| BlsScalarSerde(nullifier.clone())) + .collect(); + struct_ser.serialize_field("root", &BlsScalarSerde(self.root))?; + struct_ser.serialize_field("nullifiers", &serde_nullifiers)?; + struct_ser.serialize_field("outputs", &self.outputs)?; + struct_ser.serialize_field("max_fee", &Bigint(self.max_fee))?; + struct_ser.serialize_field("deposit", &Bigint(self.deposit))?; + struct_ser.end() + } +} + +impl<'de> Deserialize<'de> for TxSkeleton { + fn deserialize>( + deserializer: D, + ) -> Result { + struct TxSkeletonVisitor; + static FIELDS: [&'static str; 5] = + ["root", "nullifiers", "outputs", "max_fee", "deposit"]; + + impl<'de> Visitor<'de> for TxSkeletonVisitor { + type Value = TxSkeleton; + + fn expecting( + &self, + formatter: &mut alloc::fmt::Formatter, + ) -> alloc::fmt::Result { + formatter.write_str("a struct with fields: root, nullifiers, outputs, max_fee, and deposit") + } + + fn visit_map>( + self, + mut map: A, + ) -> Result { + let mut root: Option = None; + let mut nullifiers: Option> = None; + let mut outputs = None; + let mut deposit: Option = None; + let mut max_fee: Option = None; + + while let Some(key) = map.next_key()? { + match key { + "root" => { + if root.is_some() { + return Err(SerdeError::duplicate_field( + "root", + )); + } + root = Some(map.next_value()?); + } + "nullifiers" => { + if nullifiers.is_some() { + return Err(SerdeError::duplicate_field( + "nullifiers", + )); + } + nullifiers = Some(map.next_value()?); + } + "outputs" => { + if outputs.is_some() { + return Err(SerdeError::duplicate_field( + "outputs", + )); + } + outputs = Some(map.next_value()?); + } + "max_fee" => { + if max_fee.is_some() { + return Err(SerdeError::duplicate_field( + "max_fee", + )); + } + max_fee = Some(map.next_value()?); + } + "deposit" => { + if deposit.is_some() { + return Err(SerdeError::duplicate_field( + "deposit", + )); + } + deposit = Some(map.next_value()?); + } + field => { + return Err(SerdeError::unknown_field( + field, &FIELDS, + )) + } + } + } + Ok(TxSkeleton { + root: root + .ok_or_else(|| SerdeError::missing_field("root"))? + .0, + nullifiers: nullifiers + .ok_or_else(|| SerdeError::missing_field("nullifiers"))? + .into_iter() + .map(|serde_nullifier| serde_nullifier.0) + .collect(), + outputs: outputs + .ok_or_else(|| SerdeError::missing_field("output"))?, + max_fee: max_fee + .ok_or_else(|| SerdeError::missing_field("max_fee"))? + .0, + deposit: deposit + .ok_or_else(|| SerdeError::missing_field("deposit"))? + .0, + }) + } + } + + deserializer.deserialize_struct( + "TxSkeleton", + &FIELDS, + TxSkeletonVisitor, + ) + } +} diff --git a/core/tests/serde.rs b/core/tests/serde.rs new file mode 100644 index 0000000..bcd93fb --- /dev/null +++ b/core/tests/serde.rs @@ -0,0 +1,212 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +#![cfg(feature = "serde")] + +use dusk_bls12_381::BlsScalar; +use dusk_bytes::Serializable; +use dusk_jubjub::{JubJubAffine, JubJubScalar}; +use ff::Field; +use jubjub_schnorr::{PublicKey as NotePublicKey, SecretKey as NoteSecretKey}; +use phoenix_core::{ + Note, NoteType, PublicKey, SecretKey, Sender, StealthAddress, TxSkeleton, + ViewKey, +}; +use rand::rngs::StdRng; +use rand::{Rng, RngCore, SeedableRng}; + +fn create_note(rng: &mut StdRng) -> Note { + let sender_pk = PublicKey::from(&SecretKey::random(rng)); + let receiver_sk = SecretKey::random(rng); + let receiver_pk = PublicKey::from(&receiver_sk); + let value = 25; + let value_blinder = JubJubScalar::random(StdRng::seed_from_u64(0xc0b)); + let sender_blinder = [ + JubJubScalar::random(StdRng::seed_from_u64(0xdead)), + JubJubScalar::random(StdRng::seed_from_u64(0xbeef)), + ]; + Note::new( + rng, + NoteType::Obfuscated, + &sender_pk, + &receiver_pk, + value, + value_blinder, + sender_blinder, + ) +} + +#[test] +fn serde_public_key() { + let mut rng = StdRng::seed_from_u64(0xc0b); + let sk = SecretKey::random(&mut rng); + let pk = PublicKey::from(&sk); + let ser = serde_json::to_string(&pk).unwrap(); + let deser = serde_json::from_str(&ser).unwrap(); + assert_eq!(pk, deser); +} + +#[test] +fn serde_secret_key() { + let mut rng = StdRng::seed_from_u64(0xc0b); + let sk = SecretKey::random(&mut rng); + let ser = serde_json::to_string(&sk).unwrap(); + let deser = serde_json::from_str(&ser).unwrap(); + assert_eq!(sk, deser); +} + +#[test] +fn serde_view_key() { + let mut rng = StdRng::seed_from_u64(0xc0b); + let sk = SecretKey::random(&mut rng); + let vk = ViewKey::from(&sk); + let ser = serde_json::to_string(&vk).unwrap(); + let deser = serde_json::from_str(&ser).unwrap(); + assert_eq!(vk, deser); +} + +#[test] +fn serde_note_type() { + let obfuscated = NoteType::Obfuscated; + let transparent = NoteType::Transparent; + let obf_ser = serde_json::to_string(&obfuscated).unwrap(); + let trans_ser = serde_json::to_string(&transparent).unwrap(); + let obf_deser = serde_json::from_str(&obf_ser).unwrap(); + let trans_deser = serde_json::from_str(&trans_ser).unwrap(); + assert_eq!(obfuscated, obf_deser); + assert_eq!(transparent, trans_deser); +} + +#[test] +fn serde_stealth_address() { + let mut rng = StdRng::seed_from_u64(0xc0b); + let scalar = JubJubScalar::random(&mut rng); + let pk = PublicKey::from(&SecretKey::random(&mut rng)); + let stealth_addr = pk.gen_stealth_address(&scalar); + let ser = serde_json::to_string(&stealth_addr).unwrap(); + let deser = serde_json::from_str(&ser).unwrap(); + assert_eq!(stealth_addr, deser); +} + +#[test] +fn serde_sender() { + let mut rng = StdRng::seed_from_u64(0xc0b); + let mut contract_info = [0; 4 * JubJubAffine::SIZE]; + rng.fill_bytes(&mut contract_info); + let s1 = Sender::ContractInfo(contract_info); + let sender_pk = PublicKey::from(&SecretKey::random(&mut rng)); + let note_pk = NotePublicKey::from(&NoteSecretKey::random(&mut rng)); + let blinder = [ + JubJubScalar::random(&mut rng), + JubJubScalar::random(&mut rng), + ]; + let s2 = Sender::encrypt(¬e_pk, &sender_pk, &blinder); + + let ser1 = serde_json::to_string(&s1).unwrap(); + let ser2 = serde_json::to_string(&s2).unwrap(); + let deser1 = serde_json::from_str(&ser1).unwrap(); + let deser2 = serde_json::from_str(&ser2).unwrap(); + + assert_eq!(s1, deser1); + assert_eq!(s2, deser2); +} + +#[test] +fn serde_note() { + let mut rng = StdRng::seed_from_u64(0xc0b); + let note = create_note(&mut rng); + let ser = serde_json::to_string(¬e).unwrap(); + let deser = serde_json::from_str(&ser).unwrap(); + assert_eq!(note, deser); +} + +#[test] +fn serde_tx_skeleton() { + let mut rng = StdRng::seed_from_u64(0xc0b); + let root = BlsScalar::random(&mut rng); + let mut nullifiers = Vec::new(); + for _ in 0..rng.gen_range(0..10) { + nullifiers.push(BlsScalar::random(&mut rng)); + } + let outputs = [create_note(&mut rng), create_note(&mut rng)]; + let max_fee = rng.gen_range(10..1000); + let deposit = rng.gen_range(10..1000); + + let ts = TxSkeleton { + root, + nullifiers, + outputs, + max_fee, + deposit, + }; + let ser = serde_json::to_string(&ts).unwrap(); + let deser = serde_json::from_str(&ser).unwrap(); + assert_eq!(ts, deser); +} + +#[test] +fn serde_wrong_encoded() { + let wrong_encoded = "wrong-encoded"; + let public_key: Result = serde_json::from_str(&wrong_encoded); + assert!(public_key.is_err()); + + let secret_key: Result = serde_json::from_str(&wrong_encoded); + assert!(secret_key.is_err()); + + let view_key: Result = serde_json::from_str(&wrong_encoded); + assert!(view_key.is_err()); + + let note_type: Result = serde_json::from_str(&wrong_encoded); + assert!(note_type.is_err()); + + let stealth_address: Result = + serde_json::from_str(&wrong_encoded); + assert!(stealth_address.is_err()); +} + +#[test] +fn serde_too_long_encoded() { + let length_65_enc = "\"Hovyh2MvKLSnTfv2aKMMD1s7MgzWVCdzKJbbLwzU3kgVmo2JugxpGPASJWVQVXcxUqxtxVrQ63myzLRr1ko6oJvyv\""; + + let public_key: Result = serde_json::from_str(&length_65_enc); + assert!(public_key.is_err()); + + let secret_key: Result = serde_json::from_str(&length_65_enc); + assert!(secret_key.is_err()); + + let view_key: Result = serde_json::from_str(&length_65_enc); + assert!(view_key.is_err()); + + let stealth_address: Result = + serde_json::from_str(&length_65_enc); + assert!(stealth_address.is_err()); +} + +#[test] +fn serde_too_short_encoded() { + let length_63_enc = "\"YrHj6pQ3kRkpELFJK8a8ESdYyXaH9fQeb4pXRNEb8mSxDCrin1bF4uHz9BN13kN15mmH5fxXXSAusfLLGLrjCF\""; + + let public_key: Result = serde_json::from_str(&length_63_enc); + assert!(public_key.is_err()); + + let secret_key: Result = serde_json::from_str(&length_63_enc); + assert!(secret_key.is_err()); + + let view_key: Result = serde_json::from_str(&length_63_enc); + assert!(view_key.is_err()); + + let stealth_address: Result = + serde_json::from_str(&length_63_enc); + assert!(stealth_address.is_err()); +} + +#[test] +fn serde_unknown_variant() { + let unknown = "\"unknown-variant\""; + + let note_type: Result = serde_json::from_str(&unknown); + assert!(note_type.is_err()); +}