Adds high-level encoding and decoding functions for unified types.
This renames the `FromReceivers` trait to `UnifiedEncoding` and makes its public methods (as well as the private to_bytes method) function in terms of network values rather than explicit HRP arguments. It also adds high-level encoding and decoding methods that handle conversion to and from the Bech32m-encoded transport format for all supported types, and then delegates to these from locations that previously used lower-level utilities.
This commit is contained in:
parent
3b70731cc4
commit
7e629db29f
|
@ -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, Encoding};
|
||||
use crate::kind::unified::Encoding;
|
||||
use crate::{kind::*, AddressKind, Network, ZcashAddress};
|
||||
|
||||
/// An error while attempting to parse a string as a Zcash address.
|
||||
|
@ -45,28 +45,19 @@ impl FromStr for ZcashAddress {
|
|||
// Remove leading and trailing whitespace, to handle copy-paste errors.
|
||||
let s = s.trim();
|
||||
|
||||
// Most Zcash addresses use Bech32 or Bech32m, so try those first.
|
||||
match bech32::decode(s) {
|
||||
Ok((hrp, data, Variant::Bech32m)) => {
|
||||
// If we reached this point, the encoding is supposed to be valid Bech32m.
|
||||
let data =
|
||||
Vec::<u8>::from_base32(&data).map_err(|_| ParseError::InvalidEncoding)?;
|
||||
|
||||
let net = match hrp.as_str() {
|
||||
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 unified::Address::try_from_bytes(hrp.as_str(), &data[..])
|
||||
.map(AddressKind::Unified)
|
||||
.map_err(|_| ParseError::InvalidEncoding)
|
||||
.map(|kind| ZcashAddress { net, kind });
|
||||
// Try decoding as a unified address
|
||||
match unified::Address::decode(s) {
|
||||
Ok((net, data)) => {
|
||||
return Ok(ZcashAddress {
|
||||
net,
|
||||
kind: AddressKind::Unified(data),
|
||||
})
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
|
||||
// Try decoding as a Sapling address (Bech32)
|
||||
match bech32::decode(s) {
|
||||
Ok((hrp, data, Variant::Bech32)) => {
|
||||
// If we reached this point, the encoding is supposed to be valid Bech32.
|
||||
let data =
|
||||
|
@ -88,7 +79,7 @@ impl FromStr for ZcashAddress {
|
|||
.map_err(|_| ParseError::InvalidEncoding)
|
||||
.map(|kind| ZcashAddress { net, kind });
|
||||
}
|
||||
Err(_) => (),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// The rest use Base58Check.
|
||||
|
@ -117,10 +108,6 @@ impl FromStr for ZcashAddress {
|
|||
}
|
||||
}
|
||||
|
||||
fn encode_bech32m(hrp: &str, data: &[u8]) -> String {
|
||||
bech32::encode(hrp, data.to_base32(), Variant::Bech32m).expect("hrp is invalid")
|
||||
}
|
||||
|
||||
fn encode_bech32(hrp: &str, data: &[u8]) -> String {
|
||||
bech32::encode(hrp, data.to_base32(), Variant::Bech32).expect("hrp is invalid")
|
||||
}
|
||||
|
@ -150,14 +137,7 @@ impl fmt::Display for ZcashAddress {
|
|||
},
|
||||
data,
|
||||
),
|
||||
AddressKind::Unified(data) => {
|
||||
let hrp = match self.net {
|
||||
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))
|
||||
}
|
||||
AddressKind::Unified(addr) => addr.encode(&self.net),
|
||||
AddressKind::P2pkh(data) => encode_b58(
|
||||
match self.net {
|
||||
Network::Main => p2pkh::MAINNET,
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
use bech32::{self, FromBase32, ToBase32, Variant};
|
||||
use std::cmp;
|
||||
use std::collections::HashSet;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::convert::TryFrom;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use zcash_encoding::CompactSize;
|
||||
|
||||
use crate::Network;
|
||||
|
||||
pub(crate) mod address;
|
||||
pub(crate) mod fvk;
|
||||
pub(crate) mod ivk;
|
||||
|
||||
pub use address::Address;
|
||||
pub use ivk::{Ivk, Uivk};
|
||||
pub use address::{Address, Receiver};
|
||||
pub use fvk::{Fvk, Ufvk};
|
||||
pub use ivk::{Ivk, Uivk};
|
||||
|
||||
const PADDING_LEN: usize = 16;
|
||||
|
||||
|
@ -128,13 +130,13 @@ impl Error for ParseError {}
|
|||
|
||||
pub(crate) mod private {
|
||||
use super::{ParseError, Typecode, PADDING_LEN};
|
||||
use crate::Network;
|
||||
use std::{
|
||||
cmp,
|
||||
convert::{TryFrom, TryInto},
|
||||
io::Write,
|
||||
};
|
||||
use zcash_encoding::CompactSize;
|
||||
use crate::Network;
|
||||
|
||||
/// A raw address or viewing key.
|
||||
pub trait SealedItem:
|
||||
|
@ -163,6 +165,18 @@ pub(crate) mod private {
|
|||
}
|
||||
}
|
||||
|
||||
fn hrp_network(hrp: &str) -> Option<Network> {
|
||||
if hrp == Self::MAINNET {
|
||||
Some(Network::Main)
|
||||
} else if hrp == Self::TESTNET {
|
||||
Some(Network::Test)
|
||||
} else if hrp == Self::REGTEST {
|
||||
Some(Network::Regtest)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn write_raw_encoding<W: Write>(&self, mut writer: W) {
|
||||
for item in &self.items() {
|
||||
let addr = item.data();
|
||||
|
@ -189,79 +203,82 @@ pub(crate) mod private {
|
|||
|
||||
f4jumble::f4jumble(&writer.into_inner()).unwrap()
|
||||
}
|
||||
|
||||
/// Parse the items of the unified container.
|
||||
fn parse_items(hrp: &str, buf: &[u8]) -> Result<Vec<Self::Item>, ParseError> {
|
||||
fn read_receiver<R: SealedItem>(
|
||||
mut cursor: &mut std::io::Cursor<&[u8]>,
|
||||
) -> Result<R, ParseError> {
|
||||
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());
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use private::SealedItem;
|
||||
|
||||
/// Trait providing common encoding logic for Unified containers.
|
||||
/// Trait providing common encoding and decoding logic for Unified containers.
|
||||
pub trait Encoding: private::SealedContainer + std::marker::Sized {
|
||||
fn try_from_bytes(hrp: &str, buf: &[u8]) -> Result<Self, ParseError> {
|
||||
fn read_receiver<R: SealedItem>(
|
||||
mut cursor: &mut std::io::Cursor<&[u8]>,
|
||||
) -> Result<R, ParseError> {
|
||||
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_items(result)
|
||||
}
|
||||
|
||||
fn try_from_items(items: Vec<Self::Item>) -> Result<Self, ParseError> {
|
||||
let mut typecodes = HashSet::with_capacity(items.len());
|
||||
for item in &items {
|
||||
|
@ -284,6 +301,35 @@ pub trait Encoding: private::SealedContainer + std::marker::Sized {
|
|||
Ok(Self::from_inner(items))
|
||||
}
|
||||
}
|
||||
|
||||
fn decode(s: &str) -> Result<(Network, Self), ParseError> {
|
||||
match bech32::decode(s) {
|
||||
Ok((hrp, data, Variant::Bech32m)) => {
|
||||
let hrp = hrp.as_str();
|
||||
// validate that the HRP corresponds to a known network.
|
||||
let net = Self::hrp_network(hrp).ok_or_else(|| {
|
||||
ParseError::InvalidEncoding(format!(
|
||||
"Unrecognized Bech32m human-readable prefix {}",
|
||||
hrp
|
||||
))
|
||||
})?;
|
||||
|
||||
let data = Vec::<u8>::from_base32(&data)
|
||||
.map_err(|e| ParseError::InvalidEncoding(e.to_string()))?;
|
||||
|
||||
Self::parse_items(hrp, &data[..])
|
||||
.and_then(|xs| Self::try_from_items(xs))
|
||||
.map(|value| (net, value))
|
||||
}
|
||||
_ => Err(ParseError::InvalidEncoding("Expected bech32m".to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
fn encode(&self, network: &Network) -> String {
|
||||
let hrp = Self::network_hrp(network);
|
||||
bech32::encode(hrp, self.to_bytes(hrp).to_base32(), Variant::Bech32m)
|
||||
.expect("hrp is invalid")
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for for Unified containers, that exposes the items within them.
|
||||
|
|
|
@ -114,10 +114,15 @@ pub(crate) mod test_vectors;
|
|||
mod tests {
|
||||
use assert_matches::assert_matches;
|
||||
|
||||
use crate::kind::unified::{private::SealedContainer, Container, Encoding};
|
||||
use crate::{
|
||||
kind::unified::{private::SealedContainer, Container, Encoding},
|
||||
Network,
|
||||
};
|
||||
|
||||
use proptest::{
|
||||
array::{uniform11, uniform20, uniform32},
|
||||
prelude::*,
|
||||
sample::select,
|
||||
};
|
||||
|
||||
use super::{Address, ParseError, Receiver, Typecode};
|
||||
|
@ -163,12 +168,12 @@ mod tests {
|
|||
proptest! {
|
||||
#[test]
|
||||
fn ua_roundtrip(
|
||||
hrp in prop_oneof![Address::MAINNET, Address::TESTNET, Address::REGTEST],
|
||||
network in select(vec![Network::Main, Network::Test, Network::Regtest]),
|
||||
ua in arb_unified_address(),
|
||||
) {
|
||||
let bytes = ua.to_bytes(&hrp);
|
||||
let decoded = Address::try_from_bytes(hrp.as_str(), &bytes[..]);
|
||||
prop_assert_eq!(decoded, Ok(ua));
|
||||
let encoded = ua.encode(&network);
|
||||
let decoded = Address::decode(&encoded);
|
||||
prop_assert_eq!(decoded, Ok((network, ua)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -185,7 +190,7 @@ mod tests {
|
|||
0x7b, 0x28, 0x69, 0xc9, 0x84,
|
||||
];
|
||||
assert_eq!(
|
||||
Address::try_from_bytes(Address::MAINNET, &invalid_padding[..]),
|
||||
Address::parse_items(&Address::MAINNET, &invalid_padding[..]),
|
||||
Err(ParseError::InvalidEncoding(
|
||||
"Invalid padding bytes".to_owned()
|
||||
))
|
||||
|
@ -200,7 +205,7 @@ mod tests {
|
|||
0x4b, 0x31, 0xee, 0x5a,
|
||||
];
|
||||
assert_eq!(
|
||||
Address::try_from_bytes(Address::MAINNET, &truncated_padding[..]),
|
||||
Address::parse_items(&Address::MAINNET, &truncated_padding[..]),
|
||||
Err(ParseError::InvalidEncoding(
|
||||
"Invalid padding bytes".to_owned()
|
||||
))
|
||||
|
@ -225,7 +230,7 @@ mod tests {
|
|||
0xc6, 0x5e, 0x68, 0xa2, 0x78, 0x6c, 0x9e,
|
||||
];
|
||||
assert_matches!(
|
||||
Address::try_from_bytes(Address::MAINNET, &truncated_sapling_data[..]),
|
||||
Address::parse_items(&Address::MAINNET, &truncated_sapling_data[..]),
|
||||
Err(ParseError::InvalidEncoding(_))
|
||||
);
|
||||
|
||||
|
@ -238,29 +243,32 @@ mod tests {
|
|||
0xe6, 0x70, 0x36, 0x5b, 0x7b, 0x9e,
|
||||
];
|
||||
assert_matches!(
|
||||
Address::try_from_bytes(Address::MAINNET, &truncated_after_sapling_typecode[..]),
|
||||
Address::parse_items(&Address::MAINNET, &truncated_after_sapling_typecode[..]),
|
||||
Err(ParseError::InvalidEncoding(_))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplicate_typecode() {
|
||||
// Construct and serialize an invalid UA.
|
||||
// Construct and serialize an invalid UA. This must be done using private
|
||||
// methods, as the public API does not permit construction of such invalid values.
|
||||
let ua = Address(vec![Receiver::Sapling([1; 43]), Receiver::Sapling([2; 43])]);
|
||||
let encoded = ua.to_bytes(Address::MAINNET);
|
||||
let encoded = ua.to_bytes(&Address::MAINNET);
|
||||
assert_eq!(
|
||||
Address::try_from_bytes(Address::MAINNET, &encoded[..]),
|
||||
Address::parse_items(&Address::MAINNET, &encoded[..]).and_then(Address::try_from_items),
|
||||
Err(ParseError::DuplicateTypecode(Typecode::Sapling))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn p2pkh_and_p2sh() {
|
||||
// Construct and serialize an invalid UA.
|
||||
// Construct and serialize an invalid UA. This must be done using private
|
||||
// methods, as the public API does not permit construction of such invalid values.
|
||||
let ua = Address(vec![Receiver::P2pkh([0; 20]), Receiver::P2sh([0; 20])]);
|
||||
let encoded = ua.to_bytes(Address::MAINNET);
|
||||
let encoded = ua.to_bytes(&Address::MAINNET);
|
||||
// ensure that decoding catches the error
|
||||
assert_eq!(
|
||||
Address::try_from_bytes(Address::MAINNET, &encoded[..]),
|
||||
Address::parse_items(&Address::MAINNET, &encoded[..]).and_then(Address::try_from_items),
|
||||
Err(ParseError::BothP2phkAndP2sh)
|
||||
);
|
||||
}
|
||||
|
@ -279,7 +287,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_bytes(Address::MAINNET, &encoded[..]),
|
||||
Address::parse_items(&Address::MAINNET, &encoded[..]),
|
||||
Err(ParseError::InvalidEncoding(_))
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::convert::{TryFrom, TryInto};
|
|||
|
||||
use super::{
|
||||
private::{SealedContainer, SealedItem},
|
||||
Encoding, ParseError, Container, Typecode,
|
||||
Container, Encoding, ParseError, Typecode,
|
||||
};
|
||||
|
||||
/// The set of known FVKs for Unified FVKs.
|
||||
|
@ -105,6 +105,7 @@ impl Container for Ufvk {
|
|||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Encoding for Ufvk {}
|
||||
|
||||
impl SealedContainer for Ufvk {
|
||||
|
@ -134,10 +135,13 @@ impl SealedContainer for Ufvk {
|
|||
mod tests {
|
||||
use assert_matches::assert_matches;
|
||||
|
||||
use proptest::{array::uniform32, prelude::*};
|
||||
use proptest::{array::uniform32, prelude::*, sample::select};
|
||||
|
||||
use super::{Fvk, ParseError, Typecode, Ufvk};
|
||||
use crate::kind::unified::{private::SealedContainer, Encoding, Container};
|
||||
use crate::{
|
||||
kind::unified::{private::SealedContainer, Container, Encoding},
|
||||
Network,
|
||||
};
|
||||
|
||||
prop_compose! {
|
||||
fn uniform96()(a in uniform32(0u8..), b in uniform32(0u8..), c in uniform32(0u8..)) -> [u8; 96] {
|
||||
|
@ -185,12 +189,12 @@ mod tests {
|
|||
proptest! {
|
||||
#[test]
|
||||
fn ufvk_roundtrip(
|
||||
hrp in prop_oneof![Ufvk::MAINNET, Ufvk::TESTNET, Ufvk::REGTEST],
|
||||
network in select(vec![Network::Main, Network::Test, Network::Regtest]),
|
||||
ufvk in arb_unified_fvk(),
|
||||
) {
|
||||
let bytes = ufvk.to_bytes(&hrp);
|
||||
let decoded = Ufvk::try_from_bytes(hrp.as_str(), &bytes[..]);
|
||||
prop_assert_eq!(decoded, Ok(ufvk));
|
||||
let encoded = ufvk.encode(&network);
|
||||
let decoded = Ufvk::decode(&encoded);
|
||||
prop_assert_eq!(decoded, Ok((network, ufvk)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -210,7 +214,7 @@ mod tests {
|
|||
0xdf, 0x63, 0xe7, 0xef, 0x65, 0x6b, 0x18, 0x23, 0xf7, 0x3e, 0x35, 0x7c, 0xf3, 0xc4,
|
||||
];
|
||||
assert_eq!(
|
||||
Ufvk::try_from_bytes(Ufvk::MAINNET, &invalid_padding[..]),
|
||||
Ufvk::parse_items(&Ufvk::MAINNET, &invalid_padding[..]),
|
||||
Err(ParseError::InvalidEncoding(
|
||||
"Invalid padding bytes".to_owned()
|
||||
))
|
||||
|
@ -228,7 +232,7 @@ mod tests {
|
|||
0x43, 0x8e, 0xc0, 0x3e, 0x9f, 0xf4, 0xf1, 0x80, 0x32, 0xcf, 0x2f, 0x7e, 0x7f, 0x91,
|
||||
];
|
||||
assert_eq!(
|
||||
Ufvk::try_from_bytes(Ufvk::MAINNET, &truncated_padding[..]),
|
||||
Ufvk::parse_items(&Ufvk::MAINNET, &truncated_padding[..]),
|
||||
Err(ParseError::InvalidEncoding(
|
||||
"Invalid padding bytes".to_owned()
|
||||
))
|
||||
|
@ -260,7 +264,7 @@ mod tests {
|
|||
0x8c, 0x7a, 0xbf, 0x7b, 0x9a, 0xdd, 0xee, 0x18, 0x2c, 0x2d, 0xc2, 0xfc,
|
||||
];
|
||||
assert_matches!(
|
||||
Ufvk::try_from_bytes(Ufvk::MAINNET, &truncated_sapling_data[..]),
|
||||
Ufvk::parse_items(&Ufvk::MAINNET, &truncated_sapling_data[..]),
|
||||
Err(ParseError::InvalidEncoding(_))
|
||||
);
|
||||
|
||||
|
@ -275,25 +279,26 @@ mod tests {
|
|||
0x54, 0xd1, 0x9e, 0xec, 0x8b, 0xef, 0x35, 0xb8, 0x44, 0xdd, 0xab, 0x9a, 0x8d,
|
||||
];
|
||||
assert_matches!(
|
||||
Ufvk::try_from_bytes(Ufvk::MAINNET, &truncated_after_sapling_typecode[..]),
|
||||
Ufvk::parse_items(&Ufvk::MAINNET, &truncated_after_sapling_typecode[..]),
|
||||
Err(ParseError::InvalidEncoding(_))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplicate_typecode() {
|
||||
// Construct and serialize an invalid UFVK.
|
||||
// Construct and serialize an invalid Ufvk. This must be done using private
|
||||
// methods, as the public API does not permit construction of such invalid values.
|
||||
let ufvk = Ufvk(vec![Fvk::Sapling([1; 96]), Fvk::Sapling([2; 96])]);
|
||||
let encoded = ufvk.to_bytes(Ufvk::MAINNET);
|
||||
let encoded = ufvk.to_bytes(&Ufvk::MAINNET);
|
||||
assert_eq!(
|
||||
Ufvk::try_from_bytes(Ufvk::MAINNET, &encoded[..]),
|
||||
Ufvk::parse_items(&Ufvk::MAINNET, &encoded[..]).and_then(Ufvk::try_from_items),
|
||||
Err(ParseError::DuplicateTypecode(Typecode::Sapling))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_transparent() {
|
||||
// Encoding of `Ufvk(vec![Fvk::P2pkh([0; 78])])`.
|
||||
// Raw encoding of `Ufvk(vec![Fvk::P2pkh([0; 78])])`.
|
||||
let encoded = vec![
|
||||
0xce, 0x3b, 0x36, 0xd9, 0x15, 0xf4, 0xc0, 0x78, 0x86, 0xf8, 0x21, 0xb6, 0x9a, 0xef,
|
||||
0x40, 0x6d, 0xe6, 0x4d, 0xbd, 0x17, 0x8c, 0x7a, 0xa5, 0x4b, 0xd7, 0x0, 0x8d, 0x64, 0x2,
|
||||
|
@ -305,7 +310,7 @@ mod tests {
|
|||
];
|
||||
|
||||
assert_eq!(
|
||||
Ufvk::try_from_bytes(Ufvk::MAINNET, &encoded[..]),
|
||||
Ufvk::parse_items(&Ufvk::MAINNET, &encoded[..]).and_then(Ufvk::try_from_items),
|
||||
Err(ParseError::OnlyTransparent)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::convert::{TryFrom, TryInto};
|
|||
|
||||
use super::{
|
||||
private::{SealedContainer, SealedItem},
|
||||
Encoding, ParseError, Container, Typecode,
|
||||
Container, Encoding, ParseError, Typecode,
|
||||
};
|
||||
|
||||
/// The set of known IVKs for Unified IVKs.
|
||||
|
@ -109,6 +109,7 @@ impl Container for Uivk {
|
|||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Encoding for Uivk {}
|
||||
|
||||
impl SealedContainer for Uivk {
|
||||
|
@ -141,10 +142,14 @@ mod tests {
|
|||
use proptest::{
|
||||
array::{uniform14, uniform32},
|
||||
prelude::*,
|
||||
sample::select,
|
||||
};
|
||||
|
||||
use super::{Ivk, ParseError, Typecode, Uivk};
|
||||
use crate::kind::unified::{private::SealedContainer, Encoding, Container};
|
||||
use crate::{
|
||||
kind::unified::{private::SealedContainer, Container, Encoding},
|
||||
Network,
|
||||
};
|
||||
|
||||
prop_compose! {
|
||||
fn uniform64()(a in uniform32(0u8..), b in uniform32(0u8..)) -> [u8; 64] {
|
||||
|
@ -192,12 +197,12 @@ mod tests {
|
|||
proptest! {
|
||||
#[test]
|
||||
fn uivk_roundtrip(
|
||||
hrp in prop_oneof![Uivk::MAINNET, Uivk::TESTNET, Uivk::REGTEST],
|
||||
network in select(vec![Network::Main, Network::Test, Network::Regtest]),
|
||||
uivk in arb_unified_ivk(),
|
||||
) {
|
||||
let bytes = uivk.to_bytes(&hrp);
|
||||
let decoded = Uivk::try_from_bytes(hrp.as_str(), &bytes[..]);
|
||||
prop_assert_eq!(decoded, Ok(uivk));
|
||||
let encoded = uivk.encode(&network);
|
||||
let decoded = Uivk::decode(&encoded);
|
||||
prop_assert_eq!(decoded, Ok((network, uivk)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -215,7 +220,7 @@ mod tests {
|
|||
0x83, 0xe8, 0x92, 0x18, 0x28, 0x70, 0x1e, 0x81, 0x76, 0x56, 0xb6, 0x15,
|
||||
];
|
||||
assert_eq!(
|
||||
Uivk::try_from_bytes(Uivk::MAINNET, &invalid_padding[..]),
|
||||
Uivk::parse_items(&Uivk::MAINNET, &invalid_padding[..]),
|
||||
Err(ParseError::InvalidEncoding(
|
||||
"Invalid padding bytes".to_owned()
|
||||
))
|
||||
|
@ -231,7 +236,7 @@ mod tests {
|
|||
0xf9, 0x65, 0x49, 0x14, 0xab, 0x7c, 0x55, 0x7b, 0x39, 0x47,
|
||||
];
|
||||
assert_eq!(
|
||||
Uivk::try_from_bytes(Uivk::MAINNET, &truncated_padding[..]),
|
||||
Uivk::parse_items(&Uivk::MAINNET, &truncated_padding[..]),
|
||||
Err(ParseError::InvalidEncoding(
|
||||
"Invalid padding bytes".to_owned()
|
||||
))
|
||||
|
@ -259,7 +264,7 @@ mod tests {
|
|||
0xf5, 0xd5, 0x8a, 0xb5, 0x1a,
|
||||
];
|
||||
assert_matches!(
|
||||
Uivk::try_from_bytes(Uivk::MAINNET, &truncated_sapling_data[..]),
|
||||
Uivk::parse_items(&Uivk::MAINNET, &truncated_sapling_data[..]),
|
||||
Err(ParseError::InvalidEncoding(_))
|
||||
);
|
||||
|
||||
|
@ -272,7 +277,7 @@ mod tests {
|
|||
0xd8, 0x21, 0x5e, 0x8, 0xa, 0x82, 0x95, 0x21, 0x74,
|
||||
];
|
||||
assert_matches!(
|
||||
Uivk::try_from_bytes(Uivk::MAINNET, &truncated_after_sapling_typecode[..]),
|
||||
Uivk::parse_items(&Uivk::MAINNET, &truncated_after_sapling_typecode[..]),
|
||||
Err(ParseError::InvalidEncoding(_))
|
||||
);
|
||||
}
|
||||
|
@ -281,16 +286,16 @@ mod tests {
|
|||
fn duplicate_typecode() {
|
||||
// Construct and serialize an invalid UIVK.
|
||||
let uivk = Uivk(vec![Ivk::Sapling([1; 64]), Ivk::Sapling([2; 64])]);
|
||||
let encoded = uivk.to_bytes(Uivk::MAINNET);
|
||||
let encoded = uivk.encode(&Network::Main);
|
||||
assert_eq!(
|
||||
Uivk::try_from_bytes(Uivk::MAINNET, &encoded[..]),
|
||||
Uivk::decode(&encoded),
|
||||
Err(ParseError::DuplicateTypecode(Typecode::Sapling))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_transparent() {
|
||||
// Encoding of `Uivk(vec![Ivk::P2pkh([0; 78])])`.
|
||||
// Raw Encoding of `Uivk(vec![Ivk::P2pkh([0; 78])])`.
|
||||
let encoded = vec![
|
||||
0xda, 0x41, 0xe7, 0x2b, 0xae, 0x1e, 0x95, 0x89, 0x0, 0xac, 0x28, 0x68, 0xb8, 0x50,
|
||||
0x71, 0x20, 0xa8, 0xfd, 0xdf, 0x29, 0x74, 0x3f, 0x34, 0x4f, 0xbc, 0x28, 0xe8, 0x29,
|
||||
|
@ -302,7 +307,7 @@ mod tests {
|
|||
];
|
||||
|
||||
assert_eq!(
|
||||
Uivk::try_from_bytes(Uivk::MAINNET, &encoded[..]),
|
||||
Uivk::parse_items(&Uivk::MAINNET, &encoded[..]).and_then(Uivk::try_from_items),
|
||||
Err(ParseError::OnlyTransparent)
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue