diff --git a/.cargo/config.toml b/.cargo/config.toml index b2bd49322..de0297e06 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,2 +1,2 @@ [env] -ACCOUNT_ADDRESS_PREFIX = "cosmos" +BECH_32_MAIN_PREFIX = "cosmos" diff --git a/keyring/src/key_pair/secp256k1_key_pair.rs b/keyring/src/key_pair/secp256k1_key_pair.rs index 9628e69fa..2769d0d38 100644 --- a/keyring/src/key_pair/secp256k1_key_pair.rs +++ b/keyring/src/key_pair/secp256k1_key_pair.rs @@ -33,7 +33,7 @@ impl Secp256k1KeyPair { &self, password: impl AsRef<[u8]>, ) -> k256::elliptic_curve::zeroize::Zeroizing { - // The pkcs8 crate doesn't directly support encrypting with the same scrypt params as openssl. + // TODO: The pkcs8 crate doesn't directly support encrypting with the same scrypt params as openssl. // The following implementation is a workaround to achieve the same result. // See https://github.com/RustCrypto/formats/issues/1205 // Once this is fixed, we can replace the following code with: diff --git a/proto-types/build.rs b/proto-types/build.rs index 8882667d1..14da80b12 100644 --- a/proto-types/build.rs +++ b/proto-types/build.rs @@ -1,8 +1,8 @@ use std::{env, error::Error}; fn main() -> Result<(), Box> { - let account_address_prefix = env::var("ACCOUNT_ADDRESS_PREFIX").map_err(|_| "ACCOUNT_ADDRESS_PREFIX environment variable must be set. This is best done in a .cargo/config.toml file in the root of your project")?; + let account_address_prefix = env::var("BECH_32_MAIN_PREFIX").map_err(|_| "BECH_32_MAIN_PREFIX environment variable must be set. This is best done in a .cargo/config.toml file in the root of your project")?; println!( - "cargo:rustc-env=ACCOUNT_ADDRESS_PREFIX={}", + "cargo:rustc-env=BECH_32_MAIN_PREFIX={}", account_address_prefix ); diff --git a/proto-types/src/address.rs b/proto-types/src/address.rs index a127fbcc0..463c696a7 100644 --- a/proto-types/src/address.rs +++ b/proto-types/src/address.rs @@ -8,23 +8,32 @@ use serde::{Deserialize, Deserializer, Serialize}; use crate::error::AddressError; -#[derive(Debug, PartialEq, Clone)] -pub struct AccAddress(Vec); +const BECH_32_PREFIX_ACC_ADDR: &str = env!("BECH_32_MAIN_PREFIX"); +const BECH_32_PREFIX_VAL_ADDR: &str = concat!(env!("BECH_32_MAIN_PREFIX"), "valoper"); -const ACCOUNT_ADDRESS_PREFIX: &str = env!("ACCOUNT_ADDRESS_PREFIX"); const MAX_ADDR_LEN: u8 = 255; -impl AccAddress { - pub fn into_inner(self) -> Vec { - self.0 - } +pub type AccAddress = BaseAddress<0>; +pub type ValAddress = BaseAddress<1>; + +// TODO: when more complex const parameter types arrive, replace u8 with &'static str +// https://github.com/rust-lang/rust/issues/95174 +#[derive(Debug, PartialEq, Clone)] +pub struct BaseAddress(Vec); +impl BaseAddress { pub fn from_bech32(address: &str) -> Result { let (hrp, data, variant) = bech32::decode(address)?; - if hrp != ACCOUNT_ADDRESS_PREFIX { + let prefix = if PREFIX == 0 { + BECH_32_PREFIX_ACC_ADDR + } else { + BECH_32_PREFIX_VAL_ADDR + }; + + if hrp != prefix { return Err(AddressError::InvalidPrefix { - expected: ACCOUNT_ADDRESS_PREFIX.into(), + expected: prefix.into(), found: hrp, }); }; @@ -40,13 +49,7 @@ impl AccAddress { // already returns a Result there's no harm in returning an error here. let address = Vec::::from_base32(&data)?; - if address.len() > MAX_ADDR_LEN.into() { - return Err(AddressError::InvalidLength { - max: MAX_ADDR_LEN, - found: address.len(), - }); - } - + Self::verify_length(&address)?; Ok(Self(address)) } @@ -61,12 +64,25 @@ impl AccAddress { hex::encode(&self.0) } + fn verify_length(v: &[u8]) -> Result<(), AddressError> { + if v.len() > MAX_ADDR_LEN.into() { + Err(AddressError::InvalidLength { + max: MAX_ADDR_LEN, + found: v.len(), + }) + } else if v.len() == 0 { + Err(AddressError::EmptyAddress) + } else { + Ok(()) + } + } + pub fn as_upper_hex(&self) -> String { data_encoding::HEXUPPER.encode(&self.0) } } -impl Serialize for AccAddress { +impl Serialize for BaseAddress { fn serialize(&self, serializer: S) -> Result where S: serde::ser::Serializer, @@ -75,19 +91,19 @@ impl Serialize for AccAddress { } } -impl<'de> Deserialize<'de> for AccAddress { - fn deserialize(deserializer: D) -> Result +impl<'de, const PREFIX: u8> Deserialize<'de> for BaseAddress { + fn deserialize(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { - deserializer.deserialize_str(AccAddressVisitor) + deserializer.deserialize_str(BaseAddressVisitor) } } -struct AccAddressVisitor; +struct BaseAddressVisitor; -impl<'de> serde::de::Visitor<'de> for AccAddressVisitor { - type Value = AccAddress; +impl<'de, const PREFIX: u8> serde::de::Visitor<'de> for BaseAddressVisitor { + type Value = BaseAddress; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("bech32 encoded address") @@ -97,63 +113,56 @@ impl<'de> serde::de::Visitor<'de> for AccAddressVisitor { where E: serde::de::Error, { - AccAddress::from_str(v).map_err(|e| E::custom(format!("invalid address '{}' - {}", v, e))) + BaseAddress::from_str(v).map_err(|e| E::custom(format!("invalid address '{}' - {}", v, e))) } } -impl FromStr for AccAddress { - type Err = AddressError; +impl Display for BaseAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let hrp = if PREFIX == 0 { + BECH_32_PREFIX_ACC_ADDR + } else { + BECH_32_PREFIX_VAL_ADDR + }; + let addr = bech32::encode(hrp, self.0.to_base32(), Variant::Bech32) + .expect("method can only error if HRP is not valid, hard coded HRP is valid"); + write!(f, "{}", addr) + } +} - fn from_str(s: &str) -> Result { - Self::from_bech32(s) +impl From> for String { + fn from(v: BaseAddress) -> String { + format!("{}", v) } } -impl TryFrom> for AccAddress { - type Error = AddressError; +impl FromStr for BaseAddress { + type Err = AddressError; - fn try_from(v: Vec) -> Result { - if v.len() > MAX_ADDR_LEN.into() { - return Err(AddressError::InvalidLength { - max: MAX_ADDR_LEN, - found: v.len(), - }); - } - Ok(AccAddress(v)) + fn from_str(s: &str) -> Result { + Self::from_bech32(s) } } -impl TryFrom<&[u8]> for AccAddress { +impl TryFrom> for BaseAddress { type Error = AddressError; - fn try_from(v: &[u8]) -> Result { - if v.len() > MAX_ADDR_LEN.into() { - return Err(AddressError::InvalidLength { - max: MAX_ADDR_LEN, - found: v.len(), - }); - } - Ok(AccAddress(v.to_vec())) + fn try_from(v: Vec) -> Result, AddressError> { + Self::verify_length(&v)?; + Ok(BaseAddress(v)) } } -impl From for String { - fn from(v: AccAddress) -> String { - bech32::encode(ACCOUNT_ADDRESS_PREFIX, v.0.to_base32(), Variant::Bech32) - .expect("method can only error if HRP is not valid, hard coded HRP is valid") - } -} +impl TryFrom<&[u8]> for BaseAddress { + type Error = AddressError; -impl Display for AccAddress { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let addr = bech32::encode(ACCOUNT_ADDRESS_PREFIX, self.0.to_base32(), Variant::Bech32) - .expect("method can only error if HRP is not valid, hard coded HRP is valid"); - write!(f, "{}", addr) + fn try_from(v: &[u8]) -> Result, AddressError> { + v.to_vec().try_into() } } -impl From for Vec { - fn from(v: AccAddress) -> Vec { +impl From> for Vec { + fn from(v: BaseAddress) -> Vec { v.0 } } @@ -169,12 +178,12 @@ mod tests { fn from_bech32_success() { let input_address = vec![0x00, 0x01, 0x02]; let encoded = bech32::encode( - ACCOUNT_ADDRESS_PREFIX, + BECH_32_PREFIX_ACC_ADDR, input_address.to_base32(), Variant::Bech32, ) .unwrap(); - let expected_address = AccAddress(input_address); + let expected_address = BaseAddress::<0>(input_address); let address = AccAddress::from_bech32(&encoded).unwrap(); @@ -185,7 +194,7 @@ mod tests { fn from_bech32_failure_checksum() { let input_address = vec![0x00, 0x01, 0x02]; let mut encoded = bech32::encode( - ACCOUNT_ADDRESS_PREFIX, + BECH_32_PREFIX_ACC_ADDR, input_address.to_base32(), Variant::Bech32, ) @@ -199,8 +208,8 @@ mod tests { #[test] fn from_bech32_failure_wrong_prefix() { - let mut hrp = ACCOUNT_ADDRESS_PREFIX.to_string(); - hrp.push_str("atom"); // adding to the ACCOUNT_ADDRESS_PREFIX ensures that hrp is different + let mut hrp = BECH_32_PREFIX_ACC_ADDR.to_string(); + hrp.push_str("atom"); // adding to the BECH_32_PREFIX_ACC_ADDR ensures that hrp is different let encoded = bech32::encode(&hrp, vec![0x00, 0x01, 0x02].to_base32(), Variant::Bech32).unwrap(); @@ -209,7 +218,7 @@ mod tests { assert_eq!( err, AddressError::InvalidPrefix { - expected: ACCOUNT_ADDRESS_PREFIX.into(), + expected: BECH_32_PREFIX_ACC_ADDR.into(), found: hrp, } ); @@ -218,7 +227,7 @@ mod tests { #[test] fn from_bech32_failure_wrong_variant() { let encoded = bech32::encode( - ACCOUNT_ADDRESS_PREFIX, + BECH_32_PREFIX_ACC_ADDR, vec![0x00, 0x01, 0x02].to_base32(), Variant::Bech32m, ) @@ -238,7 +247,7 @@ mod tests { #[test] fn from_bech32_failure_too_long() { let encoded = bech32::encode( - ACCOUNT_ADDRESS_PREFIX, + BECH_32_PREFIX_ACC_ADDR, vec![0x00; 300].to_base32(), Variant::Bech32, ) @@ -255,6 +264,23 @@ mod tests { ); } + #[test] + fn from_bech32_failure_empty_address() { + let encoded = + bech32::encode(BECH_32_PREFIX_ACC_ADDR, vec![].to_base32(), Variant::Bech32).unwrap(); + + let err = AccAddress::from_bech32(&encoded).unwrap_err(); + + assert_eq!(err, AddressError::EmptyAddress); + } + + #[test] + fn from_slice_failure_empty_address() { + let address: Vec = vec![]; + let err = AccAddress::try_from(address.as_slice()).unwrap_err(); + assert_eq!(err, AddressError::EmptyAddress); + } + #[test] fn to_string_success() { let addr = "cosmos1syavy2npfyt9tcncdtsdzf7kny9lh777pahuux".to_string(); @@ -264,6 +290,15 @@ mod tests { assert_eq!(addr, acc_addr.to_string()); } + #[test] + fn string_from_self_success() { + let addr = "cosmos1syavy2npfyt9tcncdtsdzf7kny9lh777pahuux".to_string(); + + let acc_addr = AccAddress::from_bech32(&addr).unwrap(); + + assert_eq!(addr, String::from(acc_addr)); + } + #[test] fn serialize_works() { let addr = "cosmos1syavy2npfyt9tcncdtsdzf7kny9lh777pahuux".to_string(); diff --git a/proto-types/src/error.rs b/proto-types/src/error.rs index d9051090a..5afe22c82 100644 --- a/proto-types/src/error.rs +++ b/proto-types/src/error.rs @@ -17,6 +17,9 @@ pub enum AddressError { #[error("invalid length, max length is: {max:?}, found {found:?})")] InvalidLength { max: u8, found: usize }, + + #[error("bech32 decode error: address is empty")] + EmptyAddress, } #[derive(Error, Debug, PartialEq, Eq)] diff --git a/proto-types/src/lib.rs b/proto-types/src/lib.rs index 0f7f42171..225b62fac 100644 --- a/proto-types/src/lib.rs +++ b/proto-types/src/lib.rs @@ -5,7 +5,7 @@ mod decimal256; mod denom; mod error; -pub use address::AccAddress; +pub use address::{AccAddress, ValAddress}; pub use decimal256::Decimal256; pub use denom::Denom; pub use error::{AddressError, Error};