From 0554ac23022038b4bf00b1f0db846d31519e615c Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Wed, 20 Apr 2022 13:57:35 -0600 Subject: [PATCH] cosmrs: fully variable-width `AccountId` This commit changes the constructor and `to_bytes` methods to fully support variable-width account IDs, as added in cosmos/cosmos-sdk#8363. This is a breaking change. --- cosmrs/Cargo.toml | 2 +- cosmrs/src/base.rs | 65 +++++++++++++++++++-------------- cosmrs/src/crypto/public_key.rs | 2 +- 3 files changed, 39 insertions(+), 30 deletions(-) diff --git a/cosmrs/Cargo.toml b/cosmrs/Cargo.toml index 0bcbf680..b4589946 100644 --- a/cosmrs/Cargo.toml +++ b/cosmrs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cosmrs" -version = "0.5.1" +version = "0.6.0-pre" authors = ["Tony Arcieri "] license = "Apache-2.0" repository = "https://github.com/cosmos/cosmos-rust/tree/main/cosmrs" diff --git a/cosmrs/src/base.rs b/cosmrs/src/base.rs index bd3819b3..e012bd73 100644 --- a/cosmrs/src/base.rs +++ b/cosmrs/src/base.rs @@ -22,18 +22,28 @@ pub struct AccountId { impl AccountId { /// Create an [`AccountId`] with the given human-readable prefix and /// public key hash. - pub fn new(prefix: &str, bytes: [u8; tendermint::account::LENGTH]) -> Result { + pub fn new(prefix: &str, bytes: &[u8]) -> Result { let id = bech32::encode(prefix, &bytes); // TODO(tarcieri): ensure this is the proper validation for an account prefix - if prefix.chars().all(|c| matches!(c, 'a'..='z')) { + if !prefix.chars().all(|c| matches!(c, 'a'..='z')) { + return Err(Error::AccountId { id }) + .wrap_err("expected prefix to be lowercase alphabetical characters only"); + } + + if matches!(bytes.len(), 1..=MAX_ADDRESS_LENGTH) { Ok(Self { bech32: id, hrp_length: prefix.len(), }) } else { - Err(Error::AccountId { id }) - .wrap_err("expected prefix to be lowercase alphabetical characters only") + Err(Error::AccountId { id }).wrap_err_with(|| { + format!( + "account ID should be at most {} bytes long, but was {} bytes long", + MAX_ADDRESS_LENGTH, + bytes.len() + ) + }) } } @@ -43,11 +53,10 @@ impl AccountId { } /// Decode an account ID from Bech32 to an inner byte value. - pub fn to_bytes(&self) -> [u8; tendermint::account::LENGTH] { + pub fn to_bytes(&self) -> Vec { bech32::decode(&self.bech32) - .ok() - .and_then(|result| result.1.try_into().ok()) .expect("malformed Bech32 AccountId") + .1 } } @@ -74,33 +83,33 @@ impl FromStr for AccountId { fn from_str(s: &str) -> Result { let (hrp, bytes) = bech32::decode(s).wrap_err("failed to decode bech32")?; - - if matches!(bytes.len(), 1..=MAX_ADDRESS_LENGTH) { - Ok(Self { - bech32: s.to_owned(), - hrp_length: hrp.len(), - }) - } else { - Err(Error::AccountId { id: s.to_owned() }).wrap_err_with(|| { - format!( - "account ID should be at most {} bytes long, but was {} bytes long", - MAX_ADDRESS_LENGTH, - bytes.len() - ) - }) - } + Self::new(&hrp, &bytes) } } -impl From for tendermint::account::Id { - fn from(id: AccountId) -> tendermint::account::Id { - tendermint::account::Id::from(&id) +impl TryFrom for tendermint::account::Id { + type Error = ErrorReport; + + fn try_from(id: AccountId) -> Result { + tendermint::account::Id::try_from(&id) } } -impl From<&AccountId> for tendermint::account::Id { - fn from(id: &AccountId) -> tendermint::account::Id { - tendermint::account::Id::new(id.to_bytes()) +// TODO(tarcieri): non-fixed-width account ID type +impl TryFrom<&AccountId> for tendermint::account::Id { + type Error = ErrorReport; + + fn try_from(id: &AccountId) -> Result { + let bytes = id.to_bytes(); + let len = bytes.len(); + + match bytes.try_into() { + Ok(bytes) => Ok(tendermint::account::Id::new(bytes)), + _ => Err(Error::AccountId { + id: id.bech32.clone(), + }) + .wrap_err_with(|| format!("invalid length for account ID: {}", len)), + } } } diff --git a/cosmrs/src/crypto/public_key.rs b/cosmrs/src/crypto/public_key.rs index bf41954b..51b0a71c 100644 --- a/cosmrs/src/crypto/public_key.rs +++ b/cosmrs/src/crypto/public_key.rs @@ -35,7 +35,7 @@ impl PublicKey { match &self.0 { tendermint::PublicKey::Secp256k1(encoded_point) => { let id = tendermint::account::Id::from(*encoded_point); - AccountId::new(prefix, id.as_bytes().try_into()?) + AccountId::new(prefix, id.as_bytes()) } _ => Err(Error::Crypto.into()), }