From ac8c8fb817632089cd33034fb213037ae1e71bc1 Mon Sep 17 00:00:00 2001 From: Santiago Carmuega Date: Fri, 22 Jul 2022 22:24:42 -0300 Subject: [PATCH 1/2] fix(addresses): Fix Byron cbor structure --- pallas-addresses/src/byron.rs | 6 ++- pallas-addresses/src/lib.rs | 69 ++++++++++++++++++++++++----------- pallas-codec/src/utils.rs | 2 +- pallas-traverse/src/output.rs | 9 ++++- 4 files changed, 62 insertions(+), 24 deletions(-) diff --git a/pallas-addresses/src/byron.rs b/pallas-addresses/src/byron.rs index 780b6fdc..49ce2180 100644 --- a/pallas-addresses/src/byron.rs +++ b/pallas-addresses/src/byron.rs @@ -1,6 +1,6 @@ use pallas_codec::{ minicbor::{self, bytes::ByteVec, Decode, Encode}, - utils::OrderPreservingProperties, + utils::{OrderPreservingProperties, TagWrap}, }; use pallas_crypto::hash::Hash; @@ -157,3 +157,7 @@ pub struct AddressPayload { #[n(2)] pub addrtype: AddrType, } + +pub type WrappedAddressPayload = TagWrap; + +pub type AddressCrc = u64; diff --git a/pallas-addresses/src/lib.rs b/pallas-addresses/src/lib.rs index 232a850b..0646c9bd 100644 --- a/pallas-addresses/src/lib.rs +++ b/pallas-addresses/src/lib.rs @@ -20,6 +20,9 @@ pub enum Error { #[error("error converting from/to bech32 {0}")] BadBech32(bech32::Error), + #[error("error decoding base58 value")] + BadBase58(base58::FromBase58Error), + #[error("address header not found")] MissingHeader, @@ -237,11 +240,26 @@ pub struct StakeAddress(Network, StakePayload); /// New type wrapping a Byron address primitive #[derive(Debug, Clone, PartialEq, PartialOrd)] -pub struct ByronAddress(byron::AddressPayload); +pub struct ByronAddress(byron::WrappedAddressPayload, byron::AddressCrc); impl ByronAddress { - pub fn new(primitive: byron::AddressPayload) -> Self { - Self(primitive) + pub fn new(payload: &[u8], crc: u64) -> Self { + let payload = byron::WrappedAddressPayload(ByteVec::from(Vec::from(payload))); + Self(payload, crc) + } + + /// Gets a numeric id describing the type of the address + pub fn typeid(&self) -> u8 { + 0b1000 + } + + fn to_vec(&self) -> Vec { + pallas_codec::minicbor::to_vec(&self.0).unwrap() + } + + pub fn to_hex(&self) -> String { + let bytes = self.to_vec(); + hex::encode(bytes) } } @@ -335,8 +353,9 @@ parse_shelley_fn!(parse_type_7, script_hash); // type 8 (1000) are Byron addresses fn parse_type_8(header: u8, payload: &[u8]) -> Result { let vec = [&[header], payload].concat(); - let prim = pallas_codec::minicbor::decode(&vec).map_err(|_| Error::InvalidByronCbor)?; - Ok(Address::Byron(ByronAddress(prim))) + let (payload, crc) = + pallas_codec::minicbor::decode(&vec).map_err(|_| Error::InvalidByronCbor)?; + Ok(Address::Byron(ByronAddress(payload, crc))) } // types 14-15 are Stake addresses @@ -382,22 +401,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 { - 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, @@ -564,6 +567,12 @@ impl Address { bytes_to_address(bytes) } + // Tries to decode an address from its hex representation + pub fn from_base58(value: &str) -> Result { + let bytes = base58::FromBase58::from_base58(value).map_err(Error::BadBase58)?; + Self::from_bytes(&bytes) + } + /// Gets the network assoaciated with this address pub fn network(&self) -> Option { match self { @@ -625,6 +634,16 @@ impl Address { } } +impl ToString for Address { + fn to_string(&self) -> String { + match self { + Address::Byron(x) => x.to_hex(), + 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 TryFrom<&[u8]> for Address { type Error = Error; @@ -670,6 +689,8 @@ mod tests { ("stake178phkx6acpnf78fuvxn0mkew3l0fd058hzquvz7w36x4gtcccycj5", 15u8), ]; + const MAINNET_BYRON_TEST_VECTOR: &str = "DdzFFzCqrhtCwukpfkhkpDGtzJN9keTB9YFX31dEjm77yBbuH6yDATU2zb9s2jnsmqQGCiz4RQyksenuEeVhngyMTLtCMRdHmpheaV5o"; + const PAYMENT_PUBLIC_KEY: &str = "addr_vk1w0l2sr2zgfm26ztc6nl9xy8ghsk5sh6ldwemlpmp9xylzy4dtf7st80zhd"; const STAKE_PUBLIC_KEY: &str = @@ -763,6 +784,12 @@ mod tests { } } + #[test] + fn byron_base58_roundtrip() { + let addr = Address::from_base58(MAINNET_BYRON_TEST_VECTOR).unwrap(); + assert!(matches!(addr, Address::Byron(_))) + } + #[test] fn construct_from_parts() { let payment_hash = hash_vector_key(PAYMENT_PUBLIC_KEY); diff --git a/pallas-codec/src/utils.rs b/pallas-codec/src/utils.rs index 41face87..864065d8 100644 --- a/pallas-codec/src/utils.rs +++ b/pallas-codec/src/utils.rs @@ -274,7 +274,7 @@ impl Deref for CborWrap { } #[derive(Debug, Clone, PartialEq, PartialOrd)] -pub struct TagWrap(I); +pub struct TagWrap(pub I); impl TagWrap { pub fn new(inner: I) -> Self { diff --git a/pallas-traverse/src/output.rs b/pallas-traverse/src/output.rs index 6261f654..80644290 100644 --- a/pallas-traverse/src/output.rs +++ b/pallas-traverse/src/output.rs @@ -31,7 +31,14 @@ impl<'b> MultiEraOutput<'b> { } pub fn address(&self) -> Result { - 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(Address::Byron(x.address.clone())), + } } pub fn ada_amount(&self) -> u64 { From 6101ebcf69e307fbcd642cc7c278ad7e06dffa97 Mon Sep 17 00:00:00 2001 From: Santiago Carmuega Date: Sat, 23 Jul 2022 08:12:38 -0300 Subject: [PATCH 2/2] Make it work --- pallas-addresses/src/byron.rs | 77 ++++++++++++++++++++++- pallas-addresses/src/lib.rs | 115 +++++++++++++++++++--------------- pallas-traverse/src/output.rs | 17 ++--- 3 files changed, 145 insertions(+), 64 deletions(-) diff --git a/pallas-addresses/src/byron.rs b/pallas-addresses/src/byron.rs index 49ce2180..9bd4c215 100644 --- a/pallas-addresses/src/byron.rs +++ b/pallas-addresses/src/byron.rs @@ -5,6 +5,8 @@ use pallas_codec::{ use pallas_crypto::hash::Hash; +use crate::Error; + pub type Blake2b224 = Hash<28>; pub type AddressId = Blake2b224; @@ -158,6 +160,77 @@ pub struct AddressPayload { pub addrtype: AddrType, } -pub type WrappedAddressPayload = TagWrap; +/// New type wrapping a Byron address primitive +#[derive(Debug, Encode, Decode, Clone, PartialEq, PartialOrd)] +pub struct ByronAddress { + #[n(0)] + payload: TagWrap, + + #[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 { + 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 { + 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 type AddressCrc = u64; + pub fn to_vec(&self) -> Vec { + 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 { + 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); + } +} diff --git a/pallas-addresses/src/lib.rs b/pallas-addresses/src/lib.rs index 0646c9bd..82ad2881 100644 --- a/pallas-addresses/src/lib.rs +++ b/pallas-addresses/src/lib.rs @@ -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; @@ -23,6 +23,12 @@ pub enum 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, @@ -238,30 +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::WrappedAddressPayload, byron::AddressCrc); - -impl ByronAddress { - pub fn new(payload: &[u8], crc: u64) -> Self { - let payload = byron::WrappedAddressPayload(ByteVec::from(Vec::from(payload))); - Self(payload, crc) - } - - /// Gets a numeric id describing the type of the address - pub fn typeid(&self) -> u8 { - 0b1000 - } - - fn to_vec(&self) -> Vec { - pallas_codec::minicbor::to_vec(&self.0).unwrap() - } - - pub fn to_hex(&self) -> String { - let bytes = self.to_vec(); - hex::encode(bytes) - } -} +pub use byron::ByronAddress; /// A decoded Cardano address of any type #[derive(Debug, Clone, PartialEq, PartialOrd)] @@ -353,9 +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 { let vec = [&[header], payload].concat(); - let (payload, crc) = - pallas_codec::minicbor::decode(&vec).map_err(|_| Error::InvalidByronCbor)?; - Ok(Address::Byron(ByronAddress(payload, crc))) + let inner = pallas_codec::minicbor::decode(&vec).map_err(|_| Error::InvalidByronCbor)?; + Ok(Address::Byron(inner)) } // types 14-15 are Stake addresses @@ -557,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 { bech32_to_address(bech32) } @@ -567,10 +549,10 @@ impl Address { bytes_to_address(bytes) } - // Tries to decode an address from its hex representation - pub fn from_base58(value: &str) -> Result { - let bytes = base58::FromBase58::from_base58(value).map_err(Error::BadBase58)?; - Self::from_bytes(&bytes) + // Tries to parse a hex value into an Address + pub fn from_hex(bytes: &str) -> Result { + let bytes = hex::decode(bytes).map_err(|_| Error::BadHex)?; + bytes_to_address(&bytes) } /// Gets the network assoaciated with this address @@ -637,13 +619,33 @@ impl Address { impl ToString for Address { fn to_string(&self) -> String { match self { - Address::Byron(x) => x.to_hex(), + 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 { + 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; @@ -687,10 +689,9 @@ mod tests { ("addr1w8phkx6acpnf78fuvxn0mkew3l0fd058hzquvz7w36x4gtcyjy7wx", 07u8), ("stake1uyehkck0lajq8gr28t9uxnuvgcqrc6070x3k9r8048z8y5gh6ffgw", 14u8), ("stake178phkx6acpnf78fuvxn0mkew3l0fd058hzquvz7w36x4gtcccycj5", 15u8), + ("37btjrVyb4KDXBNC4haBVPCrro8AQPHwvCMp3RFhhSVWwfFmZ6wwzSK6JK1hY6wHNmtrpTf1kdbva8TCneM2YsiXT7mrzT21EacHnPpz5YyUdj64na", 08u8), ]; - const MAINNET_BYRON_TEST_VECTOR: &str = "DdzFFzCqrhtCwukpfkhkpDGtzJN9keTB9YFX31dEjm77yBbuH6yDATU2zb9s2jnsmqQGCiz4RQyksenuEeVhngyMTLtCMRdHmpheaV5o"; - const PAYMENT_PUBLIC_KEY: &str = "addr_vk1w0l2sr2zgfm26ztc6nl9xy8ghsk5sh6ldwemlpmp9xylzy4dtf7st80zhd"; const STAKE_PUBLIC_KEY: &str = @@ -706,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); } } @@ -716,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); } } @@ -725,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))), + } } } @@ -734,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) => { @@ -779,17 +799,14 @@ mod tests { assert_eq!(hash, expected); } }, - Address::Byron(_) => (), + Address::Byron(_) => { + // byron has it's own payload tests + () + } }; } } - #[test] - fn byron_base58_roundtrip() { - let addr = Address::from_base58(MAINNET_BYRON_TEST_VECTOR).unwrap(); - assert!(matches!(addr, Address::Byron(_))) - } - #[test] fn construct_from_parts() { let payment_hash = hash_vector_key(PAYMENT_PUBLIC_KEY); diff --git a/pallas-traverse/src/output.rs b/pallas-traverse/src/output.rs index 80644290..6a4e8f9e 100644 --- a/pallas-traverse/src/output.rs +++ b/pallas-traverse/src/output.rs @@ -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,17 +19,6 @@ 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 { match self { MultiEraOutput::AlonzoCompatible(x) => Address::from_bytes(&x.address), @@ -37,7 +26,9 @@ impl<'b> MultiEraOutput<'b> { babbage::TransactionOutput::Legacy(x) => Address::from_bytes(&x.address), babbage::TransactionOutput::PostAlonzo(x) => Address::from_bytes(&x.address), }, - MultiEraOutput::Byron(x) => Ok(Address::Byron(x.address.clone())), + MultiEraOutput::Byron(x) => { + Ok(ByronAddress::new(&x.address.payload.0, x.address.crc).into()) + } } }