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:
Kris Nuttycombe 2021-12-01 16:28:40 -07:00
parent 3b70731cc4
commit 7e629db29f
5 changed files with 197 additions and 153 deletions

View File

@ -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,

View File

@ -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.

View File

@ -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(_))
);
}

View File

@ -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)
);
}

View File

@ -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)
);
}