Make unified container construction correctly sort items.
Newly constructed unified containers now obey ZIP 316 item ordering rules, while parsing and reserialization preserve order.
This commit is contained in:
parent
e7c57e4a02
commit
4e906508ae
|
@ -1,6 +1,5 @@
|
|||
use bech32::{self, FromBase32, ToBase32, Variant};
|
||||
use std::cmp;
|
||||
use std::collections::HashSet;
|
||||
use std::convert::TryFrom;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
@ -141,6 +140,7 @@ pub(crate) mod private {
|
|||
use crate::Network;
|
||||
use std::{
|
||||
cmp,
|
||||
collections::HashSet,
|
||||
convert::{TryFrom, TryInto},
|
||||
io::Write,
|
||||
};
|
||||
|
@ -155,7 +155,7 @@ pub(crate) mod private {
|
|||
}
|
||||
|
||||
/// A Unified Container containing addresses or viewing keys.
|
||||
pub trait SealedContainer: super::Container {
|
||||
pub trait SealedContainer: super::Container + std::marker::Sized {
|
||||
const MAINNET: &'static str;
|
||||
const TESTNET: &'static str;
|
||||
const REGTEST: &'static str;
|
||||
|
@ -281,36 +281,73 @@ pub(crate) mod private {
|
|||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// A private function that constructs a unified container with the
|
||||
/// items in their given order.
|
||||
fn try_from_items_internal(items: Vec<Self::Item>) -> Result<Self, ParseError> {
|
||||
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))
|
||||
|| (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(items))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_internal(hrp: &str, buf: &[u8]) -> Result<Self, ParseError> {
|
||||
Self::parse_items(hrp, buf).and_then(Self::try_from_items_internal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use private::SealedItem;
|
||||
|
||||
/// Trait providing common encoding and decoding logic for Unified containers.
|
||||
pub trait Encoding: private::SealedContainer + std::marker::Sized {
|
||||
fn try_from_items(items: Vec<Self::Item>) -> Result<Self, ParseError> {
|
||||
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))
|
||||
|| (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(items))
|
||||
}
|
||||
pub trait Encoding: private::SealedContainer {
|
||||
/// Constructs a value of a unified container type from a vector
|
||||
/// of container items, sorted according to typecode as specified
|
||||
/// in ZIP 316.
|
||||
///
|
||||
/// This function will return an error in the case that the following ZIP 316
|
||||
/// invariants concerning the composition of a unified container are
|
||||
/// violated:
|
||||
/// * the item list may not contain two items having the same typecode
|
||||
/// * the item list may not contain only a single transparent item
|
||||
fn try_from_items(mut items: Vec<Self::Item>) -> Result<Self, ParseError> {
|
||||
items.sort_unstable_by_key(|i| i.typecode());
|
||||
Self::try_from_items_internal(items)
|
||||
}
|
||||
|
||||
/// Constructs a value of a unified container type from a vector
|
||||
/// of container items, preserving the order of the provided vector
|
||||
/// in the serialized form, potentially contravening the ordering
|
||||
/// recommended by ZIP 316.
|
||||
///
|
||||
/// This function will return an error in the case that the following ZIP 316
|
||||
/// invariants concerning the composition of a unified container are
|
||||
/// violated:
|
||||
/// * the item list may not contain two items having the same typecode
|
||||
/// * the item list may not contain only a single transparent item
|
||||
fn try_from_items_preserving_order(items: Vec<Self::Item>) -> Result<Self, ParseError> {
|
||||
Self::try_from_items_internal(items)
|
||||
}
|
||||
|
||||
/// Decodes a unified container from its string representation, preserving
|
||||
/// the order of its components so that it correctly obeys round-trip
|
||||
/// serialization invariants.
|
||||
fn decode(s: &str) -> Result<(Network, Self), ParseError> {
|
||||
if let Ok((hrp, data, Variant::Bech32m)) = bech32::decode(s) {
|
||||
let hrp = hrp.as_str();
|
||||
|
@ -321,14 +358,16 @@ pub trait Encoding: private::SealedContainer + std::marker::Sized {
|
|||
let data = Vec::<u8>::from_base32(&data)
|
||||
.map_err(|e| ParseError::InvalidEncoding(e.to_string()))?;
|
||||
|
||||
Self::parse_items(hrp, &data[..])
|
||||
.and_then(Self::try_from_items)
|
||||
.map(|value| (net, value))
|
||||
Self::parse_internal(hrp, &data[..]).map(|value| (net, value))
|
||||
} else {
|
||||
Err(ParseError::NotUnified)
|
||||
}
|
||||
}
|
||||
|
||||
/// Encodes the contents of the unified container to its string representation
|
||||
/// using the correct constants for the specified network, preserving the
|
||||
/// ordering of the contained items such that it correctly obeys round-trip
|
||||
/// serialization invariants.
|
||||
fn encode(&self, network: &Network) -> String {
|
||||
let hrp = Self::network_hrp(network);
|
||||
bech32::encode(
|
||||
|
|
|
@ -213,7 +213,7 @@ mod tests {
|
|||
0x7b, 0x28, 0x69, 0xc9, 0x84,
|
||||
];
|
||||
assert_eq!(
|
||||
Address::parse_items(Address::MAINNET, &invalid_padding[..]),
|
||||
Address::parse_internal(Address::MAINNET, &invalid_padding[..]),
|
||||
Err(ParseError::InvalidEncoding(
|
||||
"Invalid padding bytes".to_owned()
|
||||
))
|
||||
|
@ -228,7 +228,7 @@ mod tests {
|
|||
0x4b, 0x31, 0xee, 0x5a,
|
||||
];
|
||||
assert_eq!(
|
||||
Address::parse_items(Address::MAINNET, &truncated_padding[..]),
|
||||
Address::parse_internal(Address::MAINNET, &truncated_padding[..]),
|
||||
Err(ParseError::InvalidEncoding(
|
||||
"Invalid padding bytes".to_owned()
|
||||
))
|
||||
|
@ -253,7 +253,7 @@ mod tests {
|
|||
0xc6, 0x5e, 0x68, 0xa2, 0x78, 0x6c, 0x9e,
|
||||
];
|
||||
assert_matches!(
|
||||
Address::parse_items(Address::MAINNET, &truncated_sapling_data[..]),
|
||||
Address::parse_internal(Address::MAINNET, &truncated_sapling_data[..]),
|
||||
Err(ParseError::InvalidEncoding(_))
|
||||
);
|
||||
|
||||
|
@ -266,7 +266,7 @@ mod tests {
|
|||
0xe6, 0x70, 0x36, 0x5b, 0x7b, 0x9e,
|
||||
];
|
||||
assert_matches!(
|
||||
Address::parse_items(Address::MAINNET, &truncated_after_sapling_typecode[..]),
|
||||
Address::parse_internal(Address::MAINNET, &truncated_after_sapling_typecode[..]),
|
||||
Err(ParseError::InvalidEncoding(_))
|
||||
);
|
||||
}
|
||||
|
@ -278,7 +278,7 @@ mod tests {
|
|||
let ua = Address(vec![Receiver::Sapling([1; 43]), Receiver::Sapling([2; 43])]);
|
||||
let encoded = ua.to_jumbled_bytes(Address::MAINNET);
|
||||
assert_eq!(
|
||||
Address::parse_items(Address::MAINNET, &encoded[..]).and_then(Address::try_from_items),
|
||||
Address::parse_internal(Address::MAINNET, &encoded[..]),
|
||||
Err(ParseError::DuplicateTypecode(Typecode::Sapling))
|
||||
);
|
||||
}
|
||||
|
@ -291,7 +291,7 @@ mod tests {
|
|||
let encoded = ua.to_jumbled_bytes(Address::MAINNET);
|
||||
// ensure that decoding catches the error
|
||||
assert_eq!(
|
||||
Address::parse_items(Address::MAINNET, &encoded[..]).and_then(Address::try_from_items),
|
||||
Address::parse_internal(Address::MAINNET, &encoded[..]),
|
||||
Err(ParseError::BothP2phkAndP2sh)
|
||||
);
|
||||
}
|
||||
|
@ -310,7 +310,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::parse_items(Address::MAINNET, &encoded[..]),
|
||||
Address::parse_internal(Address::MAINNET, &encoded[..]),
|
||||
Err(ParseError::InvalidEncoding(_))
|
||||
);
|
||||
}
|
||||
|
|
|
@ -232,7 +232,7 @@ mod tests {
|
|||
0xdf, 0x63, 0xe7, 0xef, 0x65, 0x6b, 0x18, 0x23, 0xf7, 0x3e, 0x35, 0x7c, 0xf3, 0xc4,
|
||||
];
|
||||
assert_eq!(
|
||||
Ufvk::parse_items(Ufvk::MAINNET, &invalid_padding[..]),
|
||||
Ufvk::parse_internal(Ufvk::MAINNET, &invalid_padding[..]),
|
||||
Err(ParseError::InvalidEncoding(
|
||||
"Invalid padding bytes".to_owned()
|
||||
))
|
||||
|
@ -250,7 +250,7 @@ mod tests {
|
|||
0x43, 0x8e, 0xc0, 0x3e, 0x9f, 0xf4, 0xf1, 0x80, 0x32, 0xcf, 0x2f, 0x7e, 0x7f, 0x91,
|
||||
];
|
||||
assert_eq!(
|
||||
Ufvk::parse_items(Ufvk::MAINNET, &truncated_padding[..]),
|
||||
Ufvk::parse_internal(Ufvk::MAINNET, &truncated_padding[..]),
|
||||
Err(ParseError::InvalidEncoding(
|
||||
"Invalid padding bytes".to_owned()
|
||||
))
|
||||
|
@ -282,7 +282,7 @@ mod tests {
|
|||
0x8c, 0x7a, 0xbf, 0x7b, 0x9a, 0xdd, 0xee, 0x18, 0x2c, 0x2d, 0xc2, 0xfc,
|
||||
];
|
||||
assert_matches!(
|
||||
Ufvk::parse_items(Ufvk::MAINNET, &truncated_sapling_data[..]),
|
||||
Ufvk::parse_internal(Ufvk::MAINNET, &truncated_sapling_data[..]),
|
||||
Err(ParseError::InvalidEncoding(_))
|
||||
);
|
||||
|
||||
|
@ -297,7 +297,7 @@ mod tests {
|
|||
0x54, 0xd1, 0x9e, 0xec, 0x8b, 0xef, 0x35, 0xb8, 0x44, 0xdd, 0xab, 0x9a, 0x8d,
|
||||
];
|
||||
assert_matches!(
|
||||
Ufvk::parse_items(Ufvk::MAINNET, &truncated_after_sapling_typecode[..]),
|
||||
Ufvk::parse_internal(Ufvk::MAINNET, &truncated_after_sapling_typecode[..]),
|
||||
Err(ParseError::InvalidEncoding(_))
|
||||
);
|
||||
}
|
||||
|
@ -309,7 +309,7 @@ mod tests {
|
|||
let ufvk = Ufvk(vec![Fvk::Sapling([1; 128]), Fvk::Sapling([2; 128])]);
|
||||
let encoded = ufvk.to_jumbled_bytes(Ufvk::MAINNET);
|
||||
assert_eq!(
|
||||
Ufvk::parse_items(Ufvk::MAINNET, &encoded[..]).and_then(Ufvk::try_from_items),
|
||||
Ufvk::parse_internal(Ufvk::MAINNET, &encoded[..]),
|
||||
Err(ParseError::DuplicateTypecode(Typecode::Sapling))
|
||||
);
|
||||
}
|
||||
|
@ -327,7 +327,7 @@ mod tests {
|
|||
];
|
||||
|
||||
assert_eq!(
|
||||
Ufvk::parse_items(Ufvk::MAINNET, &encoded[..]).and_then(Ufvk::try_from_items),
|
||||
Ufvk::parse_internal(Ufvk::MAINNET, &encoded[..]),
|
||||
Err(ParseError::OnlyTransparent)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -223,7 +223,7 @@ mod tests {
|
|||
0x83, 0xe8, 0x92, 0x18, 0x28, 0x70, 0x1e, 0x81, 0x76, 0x56, 0xb6, 0x15,
|
||||
];
|
||||
assert_eq!(
|
||||
Uivk::parse_items(Uivk::MAINNET, &invalid_padding[..]),
|
||||
Uivk::parse_internal(Uivk::MAINNET, &invalid_padding[..]),
|
||||
Err(ParseError::InvalidEncoding(
|
||||
"Invalid padding bytes".to_owned()
|
||||
))
|
||||
|
@ -239,7 +239,7 @@ mod tests {
|
|||
0xf9, 0x65, 0x49, 0x14, 0xab, 0x7c, 0x55, 0x7b, 0x39, 0x47,
|
||||
];
|
||||
assert_eq!(
|
||||
Uivk::parse_items(Uivk::MAINNET, &truncated_padding[..]),
|
||||
Uivk::parse_internal(Uivk::MAINNET, &truncated_padding[..]),
|
||||
Err(ParseError::InvalidEncoding(
|
||||
"Invalid padding bytes".to_owned()
|
||||
))
|
||||
|
@ -267,7 +267,7 @@ mod tests {
|
|||
0xf5, 0xd5, 0x8a, 0xb5, 0x1a,
|
||||
];
|
||||
assert_matches!(
|
||||
Uivk::parse_items(Uivk::MAINNET, &truncated_sapling_data[..]),
|
||||
Uivk::parse_internal(Uivk::MAINNET, &truncated_sapling_data[..]),
|
||||
Err(ParseError::InvalidEncoding(_))
|
||||
);
|
||||
|
||||
|
@ -280,7 +280,7 @@ mod tests {
|
|||
0xd8, 0x21, 0x5e, 0x8, 0xa, 0x82, 0x95, 0x21, 0x74,
|
||||
];
|
||||
assert_matches!(
|
||||
Uivk::parse_items(Uivk::MAINNET, &truncated_after_sapling_typecode[..]),
|
||||
Uivk::parse_internal(Uivk::MAINNET, &truncated_after_sapling_typecode[..]),
|
||||
Err(ParseError::InvalidEncoding(_))
|
||||
);
|
||||
}
|
||||
|
@ -309,7 +309,7 @@ mod tests {
|
|||
];
|
||||
|
||||
assert_eq!(
|
||||
Uivk::parse_items(Uivk::MAINNET, &encoded[..]).and_then(Uivk::try_from_items),
|
||||
Uivk::parse_internal(Uivk::MAINNET, &encoded[..]),
|
||||
Err(ParseError::OnlyTransparent)
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue