Skip to content

Commit

Permalink
feat(addresses): Improve API ergonomics (#148)
Browse files Browse the repository at this point in the history
  • Loading branch information
scarmuega authored Jul 4, 2022
1 parent 4d2950d commit 30231d1
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 54 deletions.
1 change: 1 addition & 0 deletions pallas-addresses/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ authors = [
hex = "0.4.3"
pallas-crypto = { version = "0.11.0", path = "../pallas-crypto" }
pallas-codec = { version = "0.11.0", path = "../pallas-codec" }
pallas-primitives = { version = "0.11.0", path = "../pallas-primitives" }
base58 = "0.2.0"
bech32 = "0.8.1"
thiserror = "1.0.31"
184 changes: 130 additions & 54 deletions pallas-addresses/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,7 @@ pub enum Error {
}

pub type PaymentKeyHash = Hash<28>;

pub type StakeKeyHash = Hash<28>;

pub type ScriptHash = Hash<28>;

pub type Slot = u64;
Expand Down Expand Up @@ -104,31 +102,31 @@ impl Pointer {
/// The payment part of a Shelley address
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub enum ShelleyPaymentPart {
PaymentKey(PaymentKeyHash),
Key(PaymentKeyHash),
Script(ScriptHash),
}

impl ShelleyPaymentPart {
fn payment_key(bytes: &[u8]) -> Result<Self, Error> {
slice_to_hash(bytes).map(ShelleyPaymentPart::PaymentKey)
pub fn key_hash(hash: Hash<28>) -> Self {
Self::Key(hash)
}

fn script(bytes: &[u8]) -> Result<Self, Error> {
slice_to_hash(bytes).map(ShelleyPaymentPart::Script)
pub fn script_hash(hash: Hash<28>) -> Self {
Self::Script(hash)
}

/// Get a reference to the inner hash of this address part
pub fn as_hash(&self) -> &Hash<28> {
match self {
Self::PaymentKey(x) => x,
Self::Key(x) => x,
Self::Script(x) => x,
}
}

/// Encodes this address as a sequence of bytes
pub fn to_vec(&self) -> Vec<u8> {
match self {
Self::PaymentKey(x) => x.to_vec(),
Self::Key(x) => x.to_vec(),
Self::Script(x) => x.to_vec(),
}
}
Expand All @@ -142,30 +140,30 @@ impl ShelleyPaymentPart {
/// The delegation part of a Shelley address
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub enum ShelleyDelegationPart {
StakeKey(StakeKeyHash),
Key(StakeKeyHash),
Script(ScriptHash),
Pointer(Pointer),
Null,
}

impl ShelleyDelegationPart {
fn stake_key(bytes: &[u8]) -> Result<Self, Error> {
slice_to_hash(bytes).map(Self::StakeKey)
pub fn key_hash(hash: Hash<28>) -> Self {
Self::Key(hash)
}

fn script(bytes: &[u8]) -> Result<Self, Error> {
slice_to_hash(bytes).map(Self::Script)
pub fn script_hash(hash: Hash<28>) -> Self {
Self::Script(hash)
}

fn pointer(bytes: &[u8]) -> Result<Self, Error> {
pub fn from_pointer(bytes: &[u8]) -> Result<Self, Error> {
let pointer = Pointer::parse(bytes)?;
Ok(Self::Pointer(pointer))
}

/// Get a reference to the inner hash of this address part
pub fn as_hash(&self) -> Option<&Hash<28>> {
match self {
Self::StakeKey(x) => Some(x),
Self::Key(x) => Some(x),
Self::Script(x) => Some(x),
Self::Pointer(_) => todo!(),
Self::Null => todo!(),
Expand All @@ -174,7 +172,7 @@ impl ShelleyDelegationPart {

pub fn to_vec(&self) -> Vec<u8> {
match self {
Self::StakeKey(x) => x.to_vec(),
Self::Key(x) => x.to_vec(),
Self::Script(x) => x.to_vec(),
Self::Pointer(x) => x.to_vec(),
Self::Null => vec![],
Expand Down Expand Up @@ -208,6 +206,16 @@ pub enum Network {
Other(u8),
}

impl From<u8> for Network {
fn from(id: u8) -> Self {
match id {
0 => Network::Testnet,
1 => Network::Mainnet,
x => Network::Other(x),
}
}
}

/// A decoded Shelley address
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct ShelleyAddress(Network, ShelleyPaymentPart, ShelleyDelegationPart);
Expand Down Expand Up @@ -260,8 +268,9 @@ macro_rules! parse_shelley_fn {
($name:tt, $payment:tt, pointer) => {
fn $name(header: u8, payload: &[u8]) -> Result<Address, Error> {
let net = parse_network(header);
let p1 = ShelleyPaymentPart::$payment(&payload[0..=27])?;
let p2 = ShelleyDelegationPart::pointer(&payload[28..])?;
let h1 = slice_to_hash(&payload[0..=27])?;
let p1 = ShelleyPaymentPart::$payment(h1);
let p2 = ShelleyDelegationPart::from_pointer(&payload[28..])?;
let addr = ShelleyAddress(net, p1, p2);

Ok(addr.into())
Expand All @@ -270,8 +279,10 @@ macro_rules! parse_shelley_fn {
($name:tt, $payment:tt, $delegation:tt) => {
fn $name(header: u8, payload: &[u8]) -> Result<Address, Error> {
let net = parse_network(header);
let p1 = ShelleyPaymentPart::$payment(&payload[0..=27])?;
let p2 = ShelleyDelegationPart::$delegation(&payload[28..=55])?;
let h1 = slice_to_hash(&payload[0..=27])?;
let p1 = ShelleyPaymentPart::$payment(h1);
let h2 = slice_to_hash(&payload[28..=55])?;
let p2 = ShelleyDelegationPart::$delegation(h2);
let addr = ShelleyAddress(net, p1, p2);

Ok(addr.into())
Expand All @@ -280,7 +291,8 @@ macro_rules! parse_shelley_fn {
($name:tt, $payment:tt) => {
fn $name(header: u8, payload: &[u8]) -> Result<Address, Error> {
let net = parse_network(header);
let p1 = ShelleyPaymentPart::$payment(&payload[0..=27])?;
let h1 = slice_to_hash(&payload[0..=27])?;
let p1 = ShelleyPaymentPart::$payment(h1);
let addr = ShelleyAddress(net, p1, ShelleyDelegationPart::Null);

Ok(addr.into())
Expand All @@ -301,14 +313,14 @@ macro_rules! parse_stake_fn {
}

// types 0-7 are Shelley addresses
parse_shelley_fn!(parse_type_0, payment_key, stake_key);
parse_shelley_fn!(parse_type_1, script, stake_key);
parse_shelley_fn!(parse_type_2, payment_key, script);
parse_shelley_fn!(parse_type_3, script, script);
parse_shelley_fn!(parse_type_4, payment_key, pointer);
parse_shelley_fn!(parse_type_5, script, pointer);
parse_shelley_fn!(parse_type_6, payment_key);
parse_shelley_fn!(parse_type_7, script);
parse_shelley_fn!(parse_type_0, key_hash, key_hash);
parse_shelley_fn!(parse_type_1, script_hash, key_hash);
parse_shelley_fn!(parse_type_2, key_hash, script_hash);
parse_shelley_fn!(parse_type_3, script_hash, script_hash);
parse_shelley_fn!(parse_type_4, key_hash, pointer);
parse_shelley_fn!(parse_type_5, script_hash, pointer);
parse_shelley_fn!(parse_type_6, key_hash);
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> {
Expand Down Expand Up @@ -345,22 +357,6 @@ fn bech32_to_address(bech32: &str) -> Result<Address, Error> {
bytes_to_address(&bytes)
}

fn address_to_bech32(addr: &Address) -> Result<String, Error> {
match addr {
Address::Byron(_) => Err(Error::InvalidForByron),
Address::Shelley(ref x) => {
let hrp = x.hrp()?;
let bytes = x.to_vec();
encode_bech32(&bytes, hrp)
}
Address::Stake(ref x) => {
let hrp = x.hrp()?;
let bytes = x.to_vec();
encode_bech32(&bytes, hrp)
}
}
}

impl Network {
pub fn is_mainnet(&self) -> bool {
matches!(self, Network::Mainnet)
Expand All @@ -380,9 +376,32 @@ impl ByronAddress {
pub fn typeid(&self) -> u8 {
0b1000
}

fn to_vec(&self) -> Vec<u8> {
self.0.clone()
}

pub fn to_hex(&self) -> String {
let bytes = self.to_vec();
hex::encode(bytes)
}
}

impl AsRef<[u8]> for ByronAddress {
fn as_ref(&self) -> &[u8] {
&self.0
}
}

impl ShelleyAddress {
pub fn new(
network: Network,
payment: ShelleyPaymentPart,
delegation: ShelleyDelegationPart,
) -> Self {
Self(network, payment, delegation)
}

/// Gets the network assoaciated with this address
pub fn network(&self) -> Network {
self.0
Expand All @@ -391,13 +410,13 @@ impl ShelleyAddress {
/// Gets a numeric id describing the type of the address
pub fn typeid(&self) -> u8 {
match (&self.1, &self.2) {
(ShelleyPaymentPart::PaymentKey(_), ShelleyDelegationPart::StakeKey(_)) => 0b0000,
(ShelleyPaymentPart::Script(_), ShelleyDelegationPart::StakeKey(_)) => 0b0001,
(ShelleyPaymentPart::PaymentKey(_), ShelleyDelegationPart::Script(_)) => 0b0010,
(ShelleyPaymentPart::Key(_), ShelleyDelegationPart::Key(_)) => 0b0000,
(ShelleyPaymentPart::Script(_), ShelleyDelegationPart::Key(_)) => 0b0001,
(ShelleyPaymentPart::Key(_), ShelleyDelegationPart::Script(_)) => 0b0010,
(ShelleyPaymentPart::Script(_), ShelleyDelegationPart::Script(_)) => 0b0011,
(ShelleyPaymentPart::PaymentKey(_), ShelleyDelegationPart::Pointer(_)) => 0b0100,
(ShelleyPaymentPart::Key(_), ShelleyDelegationPart::Pointer(_)) => 0b0100,
(ShelleyPaymentPart::Script(_), ShelleyDelegationPart::Pointer(_)) => 0b0101,
(ShelleyPaymentPart::PaymentKey(_), ShelleyDelegationPart::Null) => 0b0110,
(ShelleyPaymentPart::Key(_), ShelleyDelegationPart::Null) => 0b0110,
(ShelleyPaymentPart::Script(_), ShelleyDelegationPart::Null) => 0b0111,
}
}
Expand Down Expand Up @@ -435,6 +454,17 @@ impl ShelleyAddress {
[&[header], payment.as_slice(), delegation.as_slice()].concat()
}

pub fn to_hex(&self) -> String {
let bytes = self.to_vec();
hex::encode(bytes)
}

pub fn to_bech32(&self) -> Result<String, Error> {
let hrp = self.hrp()?;
let bytes = self.to_vec();
encode_bech32(&bytes, hrp)
}

/// Indicates if either the payment or delegation part is a script
pub fn has_script(&self) -> bool {
self.payment().is_script() || self.delegation().is_script()
Expand Down Expand Up @@ -493,6 +523,17 @@ impl StakeAddress {
[&[header], self.1.as_ref()].concat()
}

pub fn to_hex(&self) -> String {
let bytes = self.to_vec();
hex::encode(bytes)
}

pub fn to_bech32(&self) -> Result<String, Error> {
let hrp = self.hrp()?;
let bytes = self.to_vec();
encode_bech32(&bytes, hrp)
}

pub fn is_script(&self) -> bool {
self.payload().is_script()
}
Expand All @@ -501,7 +542,11 @@ impl StakeAddress {
impl Address {
/// Tries to encode an Address into a bech32 string
pub fn to_bech32(&self) -> Result<String, Error> {
address_to_bech32(self)
match self {
Address::Byron(_) => Err(Error::InvalidForByron),
Address::Shelley(x) => x.to_bech32(),
Address::Stake(x) => x.to_bech32(),
}
}

/// Tries to parse a bech32 address into an Address
Expand Down Expand Up @@ -552,6 +597,22 @@ impl Address {
_ => false,
}
}

pub fn to_vec(&self) -> Vec<u8> {
match self {
Address::Byron(x) => x.to_vec(),
Address::Shelley(x) => x.to_vec(),
Address::Stake(x) => x.to_vec(),
}
}

pub fn to_hex(&self) -> String {
match self {
Address::Byron(x) => x.to_hex(),
Address::Shelley(x) => x.to_hex(),
Address::Stake(x) => x.to_hex(),
}
}
}

impl From<ByronAddress> for Address {
Expand Down Expand Up @@ -639,7 +700,7 @@ mod tests {
match addr {
Address::Shelley(x) => {
match x.payment() {
ShelleyPaymentPart::PaymentKey(hash) => {
ShelleyPaymentPart::Key(hash) => {
let expected = &hash_vector_key(PAYMENT_PUBLIC_KEY);
assert_eq!(hash, expected);
}
Expand All @@ -651,7 +712,7 @@ mod tests {
};

match x.delegation() {
ShelleyDelegationPart::StakeKey(hash) => {
ShelleyDelegationPart::Key(hash) => {
let expected = &hash_vector_key(STAKE_PUBLIC_KEY);
assert_eq!(hash, expected);
}
Expand Down Expand Up @@ -683,4 +744,19 @@ mod tests {
};
}
}

#[test]
fn construct_from_parts() {
let payment_hash = hash_vector_key(PAYMENT_PUBLIC_KEY);
let delegation_hash = hash_vector_key(STAKE_PUBLIC_KEY);

let addr: Address = ShelleyAddress::new(
Network::Mainnet,
ShelleyPaymentPart::key_hash(payment_hash),
ShelleyDelegationPart::key_hash(delegation_hash),
)
.into();

assert_eq!(addr.to_bech32().unwrap(), MAINNET_TEST_VECTORS[0].0);
}
}

0 comments on commit 30231d1

Please sign in to comment.