From f8f5902312a112cf473f98261c31d06b420afb8f Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Wed, 14 Feb 2024 12:30:23 +0200 Subject: [PATCH 1/3] Added Borsh schemas to facilitate parser construction. --- masp_primitives/src/asset_type.rs | 3 +- masp_primitives/src/consensus.rs | 6 +- masp_primitives/src/convert.rs | 24 ++++ masp_primitives/src/memo.rs | 4 +- masp_primitives/src/merkle_tree.rs | 39 +++++- masp_primitives/src/sapling.rs | 132 ++++++++++++++---- masp_primitives/src/sapling/keys.rs | 5 +- masp_primitives/src/transaction.rs | 16 ++- masp_primitives/src/transaction/builder.rs | 4 +- .../src/transaction/components/amount.rs | 5 +- .../transaction/components/sapling/builder.rs | 92 +++++++++++- .../src/transaction/components/transparent.rs | 27 +++- .../components/transparent/builder.rs | 5 +- 13 files changed, 320 insertions(+), 42 deletions(-) diff --git a/masp_primitives/src/asset_type.rs b/masp_primitives/src/asset_type.rs index 0a5981ba..91de6458 100644 --- a/masp_primitives/src/asset_type.rs +++ b/masp_primitives/src/asset_type.rs @@ -6,6 +6,7 @@ use crate::{ sapling::ValueCommitment, }; use blake2s_simd::Params as Blake2sParams; +use borsh::BorshSchema; use borsh::{BorshDeserialize, BorshSerialize}; use group::{cofactor::CofactorGroup, Group, GroupEncoding}; use std::{ @@ -14,7 +15,7 @@ use std::{ hash::{Hash, Hasher}, }; -#[derive(Debug, BorshSerialize, BorshDeserialize, Clone, Copy, Eq)] +#[derive(Debug, BorshSerialize, BorshDeserialize, Clone, Copy, Eq, BorshSchema)] pub struct AssetType { identifier: [u8; ASSET_IDENTIFIER_LENGTH], //32 byte asset type preimage #[borsh(skip)] diff --git a/masp_primitives/src/consensus.rs b/masp_primitives/src/consensus.rs index 1be8a9cb..b497e87c 100644 --- a/masp_primitives/src/consensus.rs +++ b/masp_primitives/src/consensus.rs @@ -1,6 +1,6 @@ //! Consensus logic and parameters. -use borsh::{BorshDeserialize, BorshSerialize}; +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use memuse::DynamicUsage; use std::cmp::{Ord, Ordering}; use std::convert::TryFrom; @@ -10,7 +10,9 @@ use std::ops::{Add, Bound, RangeBounds, Sub}; /// A wrapper type representing blockchain heights. Safe conversion from /// various integer types, as well as addition and subtraction, are provided. #[repr(transparent)] -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize)] +#[derive( + Clone, Copy, Debug, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize, BorshSchema, +)] pub struct BlockHeight(u32); memuse::impl_no_dynamic_usage!(BlockHeight); diff --git a/masp_primitives/src/convert.rs b/masp_primitives/src/convert.rs index b5aa63ac..2821dbc1 100644 --- a/masp_primitives/src/convert.rs +++ b/masp_primitives/src/convert.rs @@ -5,8 +5,14 @@ use crate::{ }, transaction::components::amount::{I128Sum, ValueSum}, }; +use borsh::schema::add_definition; +use borsh::schema::Declaration; +use borsh::schema::Definition; +use borsh::schema::Fields; +use borsh::BorshSchema; use borsh::{BorshDeserialize, BorshSerialize}; use group::{Curve, GroupEncoding}; +use std::collections::BTreeMap; use std::{ io::{self, Write}, iter::Sum, @@ -110,6 +116,24 @@ impl From for AllowedConversion { } } +impl BorshSchema for AllowedConversion { + fn add_definitions_recursively(definitions: &mut BTreeMap) { + let definition = Definition::Struct { + fields: Fields::NamedFields(vec![ + ("assets".into(), I128Sum::declaration()), + ("generator".into(), <[u8; 32]>::declaration()), + ]), + }; + add_definition(Self::declaration(), definition, definitions); + I128Sum::add_definitions_recursively(definitions); + <[u8; 32]>::add_definitions_recursively(definitions); + } + + fn declaration() -> Declaration { + "AllowedConversion".into() + } +} + impl BorshSerialize for AllowedConversion { fn serialize(&self, writer: &mut W) -> io::Result<()> { self.assets.write(writer)?; diff --git a/masp_primitives/src/memo.rs b/masp_primitives/src/memo.rs index 4c53a478..f367be4a 100644 --- a/masp_primitives/src/memo.rs +++ b/masp_primitives/src/memo.rs @@ -1,6 +1,6 @@ //! Structs for handling encrypted memos. -use borsh::{BorshDeserialize, BorshSerialize}; +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use std::cmp::Ordering; use std::convert::{TryFrom, TryInto}; use std::error; @@ -48,7 +48,7 @@ impl fmt::Display for Error { impl error::Error for Error {} /// The unencrypted memo bytes received alongside a shielded note in a Zcash transaction. -#[derive(Clone, BorshSerialize, BorshDeserialize)] +#[derive(Clone, BorshSerialize, BorshDeserialize, BorshSchema)] pub struct MemoBytes(pub(crate) Box<[u8; 512]>); impl fmt::Debug for MemoBytes { diff --git a/masp_primitives/src/merkle_tree.rs b/masp_primitives/src/merkle_tree.rs index fbdfd247..87e7fd25 100644 --- a/masp_primitives/src/merkle_tree.rs +++ b/masp_primitives/src/merkle_tree.rs @@ -2,6 +2,11 @@ use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use borsh::schema::add_definition; +use borsh::schema::Declaration; +use borsh::schema::Definition; +use borsh::schema::Fields; +use borsh::BorshSchema; use borsh::{BorshDeserialize, BorshSerialize}; use core::convert::TryFrom; use incrementalmerkletree::{ @@ -9,6 +14,7 @@ use incrementalmerkletree::{ bridgetree::{self, Leaf}, Altitude, }; +use std::collections::BTreeMap; use std::collections::VecDeque; use std::io::{self, Read, Write}; use std::iter::repeat; @@ -711,7 +717,7 @@ impl BorshDeserialize for IncrementalWitness { /// A path from a position in a particular commitment tree to the root of that tree. #[derive(Clone, Debug, PartialEq, Eq)] -pub struct MerklePath { +pub struct MerklePath { pub auth_path: Vec<(Node, bool)>, pub position: u64, } @@ -837,6 +843,37 @@ impl BorshSerialize for MerklePath { } } +impl BorshSchema for MerklePath { + fn add_definitions_recursively(definitions: &mut BTreeMap) { + let definition = Definition::Sequence { + length_width: 1, + length_range: ((u8::MIN as u64)..=(u8::MAX as u64)), + elements: <(u8, Node)>::declaration(), + }; + add_definition( + format!(r#"{}::auth_path"#, Self::declaration()), + definition, + definitions, + ); + let definition = Definition::Struct { + fields: Fields::NamedFields(vec![ + ( + "auth_path".into(), + format!(r#"{}::auth_path"#, Self::declaration()), + ), + ("position".into(), u64::declaration()), + ]), + }; + add_definition(Self::declaration(), definition, definitions); + <(u8, Node)>::add_definitions_recursively(definitions); + u64::add_definitions_recursively(definitions); + } + + fn declaration() -> Declaration { + format!(r#"MerklePath<{}>"#, Node::declaration()) + } +} + #[cfg(test)] mod tests { diff --git a/masp_primitives/src/sapling.rs b/masp_primitives/src/sapling.rs index 52d78191..bfdd785f 100644 --- a/masp_primitives/src/sapling.rs +++ b/masp_primitives/src/sapling.rs @@ -41,6 +41,12 @@ use self::{ pedersen_hash::{pedersen_hash, Personalization}, redjubjub::{PrivateKey, PublicKey, Signature}, }; +use borsh::schema::add_definition; +use borsh::schema::Declaration; +use borsh::schema::Definition; +use borsh::schema::Fields; +use borsh::BorshSchema; +use std::collections::BTreeMap; pub const SAPLING_COMMITMENT_TREE_DEPTH: usize = 32; @@ -341,7 +347,9 @@ impl SaplingIvk { } } -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize)] +#[derive( + Copy, Clone, Debug, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize, BorshSchema, +)] pub struct Diversifier(pub [u8; 11]); impl Diversifier { @@ -490,6 +498,23 @@ impl BorshDeserialize for PaymentAddress { Ok(pa) } } +impl BorshSchema for PaymentAddress { + fn add_definitions_recursively(definitions: &mut BTreeMap) { + let definition = Definition::Struct { + fields: Fields::NamedFields(vec![ + ("diversifier".into(), Diversifier::declaration()), + ("pk_d".into(), <[u8; 32]>::declaration()), + ]), + }; + add_definition(Self::declaration(), definition, definitions); + Diversifier::add_definitions_recursively(definitions); + <[u8; 32]>::add_definitions_recursively(definitions); + } + + fn declaration() -> Declaration { + "PaymentAddress".into() + } +} /// Enum for note randomness before and after [ZIP 212](https://zips.z.cash/zip-0212). /// @@ -502,6 +527,61 @@ pub enum Rseed { AfterZip212([u8; 32]), } +impl BorshSchema for Rseed { + fn add_definitions_recursively(definitions: &mut BTreeMap) { + let definition = Definition::Enum { + tag_width: 1, + variants: vec![ + (1, "BeforeZip212".into(), <[u8; 32]>::declaration()), + (2, "AfterZip212".into(), <[u8; 32]>::declaration()), + ], + }; + add_definition(Self::declaration(), definition, definitions); + <[u8; 32]>::add_definitions_recursively(definitions); + } + + fn declaration() -> Declaration { + "Rseed".into() + } +} + +impl BorshSerialize for Rseed { + fn serialize(&self, writer: &mut W) -> io::Result<()> { + match self { + Rseed::BeforeZip212(rcm) => { + // Write note plaintext lead byte + writer.write_u8(1)?; + // Write rseed + writer.write_all(&rcm.to_repr()) + } + Rseed::AfterZip212(rseed) => { + // Write note plaintext lead byte + writer.write_u8(2)?; + // Write rseed + writer.write_all(rseed) + } + }?; + Ok(()) + } +} + +impl BorshDeserialize for Rseed { + fn deserialize_reader(reader: &mut R) -> io::Result { + // Read note plaintext lead byte + let rseed_type = reader.read_u8()?; + // Read rseed + let rseed_bytes = <[u8; 32]>::deserialize_reader(reader)?; + let rseed = if rseed_type == 0x01 { + let data = Option::from(jubjub::Fr::from_bytes(&rseed_bytes)) + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "rseed not in field"))?; + Rseed::BeforeZip212(data) + } else { + Rseed::AfterZip212(rseed_bytes) + }; + Ok(rseed) + } +} + /// Typesafe wrapper for nullifier values. #[derive( Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, BorshSerialize, BorshDeserialize, @@ -681,6 +761,29 @@ impl Note { } } +impl BorshSchema for Note { + fn add_definitions_recursively(definitions: &mut BTreeMap) { + let definition = Definition::Struct { + fields: Fields::NamedFields(vec![ + ("asset_type".into(), AssetType::declaration()), + ("value".into(), u64::declaration()), + ("g_d".into(), <[u8; 32]>::declaration()), + ("pk_d".into(), <[u8; 32]>::declaration()), + ("rseed".into(), Rseed::declaration()), + ]), + }; + add_definition(Self::declaration(), definition, definitions); + AssetType::add_definitions_recursively(definitions); + u64::add_definitions_recursively(definitions); + <[u8; 32]>::add_definitions_recursively(definitions); + Rseed::add_definitions_recursively(definitions); + } + + fn declaration() -> Declaration { + "Note".into() + } +} + impl BorshSerialize for Note { fn serialize(&self, writer: &mut W) -> io::Result<()> { // Write asset type @@ -691,20 +794,8 @@ impl BorshSerialize for Note { writer.write_all(&self.g_d.to_bytes())?; // Write diversified transmission key writer.write_all(&self.pk_d.to_bytes())?; - match self.rseed { - Rseed::BeforeZip212(rcm) => { - // Write note plaintext lead byte - writer.write_u8(1)?; - // Write rseed - writer.write_all(&rcm.to_repr()) - } - Rseed::AfterZip212(rseed) => { - // Write note plaintext lead byte - writer.write_u8(2)?; - // Write rseed - writer.write_all(&rseed) - } - }?; + // Write the rseed + self.rseed.serialize(writer)?; Ok(()) } } @@ -724,16 +815,7 @@ impl BorshDeserialize for Note { let pk_d = Option::from(jubjub::SubgroupPoint::from_bytes(&pk_d_bytes)) .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "pk_d not in field"))?; // Read note plaintext lead byte - let rseed_type = reader.read_u8()?; - // Read rseed - let rseed_bytes = <[u8; 32]>::deserialize_reader(reader)?; - let rseed = if rseed_type == 0x01 { - let data = Option::from(jubjub::Fr::from_bytes(&rseed_bytes)) - .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "rseed not in field"))?; - Rseed::BeforeZip212(data) - } else { - Rseed::AfterZip212(rseed_bytes) - }; + let rseed = Rseed::deserialize_reader(reader)?; // Finally construct note object Ok(Note { asset_type, diff --git a/masp_primitives/src/sapling/keys.rs b/masp_primitives/src/sapling/keys.rs index c73dbe9b..be60436e 100644 --- a/masp_primitives/src/sapling/keys.rs +++ b/masp_primitives/src/sapling/keys.rs @@ -8,6 +8,7 @@ use crate::{ constants::{PROOF_GENERATION_KEY_GENERATOR, SPENDING_KEY_GENERATOR}, keys::prf_expand, }; +use borsh::BorshSchema; use borsh::{BorshDeserialize, BorshSerialize}; use ff::PrimeField; use group::{Group, GroupEncoding}; @@ -32,7 +33,9 @@ pub enum DecodingError { } /// An outgoing viewing key -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize)] +#[derive( + Clone, Copy, Debug, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize, BorshSchema, +)] pub struct OutgoingViewingKey(pub [u8; 32]); /// A Sapling expanded spending key diff --git a/masp_primitives/src/transaction.rs b/masp_primitives/src/transaction.rs index 3f55ab9d..4848749c 100644 --- a/masp_primitives/src/transaction.rs +++ b/masp_primitives/src/transaction.rs @@ -1,4 +1,4 @@ -use borsh::{BorshDeserialize, BorshSerialize}; +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; pub mod builder; pub mod components; @@ -35,7 +35,19 @@ use self::{ txid::{to_txid, BlockTxCommitmentDigester, TxIdDigester}, }; -#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash, Debug)] +#[derive( + Clone, + Copy, + PartialOrd, + Ord, + PartialEq, + Eq, + Hash, + Debug, + BorshSerialize, + BorshDeserialize, + BorshSchema, +)] pub struct TransparentAddress(pub [u8; 20]); pub const GROTH_PROOF_SIZE: usize = 48 + 96 + 48; diff --git a/masp_primitives/src/transaction/builder.rs b/masp_primitives/src/transaction/builder.rs index f4583da6..c14e7897 100644 --- a/masp_primitives/src/transaction/builder.rs +++ b/masp_primitives/src/transaction/builder.rs @@ -4,7 +4,7 @@ use std::error; use std::fmt; use std::sync::mpsc::Sender; -use borsh::{BorshDeserialize, BorshSerialize}; +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use rand::{rngs::OsRng, CryptoRng, RngCore}; @@ -115,7 +115,7 @@ impl Progress { } /// Generates a [`Transaction`] from its inputs and outputs. -#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] pub struct Builder> { params: P, rng: RN, diff --git a/masp_primitives/src/transaction/components/amount.rs b/masp_primitives/src/transaction/components/amount.rs index 6920a14a..0cd6fd69 100644 --- a/masp_primitives/src/transaction/components/amount.rs +++ b/masp_primitives/src/transaction/components/amount.rs @@ -1,4 +1,5 @@ use crate::asset_type::AssetType; +use borsh::BorshSchema; use borsh::{BorshDeserialize, BorshSerialize}; use num_traits::{CheckedAdd, CheckedMul, CheckedNeg, CheckedSub, One}; use std::cmp::Ordering; @@ -46,7 +47,9 @@ pub type I128Sum = ValueSum; pub type U128Sum = ValueSum; -#[derive(Clone, Default, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Hash)] +#[derive( + Clone, Default, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, Hash, +)] pub struct ValueSum< Unit: Hash + Ord + BorshSerialize + BorshDeserialize, Value: BorshSerialize + BorshDeserialize + PartialEq + Eq, diff --git a/masp_primitives/src/transaction/components/sapling/builder.rs b/masp_primitives/src/transaction/components/sapling/builder.rs index b902e603..98cefe10 100644 --- a/masp_primitives/src/transaction/components/sapling/builder.rs +++ b/masp_primitives/src/transaction/components/sapling/builder.rs @@ -34,7 +34,12 @@ use crate::{ }, zip32::ExtendedSpendingKey, }; -use borsh::{BorshDeserialize, BorshSerialize}; +use borsh::schema::add_definition; +use borsh::schema::Declaration; +use borsh::schema::Definition; +use borsh::schema::Fields; +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use std::collections::BTreeMap; use std::io::Write; /// If there are any shielded inputs, always have at least two shielded outputs, padding @@ -75,6 +80,30 @@ pub struct SpendDescriptionInfo { merkle_path: MerklePath, } +impl BorshSchema for SpendDescriptionInfo { + fn add_definitions_recursively(definitions: &mut BTreeMap) { + let definition = Definition::Struct { + fields: Fields::NamedFields(vec![ + ("extsk".into(), Key::declaration()), + ("diversifier".into(), Diversifier::declaration()), + ("note".into(), Note::declaration()), + ("alpha".into(), <[u8; 32]>::declaration()), + ("merkle_path".into(), MerklePath::<[u8; 32]>::declaration()), + ]), + }; + add_definition(Self::declaration(), definition, definitions); + Key::add_definitions_recursively(definitions); + Diversifier::add_definitions_recursively(definitions); + Note::add_definitions_recursively(definitions); + <[u8; 32]>::add_definitions_recursively(definitions); + MerklePath::<[u8; 32]>::add_definitions_recursively(definitions); + } + + fn declaration() -> Declaration { + format!(r#"SpendDescriptionInfo<{}>"#, Key::declaration()) + } +} + impl BorshSerialize for SpendDescriptionInfo { fn serialize(&self, writer: &mut W) -> std::io::Result<()> { self.extsk.serialize(writer)?; @@ -125,7 +154,7 @@ impl fees::InputView<(), K> for SpendDescriptionInfo { /// A struct containing the information required in order to construct a /// MASP output to a transaction. -#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] pub struct SaplingOutputInfo { /// `None` represents the `ovk = ⊥` case. ovk: Option, @@ -280,6 +309,45 @@ pub struct SaplingBuilder { outputs: Vec, } +impl BorshSchema for SaplingBuilder { + fn add_definitions_recursively(definitions: &mut BTreeMap) { + let definition = Definition::Struct { + fields: Fields::NamedFields(vec![ + ("params".into(), P::declaration()), + ("spend_anchor".into(), Option::<[u8; 32]>::declaration()), + ("target_height".into(), BlockHeight::declaration()), + ("value_balance".into(), I128Sum::declaration()), + ("convert_anchor".into(), Option::<[u8; 32]>::declaration()), + ( + "spends".into(), + Vec::>::declaration(), + ), + ( + "converts".into(), + Vec::::declaration(), + ), + ("outputs".into(), Vec::::declaration()), + ]), + }; + add_definition(Self::declaration(), definition, definitions); + P::add_definitions_recursively(definitions); + Option::<[u8; 32]>::add_definitions_recursively(definitions); + BlockHeight::add_definitions_recursively(definitions); + I128Sum::add_definitions_recursively(definitions); + Vec::>::add_definitions_recursively(definitions); + Vec::::add_definitions_recursively(definitions); + Vec::::add_definitions_recursively(definitions); + } + + fn declaration() -> Declaration { + format!( + r#"SaplingBuilder<{}, {}>"#, + P::declaration(), + Key::declaration() + ) + } +} + impl BorshSerialize for SaplingBuilder { fn serialize(&self, writer: &mut W) -> std::io::Result<()> { self.params.serialize(writer)?; @@ -792,6 +860,26 @@ pub struct ConvertDescriptionInfo { merkle_path: MerklePath, } +impl BorshSchema for ConvertDescriptionInfo { + fn add_definitions_recursively(definitions: &mut BTreeMap) { + let definition = Definition::Struct { + fields: Fields::NamedFields(vec![ + ("allowed".into(), AllowedConversion::declaration()), + ("value".into(), u64::declaration()), + ("merkle_path".into(), MerklePath::<[u8; 32]>::declaration()), + ]), + }; + add_definition(Self::declaration(), definition, definitions); + AllowedConversion::add_definitions_recursively(definitions); + u64::add_definitions_recursively(definitions); + MerklePath::<[u8; 32]>::add_definitions_recursively(definitions); + } + + fn declaration() -> Declaration { + "ConvertDescriptionInfo".into() + } +} + impl fees::ConvertView for ConvertDescriptionInfo { fn value(&self) -> u64 { self.value diff --git a/masp_primitives/src/transaction/components/transparent.rs b/masp_primitives/src/transaction/components/transparent.rs index 96305ee6..07fb2b1e 100644 --- a/masp_primitives/src/transaction/components/transparent.rs +++ b/masp_primitives/src/transaction/components/transparent.rs @@ -1,11 +1,16 @@ //! Structs representing the components within Zcash transactions. -use borsh::{BorshDeserialize, BorshSerialize}; +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use std::fmt::{self, Debug}; use std::io::{self, Read, Write}; use crate::asset_type::AssetType; use crate::transaction::TransparentAddress; +use borsh::schema::add_definition; +use borsh::schema::Declaration; +use borsh::schema::Definition; +use borsh::schema::Fields; +use std::collections::BTreeMap; use super::amount::{BalanceError, I128Sum, ValueSum, MAX_MONEY}; @@ -183,6 +188,26 @@ impl BorshSerialize for TxOut { } } +impl BorshSchema for TxOut { + fn add_definitions_recursively(definitions: &mut BTreeMap) { + let definition = Definition::Struct { + fields: Fields::NamedFields(vec![ + ("asset_type".into(), AssetType::declaration()), + ("value".into(), u64::declaration()), + ("address".into(), TransparentAddress::declaration()), + ]), + }; + add_definition(Self::declaration(), definition, definitions); + AssetType::add_definitions_recursively(definitions); + u64::add_definitions_recursively(definitions); + TransparentAddress::add_definitions_recursively(definitions); + } + + fn declaration() -> Declaration { + "TxOut".into() + } +} + #[cfg(any(test, feature = "test-dependencies"))] pub mod testing { use proptest::collection::vec; diff --git a/masp_primitives/src/transaction/components/transparent/builder.rs b/masp_primitives/src/transaction/components/transparent/builder.rs index f97eb491..31fbd4c4 100644 --- a/masp_primitives/src/transaction/components/transparent/builder.rs +++ b/masp_primitives/src/transaction/components/transparent/builder.rs @@ -13,6 +13,7 @@ use crate::{ TransparentAddress, }, }; +use borsh::BorshSchema; use borsh::{BorshDeserialize, BorshSerialize}; #[derive(Debug, PartialEq, Eq)] @@ -45,7 +46,7 @@ impl fees::InputView for InvalidTransparentInput { } #[cfg(feature = "transparent-inputs")] -#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema)] struct TransparentInputInfo { coin: TxOut, } @@ -57,7 +58,7 @@ impl fees::InputView for TransparentInputInfo { } } -#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] pub struct TransparentBuilder { #[cfg(feature = "transparent-inputs")] inputs: Vec, From 9f491a26342420375fe83e4c80eb08526629df22 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Wed, 14 Feb 2024 15:21:44 +0200 Subject: [PATCH 2/3] Added Borsh schemas and (de)serializers for FullViewingKey. --- masp_primitives/src/sapling.rs | 55 +++++++++++++++++++ masp_primitives/src/sapling/keys.rs | 35 ++++++++++++ .../transaction/components/sapling/builder.rs | 2 +- masp_primitives/src/zip32.rs | 38 ++++++++++++- masp_primitives/src/zip32/sapling.rs | 40 +++++++++++++- 5 files changed, 166 insertions(+), 4 deletions(-) diff --git a/masp_primitives/src/sapling.rs b/masp_primitives/src/sapling.rs index bfdd785f..9a68842d 100644 --- a/masp_primitives/src/sapling.rs +++ b/masp_primitives/src/sapling.rs @@ -215,6 +215,43 @@ impl ProofGenerationKey { #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct NullifierDerivingKey(pub jubjub::SubgroupPoint); +impl BorshSerialize for NullifierDerivingKey { + fn serialize(&self, writer: &mut W) -> io::Result<()> { + writer.write_all(&self.0.to_bytes()) + } +} + +impl BorshDeserialize for NullifierDerivingKey { + fn deserialize_reader(reader: &mut R) -> io::Result { + let nk = { + let mut buf = [0u8; 32]; + reader.read_exact(&mut buf)?; + jubjub::SubgroupPoint::from_bytes(&buf) + }; + if nk.is_none().into() { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "nk not in prime-order subgroup", + )); + } + Ok(Self(nk.unwrap())) + } +} + +impl BorshSchema for NullifierDerivingKey { + fn add_definitions_recursively(definitions: &mut BTreeMap) { + let definition = Definition::Struct { + fields: Fields::UnnamedFields(vec![<[u8; 32]>::declaration()]), + }; + add_definition(Self::declaration(), definition, definitions); + <[u8; 32]>::add_definitions_recursively(definitions); + } + + fn declaration() -> Declaration { + "NullifierDerivingKey".into() + } +} + #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct ViewingKey { pub ak: jubjub::SubgroupPoint, @@ -318,6 +355,24 @@ impl BorshDeserialize for ViewingKey { } } +impl BorshSchema for ViewingKey { + fn add_definitions_recursively(definitions: &mut BTreeMap) { + let definition = Definition::Struct { + fields: Fields::NamedFields(vec![ + ("ak".into(), <[u8; 32]>::declaration()), + ("nk".into(), NullifierDerivingKey::declaration()), + ]), + }; + add_definition(Self::declaration(), definition, definitions); + <[u8; 32]>::add_definitions_recursively(definitions); + NullifierDerivingKey::add_definitions_recursively(definitions); + } + + fn declaration() -> Declaration { + "ViewingKey".into() + } +} + impl PartialOrd for ViewingKey { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) diff --git a/masp_primitives/src/sapling/keys.rs b/masp_primitives/src/sapling/keys.rs index be60436e..0cfe59d1 100644 --- a/masp_primitives/src/sapling/keys.rs +++ b/masp_primitives/src/sapling/keys.rs @@ -8,10 +8,15 @@ use crate::{ constants::{PROOF_GENERATION_KEY_GENERATOR, SPENDING_KEY_GENERATOR}, keys::prf_expand, }; +use borsh::schema::add_definition; +use borsh::schema::Declaration; +use borsh::schema::Definition; +use borsh::schema::Fields; use borsh::BorshSchema; use borsh::{BorshDeserialize, BorshSerialize}; use ff::PrimeField; use group::{Group, GroupEncoding}; +use std::collections::BTreeMap; use std::{ fmt::{Display, Formatter}, hash::{Hash, Hasher}, @@ -211,6 +216,36 @@ impl FullViewingKey { } } +impl BorshSerialize for FullViewingKey { + fn serialize(&self, writer: &mut W) -> io::Result<()> { + self.write(writer) + } +} + +impl BorshDeserialize for FullViewingKey { + fn deserialize_reader(reader: &mut R) -> io::Result { + Self::read(reader) + } +} + +impl BorshSchema for FullViewingKey { + fn add_definitions_recursively(definitions: &mut BTreeMap) { + let definition = Definition::Struct { + fields: Fields::NamedFields(vec![ + ("vk".into(), ViewingKey::declaration()), + ("ovk".into(), OutgoingViewingKey::declaration()), + ]), + }; + add_definition(Self::declaration(), definition, definitions); + ViewingKey::add_definitions_recursively(definitions); + OutgoingViewingKey::add_definitions_recursively(definitions); + } + + fn declaration() -> Declaration { + "FullViewingKey".into() + } +} + #[cfg(any(test, feature = "test-dependencies"))] pub mod testing { use proptest::collection::vec; diff --git a/masp_primitives/src/transaction/components/sapling/builder.rs b/masp_primitives/src/transaction/components/sapling/builder.rs index 98cefe10..5ac1d1ae 100644 --- a/masp_primitives/src/transaction/components/sapling/builder.rs +++ b/masp_primitives/src/transaction/components/sapling/builder.rs @@ -248,7 +248,7 @@ impl fees::OutputView for SaplingOutputInfo { } /// Metadata about a transaction created by a [`SaplingBuilder`]. -#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema)] pub struct SaplingMetadata { spend_indices: Vec, convert_indices: Vec, diff --git a/masp_primitives/src/zip32.rs b/masp_primitives/src/zip32.rs index f1119001..c21e0961 100644 --- a/masp_primitives/src/zip32.rs +++ b/masp_primitives/src/zip32.rs @@ -2,13 +2,20 @@ //! //! [ZIP 32]: https://zips.z.cash/zip-0032 +use borsh::schema::add_definition; +use borsh::schema::Declaration; +use borsh::schema::Definition; +use borsh::schema::Fields; +use borsh::BorshSchema; use memuse::{self, DynamicUsage}; +use std::collections::BTreeMap; use std::convert::{TryFrom, TryInto}; use crate::sapling::{Diversifier, NullifierDerivingKey, PaymentAddress, ViewingKey}; pub mod sapling; +use borsh::{BorshDeserialize, BorshSerialize}; #[deprecated(note = "Please use the types exported from the `zip32::sapling` module instead.")] pub use sapling::{ sapling_address, sapling_default_address, sapling_derive_internal_fvk, sapling_find_address, @@ -16,6 +23,7 @@ pub use sapling::{ ZIP32_SAPLING_FVFP_PERSONALIZATION, ZIP32_SAPLING_INT_PERSONALIZATION, ZIP32_SAPLING_MASTER_PERSONALIZATION, }; +use std::io::{Read, Write}; // ZIP 32 structures @@ -46,8 +54,36 @@ impl ChildIndex { } } +impl BorshSerialize for ChildIndex { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + self.value().serialize(writer) + } +} + +impl BorshDeserialize for ChildIndex { + fn deserialize_reader(reader: &mut R) -> std::io::Result { + u32::deserialize_reader(reader).map(Self::from_index) + } +} + +impl BorshSchema for ChildIndex { + fn add_definitions_recursively(definitions: &mut BTreeMap) { + let definition = Definition::Struct { + fields: Fields::UnnamedFields(vec![u32::declaration()]), + }; + add_definition(Self::declaration(), definition, definitions); + u32::add_definitions_recursively(definitions); + } + + fn declaration() -> Declaration { + "ChildIndex".into() + } +} + /// A BIP-32 chain code -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive( + Clone, Copy, Debug, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize, BorshSchema, +)] pub struct ChainCode([u8; 32]); impl ChainCode { diff --git a/masp_primitives/src/zip32/sapling.rs b/masp_primitives/src/zip32/sapling.rs index d8643c3f..83b122e7 100644 --- a/masp_primitives/src/zip32/sapling.rs +++ b/masp_primitives/src/zip32/sapling.rs @@ -16,9 +16,15 @@ use crate::{ }; use aes::Aes256; use blake2b_simd::Params as Blake2bParams; +use borsh::schema::add_definition; +use borsh::schema::Declaration; +use borsh::schema::Definition; +use borsh::schema::Fields; +use borsh::BorshSchema; use borsh::{BorshDeserialize, BorshSerialize}; use byteorder::{ByteOrder, LittleEndian, ReadBytesExt, WriteBytesExt}; use fpe::ff1::{BinaryNumeralString, FF1}; +use std::collections::BTreeMap; use std::{ cmp::Ordering, convert::TryInto, @@ -137,7 +143,9 @@ impl FvkFingerprint { } /// A Sapling full viewing key tag -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive( + Clone, Copy, Debug, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize, BorshSchema, +)] struct FvkTag([u8; 4]); impl FvkTag { @@ -151,7 +159,9 @@ impl FvkTag { } /// A key used to derive diversifiers for a particular child key -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive( + Clone, Copy, Debug, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize, BorshSchema, +)] pub struct DiversifierKey(pub [u8; 32]); impl DiversifierKey { @@ -552,6 +562,32 @@ impl BorshSerialize for ExtendedFullViewingKey { } } +impl BorshSchema for ExtendedFullViewingKey { + fn add_definitions_recursively(definitions: &mut BTreeMap) { + let definition = Definition::Struct { + fields: Fields::NamedFields(vec![ + ("depth".into(), u8::declaration()), + ("parent_fvk_tag".into(), FvkTag::declaration()), + ("child_index".into(), ChildIndex::declaration()), + ("chain_code".into(), ChainCode::declaration()), + ("fvk".into(), FullViewingKey::declaration()), + ("dk".into(), DiversifierKey::declaration()), + ]), + }; + add_definition(Self::declaration(), definition, definitions); + u8::add_definitions_recursively(definitions); + FvkTag::add_definitions_recursively(definitions); + ChildIndex::add_definitions_recursively(definitions); + ChainCode::add_definitions_recursively(definitions); + FullViewingKey::add_definitions_recursively(definitions); + DiversifierKey::add_definitions_recursively(definitions); + } + + fn declaration() -> Declaration { + "ExtendedFullViewingKey".into() + } +} + impl PartialOrd for ExtendedFullViewingKey { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) From daddfc404c64ce431cd2b9a6872d12187594ef7c Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Thu, 15 Feb 2024 10:22:59 +0200 Subject: [PATCH 3/3] Created a BorshSchema for Transaction. Made BorshSerialization of ValueSum consistent with read and write. Fixed minor bugs in Transaction reading. --- masp_note_encryption/src/lib.rs | 17 +- masp_primitives/src/consensus.rs | 35 +++ masp_primitives/src/sapling.rs | 12 +- masp_primitives/src/sapling/redjubjub.rs | 24 +- masp_primitives/src/transaction.rs | 222 +++++++++++++++++- .../src/transaction/components/amount.rs | 88 ++++++- .../src/transaction/components/sapling.rs | 89 ++++++- .../src/transaction/components/transparent.rs | 34 +++ 8 files changed, 507 insertions(+), 14 deletions(-) diff --git a/masp_note_encryption/src/lib.rs b/masp_note_encryption/src/lib.rs index 182b6897..24d9f4bb 100644 --- a/masp_note_encryption/src/lib.rs +++ b/masp_note_encryption/src/lib.rs @@ -21,6 +21,8 @@ #[cfg(feature = "alloc")] extern crate alloc; #[cfg(feature = "alloc")] +use crate::alloc::string::ToString; +#[cfg(feature = "alloc")] use alloc::vec::Vec; use core::convert::TryInto; @@ -34,7 +36,7 @@ use cipher::KeyIvInit; //use crate::constants::ASSET_IDENTIFIER_LENGTH; pub const ASSET_IDENTIFIER_LENGTH: usize = 32; -use borsh::{BorshDeserialize, BorshSerialize}; +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use rand_core::RngCore; use subtle::{Choice, ConstantTimeEq}; @@ -77,7 +79,18 @@ impl AsRef<[u8]> for OutgoingCipherKey { /// Newtype representing the byte encoding of an [`EphemeralPublicKey`]. /// /// [`EphemeralPublicKey`]: Domain::EphemeralPublicKey -#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive( + BorshSerialize, + BorshDeserialize, + BorshSchema, + Clone, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, +)] pub struct EphemeralKeyBytes(pub [u8; 32]); impl AsRef<[u8]> for EphemeralKeyBytes { diff --git a/masp_primitives/src/consensus.rs b/masp_primitives/src/consensus.rs index b497e87c..bea5de0e 100644 --- a/masp_primitives/src/consensus.rs +++ b/masp_primitives/src/consensus.rs @@ -1,10 +1,14 @@ //! Consensus logic and parameters. +use borsh::schema::add_definition; +use borsh::schema::Definition; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use memuse::DynamicUsage; use std::cmp::{Ord, Ordering}; +use std::collections::BTreeMap; use std::convert::TryFrom; use std::fmt; +use std::io::{Error, ErrorKind, Read, Write}; use std::ops::{Add, Bound, RangeBounds, Sub}; /// A wrapper type representing blockchain heights. Safe conversion from @@ -267,6 +271,37 @@ impl From for u32 { } } +impl BorshSerialize for BranchId { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + u32::from(*self).serialize(writer) + } +} + +impl BorshDeserialize for BranchId { + fn deserialize_reader(reader: &mut R) -> std::io::Result { + u32::deserialize_reader(reader)? + .try_into() + .map_err(|x| Error::new(ErrorKind::InvalidInput, x)) + } +} + +impl BorshSchema for BranchId { + fn add_definitions_recursively( + definitions: &mut BTreeMap, + ) { + let definition = Definition::Enum { + tag_width: 4, + variants: vec![(0xe9ff_75a6, "MASP".into(), <()>::declaration())], + }; + add_definition(Self::declaration(), definition, definitions); + <()>::add_definitions_recursively(definitions); + } + + fn declaration() -> borsh::schema::Declaration { + "BranchId".into() + } +} + impl BranchId { /// Returns the branch ID corresponding to the consensus rule set that is active at /// the given height. diff --git a/masp_primitives/src/sapling.rs b/masp_primitives/src/sapling.rs index 9a68842d..e004c19b 100644 --- a/masp_primitives/src/sapling.rs +++ b/masp_primitives/src/sapling.rs @@ -639,7 +639,17 @@ impl BorshDeserialize for Rseed { /// Typesafe wrapper for nullifier values. #[derive( - Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, BorshSerialize, BorshDeserialize, + Copy, + Clone, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + BorshSerialize, + BorshDeserialize, + BorshSchema, )] pub struct Nullifier(pub [u8; 32]); diff --git a/masp_primitives/src/sapling/redjubjub.rs b/masp_primitives/src/sapling/redjubjub.rs index 86659a44..856a12c7 100644 --- a/masp_primitives/src/sapling/redjubjub.rs +++ b/masp_primitives/src/sapling/redjubjub.rs @@ -6,11 +6,15 @@ use crate::transaction::components::sapling::read_point; use super::util::hash_to_scalar; -use borsh::{BorshDeserialize, BorshSerialize}; +use borsh::schema::add_definition; +use borsh::schema::Definition; +use borsh::schema::Fields; +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use ff::{Field, PrimeField}; use group::GroupEncoding; use jubjub::{AffinePoint, ExtendedPoint, SubgroupPoint}; use rand_core::RngCore; +use std::collections::BTreeMap; use std::{ cmp::Ordering, hash::{Hash, Hasher}, @@ -34,7 +38,7 @@ fn h_star(a: &[u8], b: &[u8]) -> jubjub::Fr { hash_to_scalar(b"MASP__RedJubjubH", a, b) } -#[derive(Copy, Clone, Debug, PartialOrd, PartialEq, Ord, Eq, Hash)] +#[derive(Copy, Clone, Debug, PartialOrd, PartialEq, Ord, Eq, Hash, BorshSchema)] pub struct Signature { rbar: [u8; 32], sbar: [u8; 32], @@ -69,6 +73,22 @@ impl BorshSerialize for PublicKey { } } +impl BorshSchema for PublicKey { + fn add_definitions_recursively( + definitions: &mut BTreeMap, + ) { + let definition = Definition::Struct { + fields: Fields::UnnamedFields(vec![<[u8; 32]>::declaration()]), + }; + add_definition(Self::declaration(), definition, definitions); + <[u8; 32]>::add_definitions_recursively(definitions); + } + + fn declaration() -> borsh::schema::Declaration { + "PublicKey".into() + } +} + impl BorshDeserialize for Signature { fn deserialize_reader(reader: &mut R) -> io::Result { Self::read(reader) diff --git a/masp_primitives/src/transaction.rs b/masp_primitives/src/transaction.rs index 4848749c..0f9fa1d0 100644 --- a/masp_primitives/src/transaction.rs +++ b/masp_primitives/src/transaction.rs @@ -34,6 +34,10 @@ use self::{ }, txid::{to_txid, BlockTxCommitmentDigester, TxIdDigester}, }; +use borsh::schema::add_definition; +use borsh::schema::Fields; +use borsh::schema::{Declaration, Definition}; +use std::ops::RangeInclusive; #[derive( Clone, @@ -155,6 +159,35 @@ impl TxVersion { } } +impl BorshSerialize for TxVersion { + fn serialize(&self, writer: &mut W) -> io::Result<()> { + self.write(writer) + } +} + +impl BorshDeserialize for TxVersion { + fn deserialize_reader(reader: &mut R) -> io::Result { + Self::read(reader) + } +} + +impl BorshSchema for TxVersion { + fn add_definitions_recursively(definitions: &mut BTreeMap) { + let definition = Definition::Struct { + fields: Fields::NamedFields(vec![ + ("header".into(), u32::declaration()), + ("version_group_id".into(), u32::declaration()), + ]), + }; + add_definition(Self::declaration(), definition, definitions); + u32::add_definitions_recursively(definitions); + } + + fn declaration() -> Declaration { + "TxVersion".into() + } +} + /// Authorization state for a bundle of transaction data. pub trait Authorization { type TransparentAuth: transparent::Authorization + PartialEq + BorshDeserialize + BorshSerialize; @@ -307,10 +340,190 @@ impl BorshDeserialize for Transaction { } } -impl borsh::BorshSchema for Transaction { +fn untagged_vec(length_range: RangeInclusive) -> Definition { + Definition::Sequence { + length_width: 0, + length_range, + elements: X::declaration(), + } +} + +fn untagged_option() -> Definition { + Definition::Enum { + tag_width: 0, + variants: vec![ + (0, "None".into(), <()>::declaration()), + (1, "Some".into(), X::declaration()), + ], + } +} + +impl BorshSchema for Transaction { fn add_definitions_recursively( - _definitions: &mut BTreeMap, + definitions: &mut BTreeMap, ) { + let definition = Definition::Enum { + tag_width: 1, + variants: vec![ + (253, "u16".into(), u16::declaration()), + (254, "u32".into(), u32::declaration()), + (255, "u64".into(), u64::declaration()), + ], + }; + add_definition( + format!("{}::CompactSize", Self::declaration()), + definition, + definitions, + ); + add_definition( + format!("{}::vin", Self::declaration()), + untagged_vec::>(u64::MIN..=u64::MAX), + definitions, + ); + add_definition( + format!("{}::vout", Self::declaration()), + untagged_vec::(u64::MIN..=u64::MAX), + definitions, + ); + add_definition( + format!("{}::sd_v5s", Self::declaration()), + untagged_vec::(u64::MIN..=u64::MAX), + definitions, + ); + add_definition( + format!("{}::cd_v5s", Self::declaration()), + untagged_vec::(u64::MIN..=u64::MAX), + definitions, + ); + add_definition( + format!("{}::od_v5s", Self::declaration()), + untagged_vec::(u64::MIN..=u64::MAX), + definitions, + ); + add_definition( + format!("{}::value_balance", Self::declaration()), + untagged_option::(), + definitions, + ); + add_definition( + format!("{}::spend_anchor", Self::declaration()), + untagged_option::<[u8; 32]>(), + definitions, + ); + add_definition( + format!("{}::convert_anchor", Self::declaration()), + untagged_option::<[u8; 32]>(), + definitions, + ); + add_definition( + format!("{}::v_spend_proofs", Self::declaration()), + untagged_vec::<[u8; GROTH_PROOF_SIZE]>(u64::MIN..=u64::MAX), + definitions, + ); + add_definition( + format!("{}::v_spend_auth_sigs", Self::declaration()), + untagged_vec::(u64::MIN..=u64::MAX), + definitions, + ); + add_definition( + format!("{}::v_convert_proofs", Self::declaration()), + untagged_vec::<[u8; GROTH_PROOF_SIZE]>(u64::MIN..=u64::MAX), + definitions, + ); + add_definition( + format!("{}::v_output_proofs", Self::declaration()), + untagged_vec::<[u8; GROTH_PROOF_SIZE]>(u64::MIN..=u64::MAX), + definitions, + ); + add_definition( + format!("{}::authorization", Self::declaration()), + untagged_option::(), + definitions, + ); + let definition = Definition::Struct { + fields: Fields::NamedFields(vec![ + ("version".into(), TxVersion::declaration()), + ("consensus_branch_id".into(), BranchId::declaration()), + ("lock_time".into(), u32::declaration()), + ("expiry_height".into(), BlockHeight::declaration()), + ( + "vin::count".into(), + format!("{}::CompactSize", Self::declaration()), + ), + ("vin".into(), format!("{}::vin", Self::declaration())), + ( + "vout::count".into(), + format!("{}::CompactSize", Self::declaration()), + ), + ("vout".into(), format!("{}::vout", Self::declaration())), + ( + "sd_v5s::count".into(), + format!("{}::CompactSize", Self::declaration()), + ), + ("sd_v5s".into(), format!("{}::sd_v5s", Self::declaration())), + ( + "cd_v5s::count".into(), + format!("{}::CompactSize", Self::declaration()), + ), + ("cd_v5s".into(), format!("{}::cd_v5s", Self::declaration())), + ( + "od_v5s::count".into(), + format!("{}::CompactSize", Self::declaration()), + ), + ("od_v5s".into(), format!("{}::od_v5s", Self::declaration())), + ( + "value_balance".into(), + format!("{}::value_balance", Self::declaration()), + ), + ( + "spend_anchor".into(), + format!("{}::spend_anchor", Self::declaration()), + ), + ( + "convert_anchor".into(), + format!("{}::convert_anchor", Self::declaration()), + ), + ( + "v_spend_proofs".into(), + format!("{}::v_spend_proofs", Self::declaration()), + ), + ( + "v_spend_auth_sigs".into(), + format!("{}::v_spend_auth_sigs", Self::declaration()), + ), + ( + "v_convert_proofs".into(), + format!("{}::v_convert_proofs", Self::declaration()), + ), + ( + "v_output_proofs".into(), + format!("{}::v_output_proofs", Self::declaration()), + ), + ( + "authorization".into(), + format!("{}::authorization", Self::declaration()), + ), + ]), + }; + add_definition(Self::declaration(), definition, definitions); + <[u8; 32]>::add_definitions_recursively(definitions); + redjubjub::Signature::add_definitions_recursively(definitions); + <[u8; GROTH_PROOF_SIZE]>::add_definitions_recursively(definitions); + sapling::Authorized::add_definitions_recursively(definitions); + I128Sum::add_definitions_recursively(definitions); + TxIn::add_definitions_recursively(definitions); + TxOut::add_definitions_recursively(definitions); + SpendDescriptionV5::add_definitions_recursively(definitions); + ConvertDescriptionV5::add_definitions_recursively(definitions); + OutputDescriptionV5::add_definitions_recursively(definitions); + u8::add_definitions_recursively(definitions); + u16::add_definitions_recursively(definitions); + u32::add_definitions_recursively(definitions); + u64::add_definitions_recursively(definitions); + TxVersion::add_definitions_recursively(definitions); + <()>::add_definitions_recursively(definitions); + BranchId::add_definitions_recursively(definitions); + BlockHeight::add_definitions_recursively(definitions); } fn declaration() -> borsh::schema::Declaration { @@ -416,7 +629,7 @@ impl Transaction { let n_spends = sd_v5s.len(); let n_converts = cd_v5s.len(); let n_outputs = od_v5s.len(); - let value_balance = if n_spends > 0 || n_outputs > 0 { + let value_balance = if n_spends > 0 || n_converts > 0 || n_outputs > 0 { Self::read_i128_sum(&mut reader)? } else { ValueSum::zero() @@ -441,7 +654,7 @@ impl Transaction { let v_convert_proofs = Array::read(&mut reader, n_converts, |r| sapling::read_zkproof(r))?; let v_output_proofs = Array::read(&mut reader, n_outputs, |r| sapling::read_zkproof(r))?; - let binding_sig = if n_spends > 0 || n_outputs > 0 { + let binding_sig = if n_spends > 0 || n_converts > 0 || n_outputs > 0 { Some(redjubjub::Signature::read(&mut reader)?) } else { None @@ -567,6 +780,7 @@ impl Transaction { } else { CompactSize::write(&mut writer, 0)?; CompactSize::write(&mut writer, 0)?; + CompactSize::write(&mut writer, 0)?; } Ok(()) diff --git a/masp_primitives/src/transaction/components/amount.rs b/masp_primitives/src/transaction/components/amount.rs index 0cd6fd69..2530894f 100644 --- a/masp_primitives/src/transaction/components/amount.rs +++ b/masp_primitives/src/transaction/components/amount.rs @@ -1,4 +1,7 @@ use crate::asset_type::AssetType; +use borsh::schema::add_definition; +use borsh::schema::Fields; +use borsh::schema::{Declaration, Definition}; use borsh::BorshSchema; use borsh::{BorshDeserialize, BorshSerialize}; use num_traits::{CheckedAdd, CheckedMul, CheckedNeg, CheckedSub, One}; @@ -47,9 +50,7 @@ pub type I128Sum = ValueSum; pub type U128Sum = ValueSum; -#[derive( - Clone, Default, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, Hash, -)] +#[derive(Clone, Default, Debug, PartialEq, Eq, Hash)] pub struct ValueSum< Unit: Hash + Ord + BorshSerialize + BorshDeserialize, Value: BorshSerialize + BorshDeserialize + PartialEq + Eq, @@ -158,6 +159,87 @@ where } } +impl BorshSerialize for ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + Clone, + Value: BorshSerialize + BorshDeserialize + PartialEq + Eq + Copy, +{ + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + let vec: Vec<_> = self.components().collect(); + Vector::write(writer, vec.as_ref(), |writer, elt| { + elt.0.serialize(writer)?; + elt.1.serialize(writer)?; + Ok(()) + }) + } +} + +impl BorshDeserialize for ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize, + Value: BorshSerialize + BorshDeserialize + PartialEq + Eq, +{ + fn deserialize_reader(reader: &mut R) -> std::io::Result { + let vec = Vector::read(reader, |reader| { + let atype = Unit::deserialize_reader(reader)?; + let value = Value::deserialize_reader(reader)?; + Ok((atype, value)) + })?; + Ok(Self(vec.into_iter().collect())) + } +} + +impl BorshSchema for ValueSum +where + Unit: Hash + Ord + BorshSerialize + BorshDeserialize + BorshSchema, + Value: BorshSerialize + BorshDeserialize + PartialEq + Eq + BorshSchema, +{ + fn add_definitions_recursively(definitions: &mut BTreeMap) { + let definition = Definition::Enum { + tag_width: 1, + variants: vec![ + (253, "u16".into(), u16::declaration()), + (254, "u32".into(), u32::declaration()), + (255, "u64".into(), u64::declaration()), + ], + }; + add_definition( + format!("{}::CompactSize", Self::declaration()), + definition, + definitions, + ); + let definition = Definition::Sequence { + length_width: 0, + length_range: u64::MIN..=u64::MAX, + elements: <(Unit, Value)>::declaration(), + }; + add_definition( + format!("{}::Sequence", Self::declaration()), + definition, + definitions, + ); + let definition = Definition::Struct { + fields: Fields::UnnamedFields(vec![ + format!("{}::CompactSize", Self::declaration()), + format!("{}::Sequence", Self::declaration()), + ]), + }; + add_definition(Self::declaration(), definition, definitions); + u16::add_definitions_recursively(definitions); + u32::add_definitions_recursively(definitions); + u64::add_definitions_recursively(definitions); + <(Unit, Value)>::add_definitions_recursively(definitions); + } + + fn declaration() -> Declaration { + format!( + r#"ValueSum<{}, {}>"#, + Unit::declaration(), + Value::declaration() + ) + } +} + impl ValueSum { /// Deserialize an ValueSum object from a list of amounts denominated by /// different assets diff --git a/masp_primitives/src/transaction/components/sapling.rs b/masp_primitives/src/transaction/components/sapling.rs index 4b9c5873..61d87c2c 100644 --- a/masp_primitives/src/transaction/components/sapling.rs +++ b/masp_primitives/src/transaction/components/sapling.rs @@ -4,6 +4,7 @@ use ff::PrimeField; use group::GroupEncoding; use memuse::DynamicUsage; +use std::collections::BTreeMap; use std::convert::TryInto; use std::hash::{Hash, Hasher}; use std::io::{self, Read, Write}; @@ -12,7 +13,10 @@ use masp_note_encryption::{ EphemeralKeyBytes, ShieldedOutput, COMPACT_NOTE_SIZE, ENC_CIPHERTEXT_SIZE, }; -use borsh::{BorshDeserialize, BorshSerialize}; +use borsh::schema::add_definition; +use borsh::schema::Definition; +use borsh::schema::Fields; +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use crate::{ consensus, @@ -43,7 +47,7 @@ impl Authorization for Unproven { type AuthSig = (); } -#[derive(Debug, Copy, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema)] pub struct Authorized { pub binding_sig: redjubjub::Signature, } @@ -267,6 +271,34 @@ impl SpendDescriptionV5 { } } +impl BorshDeserialize for SpendDescriptionV5 { + fn deserialize_reader(reader: &mut R) -> io::Result { + Self::read(reader) + } +} + +impl BorshSchema for SpendDescriptionV5 { + fn add_definitions_recursively( + definitions: &mut BTreeMap, + ) { + let definition = Definition::Struct { + fields: Fields::NamedFields(vec![ + ("cv".into(), <[u8; 32]>::declaration()), + ("nullifier".into(), Nullifier::declaration()), + ("rk".into(), PublicKey::declaration()), + ]), + }; + add_definition(Self::declaration(), definition, definitions); + <[u8; 32]>::add_definitions_recursively(definitions); + Nullifier::add_definitions_recursively(definitions); + PublicKey::add_definitions_recursively(definitions); + } + + fn declaration() -> borsh::schema::Declaration { + "SpendDescriptionV5".into() + } +} + #[derive(Clone, PartialEq, Eq)] pub struct OutputDescription { pub cv: jubjub::ExtendedPoint, @@ -377,6 +409,37 @@ impl OutputDescriptionV5 { } } +impl BorshDeserialize for OutputDescriptionV5 { + fn deserialize_reader(reader: &mut R) -> io::Result { + Self::read(reader) + } +} + +impl BorshSchema for OutputDescriptionV5 { + fn add_definitions_recursively( + definitions: &mut BTreeMap, + ) { + let definition = Definition::Struct { + fields: Fields::NamedFields(vec![ + ("cv".into(), <[u8; 32]>::declaration()), + ("cmu".into(), <[u8; 32]>::declaration()), + ("ephemeral_key".into(), EphemeralKeyBytes::declaration()), + ("enc_ciphertext".into(), <[u8; 580 + 32]>::declaration()), + ("out_ciphertext".into(), <[u8; 80]>::declaration()), + ]), + }; + add_definition(Self::declaration(), definition, definitions); + <[u8; 32]>::add_definitions_recursively(definitions); + EphemeralKeyBytes::add_definitions_recursively(definitions); + <[u8; 580 + 32]>::add_definitions_recursively(definitions); + <[u8; 80]>::add_definitions_recursively(definitions); + } + + fn declaration() -> borsh::schema::Declaration { + "OutputDescriptionV5".into() + } +} + #[derive(Clone)] pub struct CompactOutputDescription { pub ephemeral_key: EphemeralKeyBytes, @@ -520,6 +583,28 @@ impl ConvertDescriptionV5 { } } +impl BorshDeserialize for ConvertDescriptionV5 { + fn deserialize_reader(reader: &mut R) -> io::Result { + Self::read(reader) + } +} + +impl BorshSchema for ConvertDescriptionV5 { + fn add_definitions_recursively( + definitions: &mut BTreeMap, + ) { + let definition = Definition::Struct { + fields: Fields::NamedFields(vec![("cv".into(), <[u8; 32]>::declaration())]), + }; + add_definition(Self::declaration(), definition, definitions); + <[u8; 32]>::add_definitions_recursively(definitions); + } + + fn declaration() -> borsh::schema::Declaration { + "ConvertDescriptionV5".into() + } +} + #[cfg(any(test, feature = "test-dependencies"))] pub mod testing { use ff::Field; diff --git a/masp_primitives/src/transaction/components/transparent.rs b/masp_primitives/src/transaction/components/transparent.rs index 07fb2b1e..18a41b3a 100644 --- a/masp_primitives/src/transaction/components/transparent.rs +++ b/masp_primitives/src/transaction/components/transparent.rs @@ -129,6 +129,40 @@ impl TxIn { } } +impl BorshSerialize for TxIn { + fn serialize(&self, writer: &mut W) -> io::Result<()> { + self.write(writer) + } +} + +impl BorshDeserialize for TxIn { + fn deserialize_reader(reader: &mut R) -> io::Result { + Self::read(reader) + } +} + +impl BorshSchema for TxIn { + fn add_definitions_recursively( + definitions: &mut BTreeMap, + ) { + let definition = Definition::Struct { + fields: Fields::NamedFields(vec![ + ("asset_type".into(), AssetType::declaration()), + ("value".into(), u64::declaration()), + ("address".into(), TransparentAddress::declaration()), + ]), + }; + add_definition(Self::declaration(), definition, definitions); + AssetType::add_definitions_recursively(definitions); + u64::add_definitions_recursively(definitions); + TransparentAddress::add_definitions_recursively(definitions); + } + + fn declaration() -> borsh::schema::Declaration { + "TxIn".into() + } +} + #[derive(Clone, Debug, Hash, PartialOrd, PartialEq, Ord, Eq)] pub struct TxOut { pub asset_type: AssetType,