Skip to content

Commit

Permalink
fix(addresses): Fix Byron cbor structure (#155)
Browse files Browse the repository at this point in the history
* Use correct CBOR struct for Byron addresses
* Implement universal to / from string that works across all eras
  • Loading branch information
scarmuega authored Jul 23, 2022
1 parent 1482303 commit e7b7688
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 48 deletions.
79 changes: 78 additions & 1 deletion pallas-addresses/src/byron.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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);
}
}
116 changes: 80 additions & 36 deletions pallas-addresses/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,

Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
}
Expand All @@ -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 {
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -668,6 +689,7 @@ mod tests {
("addr1w8phkx6acpnf78fuvxn0mkew3l0fd058hzquvz7w36x4gtcyjy7wx", 07u8),
("stake1uyehkck0lajq8gr28t9uxnuvgcqrc6070x3k9r8048z8y5gh6ffgw", 14u8),
("stake178phkx6acpnf78fuvxn0mkew3l0fd058hzquvz7w36x4gtcccycj5", 15u8),
("37btjrVyb4KDXBNC4haBVPCrro8AQPHwvCMp3RFhhSVWwfFmZ6wwzSK6JK1hY6wHNmtrpTf1kdbva8TCneM2YsiXT7mrzT21EacHnPpz5YyUdj64na", 08u8),
];

const PAYMENT_PUBLIC_KEY: &str =
Expand All @@ -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);
}
}
Expand All @@ -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);
}
}
Expand All @@ -704,16 +741,20 @@ 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))),
}
}
}

#[test]
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) => {
Expand Down Expand Up @@ -758,7 +799,10 @@ mod tests {
assert_eq!(hash, expected);
}
},
Address::Byron(_) => (),
Address::Byron(_) => {
// byron has it's own payload tests
()
}
};
}
}
Expand Down
2 changes: 1 addition & 1 deletion pallas-codec/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
18 changes: 8 additions & 10 deletions pallas-traverse/src/output.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand All @@ -19,21 +19,19 @@ impl<'b> MultiEraOutput<'b> {
Self::Babbage(Box::new(Cow::Borrowed(output)))
}

pub fn address_raw(&self) -> &[u8] {
pub fn address(&self) -> Result<Address, AddressError> {
match self {
MultiEraOutput::AlonzoCompatible(x) => &x.address,
MultiEraOutput::AlonzoCompatible(x) => Address::from_bytes(&x.address),
MultiEraOutput::Babbage(x) => match x.deref().deref() {
babbage::TransactionOutput::Legacy(x) => &x.address,
babbage::TransactionOutput::PostAlonzo(x) => &x.address,
babbage::TransactionOutput::Legacy(x) => Address::from_bytes(&x.address),
babbage::TransactionOutput::PostAlonzo(x) => Address::from_bytes(&x.address),
},
MultiEraOutput::Byron(x) => x.address.payload.deref(),
MultiEraOutput::Byron(x) => {
Ok(ByronAddress::new(&x.address.payload.0, x.address.crc).into())
}
}
}

pub fn address(&self) -> Result<Address, AddressError> {
Address::from_bytes(self.address_raw())
}

pub fn ada_amount(&self) -> u64 {
match self {
MultiEraOutput::Byron(x) => x.amount,
Expand Down

0 comments on commit e7b7688

Please sign in to comment.