From 406e62e7e54563070df5030fb2aa6595a285355a Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Sun, 28 Nov 2021 17:09:59 -0500 Subject: [PATCH 1/9] [move-only] Move Receiver and test code into kind::unified::address. Co-authored-by: Jack Grigg --- components/zcash_address/src/encoding.rs | 18 +- components/zcash_address/src/kind/unified.rs | 437 +----------------- .../zcash_address/src/kind/unified/address.rs | 437 ++++++++++++++++++ .../unified/{ => address}/test_vectors.rs | 0 components/zcash_address/src/test_vectors.rs | 5 +- 5 files changed, 453 insertions(+), 444 deletions(-) create mode 100644 components/zcash_address/src/kind/unified/address.rs rename components/zcash_address/src/kind/unified/{ => address}/test_vectors.rs (100%) diff --git a/components/zcash_address/src/encoding.rs b/components/zcash_address/src/encoding.rs index 83cc895b6..9bc171523 100644 --- a/components/zcash_address/src/encoding.rs +++ b/components/zcash_address/src/encoding.rs @@ -52,9 +52,9 @@ impl FromStr for ZcashAddress { Vec::::from_base32(&data).map_err(|_| ParseError::InvalidEncoding)?; let net = match hrp.as_str() { - unified::MAINNET => Network::Main, - unified::TESTNET => Network::Test, - unified::REGTEST => Network::Regtest, + unified::address::MAINNET => Network::Main, + unified::address::TESTNET => Network::Test, + unified::address::REGTEST => Network::Regtest, // We will not define new Bech32m address encodings. _ => { return Err(ParseError::NotZcash); @@ -152,9 +152,9 @@ impl fmt::Display for ZcashAddress { ), AddressKind::Unified(data) => { let hrp = match self.net { - Network::Main => unified::MAINNET, - Network::Test => unified::TESTNET, - Network::Regtest => unified::REGTEST, + Network::Main => unified::address::MAINNET, + Network::Test => unified::address::TESTNET, + Network::Regtest => unified::address::REGTEST, }; encode_bech32m(hrp, &data.to_bytes(hrp)) } @@ -230,21 +230,21 @@ mod tests { "u1qpatys4zruk99pg59gcscrt7y6akvl9vrhcfyhm9yxvxz7h87q6n8cgrzzpe9zru68uq39uhmlpp5uefxu0su5uqyqfe5zp3tycn0ecl", ZcashAddress { net: Network::Main, - kind: AddressKind::Unified(unified::Address(vec![unified::Receiver::Sapling([0; 43])])), + kind: AddressKind::Unified(unified::Address(vec![unified::address::Receiver::Sapling([0; 43])])), }, ); encoding( "utest10c5kutapazdnf8ztl3pu43nkfsjx89fy3uuff8tsmxm6s86j37pe7uz94z5jhkl49pqe8yz75rlsaygexk6jpaxwx0esjr8wm5ut7d5s", ZcashAddress { net: Network::Test, - kind: AddressKind::Unified(unified::Address(vec![unified::Receiver::Sapling([0; 43])])), + kind: AddressKind::Unified(unified::Address(vec![unified::address::Receiver::Sapling([0; 43])])), }, ); encoding( "uregtest15xk7vj4grjkay6mnfl93dhsflc2yeunhxwdh38rul0rq3dfhzzxgm5szjuvtqdha4t4p2q02ks0jgzrhjkrav70z9xlvq0plpcjkd5z3", ZcashAddress { net: Network::Regtest, - kind: AddressKind::Unified(unified::Address(vec![unified::Receiver::Sapling([0; 43])])), + kind: AddressKind::Unified(unified::Address(vec![unified::address::Receiver::Sapling([0; 43])])), }, ); } diff --git a/components/zcash_address/src/kind/unified.rs b/components/zcash_address/src/kind/unified.rs index 9f013dbde..1c499ff89 100644 --- a/components/zcash_address/src/kind/unified.rs +++ b/components/zcash_address/src/kind/unified.rs @@ -1,31 +1,10 @@ use std::cmp; -use std::collections::HashSet; -use std::convert::{TryFrom, TryInto}; +use std::convert::TryFrom; use std::error::Error; use std::fmt; -use std::io::Write; -use zcash_encoding::CompactSize; +pub(crate) mod address; -use crate::kind; - -/// The HRP for a Bech32m-encoded mainnet Unified Address. -/// -/// Defined in [ZIP 316][zip-0316]. -/// -/// [zip-0316]: https://zips.z.cash/zip-0316 -pub(crate) const MAINNET: &str = "u"; - -/// The HRP for a Bech32m-encoded testnet Unified Address. -/// -/// Defined in [ZIP 316][zip-0316]. -/// -/// [zip-0316]: https://zips.z.cash/zip-0316 -pub(crate) const TESTNET: &str = "utest"; - -/// The HRP for a Bech32m-encoded regtest Unified Address. -pub(crate) const REGTEST: &str = "uregtest"; - -const PADDING_LEN: usize = 16; +pub(crate) use address::Address; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum Typecode { @@ -137,413 +116,3 @@ impl fmt::Display for ParseError { } impl Error for ParseError {} - -/// The set of known Receivers for Unified Addresses. -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub enum Receiver { - Orchard([u8; 43]), - Sapling(kind::sapling::Data), - P2pkh(kind::p2pkh::Data), - P2sh(kind::p2sh::Data), - Unknown { typecode: u32, data: Vec }, -} - -impl cmp::Ord for Receiver { - fn cmp(&self, other: &Self) -> cmp::Ordering { - match self.typecode().cmp(&other.typecode()) { - cmp::Ordering::Equal => self.addr().cmp(other.addr()), - res => res, - } - } -} - -impl cmp::PartialOrd for Receiver { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl TryFrom<(u32, &[u8])> for Receiver { - type Error = ParseError; - - fn try_from((typecode, addr): (u32, &[u8])) -> Result { - match typecode.try_into()? { - Typecode::P2pkh => addr.try_into().map(Receiver::P2pkh), - Typecode::P2sh => addr.try_into().map(Receiver::P2sh), - Typecode::Sapling => addr.try_into().map(Receiver::Sapling), - Typecode::Orchard => addr.try_into().map(Receiver::Orchard), - Typecode::Unknown(_) => Ok(Receiver::Unknown { - typecode, - data: addr.to_vec(), - }), - } - .map_err(|e| { - ParseError::InvalidEncoding(format!("Invalid address for typecode {}: {}", typecode, e)) - }) - } -} - -impl Receiver { - fn typecode(&self) -> Typecode { - match self { - Receiver::P2pkh(_) => Typecode::P2pkh, - Receiver::P2sh(_) => Typecode::P2sh, - Receiver::Sapling(_) => Typecode::Sapling, - Receiver::Orchard(_) => Typecode::Orchard, - Receiver::Unknown { typecode, .. } => Typecode::Unknown(*typecode), - } - } - - fn addr(&self) -> &[u8] { - match self { - Receiver::P2pkh(data) => data, - Receiver::P2sh(data) => data, - Receiver::Sapling(data) => data, - Receiver::Orchard(data) => data, - Receiver::Unknown { data, .. } => data, - } - } -} - -/// A Unified Address. -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct Address(pub(crate) Vec); - -impl TryFrom<(&str, &[u8])> for Address { - type Error = ParseError; - - fn try_from((hrp, buf): (&str, &[u8])) -> Result { - fn read_receiver(mut cursor: &mut std::io::Cursor<&[u8]>) -> Result { - let typecode = CompactSize::read(&mut cursor) - .map(|v| u32::try_from(v).expect("CompactSize::read enforces MAX_SIZE limit")) - .map_err(|e| { - ParseError::InvalidEncoding(format!( - "Failed to deserialize CompactSize-encoded typecode {}", - e - )) - })?; - let length = CompactSize::read(&mut cursor).map_err(|e| { - ParseError::InvalidEncoding(format!( - "Failed to deserialize CompactSize-encoded length {}", - e - )) - })?; - let addr_end = cursor.position().checked_add(length).ok_or_else(|| { - ParseError::InvalidEncoding(format!( - "Length value {} caused an overflow error", - length - )) - })?; - let buf = cursor.get_ref(); - if (buf.len() as u64) < addr_end { - return Err(ParseError::InvalidEncoding(format!( - "Truncated: unable to read {} bytes of address data", - length - ))); - } - let result = Receiver::try_from(( - typecode, - &buf[cursor.position() as usize..addr_end as usize], - )); - cursor.set_position(addr_end); - result - } - - let encoded = f4jumble::f4jumble_inv(buf) - .ok_or_else(|| ParseError::InvalidEncoding("F4Jumble decoding failed".to_owned()))?; - - // Validate and strip trailing padding bytes. - if hrp.len() > 16 { - return Err(ParseError::InvalidEncoding( - "Invalid human-readable part".to_owned(), - )); - } - let mut expected_padding = [0; PADDING_LEN]; - expected_padding[0..hrp.len()].copy_from_slice(hrp.as_bytes()); - let encoded = match encoded.split_at(encoded.len() - PADDING_LEN) { - (encoded, tail) if tail == expected_padding => Ok(encoded), - _ => Err(ParseError::InvalidEncoding( - "Invalid padding bytes".to_owned(), - )), - }?; - - let mut cursor = std::io::Cursor::new(encoded); - let mut result = vec![]; - while cursor.position() < encoded.len().try_into().unwrap() { - result.push(read_receiver(&mut cursor)?); - } - assert_eq!(cursor.position(), encoded.len().try_into().unwrap()); - result.try_into() - } -} - -impl TryFrom> for Address { - type Error = ParseError; - - fn try_from(receivers: Vec) -> Result { - let mut typecodes = HashSet::with_capacity(receivers.len()); - for receiver in &receivers { - let t = receiver.typecode(); - if typecodes.contains(&t) { - return Err(ParseError::DuplicateTypecode(t)); - } else if (t == Typecode::P2pkh && typecodes.contains(&Typecode::P2sh)) - || (t == Typecode::P2sh && typecodes.contains(&Typecode::P2pkh)) - { - return Err(ParseError::BothP2phkAndP2sh); - } else { - typecodes.insert(t); - } - } - - if typecodes.iter().all(|t| t.is_transparent()) { - Err(ParseError::OnlyTransparent) - } else { - // All checks pass! - Ok(Address(receivers)) - } - } -} - -impl Address { - /// Returns the raw encoding of this Unified Address. - pub(crate) fn to_bytes(&self, hrp: &str) -> Vec { - assert!(hrp.len() <= PADDING_LEN); - - let mut writer = std::io::Cursor::new(Vec::new()); - for receiver in &self.0 { - let addr = receiver.addr(); - CompactSize::write( - &mut writer, - ::from(receiver.typecode()).try_into().unwrap(), - ) - .unwrap(); - CompactSize::write(&mut writer, addr.len()).unwrap(); - writer.write_all(addr).unwrap(); - } - - let mut padding = [0u8; PADDING_LEN]; - padding[0..hrp.len()].copy_from_slice(&hrp.as_bytes()); - writer.write_all(&padding).unwrap(); - - f4jumble::f4jumble(&writer.into_inner()).unwrap() - } - - /// Returns the receivers contained within this address, sorted in preference order. - pub fn receivers(&self) -> Vec { - let mut receivers = self.0.clone(); - // Unstable sorting is fine, because all receivers are guaranteed by construction - // to have distinct typecodes. - receivers.sort_unstable_by_key(|r| r.typecode()); - receivers - } - - /// Returns the receivers contained within this address, in the order they were - /// parsed from the string encoding. - /// - /// This API is for advanced usage; in most cases you should use `Address::receivers`. - pub fn receivers_as_parsed(&self) -> &[Receiver] { - &self.0 - } -} - -#[cfg(test)] -pub(crate) mod test_vectors; - -#[cfg(test)] -mod tests { - use assert_matches::assert_matches; - use std::convert::TryFrom; - - use proptest::{ - array::{uniform11, uniform20, uniform32}, - prelude::*, - }; - - use super::{Address, ParseError, Receiver, Typecode, MAINNET, REGTEST, TESTNET}; - - prop_compose! { - fn uniform43()(a in uniform11(0u8..), b in uniform32(0u8..)) -> [u8; 43] { - let mut c = [0; 43]; - c[..11].copy_from_slice(&a); - c[11..].copy_from_slice(&b); - c - } - } - - fn arb_shielded_receiver() -> BoxedStrategy { - prop_oneof![ - uniform43().prop_map(Receiver::Sapling), - uniform43().prop_map(Receiver::Orchard), - ] - .boxed() - } - - fn arb_transparent_receiver() -> BoxedStrategy { - prop_oneof![ - uniform20(0u8..).prop_map(Receiver::P2pkh), - uniform20(0u8..).prop_map(Receiver::P2sh), - ] - .boxed() - } - - prop_compose! { - fn arb_unified_address()( - shielded in prop::collection::hash_set(arb_shielded_receiver(), 1..2), - transparent in prop::option::of(arb_transparent_receiver()), - ) -> Address { - Address(shielded.into_iter().chain(transparent).collect()) - } - } - - proptest! { - #[test] - fn ua_roundtrip( - hrp in prop_oneof![MAINNET, TESTNET, REGTEST], - ua in arb_unified_address(), - ) { - let bytes = ua.to_bytes(&hrp); - let decoded = Address::try_from((hrp.as_str(), &bytes[..])); - prop_assert_eq!(decoded, Ok(ua)); - } - } - - #[test] - fn padding() { - // The test cases below use `Address(vec![Receiver::Orchard([1; 43])])` as base. - - // Invalid padding ([0xff; 16] instead of [b'u', 0x00, 0x00, 0x00...]) - let invalid_padding = [ - 0xe6, 0x59, 0xd1, 0xed, 0xf7, 0x4b, 0xe3, 0x5e, 0x5a, 0x54, 0x0e, 0x41, 0x5d, 0x2f, - 0x0c, 0x0d, 0x33, 0x42, 0xbd, 0xbe, 0x9f, 0x82, 0x62, 0x01, 0xc1, 0x1b, 0xd4, 0x1e, - 0x42, 0x47, 0x86, 0x23, 0x05, 0x4b, 0x98, 0xd7, 0x76, 0x86, 0xa5, 0xe3, 0x1b, 0xd3, - 0x03, 0xca, 0x24, 0x44, 0x8e, 0x72, 0xc1, 0x4a, 0xc6, 0xbf, 0x3f, 0x2b, 0xce, 0xa7, - 0x7b, 0x28, 0x69, 0xc9, 0x84, - ]; - assert_eq!( - Address::try_from((MAINNET, &invalid_padding[..])), - Err(ParseError::InvalidEncoding( - "Invalid padding bytes".to_owned() - )) - ); - - // Short padding (padded to 15 bytes instead of 16) - let truncated_padding = [ - 0x9a, 0x56, 0x12, 0xa3, 0x43, 0x45, 0xe0, 0x82, 0x6c, 0xac, 0x24, 0x8b, 0x3b, 0x45, - 0x72, 0x9a, 0x53, 0xd5, 0xf8, 0xda, 0xec, 0x07, 0x7c, 0xba, 0x9f, 0xa8, 0xd2, 0x97, - 0x5b, 0xda, 0x73, 0x1b, 0xd2, 0xd1, 0x32, 0x6b, 0x7b, 0x36, 0xdd, 0x57, 0x84, 0x2a, - 0xa0, 0x21, 0x23, 0x89, 0x73, 0x85, 0xe1, 0x4b, 0x3e, 0x95, 0xb7, 0xd4, 0x67, 0xbc, - 0x4b, 0x31, 0xee, 0x5a, - ]; - assert_eq!( - Address::try_from((MAINNET, &truncated_padding[..])), - Err(ParseError::InvalidEncoding( - "Invalid padding bytes".to_owned() - )) - ); - } - - #[test] - fn truncated() { - // The test cases below start from an encoding of - // `Address(vec![Receiver::Orchard([1; 43]), Receiver::Sapling([2; 43])])` - // with the receiver data truncated, but valid padding. - - // - Missing the last data byte of the Sapling receiver. - let truncated_sapling_data = [ - 0xaa, 0xb0, 0x6e, 0x7b, 0x26, 0x7a, 0x22, 0x17, 0x39, 0xfa, 0x07, 0x69, 0xe9, 0x32, - 0x2b, 0xac, 0x8c, 0x9e, 0x5e, 0x8a, 0xd9, 0x24, 0x06, 0x5a, 0x13, 0x79, 0x3a, 0x8d, - 0xb4, 0x52, 0xfa, 0x18, 0x4e, 0x33, 0x4d, 0x8c, 0x17, 0x77, 0x4d, 0x63, 0x69, 0x34, - 0x22, 0x70, 0x3a, 0xea, 0x30, 0x82, 0x5a, 0x6b, 0x37, 0xd1, 0x0d, 0xbe, 0x20, 0xab, - 0x82, 0x86, 0x98, 0x34, 0x6a, 0xd8, 0x45, 0x40, 0xd0, 0x25, 0x60, 0xbf, 0x1e, 0xb6, - 0xeb, 0x06, 0x85, 0x70, 0x4c, 0x42, 0xbc, 0x19, 0x14, 0xef, 0x7a, 0x05, 0xa0, 0x71, - 0xb2, 0x63, 0x80, 0xbb, 0xdc, 0x12, 0x08, 0x48, 0x28, 0x8f, 0x1c, 0x9e, 0xc3, 0x42, - 0xc6, 0x5e, 0x68, 0xa2, 0x78, 0x6c, 0x9e, - ]; - assert_matches!( - Address::try_from((MAINNET, &truncated_sapling_data[..])), - Err(ParseError::InvalidEncoding(_)) - ); - - // - Truncated after the typecode of the Sapling receiver. - let truncated_after_sapling_typecode = [ - 0x87, 0x7a, 0xdf, 0x79, 0x6b, 0xe3, 0xb3, 0x40, 0xef, 0xe4, 0x5d, 0xc2, 0x91, 0xa2, - 0x81, 0xfc, 0x7d, 0x76, 0xbb, 0xb0, 0x58, 0x98, 0x53, 0x59, 0xd3, 0x3f, 0xbc, 0x4b, - 0x86, 0x59, 0x66, 0x62, 0x75, 0x92, 0xba, 0xcc, 0x31, 0x1e, 0x60, 0x02, 0x3b, 0xd8, - 0x4c, 0xdf, 0x36, 0xa1, 0xac, 0x82, 0x57, 0xed, 0x0c, 0x98, 0x49, 0x8f, 0x49, 0x7e, - 0xe6, 0x70, 0x36, 0x5b, 0x7b, 0x9e, - ]; - assert_matches!( - Address::try_from((MAINNET, &truncated_after_sapling_typecode[..])), - Err(ParseError::InvalidEncoding(_)) - ); - } - - #[test] - fn duplicate_typecode() { - // Construct and serialize an invalid UA. - let ua = Address(vec![Receiver::Sapling([1; 43]), Receiver::Sapling([2; 43])]); - let encoded = ua.to_bytes(MAINNET); - assert_eq!( - Address::try_from((MAINNET, &encoded[..])), - Err(ParseError::DuplicateTypecode(Typecode::Sapling)) - ); - } - - #[test] - fn p2pkh_and_p2sh() { - // Construct and serialize an invalid UA. - let ua = Address(vec![Receiver::P2pkh([0; 20]), Receiver::P2sh([0; 20])]); - let encoded = ua.to_bytes(MAINNET); - assert_eq!( - Address::try_from((MAINNET, &encoded[..])), - Err(ParseError::BothP2phkAndP2sh) - ); - } - - #[test] - fn only_transparent() { - // Encoding of `Address(vec![Receiver::P2pkh([0; 20])])`. - let encoded = vec![ - 0xf0, 0x9e, 0x9d, 0x6e, 0xf5, 0xa6, 0xac, 0x16, 0x50, 0xf0, 0xdb, 0xe1, 0x2c, 0xa5, - 0x36, 0x22, 0xa2, 0x04, 0x89, 0x86, 0xe9, 0x6a, 0x9b, 0xf3, 0xff, 0x6d, 0x2f, 0xe6, - 0xea, 0xdb, 0xc5, 0x20, 0x62, 0xf9, 0x6f, 0xa9, 0x86, 0xcc, - ]; - - // We can't actually exercise this error, because at present the only transparent - // receivers we can use are P2PKH and P2SH (which cannot be used together), and - // with only one of them we don't have sufficient data for F4Jumble (so we hit a - // different error). - assert_matches!( - Address::try_from((MAINNET, &encoded[..])), - Err(ParseError::InvalidEncoding(_)) - ); - } - - #[test] - fn receivers_are_sorted() { - // Construct a UA with receivers in an unsorted order. - let ua = Address(vec![ - Receiver::P2pkh([0; 20]), - Receiver::Orchard([0; 43]), - Receiver::Unknown { - typecode: 0xff, - data: vec![], - }, - Receiver::Sapling([0; 43]), - ]); - - // `Address::receivers` sorts the receivers in priority order. - assert_eq!( - ua.receivers(), - vec![ - Receiver::Orchard([0; 43]), - Receiver::Sapling([0; 43]), - Receiver::P2pkh([0; 20]), - Receiver::Unknown { - typecode: 0xff, - data: vec![], - }, - ] - ) - } -} diff --git a/components/zcash_address/src/kind/unified/address.rs b/components/zcash_address/src/kind/unified/address.rs new file mode 100644 index 000000000..708a83606 --- /dev/null +++ b/components/zcash_address/src/kind/unified/address.rs @@ -0,0 +1,437 @@ +use super::{ParseError, Typecode}; +use crate::kind; + +use std::cmp; +use std::collections::HashSet; +use std::convert::{TryFrom, TryInto}; +use std::io::Write; +use zcash_encoding::CompactSize; + +/// The HRP for a Bech32m-encoded mainnet Unified Address. +/// +/// Defined in [ZIP 316][zip-0316]. +/// +/// [zip-0316]: https://zips.z.cash/zip-0316 +pub(crate) const MAINNET: &str = "u"; + +/// The HRP for a Bech32m-encoded testnet Unified Address. +/// +/// Defined in [ZIP 316][zip-0316]. +/// +/// [zip-0316]: https://zips.z.cash/zip-0316 +pub(crate) const TESTNET: &str = "utest"; + +/// The HRP for a Bech32m-encoded regtest Unified Address. +pub(crate) const REGTEST: &str = "uregtest"; + +const PADDING_LEN: usize = 16; + +/// The set of known Receivers for Unified Addresses. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum Receiver { + Orchard([u8; 43]), + Sapling(kind::sapling::Data), + P2pkh(kind::p2pkh::Data), + P2sh(kind::p2sh::Data), + Unknown { typecode: u32, data: Vec }, +} + +impl cmp::Ord for Receiver { + fn cmp(&self, other: &Self) -> cmp::Ordering { + match self.typecode().cmp(&other.typecode()) { + cmp::Ordering::Equal => self.addr().cmp(other.addr()), + res => res, + } + } +} + +impl cmp::PartialOrd for Receiver { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl TryFrom<(u32, &[u8])> for Receiver { + type Error = ParseError; + + fn try_from((typecode, addr): (u32, &[u8])) -> Result { + match typecode.try_into()? { + Typecode::P2pkh => addr.try_into().map(Receiver::P2pkh), + Typecode::P2sh => addr.try_into().map(Receiver::P2sh), + Typecode::Sapling => addr.try_into().map(Receiver::Sapling), + Typecode::Orchard => addr.try_into().map(Receiver::Orchard), + Typecode::Unknown(_) => Ok(Receiver::Unknown { + typecode, + data: addr.to_vec(), + }), + } + .map_err(|e| { + ParseError::InvalidEncoding(format!("Invalid address for typecode {}: {}", typecode, e)) + }) + } +} + +impl Receiver { + fn typecode(&self) -> Typecode { + match self { + Receiver::P2pkh(_) => Typecode::P2pkh, + Receiver::P2sh(_) => Typecode::P2sh, + Receiver::Sapling(_) => Typecode::Sapling, + Receiver::Orchard(_) => Typecode::Orchard, + Receiver::Unknown { typecode, .. } => Typecode::Unknown(*typecode), + } + } + + fn addr(&self) -> &[u8] { + match self { + Receiver::P2pkh(data) => data, + Receiver::P2sh(data) => data, + Receiver::Sapling(data) => data, + Receiver::Orchard(data) => data, + Receiver::Unknown { data, .. } => data, + } + } +} + +/// A Unified Address. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Address(pub(crate) Vec); + +impl TryFrom<(&str, &[u8])> for Address { + type Error = ParseError; + + fn try_from((hrp, buf): (&str, &[u8])) -> Result { + fn read_receiver(mut cursor: &mut std::io::Cursor<&[u8]>) -> Result { + let typecode = CompactSize::read(&mut cursor) + .map(|v| u32::try_from(v).expect("CompactSize::read enforces MAX_SIZE limit")) + .map_err(|e| { + ParseError::InvalidEncoding(format!( + "Failed to deserialize CompactSize-encoded typecode {}", + e + )) + })?; + let length = CompactSize::read(&mut cursor).map_err(|e| { + ParseError::InvalidEncoding(format!( + "Failed to deserialize CompactSize-encoded length {}", + e + )) + })?; + let addr_end = cursor.position().checked_add(length).ok_or_else(|| { + ParseError::InvalidEncoding(format!( + "Length value {} caused an overflow error", + length + )) + })?; + let buf = cursor.get_ref(); + if (buf.len() as u64) < addr_end { + return Err(ParseError::InvalidEncoding(format!( + "Truncated: unable to read {} bytes of address data", + length + ))); + } + let result = Receiver::try_from(( + typecode, + &buf[cursor.position() as usize..addr_end as usize], + )); + cursor.set_position(addr_end); + result + } + + let encoded = f4jumble::f4jumble_inv(buf) + .ok_or_else(|| ParseError::InvalidEncoding("F4Jumble decoding failed".to_owned()))?; + + // Validate and strip trailing padding bytes. + if hrp.len() > 16 { + return Err(ParseError::InvalidEncoding( + "Invalid human-readable part".to_owned(), + )); + } + let mut expected_padding = [0; PADDING_LEN]; + expected_padding[0..hrp.len()].copy_from_slice(hrp.as_bytes()); + let encoded = match encoded.split_at(encoded.len() - PADDING_LEN) { + (encoded, tail) if tail == expected_padding => Ok(encoded), + _ => Err(ParseError::InvalidEncoding( + "Invalid padding bytes".to_owned(), + )), + }?; + + let mut cursor = std::io::Cursor::new(encoded); + let mut result = vec![]; + while cursor.position() < encoded.len().try_into().unwrap() { + result.push(read_receiver(&mut cursor)?); + } + assert_eq!(cursor.position(), encoded.len().try_into().unwrap()); + result.try_into() + } +} + +impl TryFrom> for Address { + type Error = ParseError; + + fn try_from(receivers: Vec) -> Result { + let mut typecodes = HashSet::with_capacity(receivers.len()); + for receiver in &receivers { + let t = receiver.typecode(); + if typecodes.contains(&t) { + return Err(ParseError::DuplicateTypecode(t)); + } else if (t == Typecode::P2pkh && typecodes.contains(&Typecode::P2sh)) + || (t == Typecode::P2sh && typecodes.contains(&Typecode::P2pkh)) + { + return Err(ParseError::BothP2phkAndP2sh); + } else { + typecodes.insert(t); + } + } + + if typecodes.iter().all(|t| t.is_transparent()) { + Err(ParseError::OnlyTransparent) + } else { + // All checks pass! + Ok(Address(receivers)) + } + } +} + +impl Address { + /// Returns the raw encoding of this Unified Address. + pub(crate) fn to_bytes(&self, hrp: &str) -> Vec { + assert!(hrp.len() <= PADDING_LEN); + + let mut writer = std::io::Cursor::new(Vec::new()); + for receiver in &self.0 { + let addr = receiver.addr(); + CompactSize::write( + &mut writer, + ::from(receiver.typecode()).try_into().unwrap(), + ) + .unwrap(); + CompactSize::write(&mut writer, addr.len()).unwrap(); + writer.write_all(addr).unwrap(); + } + + let mut padding = [0u8; PADDING_LEN]; + padding[0..hrp.len()].copy_from_slice(&hrp.as_bytes()); + writer.write_all(&padding).unwrap(); + + f4jumble::f4jumble(&writer.into_inner()).unwrap() + } + + /// Returns the receivers contained within this address, sorted in preference order. + pub fn receivers(&self) -> Vec { + let mut receivers = self.0.clone(); + // Unstable sorting is fine, because all receivers are guaranteed by construction + // to have distinct typecodes. + receivers.sort_unstable_by_key(|r| r.typecode()); + receivers + } + + /// Returns the receivers contained within this address, in the order they were + /// parsed from the string encoding. + /// + /// This API is for advanced usage; in most cases you should use `Address::receivers`. + pub fn receivers_as_parsed(&self) -> &[Receiver] { + &self.0 + } +} + +#[cfg(test)] +pub(crate) mod test_vectors; + +#[cfg(test)] +mod tests { + use assert_matches::assert_matches; + use std::convert::TryFrom; + + use proptest::{ + array::{uniform11, uniform20, uniform32}, + prelude::*, + }; + + use super::{Address, ParseError, Receiver, Typecode, MAINNET, REGTEST, TESTNET}; + + prop_compose! { + fn uniform43()(a in uniform11(0u8..), b in uniform32(0u8..)) -> [u8; 43] { + let mut c = [0; 43]; + c[..11].copy_from_slice(&a); + c[11..].copy_from_slice(&b); + c + } + } + + fn arb_shielded_receiver() -> BoxedStrategy { + prop_oneof![ + uniform43().prop_map(Receiver::Sapling), + uniform43().prop_map(Receiver::Orchard), + ] + .boxed() + } + + fn arb_transparent_receiver() -> BoxedStrategy { + prop_oneof![ + uniform20(0u8..).prop_map(Receiver::P2pkh), + uniform20(0u8..).prop_map(Receiver::P2sh), + ] + .boxed() + } + + prop_compose! { + fn arb_unified_address()( + shielded in prop::collection::hash_set(arb_shielded_receiver(), 1..2), + transparent in prop::option::of(arb_transparent_receiver()), + ) -> Address { + Address(shielded.into_iter().chain(transparent).collect()) + } + } + + proptest! { + #[test] + fn ua_roundtrip( + hrp in prop_oneof![MAINNET, TESTNET, REGTEST], + ua in arb_unified_address(), + ) { + let bytes = ua.to_bytes(&hrp); + let decoded = Address::try_from((hrp.as_str(), &bytes[..])); + prop_assert_eq!(decoded, Ok(ua)); + } + } + + #[test] + fn padding() { + // The test cases below use `Address(vec![Receiver::Orchard([1; 43])])` as base. + + // Invalid padding ([0xff; 16] instead of [b'u', 0x00, 0x00, 0x00...]) + let invalid_padding = [ + 0xe6, 0x59, 0xd1, 0xed, 0xf7, 0x4b, 0xe3, 0x5e, 0x5a, 0x54, 0x0e, 0x41, 0x5d, 0x2f, + 0x0c, 0x0d, 0x33, 0x42, 0xbd, 0xbe, 0x9f, 0x82, 0x62, 0x01, 0xc1, 0x1b, 0xd4, 0x1e, + 0x42, 0x47, 0x86, 0x23, 0x05, 0x4b, 0x98, 0xd7, 0x76, 0x86, 0xa5, 0xe3, 0x1b, 0xd3, + 0x03, 0xca, 0x24, 0x44, 0x8e, 0x72, 0xc1, 0x4a, 0xc6, 0xbf, 0x3f, 0x2b, 0xce, 0xa7, + 0x7b, 0x28, 0x69, 0xc9, 0x84, + ]; + assert_eq!( + Address::try_from((MAINNET, &invalid_padding[..])), + Err(ParseError::InvalidEncoding( + "Invalid padding bytes".to_owned() + )) + ); + + // Short padding (padded to 15 bytes instead of 16) + let truncated_padding = [ + 0x9a, 0x56, 0x12, 0xa3, 0x43, 0x45, 0xe0, 0x82, 0x6c, 0xac, 0x24, 0x8b, 0x3b, 0x45, + 0x72, 0x9a, 0x53, 0xd5, 0xf8, 0xda, 0xec, 0x07, 0x7c, 0xba, 0x9f, 0xa8, 0xd2, 0x97, + 0x5b, 0xda, 0x73, 0x1b, 0xd2, 0xd1, 0x32, 0x6b, 0x7b, 0x36, 0xdd, 0x57, 0x84, 0x2a, + 0xa0, 0x21, 0x23, 0x89, 0x73, 0x85, 0xe1, 0x4b, 0x3e, 0x95, 0xb7, 0xd4, 0x67, 0xbc, + 0x4b, 0x31, 0xee, 0x5a, + ]; + assert_eq!( + Address::try_from((MAINNET, &truncated_padding[..])), + Err(ParseError::InvalidEncoding( + "Invalid padding bytes".to_owned() + )) + ); + } + + #[test] + fn truncated() { + // The test cases below start from an encoding of + // `Address(vec![Receiver::Orchard([1; 43]), Receiver::Sapling([2; 43])])` + // with the receiver data truncated, but valid padding. + + // - Missing the last data byte of the Sapling receiver. + let truncated_sapling_data = [ + 0xaa, 0xb0, 0x6e, 0x7b, 0x26, 0x7a, 0x22, 0x17, 0x39, 0xfa, 0x07, 0x69, 0xe9, 0x32, + 0x2b, 0xac, 0x8c, 0x9e, 0x5e, 0x8a, 0xd9, 0x24, 0x06, 0x5a, 0x13, 0x79, 0x3a, 0x8d, + 0xb4, 0x52, 0xfa, 0x18, 0x4e, 0x33, 0x4d, 0x8c, 0x17, 0x77, 0x4d, 0x63, 0x69, 0x34, + 0x22, 0x70, 0x3a, 0xea, 0x30, 0x82, 0x5a, 0x6b, 0x37, 0xd1, 0x0d, 0xbe, 0x20, 0xab, + 0x82, 0x86, 0x98, 0x34, 0x6a, 0xd8, 0x45, 0x40, 0xd0, 0x25, 0x60, 0xbf, 0x1e, 0xb6, + 0xeb, 0x06, 0x85, 0x70, 0x4c, 0x42, 0xbc, 0x19, 0x14, 0xef, 0x7a, 0x05, 0xa0, 0x71, + 0xb2, 0x63, 0x80, 0xbb, 0xdc, 0x12, 0x08, 0x48, 0x28, 0x8f, 0x1c, 0x9e, 0xc3, 0x42, + 0xc6, 0x5e, 0x68, 0xa2, 0x78, 0x6c, 0x9e, + ]; + assert_matches!( + Address::try_from((MAINNET, &truncated_sapling_data[..])), + Err(ParseError::InvalidEncoding(_)) + ); + + // - Truncated after the typecode of the Sapling receiver. + let truncated_after_sapling_typecode = [ + 0x87, 0x7a, 0xdf, 0x79, 0x6b, 0xe3, 0xb3, 0x40, 0xef, 0xe4, 0x5d, 0xc2, 0x91, 0xa2, + 0x81, 0xfc, 0x7d, 0x76, 0xbb, 0xb0, 0x58, 0x98, 0x53, 0x59, 0xd3, 0x3f, 0xbc, 0x4b, + 0x86, 0x59, 0x66, 0x62, 0x75, 0x92, 0xba, 0xcc, 0x31, 0x1e, 0x60, 0x02, 0x3b, 0xd8, + 0x4c, 0xdf, 0x36, 0xa1, 0xac, 0x82, 0x57, 0xed, 0x0c, 0x98, 0x49, 0x8f, 0x49, 0x7e, + 0xe6, 0x70, 0x36, 0x5b, 0x7b, 0x9e, + ]; + assert_matches!( + Address::try_from((MAINNET, &truncated_after_sapling_typecode[..])), + Err(ParseError::InvalidEncoding(_)) + ); + } + + #[test] + fn duplicate_typecode() { + // Construct and serialize an invalid UA. + let ua = Address(vec![Receiver::Sapling([1; 43]), Receiver::Sapling([2; 43])]); + let encoded = ua.to_bytes(MAINNET); + assert_eq!( + Address::try_from((MAINNET, &encoded[..])), + Err(ParseError::DuplicateTypecode(Typecode::Sapling)) + ); + } + + #[test] + fn p2pkh_and_p2sh() { + // Construct and serialize an invalid UA. + let ua = Address(vec![Receiver::P2pkh([0; 20]), Receiver::P2sh([0; 20])]); + let encoded = ua.to_bytes(MAINNET); + assert_eq!( + Address::try_from((MAINNET, &encoded[..])), + Err(ParseError::BothP2phkAndP2sh) + ); + } + + #[test] + fn only_transparent() { + // Encoding of `Address(vec![Receiver::P2pkh([0; 20])])`. + let encoded = vec![ + 0xf0, 0x9e, 0x9d, 0x6e, 0xf5, 0xa6, 0xac, 0x16, 0x50, 0xf0, 0xdb, 0xe1, 0x2c, 0xa5, + 0x36, 0x22, 0xa2, 0x04, 0x89, 0x86, 0xe9, 0x6a, 0x9b, 0xf3, 0xff, 0x6d, 0x2f, 0xe6, + 0xea, 0xdb, 0xc5, 0x20, 0x62, 0xf9, 0x6f, 0xa9, 0x86, 0xcc, + ]; + + // We can't actually exercise this error, because at present the only transparent + // receivers we can use are P2PKH and P2SH (which cannot be used together), and + // with only one of them we don't have sufficient data for F4Jumble (so we hit a + // different error). + assert_matches!( + Address::try_from((MAINNET, &encoded[..])), + Err(ParseError::InvalidEncoding(_)) + ); + } + + #[test] + fn receivers_are_sorted() { + // Construct a UA with receivers in an unsorted order. + let ua = Address(vec![ + Receiver::P2pkh([0; 20]), + Receiver::Orchard([0; 43]), + Receiver::Unknown { + typecode: 0xff, + data: vec![], + }, + Receiver::Sapling([0; 43]), + ]); + + // `Address::receivers` sorts the receivers in priority order. + assert_eq!( + ua.receivers(), + vec![ + Receiver::Orchard([0; 43]), + Receiver::Sapling([0; 43]), + Receiver::P2pkh([0; 20]), + Receiver::Unknown { + typecode: 0xff, + data: vec![], + }, + ] + ) + } +} diff --git a/components/zcash_address/src/kind/unified/test_vectors.rs b/components/zcash_address/src/kind/unified/address/test_vectors.rs similarity index 100% rename from components/zcash_address/src/kind/unified/test_vectors.rs rename to components/zcash_address/src/kind/unified/address/test_vectors.rs diff --git a/components/zcash_address/src/test_vectors.rs b/components/zcash_address/src/test_vectors.rs index 46b2a612f..f5dc06296 100644 --- a/components/zcash_address/src/test_vectors.rs +++ b/components/zcash_address/src/test_vectors.rs @@ -1,7 +1,10 @@ use std::iter; use crate::{ - unified::{self, test_vectors::TEST_VECTORS, Receiver}, + unified::{ + self, + address::{test_vectors::TEST_VECTORS, Receiver}, + }, Network, ToAddress, ZcashAddress, }; From 18393eefa8be5f4be1e87ab9f16a646f47745568 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Sun, 28 Nov 2021 18:18:25 -0500 Subject: [PATCH 2/9] kind::unified: Introduce private::SealedReceiver trait. Co-authored-by: Jack Grigg --- components/zcash_address/src/kind/unified.rs | 13 +++++++++++++ .../zcash_address/src/kind/unified/address.rs | 10 +++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/components/zcash_address/src/kind/unified.rs b/components/zcash_address/src/kind/unified.rs index 1c499ff89..2360c201f 100644 --- a/components/zcash_address/src/kind/unified.rs +++ b/components/zcash_address/src/kind/unified.rs @@ -116,3 +116,16 @@ impl fmt::Display for ParseError { } impl Error for ParseError {} + +mod private { + use super::{ParseError, Typecode}; + use std::{cmp, convert::TryFrom}; + + /// A raw address or viewing key. + pub trait SealedReceiver: + for<'a> TryFrom<(u32, &'a [u8]), Error = ParseError> + cmp::Ord + cmp::PartialOrd + Clone + { + fn typecode(&self) -> Typecode; + fn data(&self) -> &[u8]; + } +} diff --git a/components/zcash_address/src/kind/unified/address.rs b/components/zcash_address/src/kind/unified/address.rs index 708a83606..1c2dd19fa 100644 --- a/components/zcash_address/src/kind/unified/address.rs +++ b/components/zcash_address/src/kind/unified/address.rs @@ -1,4 +1,4 @@ -use super::{ParseError, Typecode}; +use super::{private::SealedReceiver, ParseError, Typecode}; use crate::kind; use std::cmp; @@ -39,7 +39,7 @@ pub enum Receiver { impl cmp::Ord for Receiver { fn cmp(&self, other: &Self) -> cmp::Ordering { match self.typecode().cmp(&other.typecode()) { - cmp::Ordering::Equal => self.addr().cmp(other.addr()), + cmp::Ordering::Equal => self.data().cmp(other.data()), res => res, } } @@ -71,7 +71,7 @@ impl TryFrom<(u32, &[u8])> for Receiver { } } -impl Receiver { +impl SealedReceiver for Receiver { fn typecode(&self) -> Typecode { match self { Receiver::P2pkh(_) => Typecode::P2pkh, @@ -82,7 +82,7 @@ impl Receiver { } } - fn addr(&self) -> &[u8] { + fn data(&self) -> &[u8] { match self { Receiver::P2pkh(data) => data, Receiver::P2sh(data) => data, @@ -199,7 +199,7 @@ impl Address { let mut writer = std::io::Cursor::new(Vec::new()); for receiver in &self.0 { - let addr = receiver.addr(); + let addr = receiver.data(); CompactSize::write( &mut writer, ::from(receiver.typecode()).try_into().unwrap(), From b8ff3d2d483c5aea77ea4939ae6b368607142318 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Sun, 28 Nov 2021 18:38:53 -0500 Subject: [PATCH 3/9] kind::unified: Introduce private::SealedContainer and Unified traits. Co-authored-by: Jack Grigg --- components/zcash_address/src/encoding.rs | 16 +- components/zcash_address/src/kind/unified.rs | 149 +++++++++++++- .../zcash_address/src/kind/unified/address.rs | 190 +++--------------- 3 files changed, 189 insertions(+), 166 deletions(-) diff --git a/components/zcash_address/src/encoding.rs b/components/zcash_address/src/encoding.rs index 9bc171523..326ab5069 100644 --- a/components/zcash_address/src/encoding.rs +++ b/components/zcash_address/src/encoding.rs @@ -2,6 +2,7 @@ use std::{convert::TryInto, error::Error, fmt, str::FromStr}; use bech32::{self, FromBase32, ToBase32, Variant}; +use crate::kind::unified::Unified; use crate::{kind::*, AddressKind, Network, ZcashAddress}; /// An error while attempting to parse a string as a Zcash address. @@ -52,17 +53,16 @@ impl FromStr for ZcashAddress { Vec::::from_base32(&data).map_err(|_| ParseError::InvalidEncoding)?; let net = match hrp.as_str() { - unified::address::MAINNET => Network::Main, - unified::address::TESTNET => Network::Test, - unified::address::REGTEST => Network::Regtest, + unified::address::Address::MAINNET => Network::Main, + unified::address::Address::TESTNET => Network::Test, + unified::address::Address::REGTEST => Network::Regtest, // We will not define new Bech32m address encodings. _ => { return Err(ParseError::NotZcash); } }; - return (hrp.as_str(), &data[..]) - .try_into() + return unified::Address::try_from_bytes(hrp.as_str(), &data[..]) .map(AddressKind::Unified) .map_err(|_| ParseError::InvalidEncoding) .map(|kind| ZcashAddress { net, kind }); @@ -152,9 +152,9 @@ impl fmt::Display for ZcashAddress { ), AddressKind::Unified(data) => { let hrp = match self.net { - Network::Main => unified::address::MAINNET, - Network::Test => unified::address::TESTNET, - Network::Regtest => unified::address::REGTEST, + Network::Main => unified::address::Address::MAINNET, + Network::Test => unified::address::Address::TESTNET, + Network::Regtest => unified::address::Address::REGTEST, }; encode_bech32m(hrp, &data.to_bytes(hrp)) } diff --git a/components/zcash_address/src/kind/unified.rs b/components/zcash_address/src/kind/unified.rs index 2360c201f..1f6ef4f3b 100644 --- a/components/zcash_address/src/kind/unified.rs +++ b/components/zcash_address/src/kind/unified.rs @@ -1,11 +1,17 @@ use std::cmp; -use std::convert::TryFrom; +use std::collections::HashSet; +use std::convert::{TryFrom, TryInto}; use std::error::Error; use std::fmt; +use std::io::Write; +use zcash_encoding::CompactSize; + pub(crate) mod address; pub(crate) use address::Address; +const PADDING_LEN: usize = 16; + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum Typecode { P2pkh, @@ -128,4 +134,145 @@ mod private { fn typecode(&self) -> Typecode; fn data(&self) -> &[u8]; } + + pub trait SealedContainer { + type Receiver: SealedReceiver; + + fn from_inner(receivers: Vec) -> Self; + } +} + +use private::SealedReceiver; + +/// Trait providing common encoding logic for Unified containers. +pub trait Unified: private::SealedContainer + std::marker::Sized { + const MAINNET: &'static str; + const TESTNET: &'static str; + const REGTEST: &'static str; + + fn try_from_bytes(hrp: &str, buf: &[u8]) -> Result { + fn read_receiver( + mut cursor: &mut std::io::Cursor<&[u8]>, + ) -> Result { + let typecode = CompactSize::read(&mut cursor) + .map(|v| u32::try_from(v).expect("CompactSize::read enforces MAX_SIZE limit")) + .map_err(|e| { + ParseError::InvalidEncoding(format!( + "Failed to deserialize CompactSize-encoded typecode {}", + e + )) + })?; + let length = CompactSize::read(&mut cursor).map_err(|e| { + ParseError::InvalidEncoding(format!( + "Failed to deserialize CompactSize-encoded length {}", + e + )) + })?; + let addr_end = cursor.position().checked_add(length).ok_or_else(|| { + ParseError::InvalidEncoding(format!( + "Length value {} caused an overflow error", + length + )) + })?; + let buf = cursor.get_ref(); + if (buf.len() as u64) < addr_end { + return Err(ParseError::InvalidEncoding(format!( + "Truncated: unable to read {} bytes of address data", + length + ))); + } + let result = R::try_from(( + typecode, + &buf[cursor.position() as usize..addr_end as usize], + )); + cursor.set_position(addr_end); + result + } + + let encoded = f4jumble::f4jumble_inv(buf) + .ok_or_else(|| ParseError::InvalidEncoding("F4Jumble decoding failed".to_owned()))?; + + // Validate and strip trailing padding bytes. + if hrp.len() > 16 { + return Err(ParseError::InvalidEncoding( + "Invalid human-readable part".to_owned(), + )); + } + let mut expected_padding = [0; PADDING_LEN]; + expected_padding[0..hrp.len()].copy_from_slice(hrp.as_bytes()); + let encoded = match encoded.split_at(encoded.len() - PADDING_LEN) { + (encoded, tail) if tail == expected_padding => Ok(encoded), + _ => Err(ParseError::InvalidEncoding( + "Invalid padding bytes".to_owned(), + )), + }?; + + let mut cursor = std::io::Cursor::new(encoded); + let mut result = vec![]; + while cursor.position() < encoded.len().try_into().unwrap() { + result.push(read_receiver(&mut cursor)?); + } + assert_eq!(cursor.position(), encoded.len().try_into().unwrap()); + Self::try_from_receivers(result) + } + + fn try_from_receivers(receivers: Vec) -> Result { + let mut typecodes = HashSet::with_capacity(receivers.len()); + for receiver in &receivers { + let t = receiver.typecode(); + if typecodes.contains(&t) { + return Err(ParseError::DuplicateTypecode(t)); + } else if (t == Typecode::P2pkh && typecodes.contains(&Typecode::P2sh)) + || (t == Typecode::P2sh && typecodes.contains(&Typecode::P2pkh)) + { + return Err(ParseError::BothP2phkAndP2sh); + } else { + typecodes.insert(t); + } + } + + if typecodes.iter().all(|t| t.is_transparent()) { + Err(ParseError::OnlyTransparent) + } else { + // All checks pass! + Ok(Self::from_inner(receivers)) + } + } + + /// Returns the raw encoding of this Unified Address or viewing key. + fn to_bytes(&self, hrp: &str) -> Vec { + assert!(hrp.len() <= PADDING_LEN); + + let mut writer = std::io::Cursor::new(Vec::new()); + for receiver in &self.receivers() { + let addr = receiver.data(); + CompactSize::write( + &mut writer, + ::from(receiver.typecode()).try_into().unwrap(), + ) + .unwrap(); + CompactSize::write(&mut writer, addr.len()).unwrap(); + writer.write_all(addr).unwrap(); + } + + let mut padding = [0u8; PADDING_LEN]; + padding[0..hrp.len()].copy_from_slice(&hrp.as_bytes()); + writer.write_all(&padding).unwrap(); + + f4jumble::f4jumble(&writer.into_inner()).unwrap() + } + + /// Returns the receivers contained within this address, sorted in preference order. + fn receivers(&self) -> Vec { + let mut receivers = self.receivers_as_parsed().to_vec(); + // Unstable sorting is fine, because all receivers are guaranteed by construction + // to have distinct typecodes. + receivers.sort_unstable_by_key(|r| r.typecode()); + receivers + } + + /// Returns the receivers in the order they were parsed from the string encoding. + /// + /// This API is for advanced usage; in most cases you should use `Self::receivers`. + fn receivers_as_parsed(&self) -> &[Self::Receiver]; } diff --git a/components/zcash_address/src/kind/unified/address.rs b/components/zcash_address/src/kind/unified/address.rs index 1c2dd19fa..22b695273 100644 --- a/components/zcash_address/src/kind/unified/address.rs +++ b/components/zcash_address/src/kind/unified/address.rs @@ -2,29 +2,7 @@ use super::{private::SealedReceiver, ParseError, Typecode}; use crate::kind; use std::cmp; -use std::collections::HashSet; use std::convert::{TryFrom, TryInto}; -use std::io::Write; -use zcash_encoding::CompactSize; - -/// The HRP for a Bech32m-encoded mainnet Unified Address. -/// -/// Defined in [ZIP 316][zip-0316]. -/// -/// [zip-0316]: https://zips.z.cash/zip-0316 -pub(crate) const MAINNET: &str = "u"; - -/// The HRP for a Bech32m-encoded testnet Unified Address. -/// -/// Defined in [ZIP 316][zip-0316]. -/// -/// [zip-0316]: https://zips.z.cash/zip-0316 -pub(crate) const TESTNET: &str = "utest"; - -/// The HRP for a Bech32m-encoded regtest Unified Address. -pub(crate) const REGTEST: &str = "uregtest"; - -const PADDING_LEN: usize = 16; /// The set of known Receivers for Unified Addresses. #[derive(Clone, Debug, PartialEq, Eq, Hash)] @@ -97,139 +75,37 @@ impl SealedReceiver for Receiver { #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Address(pub(crate) Vec); -impl TryFrom<(&str, &[u8])> for Address { - type Error = ParseError; +impl super::private::SealedContainer for Address { + type Receiver = Receiver; - fn try_from((hrp, buf): (&str, &[u8])) -> Result { - fn read_receiver(mut cursor: &mut std::io::Cursor<&[u8]>) -> Result { - let typecode = CompactSize::read(&mut cursor) - .map(|v| u32::try_from(v).expect("CompactSize::read enforces MAX_SIZE limit")) - .map_err(|e| { - ParseError::InvalidEncoding(format!( - "Failed to deserialize CompactSize-encoded typecode {}", - e - )) - })?; - let length = CompactSize::read(&mut cursor).map_err(|e| { - ParseError::InvalidEncoding(format!( - "Failed to deserialize CompactSize-encoded length {}", - e - )) - })?; - let addr_end = cursor.position().checked_add(length).ok_or_else(|| { - ParseError::InvalidEncoding(format!( - "Length value {} caused an overflow error", - length - )) - })?; - let buf = cursor.get_ref(); - if (buf.len() as u64) < addr_end { - return Err(ParseError::InvalidEncoding(format!( - "Truncated: unable to read {} bytes of address data", - length - ))); - } - let result = Receiver::try_from(( - typecode, - &buf[cursor.position() as usize..addr_end as usize], - )); - cursor.set_position(addr_end); - result - } - - let encoded = f4jumble::f4jumble_inv(buf) - .ok_or_else(|| ParseError::InvalidEncoding("F4Jumble decoding failed".to_owned()))?; - - // Validate and strip trailing padding bytes. - if hrp.len() > 16 { - return Err(ParseError::InvalidEncoding( - "Invalid human-readable part".to_owned(), - )); - } - let mut expected_padding = [0; PADDING_LEN]; - expected_padding[0..hrp.len()].copy_from_slice(hrp.as_bytes()); - let encoded = match encoded.split_at(encoded.len() - PADDING_LEN) { - (encoded, tail) if tail == expected_padding => Ok(encoded), - _ => Err(ParseError::InvalidEncoding( - "Invalid padding bytes".to_owned(), - )), - }?; - - let mut cursor = std::io::Cursor::new(encoded); - let mut result = vec![]; - while cursor.position() < encoded.len().try_into().unwrap() { - result.push(read_receiver(&mut cursor)?); - } - assert_eq!(cursor.position(), encoded.len().try_into().unwrap()); - result.try_into() + fn from_inner(receivers: Vec) -> Self { + Self(receivers) } } -impl TryFrom> for Address { - type Error = ParseError; +impl super::Unified for Address { + /// The HRP for a Bech32m-encoded mainnet Unified Address. + /// + /// Defined in [ZIP 316][zip-0316]. + /// + /// [zip-0316]: https://zips.z.cash/zip-0316 + const MAINNET: &'static str = "u"; - fn try_from(receivers: Vec) -> Result { - let mut typecodes = HashSet::with_capacity(receivers.len()); - for receiver in &receivers { - let t = receiver.typecode(); - if typecodes.contains(&t) { - return Err(ParseError::DuplicateTypecode(t)); - } else if (t == Typecode::P2pkh && typecodes.contains(&Typecode::P2sh)) - || (t == Typecode::P2sh && typecodes.contains(&Typecode::P2pkh)) - { - return Err(ParseError::BothP2phkAndP2sh); - } else { - typecodes.insert(t); - } - } + /// The HRP for a Bech32m-encoded testnet Unified Address. + /// + /// Defined in [ZIP 316][zip-0316]. + /// + /// [zip-0316]: https://zips.z.cash/zip-0316 + const TESTNET: &'static str = "utest"; - if typecodes.iter().all(|t| t.is_transparent()) { - Err(ParseError::OnlyTransparent) - } else { - // All checks pass! - Ok(Address(receivers)) - } - } -} - -impl Address { - /// Returns the raw encoding of this Unified Address. - pub(crate) fn to_bytes(&self, hrp: &str) -> Vec { - assert!(hrp.len() <= PADDING_LEN); - - let mut writer = std::io::Cursor::new(Vec::new()); - for receiver in &self.0 { - let addr = receiver.data(); - CompactSize::write( - &mut writer, - ::from(receiver.typecode()).try_into().unwrap(), - ) - .unwrap(); - CompactSize::write(&mut writer, addr.len()).unwrap(); - writer.write_all(addr).unwrap(); - } - - let mut padding = [0u8; PADDING_LEN]; - padding[0..hrp.len()].copy_from_slice(&hrp.as_bytes()); - writer.write_all(&padding).unwrap(); - - f4jumble::f4jumble(&writer.into_inner()).unwrap() - } - - /// Returns the receivers contained within this address, sorted in preference order. - pub fn receivers(&self) -> Vec { - let mut receivers = self.0.clone(); - // Unstable sorting is fine, because all receivers are guaranteed by construction - // to have distinct typecodes. - receivers.sort_unstable_by_key(|r| r.typecode()); - receivers - } + /// The HRP for a Bech32m-encoded regtest Unified Address. + const REGTEST: &'static str = "uregtest"; /// Returns the receivers contained within this address, in the order they were /// parsed from the string encoding. /// /// This API is for advanced usage; in most cases you should use `Address::receivers`. - pub fn receivers_as_parsed(&self) -> &[Receiver] { + fn receivers_as_parsed(&self) -> &[Receiver] { &self.0 } } @@ -240,14 +116,14 @@ pub(crate) mod test_vectors; #[cfg(test)] mod tests { use assert_matches::assert_matches; - use std::convert::TryFrom; + use crate::kind::unified::Unified; use proptest::{ array::{uniform11, uniform20, uniform32}, prelude::*, }; - use super::{Address, ParseError, Receiver, Typecode, MAINNET, REGTEST, TESTNET}; + use super::{Address, ParseError, Receiver, Typecode}; prop_compose! { fn uniform43()(a in uniform11(0u8..), b in uniform32(0u8..)) -> [u8; 43] { @@ -286,11 +162,11 @@ mod tests { proptest! { #[test] fn ua_roundtrip( - hrp in prop_oneof![MAINNET, TESTNET, REGTEST], + hrp in prop_oneof![Address::MAINNET, Address::TESTNET, Address::REGTEST], ua in arb_unified_address(), ) { let bytes = ua.to_bytes(&hrp); - let decoded = Address::try_from((hrp.as_str(), &bytes[..])); + let decoded = Address::try_from_bytes(hrp.as_str(), &bytes[..]); prop_assert_eq!(decoded, Ok(ua)); } } @@ -308,7 +184,7 @@ mod tests { 0x7b, 0x28, 0x69, 0xc9, 0x84, ]; assert_eq!( - Address::try_from((MAINNET, &invalid_padding[..])), + Address::try_from_bytes(Address::MAINNET, &invalid_padding[..]), Err(ParseError::InvalidEncoding( "Invalid padding bytes".to_owned() )) @@ -323,7 +199,7 @@ mod tests { 0x4b, 0x31, 0xee, 0x5a, ]; assert_eq!( - Address::try_from((MAINNET, &truncated_padding[..])), + Address::try_from_bytes(Address::MAINNET, &truncated_padding[..]), Err(ParseError::InvalidEncoding( "Invalid padding bytes".to_owned() )) @@ -348,7 +224,7 @@ mod tests { 0xc6, 0x5e, 0x68, 0xa2, 0x78, 0x6c, 0x9e, ]; assert_matches!( - Address::try_from((MAINNET, &truncated_sapling_data[..])), + Address::try_from_bytes(Address::MAINNET, &truncated_sapling_data[..]), Err(ParseError::InvalidEncoding(_)) ); @@ -361,7 +237,7 @@ mod tests { 0xe6, 0x70, 0x36, 0x5b, 0x7b, 0x9e, ]; assert_matches!( - Address::try_from((MAINNET, &truncated_after_sapling_typecode[..])), + Address::try_from_bytes(Address::MAINNET, &truncated_after_sapling_typecode[..]), Err(ParseError::InvalidEncoding(_)) ); } @@ -370,9 +246,9 @@ mod tests { fn duplicate_typecode() { // Construct and serialize an invalid UA. let ua = Address(vec![Receiver::Sapling([1; 43]), Receiver::Sapling([2; 43])]); - let encoded = ua.to_bytes(MAINNET); + let encoded = ua.to_bytes(Address::MAINNET); assert_eq!( - Address::try_from((MAINNET, &encoded[..])), + Address::try_from_bytes(Address::MAINNET, &encoded[..]), Err(ParseError::DuplicateTypecode(Typecode::Sapling)) ); } @@ -381,9 +257,9 @@ mod tests { fn p2pkh_and_p2sh() { // Construct and serialize an invalid UA. let ua = Address(vec![Receiver::P2pkh([0; 20]), Receiver::P2sh([0; 20])]); - let encoded = ua.to_bytes(MAINNET); + let encoded = ua.to_bytes(Address::MAINNET); assert_eq!( - Address::try_from((MAINNET, &encoded[..])), + Address::try_from_bytes(Address::MAINNET, &encoded[..]), Err(ParseError::BothP2phkAndP2sh) ); } @@ -402,7 +278,7 @@ mod tests { // with only one of them we don't have sufficient data for F4Jumble (so we hit a // different error). assert_matches!( - Address::try_from((MAINNET, &encoded[..])), + Address::try_from_bytes(Address::MAINNET, &encoded[..]), Err(ParseError::InvalidEncoding(_)) ); } From c31db1b8393ec5c54d79d654f85295e6332fc4f9 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Mon, 29 Nov 2021 13:49:24 -0500 Subject: [PATCH 4/9] Move HRPs into from public Unified trait to SealedContainer trait. Co-authored-by: Jack Grigg --- components/zcash_address/src/encoding.rs | 2 +- components/zcash_address/src/kind/unified.rs | 10 +++++----- .../zcash_address/src/kind/unified/address.rs | 18 +++++++++--------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/components/zcash_address/src/encoding.rs b/components/zcash_address/src/encoding.rs index 326ab5069..21fe25df7 100644 --- a/components/zcash_address/src/encoding.rs +++ b/components/zcash_address/src/encoding.rs @@ -2,7 +2,7 @@ use std::{convert::TryInto, error::Error, fmt, str::FromStr}; use bech32::{self, FromBase32, ToBase32, Variant}; -use crate::kind::unified::Unified; +use crate::kind::unified::{private::SealedContainer, Unified}; use crate::{kind::*, AddressKind, Network, ZcashAddress}; /// An error while attempting to parse a string as a Zcash address. diff --git a/components/zcash_address/src/kind/unified.rs b/components/zcash_address/src/kind/unified.rs index 1f6ef4f3b..a5c748a82 100644 --- a/components/zcash_address/src/kind/unified.rs +++ b/components/zcash_address/src/kind/unified.rs @@ -123,7 +123,7 @@ impl fmt::Display for ParseError { impl Error for ParseError {} -mod private { +pub(crate) mod private { use super::{ParseError, Typecode}; use std::{cmp, convert::TryFrom}; @@ -136,6 +136,10 @@ mod private { } pub trait SealedContainer { + const MAINNET: &'static str; + const TESTNET: &'static str; + const REGTEST: &'static str; + type Receiver: SealedReceiver; fn from_inner(receivers: Vec) -> Self; @@ -146,10 +150,6 @@ use private::SealedReceiver; /// Trait providing common encoding logic for Unified containers. pub trait Unified: private::SealedContainer + std::marker::Sized { - const MAINNET: &'static str; - const TESTNET: &'static str; - const REGTEST: &'static str; - fn try_from_bytes(hrp: &str, buf: &[u8]) -> Result { fn read_receiver( mut cursor: &mut std::io::Cursor<&[u8]>, diff --git a/components/zcash_address/src/kind/unified/address.rs b/components/zcash_address/src/kind/unified/address.rs index 22b695273..32292695f 100644 --- a/components/zcash_address/src/kind/unified/address.rs +++ b/components/zcash_address/src/kind/unified/address.rs @@ -76,14 +76,6 @@ impl SealedReceiver for Receiver { pub struct Address(pub(crate) Vec); impl super::private::SealedContainer for Address { - type Receiver = Receiver; - - fn from_inner(receivers: Vec) -> Self { - Self(receivers) - } -} - -impl super::Unified for Address { /// The HRP for a Bech32m-encoded mainnet Unified Address. /// /// Defined in [ZIP 316][zip-0316]. @@ -101,6 +93,14 @@ impl super::Unified for Address { /// The HRP for a Bech32m-encoded regtest Unified Address. const REGTEST: &'static str = "uregtest"; + type Receiver = Receiver; + + fn from_inner(receivers: Vec) -> Self { + Self(receivers) + } +} + +impl super::Unified for Address { /// Returns the receivers contained within this address, in the order they were /// parsed from the string encoding. /// @@ -117,7 +117,7 @@ pub(crate) mod test_vectors; mod tests { use assert_matches::assert_matches; - use crate::kind::unified::Unified; + use crate::kind::unified::{private::SealedContainer, Unified}; use proptest::{ array::{uniform11, uniform20, uniform32}, prelude::*, From da1c6224f8ee46705d43185df30a91ef6e63e4c9 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Mon, 29 Nov 2021 14:19:41 -0500 Subject: [PATCH 5/9] kind::unified: Split Unified trait into FromReceivers, ToReceivers. This allows us to move to_bytes() into the private::SealedContainer trait without introducing a circular dependency. This also lets us move the Receivers type to a public trait. --- components/zcash_address/src/encoding.rs | 2 +- components/zcash_address/src/kind/unified.rs | 69 +++++++++++-------- .../zcash_address/src/kind/unified/address.rs | 13 ++-- 3 files changed, 45 insertions(+), 39 deletions(-) diff --git a/components/zcash_address/src/encoding.rs b/components/zcash_address/src/encoding.rs index 21fe25df7..8fed71497 100644 --- a/components/zcash_address/src/encoding.rs +++ b/components/zcash_address/src/encoding.rs @@ -2,7 +2,7 @@ use std::{convert::TryInto, error::Error, fmt, str::FromStr}; use bech32::{self, FromBase32, ToBase32, Variant}; -use crate::kind::unified::{private::SealedContainer, Unified}; +use crate::kind::unified::{private::SealedContainer, FromReceivers}; use crate::{kind::*, AddressKind, Network, ZcashAddress}; /// An error while attempting to parse a string as a Zcash address. diff --git a/components/zcash_address/src/kind/unified.rs b/components/zcash_address/src/kind/unified.rs index a5c748a82..5a8f863d7 100644 --- a/components/zcash_address/src/kind/unified.rs +++ b/components/zcash_address/src/kind/unified.rs @@ -3,7 +3,6 @@ use std::collections::HashSet; use std::convert::{TryFrom, TryInto}; use std::error::Error; use std::fmt; -use std::io::Write; use zcash_encoding::CompactSize; pub(crate) mod address; @@ -124,8 +123,13 @@ impl fmt::Display for ParseError { impl Error for ParseError {} pub(crate) mod private { - use super::{ParseError, Typecode}; - use std::{cmp, convert::TryFrom}; + use super::{ParseError, Typecode, PADDING_LEN}; + use std::{ + cmp, + convert::{TryFrom, TryInto}, + io::Write, + }; + use zcash_encoding::CompactSize; /// A raw address or viewing key. pub trait SealedReceiver: @@ -135,21 +139,43 @@ pub(crate) mod private { fn data(&self) -> &[u8]; } - pub trait SealedContainer { + /// A Unified Container containing addresses or viewing keys. + pub trait SealedContainer: super::ToReceivers { const MAINNET: &'static str; const TESTNET: &'static str; const REGTEST: &'static str; - type Receiver: SealedReceiver; - fn from_inner(receivers: Vec) -> Self; + + /// Returns the raw encoding of this Unified Address or viewing key. + fn to_bytes(&self, hrp: &str) -> Vec { + assert!(hrp.len() <= PADDING_LEN); + + let mut writer = std::io::Cursor::new(Vec::new()); + for receiver in &self.receivers() { + let addr = receiver.data(); + CompactSize::write( + &mut writer, + ::from(receiver.typecode()).try_into().unwrap(), + ) + .unwrap(); + CompactSize::write(&mut writer, addr.len()).unwrap(); + writer.write_all(addr).unwrap(); + } + + let mut padding = [0u8; PADDING_LEN]; + padding[0..hrp.len()].copy_from_slice(&hrp.as_bytes()); + writer.write_all(&padding).unwrap(); + + f4jumble::f4jumble(&writer.into_inner()).unwrap() + } } } use private::SealedReceiver; /// Trait providing common encoding logic for Unified containers. -pub trait Unified: private::SealedContainer + std::marker::Sized { +pub trait FromReceivers: private::SealedContainer + std::marker::Sized { fn try_from_bytes(hrp: &str, buf: &[u8]) -> Result { fn read_receiver( mut cursor: &mut std::io::Cursor<&[u8]>, @@ -238,31 +264,14 @@ pub trait Unified: private::SealedContainer + std::marker::Sized { Ok(Self::from_inner(receivers)) } } +} - /// Returns the raw encoding of this Unified Address or viewing key. - fn to_bytes(&self, hrp: &str) -> Vec { - assert!(hrp.len() <= PADDING_LEN); +/// Trait providing common decoding logic for Unified containers. +pub trait ToReceivers { + /// The type of receiver in this unified container. + type Receiver: SealedReceiver; - let mut writer = std::io::Cursor::new(Vec::new()); - for receiver in &self.receivers() { - let addr = receiver.data(); - CompactSize::write( - &mut writer, - ::from(receiver.typecode()).try_into().unwrap(), - ) - .unwrap(); - CompactSize::write(&mut writer, addr.len()).unwrap(); - writer.write_all(addr).unwrap(); - } - - let mut padding = [0u8; PADDING_LEN]; - padding[0..hrp.len()].copy_from_slice(&hrp.as_bytes()); - writer.write_all(&padding).unwrap(); - - f4jumble::f4jumble(&writer.into_inner()).unwrap() - } - - /// Returns the receivers contained within this address, sorted in preference order. + /// Returns the receivers contained within this container, sorted in preference order. fn receivers(&self) -> Vec { let mut receivers = self.receivers_as_parsed().to_vec(); // Unstable sorting is fine, because all receivers are guaranteed by construction diff --git a/components/zcash_address/src/kind/unified/address.rs b/components/zcash_address/src/kind/unified/address.rs index 32292695f..5777a149e 100644 --- a/components/zcash_address/src/kind/unified/address.rs +++ b/components/zcash_address/src/kind/unified/address.rs @@ -93,18 +93,15 @@ impl super::private::SealedContainer for Address { /// The HRP for a Bech32m-encoded regtest Unified Address. const REGTEST: &'static str = "uregtest"; - type Receiver = Receiver; - fn from_inner(receivers: Vec) -> Self { Self(receivers) } } -impl super::Unified for Address { - /// Returns the receivers contained within this address, in the order they were - /// parsed from the string encoding. - /// - /// This API is for advanced usage; in most cases you should use `Address::receivers`. +impl super::FromReceivers for Address {} +impl super::ToReceivers for Address { + type Receiver = Receiver; + fn receivers_as_parsed(&self) -> &[Receiver] { &self.0 } @@ -117,7 +114,7 @@ pub(crate) mod test_vectors; mod tests { use assert_matches::assert_matches; - use crate::kind::unified::{private::SealedContainer, Unified}; + use crate::kind::unified::{private::SealedContainer, FromReceivers, ToReceivers}; use proptest::{ array::{uniform11, uniform20, uniform32}, prelude::*, From a6e6f8ace2ca4679a1ad92bdee4bbcbd76c0227d Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Tue, 30 Nov 2021 09:05:24 -0500 Subject: [PATCH 6/9] kind::unified: Make address::Address available outside the crate. --- components/zcash_address/src/kind/unified.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/zcash_address/src/kind/unified.rs b/components/zcash_address/src/kind/unified.rs index 5a8f863d7..b3b67d38f 100644 --- a/components/zcash_address/src/kind/unified.rs +++ b/components/zcash_address/src/kind/unified.rs @@ -7,7 +7,7 @@ use zcash_encoding::CompactSize; pub(crate) mod address; -pub(crate) use address::Address; +pub use address::Address; const PADDING_LEN: usize = 16; From 75591047f7ab906800a99a77063c322711b25892 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 2 Dec 2021 09:04:46 -0700 Subject: [PATCH 7/9] Separates raw encoding from jumbling of unified container contents. --- components/zcash_address/src/kind/unified.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/components/zcash_address/src/kind/unified.rs b/components/zcash_address/src/kind/unified.rs index b3b67d38f..6f3db40a1 100644 --- a/components/zcash_address/src/kind/unified.rs +++ b/components/zcash_address/src/kind/unified.rs @@ -147,11 +147,8 @@ pub(crate) mod private { fn from_inner(receivers: Vec) -> Self; - /// Returns the raw encoding of this Unified Address or viewing key. - fn to_bytes(&self, hrp: &str) -> Vec { - assert!(hrp.len() <= PADDING_LEN); - - let mut writer = std::io::Cursor::new(Vec::new()); + /// Write the raw encoding of this container's receivers to a stream + fn write_raw_encoding(&self, mut writer: W) { for receiver in &self.receivers() { let addr = receiver.data(); CompactSize::write( @@ -162,9 +159,17 @@ pub(crate) mod private { CompactSize::write(&mut writer, addr.len()).unwrap(); writer.write_all(addr).unwrap(); } + } + + /// Returns the jumbled padded raw encoding of this Unified Address or viewing key. + fn to_bytes(&self, hrp: &str) -> Vec { + assert!(hrp.len() <= PADDING_LEN); + + let mut writer = std::io::Cursor::new(Vec::new()); + self.write_raw_encoding(&mut writer); let mut padding = [0u8; PADDING_LEN]; - padding[0..hrp.len()].copy_from_slice(&hrp.as_bytes()); + padding[0..hrp.len()].copy_from_slice(hrp.as_bytes()); writer.write_all(&padding).unwrap(); f4jumble::f4jumble(&writer.into_inner()).unwrap() From 82be04dfafecb15faa03a1d6e6699fcc53d800c6 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 2 Dec 2021 09:05:24 -0700 Subject: [PATCH 8/9] Generalize the naming of unified containers and items. --- components/zcash_address/src/encoding.rs | 2 +- components/zcash_address/src/kind/unified.rs | 72 ++++++++++--------- .../zcash_address/src/kind/unified/address.rs | 18 ++--- 3 files changed, 47 insertions(+), 45 deletions(-) diff --git a/components/zcash_address/src/encoding.rs b/components/zcash_address/src/encoding.rs index 8fed71497..4cb9c6ca8 100644 --- a/components/zcash_address/src/encoding.rs +++ b/components/zcash_address/src/encoding.rs @@ -2,7 +2,7 @@ use std::{convert::TryInto, error::Error, fmt, str::FromStr}; use bech32::{self, FromBase32, ToBase32, Variant}; -use crate::kind::unified::{private::SealedContainer, FromReceivers}; +use crate::kind::unified::{private::SealedContainer, Encoding}; use crate::{kind::*, AddressKind, Network, ZcashAddress}; /// An error while attempting to parse a string as a Zcash address. diff --git a/components/zcash_address/src/kind/unified.rs b/components/zcash_address/src/kind/unified.rs index 6f3db40a1..273e91811 100644 --- a/components/zcash_address/src/kind/unified.rs +++ b/components/zcash_address/src/kind/unified.rs @@ -29,10 +29,10 @@ impl Ord for Typecode { | (Self::P2sh, Self::P2sh) | (Self::P2pkh, Self::P2pkh) => cmp::Ordering::Equal, - // We don't know for certain the preference order of unknown receivers, but it + // We don't know for certain the preference order of unknown items, but it // is likely that the higher typecode has higher preference. The exact order - // doesn't really matter, as unknown receivers have lower preference than - // known receivers. + // doesn't really matter, as unknown items have lower preference than + // known items. (Self::Unknown(a), Self::Unknown(b)) => b.cmp(a), // For the remaining cases, we rely on `match` always choosing the first arm @@ -96,7 +96,7 @@ impl Typecode { /// An error while attempting to parse a string as a Zcash address. #[derive(Debug, PartialEq)] pub enum ParseError { - /// The unified address contains both P2PKH and P2SH receivers. + /// The unified address contains both P2PKH and P2SH items. BothP2phkAndP2sh, /// The unified address contains a duplicated typecode. DuplicateTypecode(Typecode), @@ -104,18 +104,18 @@ pub enum ParseError { InvalidTypecodeValue(u64), /// The string is an invalid encoding. InvalidEncoding(String), - /// The unified address only contains transparent receivers. + /// The unified address only contains transparent items. OnlyTransparent, } impl fmt::Display for ParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - ParseError::BothP2phkAndP2sh => write!(f, "UA contains both P2PKH and P2SH receivers"), + ParseError::BothP2phkAndP2sh => write!(f, "UA contains both P2PKH and P2SH items"), ParseError::DuplicateTypecode(c) => write!(f, "Duplicate typecode {}", u32::from(*c)), ParseError::InvalidTypecodeValue(v) => write!(f, "Typecode value out of range {}", v), ParseError::InvalidEncoding(msg) => write!(f, "Invalid encoding: {}", msg), - ParseError::OnlyTransparent => write!(f, "UA only contains transparent receivers"), + ParseError::OnlyTransparent => write!(f, "UA only contains transparent items"), } } } @@ -132,7 +132,7 @@ pub(crate) mod private { use zcash_encoding::CompactSize; /// A raw address or viewing key. - pub trait SealedReceiver: + pub trait SealedItem: for<'a> TryFrom<(u32, &'a [u8]), Error = ParseError> + cmp::Ord + cmp::PartialOrd + Clone { fn typecode(&self) -> Typecode; @@ -140,20 +140,22 @@ pub(crate) mod private { } /// A Unified Container containing addresses or viewing keys. - pub trait SealedContainer: super::ToReceivers { + pub trait SealedContainer: super::Container { const MAINNET: &'static str; const TESTNET: &'static str; const REGTEST: &'static str; - fn from_inner(receivers: Vec) -> Self; + /// Implementations of this method should act as unchecked constructors + /// of the container type; the caller is guaranteed to check the + /// general invariants that apply to all unified containers. + fn from_inner(items: Vec) -> Self; - /// Write the raw encoding of this container's receivers to a stream fn write_raw_encoding(&self, mut writer: W) { - for receiver in &self.receivers() { - let addr = receiver.data(); + for item in &self.items() { + let addr = item.data(); CompactSize::write( &mut writer, - ::from(receiver.typecode()).try_into().unwrap(), + ::from(item.typecode()).try_into().unwrap(), ) .unwrap(); CompactSize::write(&mut writer, addr.len()).unwrap(); @@ -177,12 +179,12 @@ pub(crate) mod private { } } -use private::SealedReceiver; +use private::SealedItem; /// Trait providing common encoding logic for Unified containers. -pub trait FromReceivers: private::SealedContainer + std::marker::Sized { +pub trait Encoding: private::SealedContainer + std::marker::Sized { fn try_from_bytes(hrp: &str, buf: &[u8]) -> Result { - fn read_receiver( + fn read_receiver( mut cursor: &mut std::io::Cursor<&[u8]>, ) -> Result { let typecode = CompactSize::read(&mut cursor) @@ -244,13 +246,13 @@ pub trait FromReceivers: private::SealedContainer + std::marker::Sized { result.push(read_receiver(&mut cursor)?); } assert_eq!(cursor.position(), encoded.len().try_into().unwrap()); - Self::try_from_receivers(result) + Self::try_from_items(result) } - fn try_from_receivers(receivers: Vec) -> Result { - let mut typecodes = HashSet::with_capacity(receivers.len()); - for receiver in &receivers { - let t = receiver.typecode(); + fn try_from_items(items: Vec) -> Result { + let mut typecodes = HashSet::with_capacity(items.len()); + for item in &items { + let t = item.typecode(); if typecodes.contains(&t) { return Err(ParseError::DuplicateTypecode(t)); } else if (t == Typecode::P2pkh && typecodes.contains(&Typecode::P2sh)) @@ -266,27 +268,27 @@ pub trait FromReceivers: private::SealedContainer + std::marker::Sized { Err(ParseError::OnlyTransparent) } else { // All checks pass! - Ok(Self::from_inner(receivers)) + Ok(Self::from_inner(items)) } } } /// Trait providing common decoding logic for Unified containers. -pub trait ToReceivers { - /// The type of receiver in this unified container. - type Receiver: SealedReceiver; +pub trait Container { + /// The type of item in this unified container. + type Item: SealedItem; - /// Returns the receivers contained within this container, sorted in preference order. - fn receivers(&self) -> Vec { - let mut receivers = self.receivers_as_parsed().to_vec(); - // Unstable sorting is fine, because all receivers are guaranteed by construction + /// Returns the items contained within this container, sorted in preference order. + fn items(&self) -> Vec { + let mut items = self.items_as_parsed().to_vec(); + // Unstable sorting is fine, because all items are guaranteed by construction // to have distinct typecodes. - receivers.sort_unstable_by_key(|r| r.typecode()); - receivers + items.sort_unstable_by_key(|r| r.typecode()); + items } - /// Returns the receivers in the order they were parsed from the string encoding. + /// Returns the items in the order they were parsed from the string encoding. /// - /// This API is for advanced usage; in most cases you should use `Self::receivers`. - fn receivers_as_parsed(&self) -> &[Self::Receiver]; + /// This API is for advanced usage; in most cases you should use `Self::items`. + fn items_as_parsed(&self) -> &[Self::Item]; } diff --git a/components/zcash_address/src/kind/unified/address.rs b/components/zcash_address/src/kind/unified/address.rs index 5777a149e..a125d7c6c 100644 --- a/components/zcash_address/src/kind/unified/address.rs +++ b/components/zcash_address/src/kind/unified/address.rs @@ -1,4 +1,4 @@ -use super::{private::SealedReceiver, ParseError, Typecode}; +use super::{private::SealedItem, ParseError, Typecode}; use crate::kind; use std::cmp; @@ -49,7 +49,7 @@ impl TryFrom<(u32, &[u8])> for Receiver { } } -impl SealedReceiver for Receiver { +impl SealedItem for Receiver { fn typecode(&self) -> Typecode { match self { Receiver::P2pkh(_) => Typecode::P2pkh, @@ -93,16 +93,16 @@ impl super::private::SealedContainer for Address { /// The HRP for a Bech32m-encoded regtest Unified Address. const REGTEST: &'static str = "uregtest"; - fn from_inner(receivers: Vec) -> Self { + fn from_inner(receivers: Vec) -> Self { Self(receivers) } } -impl super::FromReceivers for Address {} -impl super::ToReceivers for Address { - type Receiver = Receiver; +impl super::Encoding for Address {} +impl super::Container for Address { + type Item = Receiver; - fn receivers_as_parsed(&self) -> &[Receiver] { + fn items_as_parsed(&self) -> &[Receiver] { &self.0 } } @@ -114,7 +114,7 @@ pub(crate) mod test_vectors; mod tests { use assert_matches::assert_matches; - use crate::kind::unified::{private::SealedContainer, FromReceivers, ToReceivers}; + use crate::kind::unified::{private::SealedContainer, Container, Encoding}; use proptest::{ array::{uniform11, uniform20, uniform32}, prelude::*, @@ -295,7 +295,7 @@ mod tests { // `Address::receivers` sorts the receivers in priority order. assert_eq!( - ua.receivers(), + ua.items(), vec![ Receiver::Orchard([0; 43]), Receiver::Sapling([0; 43]), From 566c973ea7994fa2ae13c8e8d5e2d9ba2e93bfbf Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 2 Dec 2021 11:35:54 -0700 Subject: [PATCH 9/9] Fix comment in components/zcash_address/src/kind/unified.rs Co-authored-by: str4d --- components/zcash_address/src/kind/unified.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/zcash_address/src/kind/unified.rs b/components/zcash_address/src/kind/unified.rs index 273e91811..46e7b8bcf 100644 --- a/components/zcash_address/src/kind/unified.rs +++ b/components/zcash_address/src/kind/unified.rs @@ -273,7 +273,7 @@ pub trait Encoding: private::SealedContainer + std::marker::Sized { } } -/// Trait providing common decoding logic for Unified containers. +/// Trait for for Unified containers, that exposes the items within them. pub trait Container { /// The type of item in this unified container. type Item: SealedItem;