Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(addresses): Fix Byron cbor structure #155

Merged
merged 2 commits into from
Jul 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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