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,