diff --git a/core/Cargo.toml b/core/Cargo.toml index 2c31741493..293d165139 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -28,8 +28,14 @@ ark-bn254 = { workspace = true, features = ["curve"], optional = true } ark-relations = { workspace = true, optional = true } ark-serialize = { workspace = true, optional = true } +# serde support dependencies +serde = { workspace = true, features = ["derive"], optional = true } +hex = { workspace = true, optional = true } +base64 = { workspace = true, optional = true } + [dev-dependencies] rand = { workspace = true, features = ["std", "std_rng"] } +serde_json = { workspace = true } [features] parallel = [ @@ -63,3 +69,12 @@ groth16 = [ # Enables std feature for dusk-plonk std = ["dusk-plonk/std"] + +serde = [ + "dep:serde", + "hex", + "base64", + "piecrust-uplink/serde", + "bls12_381-bls/serde", + "phoenix-core/serde", +] diff --git a/core/Makefile b/core/Makefile index c226c0fc73..fd14fc5c16 100644 --- a/core/Makefile +++ b/core/Makefile @@ -5,7 +5,7 @@ help: ## Display this help screen @grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}' test: - cargo test --release --features zk + cargo test --release --features zk,serde cargo test --release --no-run clippy: ## Run clippy diff --git a/core/src/lib.rs b/core/src/lib.rs index d458155328..0e2c327ec1 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -28,6 +28,9 @@ pub use error::Error; mod dusk; pub use dusk::{dusk, from_dusk, Dusk, LUX}; +#[cfg(feature = "serde")] +mod serde_support; + // elliptic curve types pub use dusk_bls12_381::BlsScalar; pub use dusk_jubjub::{ @@ -135,3 +138,10 @@ fn read_arr(buf: &mut &[u8]) -> Result<[u8; N], BytesError> { *buf = &buf[N..]; Ok(a) } + +#[cfg(test)] +mod tests { + // the `unused_crate_dependencies` lint complains for dev-dependencies that + // are only used in integration tests, so adding this work-around here + use serde_json as _; +} diff --git a/core/src/serde_support.rs b/core/src/serde_support.rs new file mode 100644 index 0000000000..1295430af2 --- /dev/null +++ b/core/src/serde_support.rs @@ -0,0 +1,1149 @@ +// 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. + +use alloc::format; +use alloc::string::ToString; +use alloc::vec::Vec; + +use base64::engine::general_purpose::STANDARD as BASE64_STANDARD; +use base64::Engine; +use dusk_bytes::Serializable; +use serde::de::{Error as SerdeError, MapAccess, Unexpected, Visitor}; +use serde::ser::SerializeStruct; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use crate::signatures::bls::PublicKey as AccountPublicKey; +use crate::stake::{Reward, SlashEvent, StakeEvent}; +use crate::transfer::{ + ContractToAccountEvent, ContractToContractEvent, ConvertEvent, + DepositEvent, MoonlightTransactionEvent, PhoenixTransactionEvent, + WithdrawEvent, +}; +use crate::{BlsScalar, String}; + +// 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 StakeEvent { + fn serialize( + &self, + serializer: S, + ) -> Result { + let mut ser_struct = serializer.serialize_struct("StakeEvent", 3)?; + ser_struct.serialize_field("keys", &self.keys)?; + ser_struct.serialize_field("value", &Bigint(self.value))?; + ser_struct.serialize_field("locked", &Bigint(self.locked))?; + ser_struct.end() + } +} + +impl<'de> Deserialize<'de> for StakeEvent { + fn deserialize>( + deserializer: D, + ) -> Result { + struct StakeEventVisitor; + + const FIELDS: [&str; 3] = ["keys", "value", "locked"]; + + impl<'de> Visitor<'de> for StakeEventVisitor { + type Value = StakeEvent; + + fn expecting( + &self, + formatter: &mut alloc::fmt::Formatter, + ) -> alloc::fmt::Result { + formatter.write_str( + "expecting a struct with fields keys, value and locked", + ) + } + + fn visit_map>( + self, + mut map: A, + ) -> Result { + let mut keys = None; + let mut value: Option = None; + let mut locked: Option = None; + + while let Some(key) = map.next_key()? { + match key { + "keys" => { + if keys.is_some() { + return Err(SerdeError::duplicate_field( + "keys", + )); + } + keys = Some(map.next_value()?); + } + "value" => { + if value.is_some() { + return Err(SerdeError::duplicate_field( + "value", + )); + } + value = Some(map.next_value()?); + } + "locked" => { + if locked.is_some() { + return Err(SerdeError::duplicate_field( + "locked", + )); + } + locked = Some(map.next_value()?); + } + field => { + return Err(SerdeError::unknown_field( + field, &FIELDS, + )) + } + } + } + + Ok(StakeEvent { + keys: keys + .ok_or_else(|| SerdeError::missing_field("keys"))?, + value: value + .ok_or_else(|| SerdeError::missing_field("value"))? + .0, + locked: locked + .ok_or_else(|| SerdeError::missing_field("locked"))? + .0, + }) + } + } + + deserializer.deserialize_struct( + "StakeEvent", + &FIELDS, + StakeEventVisitor, + ) + } +} + +impl Serialize for SlashEvent { + fn serialize( + &self, + serializer: S, + ) -> Result { + let mut ser_struct = serializer.serialize_struct("SlashEvent", 3)?; + ser_struct.serialize_field("account", &self.account)?; + ser_struct.serialize_field("value", &Bigint(self.value))?; + ser_struct.serialize_field( + "next_eligibility", + &Bigint(self.next_eligibility), + )?; + ser_struct.end() + } +} + +impl<'de> Deserialize<'de> for SlashEvent { + fn deserialize>( + deserializer: D, + ) -> Result { + struct SlashEventVisitor; + + const FIELDS: [&str; 3] = ["account", "value", "next_eligibility"]; + + impl<'de> Visitor<'de> for SlashEventVisitor { + type Value = SlashEvent; + + fn expecting( + &self, + formatter: &mut alloc::fmt::Formatter, + ) -> alloc::fmt::Result { + formatter.write_str("expecting a struct with fields account, value and next_eligibility") + } + + fn visit_map>( + self, + mut map: A, + ) -> Result { + let mut account = None; + let mut value: Option = None; + let mut next_eligibility: Option = None; + + while let Some(key) = map.next_key()? { + match key { + "account" => { + if account.is_some() { + return Err(SerdeError::duplicate_field( + "account", + )); + } + account = Some(map.next_value()?); + } + "value" => { + if value.is_some() { + return Err(SerdeError::duplicate_field( + "value", + )); + } + value = Some(map.next_value()?); + } + "next_eligibility" => { + if next_eligibility.is_some() { + return Err(SerdeError::duplicate_field( + "next_eligibility", + )); + } + next_eligibility = Some(map.next_value()?); + } + field => { + return Err(SerdeError::unknown_field( + field, &FIELDS, + )) + } + } + } + + Ok(SlashEvent { + account: account + .ok_or_else(|| SerdeError::missing_field("account"))?, + value: value + .ok_or_else(|| SerdeError::missing_field("value"))? + .0, + next_eligibility: next_eligibility + .ok_or_else(|| { + SerdeError::missing_field("next_eligibility") + })? + .0, + }) + } + } + + deserializer.deserialize_struct( + "SlashEvent", + &FIELDS, + SlashEventVisitor, + ) + } +} + +impl Serialize for Reward { + fn serialize( + &self, + serializer: S, + ) -> Result { + let mut ser_struct = serializer.serialize_struct("Reward", 3)?; + ser_struct.serialize_field("account", &self.account)?; + ser_struct.serialize_field("value", &Bigint(self.value))?; + ser_struct.serialize_field("reason", &self.reason)?; + ser_struct.end() + } +} + +impl<'de> Deserialize<'de> for Reward { + fn deserialize>( + deserializer: D, + ) -> Result { + struct RewardVisitor; + + const FIELDS: [&str; 3] = ["account", "value", "reason"]; + + impl<'de> Visitor<'de> for RewardVisitor { + type Value = Reward; + + fn expecting( + &self, + formatter: &mut alloc::fmt::Formatter, + ) -> alloc::fmt::Result { + formatter.write_str( + "expecting a struct with fields account, value and reason", + ) + } + + fn visit_map>( + self, + mut map: A, + ) -> Result { + let mut account = None; + let mut value: Option = None; + let mut reason = None; + + while let Some(key) = map.next_key()? { + match key { + "account" => { + if account.is_some() { + return Err(SerdeError::duplicate_field( + "account", + )); + } + account = Some(map.next_value()?); + } + "value" => { + if value.is_some() { + return Err(SerdeError::duplicate_field( + "value", + )); + } + value = Some(map.next_value()?); + } + "reason" => { + if reason.is_some() { + return Err(SerdeError::duplicate_field( + "reason", + )); + } + reason = Some(map.next_value()?); + } + field => { + return Err(SerdeError::unknown_field( + field, &FIELDS, + )) + } + } + } + + Ok(Reward { + account: account + .ok_or_else(|| SerdeError::missing_field("account"))?, + value: value + .ok_or_else(|| SerdeError::missing_field("value"))? + .0, + reason: reason + .ok_or_else(|| SerdeError::missing_field("reason"))?, + }) + } + } + + deserializer.deserialize_struct("Reward", &FIELDS, RewardVisitor) + } +} + +impl Serialize for WithdrawEvent { + fn serialize( + &self, + serializer: S, + ) -> Result { + let mut ser_struct = serializer.serialize_struct("WithdrawEvent", 3)?; + ser_struct.serialize_field("sender", &self.sender)?; + ser_struct.serialize_field("receiver", &self.receiver)?; + ser_struct.serialize_field("value", &Bigint(self.value))?; + ser_struct.end() + } +} + +impl<'de> Deserialize<'de> for WithdrawEvent { + fn deserialize>( + deserializer: D, + ) -> Result { + struct WithdrawEventVisitor; + + const FIELDS: [&str; 3] = ["sender", "receiver", "value"]; + + impl<'de> Visitor<'de> for WithdrawEventVisitor { + type Value = WithdrawEvent; + + fn expecting( + &self, + formatter: &mut alloc::fmt::Formatter, + ) -> alloc::fmt::Result { + formatter.write_str( + "expecting a struct with fields sender, receiver and value", + ) + } + + fn visit_map>( + self, + mut map: A, + ) -> Result { + let mut sender = None; + let mut value: Option = None; + let mut receiver = None; + + while let Some(key) = map.next_key()? { + match key { + "sender" => { + if sender.is_some() { + return Err(SerdeError::duplicate_field( + "sender", + )); + } + sender = Some(map.next_value()?); + } + "receiver" => { + if receiver.is_some() { + return Err(SerdeError::duplicate_field( + "receiver", + )); + } + receiver = Some(map.next_value()?); + } + "value" => { + if value.is_some() { + return Err(SerdeError::duplicate_field( + "value", + )); + } + value = Some(map.next_value()?); + } + field => { + return Err(SerdeError::unknown_field( + field, &FIELDS, + )) + } + } + } + + Ok(WithdrawEvent { + sender: sender + .ok_or_else(|| SerdeError::missing_field("sender"))?, + value: value + .ok_or_else(|| SerdeError::missing_field("value"))? + .0, + receiver: receiver + .ok_or_else(|| SerdeError::missing_field("receiver"))?, + }) + } + } + + deserializer.deserialize_struct( + "WithdrawEvent", + &FIELDS, + WithdrawEventVisitor, + ) + } +} + +impl Serialize for ConvertEvent { + fn serialize( + &self, + serializer: S, + ) -> Result { + let mut ser_struct = serializer.serialize_struct("ConvertEvent", 3)?; + ser_struct.serialize_field("sender", &self.sender)?; + ser_struct.serialize_field("receiver", &self.receiver)?; + ser_struct.serialize_field("value", &Bigint(self.value))?; + ser_struct.end() + } +} + +impl<'de> Deserialize<'de> for ConvertEvent { + fn deserialize>( + deserializer: D, + ) -> Result { + struct ConvertEventVisitor; + + const FIELDS: [&str; 3] = ["sender", "receiver", "value"]; + + impl<'de> Visitor<'de> for ConvertEventVisitor { + type Value = ConvertEvent; + + fn expecting( + &self, + formatter: &mut alloc::fmt::Formatter, + ) -> alloc::fmt::Result { + formatter.write_str( + "expecting a struct with fields sender, receiver and value", + ) + } + + fn visit_map>( + self, + mut map: A, + ) -> Result { + let mut sender = None; + let mut value: Option = None; + let mut receiver = None; + + while let Some(key) = map.next_key()? { + match key { + "sender" => { + if sender.is_some() { + return Err(SerdeError::duplicate_field( + "sender", + )); + } + sender = Some(map.next_value()?); + } + "receiver" => { + if receiver.is_some() { + return Err(SerdeError::duplicate_field( + "receiver", + )); + } + receiver = Some(map.next_value()?); + } + "value" => { + if value.is_some() { + return Err(SerdeError::duplicate_field( + "value", + )); + } + value = Some(map.next_value()?); + } + field => { + return Err(SerdeError::unknown_field( + field, &FIELDS, + )) + } + } + } + + Ok(ConvertEvent { + sender: sender + .ok_or_else(|| SerdeError::missing_field("sender"))?, + receiver: receiver + .ok_or_else(|| SerdeError::missing_field("receiver"))?, + value: value + .ok_or_else(|| SerdeError::missing_field("value"))? + .0, + }) + } + } + + deserializer.deserialize_struct( + "ConvertEvent", + &FIELDS, + ConvertEventVisitor, + ) + } +} + +impl Serialize for DepositEvent { + fn serialize( + &self, + serializer: S, + ) -> Result { + let mut ser_struct = serializer.serialize_struct("DepositEvent", 3)?; + ser_struct.serialize_field("sender", &self.sender)?; + ser_struct.serialize_field("receiver", &self.receiver)?; + ser_struct.serialize_field("value", &Bigint(self.value))?; + ser_struct.end() + } +} + +impl<'de> Deserialize<'de> for DepositEvent { + fn deserialize>( + deserializer: D, + ) -> Result { + struct DepositEventVisitor; + + const FIELDS: [&str; 3] = ["sender", "receiver", "value"]; + + impl<'de> Visitor<'de> for DepositEventVisitor { + type Value = DepositEvent; + + fn expecting( + &self, + formatter: &mut alloc::fmt::Formatter, + ) -> alloc::fmt::Result { + formatter.write_str( + "expecting a struct with fields sender, receiver and value", + ) + } + + fn visit_map>( + self, + mut map: A, + ) -> Result { + let mut sender = None; + let mut receiver = None; + let mut value: Option = None; + + while let Some(key) = map.next_key()? { + match key { + "sender" => { + if sender.is_some() { + return Err(SerdeError::duplicate_field( + "sender", + )); + } + sender = Some(map.next_value()?); + } + "receiver" => { + if receiver.is_some() { + return Err(SerdeError::duplicate_field( + "receiver", + )); + } + receiver = Some(map.next_value()?); + } + "value" => { + if value.is_some() { + return Err(SerdeError::duplicate_field( + "value", + )); + } + value = Some(map.next_value()?); + } + field => { + return Err(SerdeError::unknown_field( + field, &FIELDS, + )) + } + } + } + + Ok(DepositEvent { + sender: sender + .ok_or_else(|| SerdeError::missing_field("sender"))?, + receiver: receiver + .ok_or_else(|| SerdeError::missing_field("receiver"))?, + value: value + .ok_or_else(|| SerdeError::missing_field("value"))? + .0, + }) + } + } + + deserializer.deserialize_struct( + "DepositEvent", + &FIELDS, + DepositEventVisitor, + ) + } +} + +impl Serialize for ContractToContractEvent { + fn serialize( + &self, + serializer: S, + ) -> Result { + let mut ser_struct = + serializer.serialize_struct("ContractToContractEvent", 3)?; + ser_struct.serialize_field("sender", &self.sender)?; + ser_struct.serialize_field("receiver", &self.receiver)?; + ser_struct.serialize_field("value", &Bigint(self.value))?; + ser_struct.end() + } +} + +impl<'de> Deserialize<'de> for ContractToContractEvent { + fn deserialize>( + deserializer: D, + ) -> Result { + struct ContractToContractEventVisitor; + + const FIELDS: [&str; 3] = ["sender", "receiver", "value"]; + + impl<'de> Visitor<'de> for ContractToContractEventVisitor { + type Value = ContractToContractEvent; + + fn expecting( + &self, + formatter: &mut alloc::fmt::Formatter, + ) -> alloc::fmt::Result { + formatter.write_str( + "expecting a struct with fields sender, receiver and value", + ) + } + + fn visit_map>( + self, + mut map: A, + ) -> Result { + let mut sender = None; + let mut receiver = None; + let mut value: Option = None; + + while let Some(key) = map.next_key()? { + match key { + "sender" => { + if sender.is_some() { + return Err(SerdeError::duplicate_field( + "sender", + )); + } + sender = Some(map.next_value()?); + } + "receiver" => { + if receiver.is_some() { + return Err(SerdeError::duplicate_field( + "receiver", + )); + } + receiver = Some(map.next_value()?); + } + "value" => { + if value.is_some() { + return Err(SerdeError::duplicate_field( + "value", + )); + } + value = Some(map.next_value()?); + } + field => { + return Err(SerdeError::unknown_field( + field, &FIELDS, + )) + } + } + } + + Ok(ContractToContractEvent { + sender: sender + .ok_or_else(|| SerdeError::missing_field("sender"))?, + receiver: receiver + .ok_or_else(|| SerdeError::missing_field("receiver"))?, + value: value + .ok_or_else(|| SerdeError::missing_field("value"))? + .0, + }) + } + } + + deserializer.deserialize_struct( + "ContractToContractEvent", + &FIELDS, + ContractToContractEventVisitor, + ) + } +} + +impl Serialize for ContractToAccountEvent { + fn serialize( + &self, + serializer: S, + ) -> Result { + let mut ser_struct = + serializer.serialize_struct("ContractToAccountEvent", 3)?; + ser_struct.serialize_field("sender", &self.sender)?; + ser_struct.serialize_field("receiver", &self.receiver)?; + ser_struct.serialize_field("value", &Bigint(self.value))?; + ser_struct.end() + } +} + +impl<'de> Deserialize<'de> for ContractToAccountEvent { + fn deserialize>( + deserializer: D, + ) -> Result { + struct ContractToAccountEventVisitor; + + const FIELDS: [&str; 3] = ["sender", "receiver", "value"]; + + impl<'de> Visitor<'de> for ContractToAccountEventVisitor { + type Value = ContractToAccountEvent; + + fn expecting( + &self, + formatter: &mut alloc::fmt::Formatter, + ) -> alloc::fmt::Result { + formatter.write_str( + "expecting a struct with fields sender, receiver and value", + ) + } + + fn visit_map>( + self, + mut map: A, + ) -> Result { + let mut sender = None; + let mut receiver = None; + let mut value: Option = None; + + while let Some(key) = map.next_key()? { + match key { + "sender" => { + if sender.is_some() { + return Err(SerdeError::duplicate_field( + "sender", + )); + } + sender = Some(map.next_value()?); + } + "receiver" => { + if receiver.is_some() { + return Err(SerdeError::duplicate_field( + "receiver", + )); + } + receiver = Some(map.next_value()?); + } + "value" => { + if value.is_some() { + return Err(SerdeError::duplicate_field( + "value", + )); + } + value = Some(map.next_value()?); + } + field => { + return Err(SerdeError::unknown_field( + field, &FIELDS, + )) + } + } + } + + Ok(ContractToAccountEvent { + sender: sender + .ok_or_else(|| SerdeError::missing_field("sender"))?, + receiver: receiver + .ok_or_else(|| SerdeError::missing_field("receiver"))?, + value: value + .ok_or_else(|| SerdeError::missing_field("value"))? + .0, + }) + } + } + + deserializer.deserialize_struct( + "ContractToAccountEvent", + &FIELDS, + ContractToAccountEventVisitor, + ) + } +} + +// 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 PhoenixTransactionEvent { + fn serialize( + &self, + serializer: S, + ) -> Result { + let mut ser_struct = + serializer.serialize_struct("PhoenixTransactionEvent", 5)?; + let nullifiers: Vec = self + .nullifiers + .iter() + .map(|scalar| BlsScalarSerde(scalar.clone())) + .collect(); + ser_struct.serialize_field("nullifiers", &nullifiers)?; + ser_struct.serialize_field("notes", &self.notes)?; + ser_struct + .serialize_field("memo", &BASE64_STANDARD.encode(&self.memo))?; + ser_struct.serialize_field("gas_spent", &Bigint(self.gas_spent))?; + ser_struct.serialize_field("refund_note", &self.refund_note)?; + ser_struct.end() + } +} + +impl<'de> Deserialize<'de> for PhoenixTransactionEvent { + fn deserialize>( + deserializer: D, + ) -> Result { + struct PhoenixTransactionEventVisitor; + + const FIELDS: [&str; 5] = + ["nullifiers", "notes", "memo", "gas_spent", "refund_note"]; + + impl<'de> Visitor<'de> for PhoenixTransactionEventVisitor { + type Value = PhoenixTransactionEvent; + + fn expecting( + &self, + formatter: &mut alloc::fmt::Formatter, + ) -> alloc::fmt::Result { + formatter.write_str("expecting a struct with fields nullifiers, notes, memo, gas_spent and refund_note") + } + + fn visit_map>( + self, + mut map: A, + ) -> Result { + let mut nullifiers: Option> = None; + let mut notes = None; + let mut memo: Option = None; + let mut gas_spent: Option = None; + let mut refund_note = None; + + while let Some(key) = map.next_key()? { + match key { + "nullifiers" => { + if nullifiers.is_some() { + return Err(SerdeError::duplicate_field( + "nullifiers", + )); + } + nullifiers = Some(map.next_value()?); + } + "notes" => { + if notes.is_some() { + return Err(SerdeError::duplicate_field( + "notes", + )); + } + notes = Some(map.next_value()?); + } + "memo" => { + if memo.is_some() { + return Err(SerdeError::duplicate_field( + "memo", + )); + } + memo = Some(map.next_value()?); + } + "gas_spent" => { + if gas_spent.is_some() { + return Err(SerdeError::duplicate_field( + "gas_spent", + )); + } + gas_spent = Some(map.next_value()?); + } + "refund_note" => { + if refund_note.is_some() { + return Err(SerdeError::duplicate_field( + "refund_note", + )); + } + refund_note = Some(map.next_value()?); + } + field => { + return Err(SerdeError::unknown_field( + field, &FIELDS, + )) + } + } + } + + let nullifiers = nullifiers + .ok_or_else(|| SerdeError::missing_field("nullifiers"))?; + let nullifiers: Vec = nullifiers + .into_iter() + .map(|scalar_serde| scalar_serde.0) + .collect(); + let memo = + memo.ok_or_else(|| SerdeError::missing_field("memo"))?; + let memo = BASE64_STANDARD + .decode(memo) + .map_err(|err| SerdeError::custom(err))?; + + Ok(PhoenixTransactionEvent { + nullifiers, + notes: notes + .ok_or_else(|| SerdeError::missing_field("memo"))?, + memo, + gas_spent: gas_spent + .ok_or_else(|| SerdeError::missing_field("gas_spent"))? + .0, + refund_note: refund_note.ok_or_else(|| { + SerdeError::missing_field("refund_note") + })?, + }) + } + } + + deserializer.deserialize_struct( + "PhoenixTransactionEvent", + &FIELDS, + PhoenixTransactionEventVisitor, + ) + } +} + +impl Serialize for MoonlightTransactionEvent { + fn serialize( + &self, + serializer: S, + ) -> Result { + let mut ser_struct = + serializer.serialize_struct("MoonlightTransactionEvent", 6)?; + let refund_info = + self.refund_info.map(|(pk, number)| (pk, Bigint(number))); + ser_struct.serialize_field("sender", &self.sender)?; + ser_struct.serialize_field("receiver", &self.receiver)?; + ser_struct.serialize_field("value", &Bigint(self.value))?; + ser_struct + .serialize_field("memo", &BASE64_STANDARD.encode(&self.memo))?; + ser_struct.serialize_field("gas_spent", &Bigint(self.gas_spent))?; + ser_struct.serialize_field("refund_info", &refund_info)?; + ser_struct.end() + } +} + +impl<'de> Deserialize<'de> for MoonlightTransactionEvent { + fn deserialize>( + deserializer: D, + ) -> Result { + struct MoonlightTransactionEventVisitor; + + const FIELDS: [&str; 6] = [ + "sender", + "receiver", + "value", + "memo", + "gas_spent", + "refund_info", + ]; + + impl<'de> Visitor<'de> for MoonlightTransactionEventVisitor { + type Value = MoonlightTransactionEvent; + + fn expecting( + &self, + formatter: &mut alloc::fmt::Formatter, + ) -> alloc::fmt::Result { + formatter.write_str("expecting a struct with fields sender, receiver, value, memo, gas_spent and refund_info") + } + + fn visit_map>( + self, + mut map: A, + ) -> Result { + let mut sender = None; + let mut receiver = None; + let mut value: Option = None; + let mut memo: Option = None; + let mut gas_spent: Option = None; + let mut refund_info: Option< + Option<(AccountPublicKey, Bigint)>, + > = None; + + while let Some(key) = map.next_key()? { + match key { + "sender" => { + if sender.is_some() { + return Err(SerdeError::duplicate_field( + "sender", + )); + } + sender = Some(map.next_value()?); + } + "receiver" => { + if receiver.is_some() { + return Err(SerdeError::duplicate_field( + "receiver", + )); + } + receiver = Some(map.next_value()?); + } + "value" => { + if value.is_some() { + return Err(SerdeError::duplicate_field( + "value", + )); + } + value = Some(map.next_value()?); + } + "memo" => { + if memo.is_some() { + return Err(SerdeError::duplicate_field( + "memo", + )); + } + memo = Some(map.next_value()?); + } + "gas_spent" => { + if gas_spent.is_some() { + return Err(SerdeError::duplicate_field( + "gas_spent", + )); + } + gas_spent = Some(map.next_value()?); + } + "refund_info" => { + if refund_info.is_some() { + return Err(SerdeError::duplicate_field( + "refund_info", + )); + } + refund_info = Some(map.next_value()?); + } + field => { + return Err(SerdeError::unknown_field( + field, &FIELDS, + )) + } + } + } + + let memo = + memo.ok_or_else(|| SerdeError::missing_field("memo"))?; + let memo = BASE64_STANDARD + .decode(memo) + .map_err(|err| SerdeError::custom(err))?; + let refund_info = refund_info + .ok_or_else(|| SerdeError::missing_field("refund_info"))? + .map(|(pk, bigint)| (pk, bigint.0)); + + Ok(MoonlightTransactionEvent { + sender: sender + .ok_or_else(|| SerdeError::missing_field("sender"))?, + receiver: receiver + .ok_or_else(|| SerdeError::missing_field("receiver"))?, + value: value + .ok_or_else(|| SerdeError::missing_field("value"))? + .0, + memo, + gas_spent: gas_spent + .ok_or_else(|| SerdeError::missing_field("gas_spent"))? + .0, + refund_info, + }) + } + } + + deserializer.deserialize_struct( + "MoonlightTransactionEvent", + &FIELDS, + MoonlightTransactionEventVisitor, + ) + } +} diff --git a/core/src/stake.rs b/core/src/stake.rs index 900132bdfd..d18a461b8d 100644 --- a/core/src/stake.rs +++ b/core/src/stake.rs @@ -442,6 +442,7 @@ pub struct StakeData { #[derive( Debug, Default, Clone, Copy, PartialEq, Eq, Archive, Deserialize, Serialize, )] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[archive_attr(derive(CheckBytes))] pub struct StakeKeys { /// Key used for consensus operations, such as voting or producing blocks. @@ -488,6 +489,7 @@ impl StakeKeys { #[derive( Debug, Clone, Copy, PartialEq, Eq, Archive, Deserialize, Serialize, )] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[archive_attr(derive(CheckBytes))] pub enum StakeFundOwner { /// Represents an account-based owner identified by a BLS public key. @@ -673,6 +675,7 @@ pub struct Reward { /// The reason that a reward is issued. #[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[archive_attr(derive(CheckBytes))] pub enum RewardReason { /// The fixed amount awarded to a generator. diff --git a/core/src/transfer/withdraw.rs b/core/src/transfer/withdraw.rs index 1e72fdeac7..e6c69dcbda 100644 --- a/core/src/transfer/withdraw.rs +++ b/core/src/transfer/withdraw.rs @@ -169,6 +169,7 @@ impl Withdraw { /// The receiver of the [`Withdraw`] value. #[derive(Debug, Clone, Copy, PartialEq, Archive, Serialize, Deserialize)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[archive_attr(derive(CheckBytes))] pub enum WithdrawReceiver { /// The stealth address to withdraw to, when the withdrawal is into Phoenix diff --git a/core/tests/serde.rs b/core/tests/serde.rs new file mode 100644 index 0000000000..b51d3ecfed --- /dev/null +++ b/core/tests/serde.rs @@ -0,0 +1,309 @@ +// 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 bls12_381_bls::{ + PublicKey as AccountPublicKey, SecretKey as AccountSecretKey, +}; +use dusk_core::stake::{ + Reward, RewardReason, SlashEvent, StakeEvent, StakeFundOwner, StakeKeys, +}; +use dusk_core::transfer::withdraw::WithdrawReceiver; +use dusk_core::transfer::WithdrawEvent; +use dusk_core::transfer::{ + ContractToAccountEvent, ContractToContractEvent, ConvertEvent, + DepositEvent, MoonlightTransactionEvent, PhoenixTransactionEvent, +}; +use dusk_core::{BlsScalar, JubJubScalar}; +use ff::Field; +use phoenix_core::{ + Note, PublicKey as PhoenixPublicKey, SecretKey as PhoenixSecretKey, +}; +use piecrust_uplink::{ContractId, CONTRACT_ID_BYTES}; +use rand::rngs::StdRng; +use rand::Rng; +use rand::{RngCore, SeedableRng}; + +#[test] +fn serde_stake_event() { + let mut rng = StdRng::seed_from_u64(42); + let mut contract_id_bytes = [0; CONTRACT_ID_BYTES]; + rng.fill_bytes(&mut contract_id_bytes); + let pk = AccountPublicKey::from(&AccountSecretKey::random(&mut rng)); + let owner1 = StakeFundOwner::Account(pk); + let owner2 = + StakeFundOwner::Contract(ContractId::from_bytes(contract_id_bytes)); + let event1 = StakeEvent { + keys: StakeKeys::new(pk, owner1), + value: rng.next_u64(), + locked: rng.next_u64(), + }; + let event2 = StakeEvent { + keys: StakeKeys::new(pk, owner2), + value: rng.next_u64(), + locked: rng.next_u64(), + }; + + let ser1 = serde_json::to_string(&event1).unwrap(); + let ser2 = serde_json::to_string(&event2).unwrap(); + let deser1 = serde_json::from_str(&ser1).unwrap(); + let deser2 = serde_json::from_str(&ser2).unwrap(); + + assert_eq!(event1, deser1); + assert_eq!(event2, deser2); + assert_ne!(deser1, deser2); +} + +#[test] +fn serde_slash_event() { + let mut rng = StdRng::seed_from_u64(42); + let event = SlashEvent { + account: AccountPublicKey::from(&AccountSecretKey::random(&mut rng)), + value: rng.next_u64(), + next_eligibility: rng.next_u64(), + }; + let ser = serde_json::to_string(&event).unwrap(); + let deser = serde_json::from_str(&ser).unwrap(); + assert_eq!(event, deser); +} + +#[test] +fn serde_reward() { + use RewardReason::*; + let mut rng = StdRng::seed_from_u64(42); + let account = AccountPublicKey::from(&AccountSecretKey::random(&mut rng)); + let mut events = vec![]; + for reason in [GeneratorExtra, GeneratorFixed, Voter, Other] { + events.push(Reward { + account, + value: rng.next_u64(), + reason, + }); + } + let mut desers = vec![]; + for event in &events { + let ser = serde_json::to_string(event).unwrap(); + let deser = serde_json::from_str(&ser).unwrap(); + desers.push(deser); + } + assert_eq!(events, desers); +} + +#[test] +fn serde_withdraw_event() { + let mut rng = StdRng::seed_from_u64(42); + let mut contract_id_bytes = [0; CONTRACT_ID_BYTES]; + rng.fill_bytes(&mut contract_id_bytes); + let sender = ContractId::from_bytes(contract_id_bytes); + let scalar = JubJubScalar::random(&mut rng); + let pk = PhoenixPublicKey::from(&PhoenixSecretKey::random(&mut rng)); + let stealth_addr = pk.gen_stealth_address(&scalar); + + let event1 = WithdrawEvent { + sender, + receiver: WithdrawReceiver::Moonlight(AccountPublicKey::from( + &AccountSecretKey::random(&mut rng), + )), + value: rng.next_u64(), + }; + let event2 = WithdrawEvent { + sender, + receiver: WithdrawReceiver::Phoenix(stealth_addr), + value: rng.next_u64(), + }; + + let ser1 = serde_json::to_string(&event1).unwrap(); + let ser2 = serde_json::to_string(&event2).unwrap(); + let deser1 = serde_json::from_str(&ser1).unwrap(); + let deser2 = serde_json::from_str(&ser2).unwrap(); + + assert_eq!(event1, deser1); + assert_eq!(event2, deser2); + assert_ne!(deser1, deser2); +} + +#[test] +fn serde_convert_event() { + let mut rng = StdRng::seed_from_u64(42); + let scalar = JubJubScalar::random(&mut rng); + let account_pk = + AccountPublicKey::from(&AccountSecretKey::random(&mut rng)); + let pk = PhoenixPublicKey::from(&PhoenixSecretKey::random(&mut rng)); + let stealth_addr = pk.gen_stealth_address(&scalar); + + let event1 = ConvertEvent { + sender: None, + receiver: WithdrawReceiver::Moonlight(account_pk.clone()), + value: rng.next_u64(), + }; + let event2 = ConvertEvent { + sender: Some(account_pk), + receiver: WithdrawReceiver::Phoenix(stealth_addr), + value: rng.next_u64(), + }; + + let ser1 = serde_json::to_string(&event1).unwrap(); + let ser2 = serde_json::to_string(&event2).unwrap(); + let deser1 = serde_json::from_str(&ser1).unwrap(); + let deser2 = serde_json::from_str(&ser2).unwrap(); + + assert_eq!(event1, deser1); + assert_eq!(event2, deser2); + assert_ne!(deser1, deser2); +} + +#[test] +fn serde_deposit_event() { + let mut rng = StdRng::seed_from_u64(42); + let mut contract_id_bytes = [0; CONTRACT_ID_BYTES]; + rng.fill_bytes(&mut contract_id_bytes); + let pk = AccountPublicKey::from(&AccountSecretKey::random(&mut rng)); + let contract_id = ContractId::from_bytes(contract_id_bytes); + + let event1 = DepositEvent { + sender: None, + receiver: contract_id, + value: rng.next_u64(), + }; + let event2 = DepositEvent { + sender: Some(pk), + receiver: contract_id, + value: rng.next_u64(), + }; + + let ser1 = serde_json::to_string(&event1).unwrap(); + let ser2 = serde_json::to_string(&event2).unwrap(); + let deser1 = serde_json::from_str(&ser1).unwrap(); + let deser2 = serde_json::from_str(&ser2).unwrap(); + + assert_eq!(event1, deser1); + assert_eq!(event2, deser2); + assert_ne!(deser1, deser2); +} + +#[test] +fn serde_contract_to_contract_event() { + let mut rng = StdRng::seed_from_u64(42); + let mut contract_id_bytes1 = [0; CONTRACT_ID_BYTES]; + let mut contract_id_bytes2 = [0; CONTRACT_ID_BYTES]; + rng.fill_bytes(&mut contract_id_bytes1); + rng.fill_bytes(&mut contract_id_bytes2); + let sender = ContractId::from_bytes(contract_id_bytes1); + let receiver = ContractId::from_bytes(contract_id_bytes2); + + let event = ContractToContractEvent { + sender, + receiver, + value: rng.next_u64(), + }; + + let ser = serde_json::to_string(&event).unwrap(); + let deser = serde_json::from_str(&ser).unwrap(); + assert_eq!(event, deser); +} + +#[test] +fn serde_contract_to_account_event() { + let mut rng = StdRng::seed_from_u64(42); + let mut contract_id_bytes = [0; CONTRACT_ID_BYTES]; + rng.fill_bytes(&mut contract_id_bytes); + let sender = ContractId::from_bytes(contract_id_bytes); + let receiver = AccountPublicKey::from(&AccountSecretKey::random(&mut rng)); + + let event = ContractToAccountEvent { + sender, + receiver, + value: rng.next_u64(), + }; + + let ser = serde_json::to_string(&event).unwrap(); + let deser = serde_json::from_str(&ser).unwrap(); + assert_eq!(event, deser); +} + +fn rand_note() -> Note { + let mut rng = StdRng::seed_from_u64(42); + let pk = PhoenixPublicKey::from(&PhoenixSecretKey::random(&mut rng)); + let blinder = JubJubScalar::random(&mut rng); + let sender_blinder = [ + JubJubScalar::random(&mut rng), + JubJubScalar::random(&mut rng), + ]; + Note::obfuscated(&mut rng, &pk, &pk, 42, blinder, sender_blinder) +} + +#[test] +fn serde_phoenix_transaction_event() { + let mut rng = StdRng::seed_from_u64(42); + let mut nullifiers = vec![]; + for _ in 0..rng.gen_range(0..10) { + nullifiers.push(BlsScalar::random(&mut rng)); + } + let mut notes = vec![]; + for _ in 0..rng.gen_range(0..10) { + notes.push(rand_note()); + } + let mut memo = vec![0; 50]; + rng.fill_bytes(&mut memo); + + let event1 = PhoenixTransactionEvent { + nullifiers: nullifiers.clone(), + notes: notes.clone(), + memo: memo.clone(), + gas_spent: rng.next_u64(), + refund_note: None, + }; + let event2 = PhoenixTransactionEvent { + nullifiers: nullifiers.clone(), + notes: notes.clone(), + memo: memo.clone(), + gas_spent: rng.next_u64(), + refund_note: Some(rand_note()), + }; + + let ser1 = serde_json::to_string(&event1).unwrap(); + let ser2 = serde_json::to_string(&event2).unwrap(); + let deser1 = serde_json::from_str(&ser1).unwrap(); + let deser2 = serde_json::from_str(&ser2).unwrap(); + + assert_eq!(event1, deser1); + assert_eq!(event2, deser2); + assert_ne!(deser1, deser2); +} + +#[test] +fn serde_moonlight_transaction_event_serde() { + let mut rng = StdRng::seed_from_u64(42); + let mut memo = vec![0; 50]; + rng.fill_bytes(&mut memo); + let pk = AccountPublicKey::from(&AccountSecretKey::random(&mut rng)); + + let event1 = MoonlightTransactionEvent { + sender: pk.clone(), + receiver: Some(pk.clone()), + value: rng.next_u64(), + memo: memo.clone(), + gas_spent: rng.next_u64(), + refund_info: Some((pk, rng.next_u64())), + }; + let event2 = MoonlightTransactionEvent { + sender: pk, + receiver: None, + value: rng.next_u64(), + memo, + gas_spent: rng.next_u64(), + refund_info: None, + }; + + let ser1 = serde_json::to_string(&event1).unwrap(); + let ser2 = serde_json::to_string(&event2).unwrap(); + let deser1 = serde_json::from_str(&ser1).unwrap(); + let deser2 = serde_json::from_str(&ser2).unwrap(); + assert_eq!(event1, deser1); + assert_eq!(event2, deser2); + assert_ne!(deser1, deser2); +}