use crate::error::*;
use crate::utils::hex_str_to_bytes;
use crate::{address::Address, utils::ArrayString};
use bech32::Variant;
use bech32::{self, FromBase32, ToBase32};
use ripemd::Ripemd160 as Ripemd;
use sha2::Digest as Sha2Digest;
use sha2::Sha256;
use std::fmt::{self, Display, Formatter};
use std::hash::Hash;
use std::str::FromStr;
use base64::{engine::general_purpose, Engine as _};
pub trait PublicKey {
const DEFAULT_PREFIX: &'static str;
fn from_slice<T: Into<String>>(bytes: &[u8], prefix: T) -> Result<Self, PublicKeyError>
where
Self: Sized;
fn from_bytes<T: Into<String>>(bytes: [u8; 33], prefix: T) -> Result<Self, PublicKeyError>
where
Self: Sized;
fn as_bytes(&self) -> &[u8];
fn to_vec(&self) -> Vec<u8>;
fn get_prefix(&self) -> String;
fn change_prefix<T: Into<String>>(&mut self, prefix: T) -> Result<(), PublicKeyError>;
fn to_address(&self) -> Address;
fn to_address_with_prefix(&self, prefix: &str) -> Result<Address, AddressError>;
fn to_amino_bytes(&self) -> Vec<u8>;
fn to_bech32<T: Into<String>>(&self, hrp: T) -> Result<String, PublicKeyError>;
fn from_bech32(s: String) -> Result<Self, PublicKeyError>
where
Self: Sized;
}
#[derive(PartialEq, Eq, Copy, Clone, Hash)]
pub struct CosmosPublicKey {
bytes: [u8; 33],
prefix: ArrayString,
}
impl PublicKey for CosmosPublicKey {
const DEFAULT_PREFIX: &'static str = "cosmospub";
fn from_slice<T: Into<String>>(bytes: &[u8], prefix: T) -> Result<Self, PublicKeyError> {
from_slice::<T, CosmosPublicKey>(bytes, prefix)
}
fn from_bytes<T: Into<String>>(bytes: [u8; 33], prefix: T) -> Result<Self, PublicKeyError> {
Ok(CosmosPublicKey {
bytes,
prefix: ArrayString::new(&prefix.into())?,
})
}
fn as_bytes(&self) -> &[u8] {
&self.bytes
}
fn to_vec(&self) -> Vec<u8> {
self.bytes.to_vec()
}
fn get_prefix(&self) -> String {
self.prefix.to_string()
}
fn change_prefix<T: Into<String>>(&mut self, prefix: T) -> Result<(), PublicKeyError> {
self.prefix = ArrayString::new(&prefix.into())?;
Ok(())
}
fn to_address(&self) -> Address {
let current_prefix = self.get_prefix();
let new_prefix = if current_prefix.ends_with("pub") {
current_prefix.trim_end_matches("pub")
} else {
¤t_prefix
};
self.to_address_with_prefix(new_prefix).unwrap()
}
fn to_address_with_prefix(&self, prefix: &str) -> Result<Address, AddressError> {
let sha256 = Sha256::digest(self.bytes);
let ripemd160 = Ripemd::digest(sha256);
let mut bytes: [u8; 20] = Default::default();
bytes.copy_from_slice(&ripemd160[..]);
Address::from_slice(&bytes, prefix)
}
fn to_amino_bytes(&self) -> Vec<u8> {
let mut key_bytes = vec![0xEB, 0x5A, 0xE9, 0x87, 0x21];
key_bytes.extend(self.as_bytes());
key_bytes
}
fn to_bech32<T: Into<String>>(&self, hrp: T) -> Result<String, PublicKeyError> {
let bech32 = bech32::encode(
&hrp.into(),
self.to_amino_bytes().to_base32(),
Variant::Bech32,
)?;
Ok(bech32)
}
fn from_bech32(s: String) -> Result<CosmosPublicKey, PublicKeyError> {
from_bech32::<CosmosPublicKey>(s)
}
}
#[cfg(feature = "ethermint")]
#[derive(PartialEq, Eq, Copy, Clone, Hash)]
pub struct EthermintPublicKey {
bytes: [u8; 33],
prefix: ArrayString,
}
#[cfg(feature = "ethermint")]
impl PublicKey for EthermintPublicKey {
const DEFAULT_PREFIX: &'static str = "gravitypub";
fn from_slice<T: Into<String>>(bytes: &[u8], prefix: T) -> Result<Self, PublicKeyError> {
from_slice::<T, EthermintPublicKey>(bytes, prefix)
}
fn from_bytes<T: Into<String>>(bytes: [u8; 33], prefix: T) -> Result<Self, PublicKeyError> {
Ok(EthermintPublicKey {
bytes,
prefix: ArrayString::new(&prefix.into())?,
})
}
fn as_bytes(&self) -> &[u8] {
&self.bytes
}
fn to_vec(&self) -> Vec<u8> {
self.bytes.to_vec()
}
fn get_prefix(&self) -> String {
self.prefix.to_string()
}
fn change_prefix<T: Into<String>>(&mut self, prefix: T) -> Result<(), PublicKeyError> {
self.prefix = ArrayString::new(&prefix.into())?;
Ok(())
}
fn to_address(&self) -> Address {
self.to_address_with_prefix(&self.prefix.to_string())
.unwrap()
}
fn to_address_with_prefix(&self, prefix: &str) -> Result<Address, AddressError> {
let ec_pub = secp256k1::PublicKey::from_slice(&self.bytes).unwrap();
let ec_pub = ec_pub.serialize_uncompressed();
let sender = sha3::Keccak256::digest(&ec_pub[1..]);
debug_assert_eq!(sender.len(), 32);
Ok(Address::from_slice(&sender[12..], prefix.to_string()).unwrap())
}
fn to_amino_bytes(&self) -> Vec<u8> {
let mut key_bytes = vec![0xEB, 0x5A, 0xE9, 0x87, 0x21];
key_bytes.extend(self.as_bytes());
key_bytes
}
#[allow(clippy::doc_overindented_list_items)]
fn to_bech32<T: Into<String>>(&self, hrp: T) -> Result<String, PublicKeyError> {
let bech32 = bech32::encode(
&hrp.into(),
self.to_amino_bytes().to_base32(),
Variant::Bech32,
)?;
Ok(bech32)
}
fn from_bech32(s: String) -> Result<EthermintPublicKey, PublicKeyError> {
from_bech32::<EthermintPublicKey>(s)
}
}
fn from_slice<T: Into<String>, PK: PublicKey + Sized>(
bytes: &[u8],
prefix: T,
) -> Result<PK, PublicKeyError> {
if bytes.len() != 33 {
return Err(PublicKeyError::BytesDecodeErrorWrongLength);
}
let mut result = [0u8; 33];
result.copy_from_slice(bytes);
PK::from_bytes(result, prefix)
}
fn from_bech32<PK: PublicKey>(s: String) -> Result<PK, PublicKeyError> {
let (hrp, data, _) = match bech32::decode(&s) {
Ok(val) => val,
Err(_e) => return Err(PublicKeyError::Bech32InvalidEncoding),
};
let vec: Vec<u8> = match FromBase32::from_base32(&data) {
Ok(val) => val,
Err(_e) => return Err(PublicKeyError::Bech32InvalidBase32),
};
let mut key = [0u8; 33];
if vec.len() != 38 {
return Err(PublicKeyError::Bech32WrongLength);
}
key.copy_from_slice(&vec[5..]);
PK::from_bytes(key, hrp)
}
impl FromStr for CosmosPublicKey {
type Err = PublicKeyError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(k) = PublicKey::from_bech32(s.to_string()) {
Ok(k)
} else if let Ok(bytes) = hex_str_to_bytes(s) {
if bytes.len() == 33 {
let mut inner = [0; 33];
inner.copy_from_slice(&bytes[0..33]);
PublicKey::from_bytes(inner, CosmosPublicKey::DEFAULT_PREFIX)
} else {
Err(PublicKeyError::HexDecodeErrorWrongLength)
}
} else {
match general_purpose::STANDARD_NO_PAD.decode(s) {
Ok(bytes) => {
if bytes.len() == 33 {
let mut inner = [0; 33];
inner.copy_from_slice(&bytes[0..33]);
Ok(PublicKey::from_bytes(
inner,
CosmosPublicKey::DEFAULT_PREFIX,
)?)
} else {
Err(PublicKeyError::BytesDecodeErrorWrongLength)
}
}
Err(e) => Err(PublicKeyError::Base64DecodeError(e)),
}
}
}
}
impl Display for CosmosPublicKey {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let display = self.to_bech32(self.get_prefix()).unwrap();
write!(f, "{display}").expect("Unable to write");
Ok(())
}
}
impl fmt::Debug for CosmosPublicKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.to_bech32(self.get_prefix()).unwrap())
}
}
#[test]
fn check_bech32() {
let raw_bytes = [
0x02, 0xA1, 0x63, 0x3C, 0xAF, 0xCC, 0x01, 0xEB, 0xFB, 0x6D, 0x78, 0xE3, 0x9F, 0x68, 0x7A,
0x1F, 0x09, 0x95, 0xC6, 0x2F, 0xC9, 0x5F, 0x51, 0xEA, 0xD1, 0x0A, 0x02, 0xEE, 0x0B, 0xE5,
0x51, 0xB5, 0xDC,
];
let public_key = CosmosPublicKey::from_slice(&raw_bytes, CosmosPublicKey::DEFAULT_PREFIX)
.expect("Unable to create bytes from slice");
assert_eq!(&public_key.bytes[..], &raw_bytes[..]);
let res = public_key.to_string();
assert_eq!(
res,
"cosmospub1addwnpepq2skx090esq7h7md0r3e76r6ruyet330e904r6k3pgpwuzl92x6actrt4uq"
);
let raw_bytes = [
2, 150, 81, 169, 170, 196, 194, 43, 39, 179, 1, 154, 238, 109, 247, 70, 38, 110, 26, 231,
70, 238, 121, 119, 42, 110, 94, 173, 25, 142, 189, 7, 195,
];
let public_key = CosmosPublicKey::from_slice(&raw_bytes, CosmosPublicKey::DEFAULT_PREFIX)
.expect("Unable to create bytes from slice");
let res = public_key
.to_bech32("cosmospub")
.expect("Unable to convert to bech32");
assert_eq!(
res,
"cosmospub1addwnpepq2t9r2d2cnpzkfanqxdwum0hgcnxuxh8gmh8jae2de026xvwh5ruxuv5let"
);
let check: Result<CosmosPublicKey, PublicKeyError> =
"cosmospub1addwnpepq2t9r2d2cnpzkfanqxdwum0hgcnxuxh8gmh8jae2de026xvwh5ruxuv5let".parse();
assert_eq!(check.unwrap(), public_key)
}
#[test]
fn parse_base64_pubkey() {
let key = "AvDDT1xY7hXKTy5ESqckNpBbQIArTkf21CfLFDnmWUY4";
let _key: CosmosPublicKey = key.parse().unwrap();
}
#[test]
fn test_default_prefix() {
CosmosPublicKey::from_bytes([0; 33], CosmosPublicKey::DEFAULT_PREFIX).unwrap();
}