feat(addresses): Improve API ergonomics (#148)

This commit is contained in:
Santiago Carmuega 2022-07-04 16:44:49 -03:00 committed by GitHub
parent 2b54eda487
commit a26773a94c
2 changed files with 131 additions and 54 deletions

View file

@ -16,6 +16,7 @@ authors = [
hex = "0.4.3" hex = "0.4.3"
pallas-crypto = { version = "0.11.0", path = "../pallas-crypto" } pallas-crypto = { version = "0.11.0", path = "../pallas-crypto" }
pallas-codec = { version = "0.11.0", path = "../pallas-codec" } pallas-codec = { version = "0.11.0", path = "../pallas-codec" }
pallas-primitives = { version = "0.11.0", path = "../pallas-primitives" }
base58 = "0.2.0" base58 = "0.2.0"
bech32 = "0.8.1" bech32 = "0.8.1"
thiserror = "1.0.31" thiserror = "1.0.31"

View file

@ -42,9 +42,7 @@ pub enum Error {
} }
pub type PaymentKeyHash = Hash<28>; pub type PaymentKeyHash = Hash<28>;
pub type StakeKeyHash = Hash<28>; pub type StakeKeyHash = Hash<28>;
pub type ScriptHash = Hash<28>; pub type ScriptHash = Hash<28>;
pub type Slot = u64; pub type Slot = u64;
@ -104,23 +102,23 @@ impl Pointer {
/// The payment part of a Shelley address /// The payment part of a Shelley address
#[derive(Debug, Clone, PartialEq, PartialOrd)] #[derive(Debug, Clone, PartialEq, PartialOrd)]
pub enum ShelleyPaymentPart { pub enum ShelleyPaymentPart {
PaymentKey(PaymentKeyHash), Key(PaymentKeyHash),
Script(ScriptHash), Script(ScriptHash),
} }
impl ShelleyPaymentPart { impl ShelleyPaymentPart {
fn payment_key(bytes: &[u8]) -> Result<Self, Error> { pub fn key_hash(hash: Hash<28>) -> Self {
slice_to_hash(bytes).map(ShelleyPaymentPart::PaymentKey) Self::Key(hash)
} }
fn script(bytes: &[u8]) -> Result<Self, Error> { pub fn script_hash(hash: Hash<28>) -> Self {
slice_to_hash(bytes).map(ShelleyPaymentPart::Script) Self::Script(hash)
} }
/// Get a reference to the inner hash of this address part /// Get a reference to the inner hash of this address part
pub fn as_hash(&self) -> &Hash<28> { pub fn as_hash(&self) -> &Hash<28> {
match self { match self {
Self::PaymentKey(x) => x, Self::Key(x) => x,
Self::Script(x) => x, Self::Script(x) => x,
} }
} }
@ -128,7 +126,7 @@ impl ShelleyPaymentPart {
/// Encodes this address as a sequence of bytes /// Encodes this address as a sequence of bytes
pub fn to_vec(&self) -> Vec<u8> { pub fn to_vec(&self) -> Vec<u8> {
match self { match self {
Self::PaymentKey(x) => x.to_vec(), Self::Key(x) => x.to_vec(),
Self::Script(x) => x.to_vec(), Self::Script(x) => x.to_vec(),
} }
} }
@ -142,22 +140,22 @@ impl ShelleyPaymentPart {
/// The delegation part of a Shelley address /// The delegation part of a Shelley address
#[derive(Debug, Clone, PartialEq, PartialOrd)] #[derive(Debug, Clone, PartialEq, PartialOrd)]
pub enum ShelleyDelegationPart { pub enum ShelleyDelegationPart {
StakeKey(StakeKeyHash), Key(StakeKeyHash),
Script(ScriptHash), Script(ScriptHash),
Pointer(Pointer), Pointer(Pointer),
Null, Null,
} }
impl ShelleyDelegationPart { impl ShelleyDelegationPart {
fn stake_key(bytes: &[u8]) -> Result<Self, Error> { pub fn key_hash(hash: Hash<28>) -> Self {
slice_to_hash(bytes).map(Self::StakeKey) Self::Key(hash)
} }
fn script(bytes: &[u8]) -> Result<Self, Error> { pub fn script_hash(hash: Hash<28>) -> Self {
slice_to_hash(bytes).map(Self::Script) Self::Script(hash)
} }
fn pointer(bytes: &[u8]) -> Result<Self, Error> { pub fn from_pointer(bytes: &[u8]) -> Result<Self, Error> {
let pointer = Pointer::parse(bytes)?; let pointer = Pointer::parse(bytes)?;
Ok(Self::Pointer(pointer)) Ok(Self::Pointer(pointer))
} }
@ -165,7 +163,7 @@ impl ShelleyDelegationPart {
/// Get a reference to the inner hash of this address part /// Get a reference to the inner hash of this address part
pub fn as_hash(&self) -> Option<&Hash<28>> { pub fn as_hash(&self) -> Option<&Hash<28>> {
match self { match self {
Self::StakeKey(x) => Some(x), Self::Key(x) => Some(x),
Self::Script(x) => Some(x), Self::Script(x) => Some(x),
Self::Pointer(_) => todo!(), Self::Pointer(_) => todo!(),
Self::Null => todo!(), Self::Null => todo!(),
@ -174,7 +172,7 @@ impl ShelleyDelegationPart {
pub fn to_vec(&self) -> Vec<u8> { pub fn to_vec(&self) -> Vec<u8> {
match self { match self {
Self::StakeKey(x) => x.to_vec(), Self::Key(x) => x.to_vec(),
Self::Script(x) => x.to_vec(), Self::Script(x) => x.to_vec(),
Self::Pointer(x) => x.to_vec(), Self::Pointer(x) => x.to_vec(),
Self::Null => vec![], Self::Null => vec![],
@ -208,6 +206,16 @@ pub enum Network {
Other(u8), 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 /// A decoded Shelley address
#[derive(Debug, Clone, PartialEq, PartialOrd)] #[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct ShelleyAddress(Network, ShelleyPaymentPart, ShelleyDelegationPart); pub struct ShelleyAddress(Network, ShelleyPaymentPart, ShelleyDelegationPart);
@ -260,8 +268,9 @@ macro_rules! parse_shelley_fn {
($name:tt, $payment:tt, pointer) => { ($name:tt, $payment:tt, pointer) => {
fn $name(header: u8, payload: &[u8]) -> Result<Address, Error> { fn $name(header: u8, payload: &[u8]) -> Result<Address, Error> {
let net = parse_network(header); let net = parse_network(header);
let p1 = ShelleyPaymentPart::$payment(&payload[0..=27])?; let h1 = slice_to_hash(&payload[0..=27])?;
let p2 = ShelleyDelegationPart::pointer(&payload[28..])?; let p1 = ShelleyPaymentPart::$payment(h1);
let p2 = ShelleyDelegationPart::from_pointer(&payload[28..])?;
let addr = ShelleyAddress(net, p1, p2); let addr = ShelleyAddress(net, p1, p2);
Ok(addr.into()) Ok(addr.into())
@ -270,8 +279,10 @@ macro_rules! parse_shelley_fn {
($name:tt, $payment:tt, $delegation:tt) => { ($name:tt, $payment:tt, $delegation:tt) => {
fn $name(header: u8, payload: &[u8]) -> Result<Address, Error> { fn $name(header: u8, payload: &[u8]) -> Result<Address, Error> {
let net = parse_network(header); let net = parse_network(header);
let p1 = ShelleyPaymentPart::$payment(&payload[0..=27])?; let h1 = slice_to_hash(&payload[0..=27])?;
let p2 = ShelleyDelegationPart::$delegation(&payload[28..=55])?; let p1 = ShelleyPaymentPart::$payment(h1);
let h2 = slice_to_hash(&payload[28..=55])?;
let p2 = ShelleyDelegationPart::$delegation(h2);
let addr = ShelleyAddress(net, p1, p2); let addr = ShelleyAddress(net, p1, p2);
Ok(addr.into()) Ok(addr.into())
@ -280,7 +291,8 @@ macro_rules! parse_shelley_fn {
($name:tt, $payment:tt) => { ($name:tt, $payment:tt) => {
fn $name(header: u8, payload: &[u8]) -> Result<Address, Error> { fn $name(header: u8, payload: &[u8]) -> Result<Address, Error> {
let net = parse_network(header); 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); let addr = ShelleyAddress(net, p1, ShelleyDelegationPart::Null);
Ok(addr.into()) Ok(addr.into())
@ -301,14 +313,14 @@ macro_rules! parse_stake_fn {
} }
// types 0-7 are Shelley addresses // types 0-7 are Shelley addresses
parse_shelley_fn!(parse_type_0, payment_key, stake_key); parse_shelley_fn!(parse_type_0, key_hash, key_hash);
parse_shelley_fn!(parse_type_1, script, stake_key); parse_shelley_fn!(parse_type_1, script_hash, key_hash);
parse_shelley_fn!(parse_type_2, payment_key, script); parse_shelley_fn!(parse_type_2, key_hash, script_hash);
parse_shelley_fn!(parse_type_3, script, script); parse_shelley_fn!(parse_type_3, script_hash, script_hash);
parse_shelley_fn!(parse_type_4, payment_key, pointer); parse_shelley_fn!(parse_type_4, key_hash, pointer);
parse_shelley_fn!(parse_type_5, script, pointer); parse_shelley_fn!(parse_type_5, script_hash, pointer);
parse_shelley_fn!(parse_type_6, payment_key); parse_shelley_fn!(parse_type_6, key_hash);
parse_shelley_fn!(parse_type_7, script); parse_shelley_fn!(parse_type_7, script_hash);
// type 8 (1000) are Byron addresses // type 8 (1000) are Byron addresses
fn parse_type_8(header: u8, payload: &[u8]) -> Result<Address, Error> { fn parse_type_8(header: u8, payload: &[u8]) -> Result<Address, Error> {
@ -345,22 +357,6 @@ fn bech32_to_address(bech32: &str) -> Result<Address, Error> {
bytes_to_address(&bytes) 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 { impl Network {
pub fn is_mainnet(&self) -> bool { pub fn is_mainnet(&self) -> bool {
matches!(self, Network::Mainnet) matches!(self, Network::Mainnet)
@ -380,9 +376,32 @@ impl ByronAddress {
pub fn typeid(&self) -> u8 { pub fn typeid(&self) -> u8 {
0b1000 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 { impl ShelleyAddress {
pub fn new(
network: Network,
payment: ShelleyPaymentPart,
delegation: ShelleyDelegationPart,
) -> Self {
Self(network, payment, delegation)
}
/// Gets the network assoaciated with this address /// Gets the network assoaciated with this address
pub fn network(&self) -> Network { pub fn network(&self) -> Network {
self.0 self.0
@ -391,13 +410,13 @@ impl ShelleyAddress {
/// Gets a numeric id describing the type of the address /// Gets a numeric id describing the type of the address
pub fn typeid(&self) -> u8 { pub fn typeid(&self) -> u8 {
match (&self.1, &self.2) { match (&self.1, &self.2) {
(ShelleyPaymentPart::PaymentKey(_), ShelleyDelegationPart::StakeKey(_)) => 0b0000, (ShelleyPaymentPart::Key(_), ShelleyDelegationPart::Key(_)) => 0b0000,
(ShelleyPaymentPart::Script(_), ShelleyDelegationPart::StakeKey(_)) => 0b0001, (ShelleyPaymentPart::Script(_), ShelleyDelegationPart::Key(_)) => 0b0001,
(ShelleyPaymentPart::PaymentKey(_), ShelleyDelegationPart::Script(_)) => 0b0010, (ShelleyPaymentPart::Key(_), ShelleyDelegationPart::Script(_)) => 0b0010,
(ShelleyPaymentPart::Script(_), ShelleyDelegationPart::Script(_)) => 0b0011, (ShelleyPaymentPart::Script(_), ShelleyDelegationPart::Script(_)) => 0b0011,
(ShelleyPaymentPart::PaymentKey(_), ShelleyDelegationPart::Pointer(_)) => 0b0100, (ShelleyPaymentPart::Key(_), ShelleyDelegationPart::Pointer(_)) => 0b0100,
(ShelleyPaymentPart::Script(_), ShelleyDelegationPart::Pointer(_)) => 0b0101, (ShelleyPaymentPart::Script(_), ShelleyDelegationPart::Pointer(_)) => 0b0101,
(ShelleyPaymentPart::PaymentKey(_), ShelleyDelegationPart::Null) => 0b0110, (ShelleyPaymentPart::Key(_), ShelleyDelegationPart::Null) => 0b0110,
(ShelleyPaymentPart::Script(_), ShelleyDelegationPart::Null) => 0b0111, (ShelleyPaymentPart::Script(_), ShelleyDelegationPart::Null) => 0b0111,
} }
} }
@ -435,6 +454,17 @@ impl ShelleyAddress {
[&[header], payment.as_slice(), delegation.as_slice()].concat() [&[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 /// Indicates if either the payment or delegation part is a script
pub fn has_script(&self) -> bool { pub fn has_script(&self) -> bool {
self.payment().is_script() || self.delegation().is_script() self.payment().is_script() || self.delegation().is_script()
@ -493,6 +523,17 @@ impl StakeAddress {
[&[header], self.1.as_ref()].concat() [&[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 { pub fn is_script(&self) -> bool {
self.payload().is_script() self.payload().is_script()
} }
@ -501,7 +542,11 @@ impl StakeAddress {
impl Address { impl Address {
/// Tries to encode an Address into a bech32 string /// Tries to encode an Address into a bech32 string
pub fn to_bech32(&self) -> Result<String, Error> { 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 /// Tries to parse a bech32 address into an Address
@ -552,6 +597,22 @@ impl Address {
_ => false, _ => 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 { impl From<ByronAddress> for Address {
@ -639,7 +700,7 @@ mod tests {
match addr { match addr {
Address::Shelley(x) => { Address::Shelley(x) => {
match x.payment() { match x.payment() {
ShelleyPaymentPart::PaymentKey(hash) => { ShelleyPaymentPart::Key(hash) => {
let expected = &hash_vector_key(PAYMENT_PUBLIC_KEY); let expected = &hash_vector_key(PAYMENT_PUBLIC_KEY);
assert_eq!(hash, expected); assert_eq!(hash, expected);
} }
@ -651,7 +712,7 @@ mod tests {
}; };
match x.delegation() { match x.delegation() {
ShelleyDelegationPart::StakeKey(hash) => { ShelleyDelegationPart::Key(hash) => {
let expected = &hash_vector_key(STAKE_PUBLIC_KEY); let expected = &hash_vector_key(STAKE_PUBLIC_KEY);
assert_eq!(hash, expected); assert_eq!(hash, expected);
} }
@ -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);
}
} }