fix(addresses): Fix Byron cbor structure (#155)

* Use correct CBOR struct for Byron addresses
* Implement universal to / from string that works across all eras
This commit is contained in:
Santiago Carmuega 2022-07-23 08:18:53 -03:00 committed by GitHub
parent 1482303616
commit e7b76889bd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 170 additions and 51 deletions

View file

@ -1,10 +1,12 @@
use pallas_codec::{
minicbor::{self, bytes::ByteVec, Decode, Encode},
utils::OrderPreservingProperties,
utils::{OrderPreservingProperties, TagWrap},
};
use pallas_crypto::hash::Hash;
use crate::Error;
pub type Blake2b224 = Hash<28>;
pub type AddressId = Blake2b224;
@ -157,3 +159,78 @@ pub struct AddressPayload {
#[n(2)]
pub addrtype: AddrType,
}
/// New type wrapping a Byron address primitive
#[derive(Debug, Encode, Decode, Clone, PartialEq, PartialOrd)]
pub struct ByronAddress {
#[n(0)]
payload: TagWrap<ByteVec, 24>,
#[n(1)]
crc: u64,
}
impl ByronAddress {
pub fn new(payload: &[u8], crc: u64) -> Self {
Self {
payload: TagWrap(ByteVec::from(Vec::from(payload))),
crc,
}
}
pub fn from_bytes(value: &[u8]) -> Result<Self, Error> {
pallas_codec::minicbor::decode(value).map_err(|_| Error::InvalidByronCbor)
}
// Tries to decode an address from its hex representation
pub fn from_base58(value: &str) -> Result<Self, Error> {
let bytes = base58::FromBase58::from_base58(value).map_err(Error::BadBase58)?;
Self::from_bytes(&bytes)
}
/// Gets a numeric id describing the type of the address
pub fn typeid(&self) -> u8 {
0b1000
}
pub fn to_vec(&self) -> Vec<u8> {
pallas_codec::minicbor::to_vec(&self).unwrap()
}
pub fn to_base58(&self) -> String {
let bytes = self.to_vec();
base58::ToBase58::to_base58(bytes.as_slice())
}
pub fn to_hex(&self) -> String {
let bytes = self.to_vec();
hex::encode(bytes)
}
pub fn decode(&self) -> Result<AddressPayload, Error> {
minicbor::decode(&self.payload.0).map_err(|_| Error::InvalidByronCbor)
}
}
#[cfg(test)]
mod tests {
use super::*;
const TEST_VECTOR: &str = "37btjrVyb4KDXBNC4haBVPCrro8AQPHwvCMp3RFhhSVWwfFmZ6wwzSK6JK1hY6wHNmtrpTf1kdbva8TCneM2YsiXT7mrzT21EacHnPpz5YyUdj64na";
const ROOT_HASH: &str = "7e9ee4a9527dea9091e2d580edd6716888c42f75d96276290f98fe0b";
#[test]
fn roundtrip_base58() {
let addr = ByronAddress::from_base58(TEST_VECTOR).unwrap();
let ours = addr.to_base58();
assert_eq!(TEST_VECTOR, ours);
}
#[test]
fn payload_matches() {
let addr = ByronAddress::from_base58(TEST_VECTOR).unwrap();
let payload = addr.decode().unwrap();
assert_eq!(payload.root.to_string(), ROOT_HASH);
}
}

View file

@ -10,7 +10,7 @@
pub mod byron;
pub mod varuint;
use std::io::Cursor;
use std::{io::Cursor, str::FromStr};
use pallas_crypto::hash::Hash;
use thiserror::Error;
@ -20,6 +20,15 @@ pub enum Error {
#[error("error converting from/to bech32 {0}")]
BadBech32(bech32::Error),
#[error("error decoding base58 value")]
BadBase58(base58::FromBase58Error),
#[error("error decoding hex value")]
BadHex,
#[error("unknown or bad string format for address {0}")]
UnknownStringFormat(String),
#[error("address header not found")]
MissingHeader,
@ -235,15 +244,7 @@ pub enum StakePayload {
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct StakeAddress(Network, StakePayload);
/// New type wrapping a Byron address primitive
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct ByronAddress(byron::AddressPayload);
impl ByronAddress {
pub fn new(primitive: byron::AddressPayload) -> Self {
Self(primitive)
}
}
pub use byron::ByronAddress;
/// A decoded Cardano address of any type
#[derive(Debug, Clone, PartialEq, PartialOrd)]
@ -335,8 +336,8 @@ parse_shelley_fn!(parse_type_7, script_hash);
// type 8 (1000) are Byron addresses
fn parse_type_8(header: u8, payload: &[u8]) -> Result<Address, Error> {
let vec = [&[header], payload].concat();
let prim = pallas_codec::minicbor::decode(&vec).map_err(|_| Error::InvalidByronCbor)?;
Ok(Address::Byron(ByronAddress(prim)))
let inner = pallas_codec::minicbor::decode(&vec).map_err(|_| Error::InvalidByronCbor)?;
Ok(Address::Byron(inner))
}
// types 14-15 are Stake addresses
@ -382,22 +383,6 @@ impl Network {
}
}
impl ByronAddress {
/// Gets a numeric id describing the type of the address
pub fn typeid(&self) -> u8 {
0b1000
}
fn to_vec(&self) -> Vec<u8> {
pallas_codec::minicbor::to_vec(&self.0).unwrap()
}
pub fn to_hex(&self) -> String {
let bytes = self.to_vec();
hex::encode(bytes)
}
}
impl ShelleyAddress {
pub fn new(
network: Network,
@ -554,7 +539,7 @@ impl Address {
}
}
/// Tries to parse a bech32 address into an Address
/// Tries to parse a bech32 value into an Address
pub fn from_bech32(bech32: &str) -> Result<Self, Error> {
bech32_to_address(bech32)
}
@ -564,6 +549,12 @@ impl Address {
bytes_to_address(bytes)
}
// Tries to parse a hex value into an Address
pub fn from_hex(bytes: &str) -> Result<Self, Error> {
let bytes = hex::decode(bytes).map_err(|_| Error::BadHex)?;
bytes_to_address(&bytes)
}
/// Gets the network assoaciated with this address
pub fn network(&self) -> Option<Network> {
match self {
@ -625,6 +616,36 @@ impl Address {
}
}
impl ToString for Address {
fn to_string(&self) -> String {
match self {
Address::Byron(x) => x.to_base58(),
Address::Shelley(x) => x.to_bech32().unwrap_or_else(|_| x.to_hex()),
Address::Stake(x) => x.to_bech32().unwrap_or_else(|_| x.to_hex()),
}
}
}
impl FromStr for Address {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(x) = Address::from_bech32(s) {
return Ok(x);
}
if let Ok(x) = ByronAddress::from_base58(s) {
return Ok(x.into());
}
if let Ok(x) = Address::from_hex(s) {
return Ok(x);
}
Err(Error::UnknownStringFormat(s.to_owned()))
}
}
impl TryFrom<&[u8]> for Address {
type Error = Error;
@ -668,6 +689,7 @@ mod tests {
("addr1w8phkx6acpnf78fuvxn0mkew3l0fd058hzquvz7w36x4gtcyjy7wx", 07u8),
("stake1uyehkck0lajq8gr28t9uxnuvgcqrc6070x3k9r8048z8y5gh6ffgw", 14u8),
("stake178phkx6acpnf78fuvxn0mkew3l0fd058hzquvz7w36x4gtcccycj5", 15u8),
("37btjrVyb4KDXBNC4haBVPCrro8AQPHwvCMp3RFhhSVWwfFmZ6wwzSK6JK1hY6wHNmtrpTf1kdbva8TCneM2YsiXT7mrzT21EacHnPpz5YyUdj64na", 08u8),
];
const PAYMENT_PUBLIC_KEY: &str =
@ -685,8 +707,23 @@ mod tests {
fn roundtrip_bech32() {
for vector in MAINNET_TEST_VECTORS {
let original = vector.0;
let addr = Address::from_bech32(original).unwrap();
let ours = addr.to_bech32().unwrap();
match Address::from_str(original) {
Ok(Address::Byron(_)) => (),
Ok(addr) => {
let ours = addr.to_bech32().unwrap();
assert_eq!(original, ours);
}
_ => panic!("should be able to decode from bech32"),
}
}
}
#[test]
fn roundtrip_string() {
for vector in MAINNET_TEST_VECTORS {
let original = vector.0;
let addr = Address::from_str(original).unwrap();
let ours = addr.to_string();
assert_eq!(original, ours);
}
}
@ -695,7 +732,7 @@ mod tests {
fn typeid_matches() {
for vector in MAINNET_TEST_VECTORS {
let original = vector.0;
let addr = Address::from_bech32(original).unwrap();
let addr = Address::from_str(original).unwrap();
assert_eq!(addr.typeid(), vector.1);
}
}
@ -704,8 +741,12 @@ mod tests {
fn network_matches() {
for vector in MAINNET_TEST_VECTORS {
let original = vector.0;
let addr = Address::from_bech32(original).unwrap();
assert!(matches!(addr.network(), Some(Network::Mainnet)));
let addr = Address::from_str(original).unwrap();
match addr {
Address::Byron(_) => assert!(matches!(addr.network(), None)),
_ => assert!(matches!(addr.network(), Some(Network::Mainnet))),
}
}
}
@ -713,7 +754,7 @@ mod tests {
fn payload_matches() {
for vector in MAINNET_TEST_VECTORS {
let original = vector.0;
let addr = Address::from_bech32(original).unwrap();
let addr = Address::from_str(original).unwrap();
match addr {
Address::Shelley(x) => {
@ -758,7 +799,10 @@ mod tests {
assert_eq!(hash, expected);
}
},
Address::Byron(_) => (),
Address::Byron(_) => {
// byron has it's own payload tests
()
}
};
}
}

View file

@ -274,7 +274,7 @@ impl<T> Deref for CborWrap<T> {
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct TagWrap<I, const T: u64>(I);
pub struct TagWrap<I, const T: u64>(pub I);
impl<I, const T: u64> TagWrap<I, T> {
pub fn new(inner: I) -> Self {

View file

@ -1,6 +1,6 @@
use std::{borrow::Cow, ops::Deref};
use pallas_addresses::{Address, Error as AddressError};
use pallas_addresses::{Address, ByronAddress, Error as AddressError};
use pallas_codec::minicbor;
use pallas_primitives::{alonzo, babbage, byron};
@ -19,19 +19,17 @@ impl<'b> MultiEraOutput<'b> {
Self::Babbage(Box::new(Cow::Borrowed(output)))
}
pub fn address_raw(&self) -> &[u8] {
match self {
MultiEraOutput::AlonzoCompatible(x) => &x.address,
MultiEraOutput::Babbage(x) => match x.deref().deref() {
babbage::TransactionOutput::Legacy(x) => &x.address,
babbage::TransactionOutput::PostAlonzo(x) => &x.address,
},
MultiEraOutput::Byron(x) => x.address.payload.deref(),
}
}
pub fn address(&self) -> Result<Address, AddressError> {
Address::from_bytes(self.address_raw())
match self {
MultiEraOutput::AlonzoCompatible(x) => Address::from_bytes(&x.address),
MultiEraOutput::Babbage(x) => match x.deref().deref() {
babbage::TransactionOutput::Legacy(x) => Address::from_bytes(&x.address),
babbage::TransactionOutput::PostAlonzo(x) => Address::from_bytes(&x.address),
},
MultiEraOutput::Byron(x) => {
Ok(ByronAddress::new(&x.address.payload.0, x.address.crc).into())
}
}
}
pub fn ada_amount(&self) -> u64 {