use crate::errors::TerraRustAPIError;
use bitcoin::bech32::{decode, encode, u5, FromBase32, ToBase32, Variant};
use crypto::digest::Digest;
use crypto::ripemd160::Ripemd160;
use crypto::sha2::Sha256;
pub use ed25519_dalek::PublicKey as Ed25519;
use serde::{Deserialize, Serialize};
static BECH32_PUBKEY_DATA_PREFIX_SECP256K1: [u8; 5] = [0xeb, 0x5a, 0xe9, 0x87, 0x21]; static BECH32_PUBKEY_DATA_PREFIX_ED25519: [u8; 5] = [0x16, 0x24, 0xde, 0x64, 0x20];
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct PublicKey {
pub raw_pub_key: Option<Vec<u8>>,
pub raw_address: Option<Vec<u8>>,
}
impl PublicKey {
pub fn from_bitcoin_public_key(bpub: &bitcoin::util::key::PublicKey) -> PublicKey {
let bpub_bytes = bpub.key.serialize();
let raw_pub_key = PublicKey::pubkey_from_public_key(&bpub_bytes);
let raw_address = PublicKey::address_from_public_key(&bpub_bytes);
PublicKey {
raw_pub_key: Some(raw_pub_key),
raw_address: Some(raw_address),
}
}
pub fn from_public_key(bpub: &[u8]) -> PublicKey {
let raw_pub_key = PublicKey::pubkey_from_public_key(bpub);
let raw_address = PublicKey::address_from_public_key(bpub);
PublicKey {
raw_pub_key: Some(raw_pub_key),
raw_address: Some(raw_address),
}
}
pub fn from_account(acc_address: &str) -> Result<PublicKey, TerraRustAPIError> {
PublicKey::check_prefix_and_length("terra", acc_address, 44).and_then(|vu5| {
let vu8 = Vec::from_base32(vu5.as_slice()).map_err(|source| {
TerraRustAPIError::Conversion {
key: acc_address.into(),
source,
}
})?;
Ok(PublicKey {
raw_pub_key: None,
raw_address: Some(vu8),
})
})
}
pub fn from_tendermint_key(
tendermint_public_key: &str,
) -> Result<PublicKey, TerraRustAPIError> {
let len = tendermint_public_key.len();
if len == 83 {
PublicKey::check_prefix_and_length("terravalconspub", tendermint_public_key, len)
.and_then(|vu5| {
let vu8 = Vec::from_base32(vu5.as_slice()).map_err(|source| {
TerraRustAPIError::Conversion {
key: tendermint_public_key.into(),
source,
}
})?;
log::debug!("{:#?}", hex::encode(&vu8));
if vu8.starts_with(&BECH32_PUBKEY_DATA_PREFIX_SECP256K1) {
let public_key = PublicKey::public_key_from_pubkey(&vu8)?;
let raw = PublicKey::address_from_public_key(&public_key);
Ok(PublicKey {
raw_pub_key: Some(vu8),
raw_address: Some(raw),
})
} else {
Err(TerraRustAPIError::ConversionSECP256k1)
}
})
} else if len == 82 {
PublicKey::check_prefix_and_length("terravalconspub", tendermint_public_key, len)
.and_then(|vu5| {
let vu8 = Vec::from_base32(vu5.as_slice()).map_err(|source| {
TerraRustAPIError::Conversion {
key: tendermint_public_key.into(),
source,
}
})?;
log::info!("ED25519 public keys are not fully supported");
if vu8.starts_with(&BECH32_PUBKEY_DATA_PREFIX_ED25519) {
let raw = PublicKey::address_from_public_ed25519_key(&vu8)?;
Ok(PublicKey {
raw_pub_key: Some(vu8),
raw_address: Some(raw),
})
} else {
Err(TerraRustAPIError::ConversionED25519)
}
})
} else {
Err(TerraRustAPIError::ConversionLength(len))
}
}
pub fn from_tendermint_address(
tendermint_hex_address: &str,
) -> Result<PublicKey, TerraRustAPIError> {
let len = tendermint_hex_address.len();
if len == 40 {
let raw = hex::decode(tendermint_hex_address)?;
Ok(PublicKey {
raw_pub_key: None,
raw_address: Some(raw),
})
} else {
Err(TerraRustAPIError::ConversionLengthED25519Hex(len))
}
}
pub fn from_operator_address(valoper_address: &str) -> Result<PublicKey, TerraRustAPIError> {
PublicKey::check_prefix_and_length("terravaloper", valoper_address, 51).and_then(|vu5| {
let vu8 = Vec::from_base32(vu5.as_slice()).map_err(|source| {
TerraRustAPIError::Conversion {
key: valoper_address.into(),
source,
}
})?;
Ok(PublicKey {
raw_pub_key: None,
raw_address: Some(vu8),
})
})
}
pub fn from_raw_address(raw_address: &str) -> Result<PublicKey, TerraRustAPIError> {
let vec1 = hex::decode(raw_address)?;
Ok(PublicKey {
raw_pub_key: None,
raw_address: Some(vec1),
})
}
fn check_prefix_and_length(
prefix: &str,
data: &str,
length: usize,
) -> Result<Vec<u5>, TerraRustAPIError> {
let (hrp, decoded_str, _) =
decode(data).map_err(|source| TerraRustAPIError::Conversion {
key: data.into(),
source,
})?;
if hrp == prefix && data.len() == length {
Ok(decoded_str)
} else {
Err(TerraRustAPIError::Bech32DecodeExpanded(
hrp,
data.len(),
prefix.into(),
length,
))
}
}
pub fn pubkey_from_public_key(public_key: &[u8]) -> Vec<u8> {
[
BECH32_PUBKEY_DATA_PREFIX_SECP256K1.to_vec(),
public_key.to_vec(),
]
.concat()
}
pub fn pubkey_from_ed25519_public_key(public_key: &[u8]) -> Vec<u8> {
[
BECH32_PUBKEY_DATA_PREFIX_ED25519.to_vec(),
public_key.to_vec(),
]
.concat()
}
pub fn public_key_from_pubkey(pub_key: &[u8]) -> Result<Vec<u8>, TerraRustAPIError> {
if pub_key.starts_with(&BECH32_PUBKEY_DATA_PREFIX_SECP256K1) {
let len = BECH32_PUBKEY_DATA_PREFIX_SECP256K1.len();
let len2 = pub_key.len();
Ok(Vec::from(&pub_key[len..len2]))
} else if pub_key.starts_with(&BECH32_PUBKEY_DATA_PREFIX_ED25519) {
let len = BECH32_PUBKEY_DATA_PREFIX_ED25519.len();
let len2 = pub_key.len();
let vec = &pub_key[len..len2];
let ed25519_pubkey = ed25519_dalek::PublicKey::from_bytes(vec)?;
Ok(ed25519_pubkey.to_bytes().to_vec())
} else {
log::info!("pub key does not start with BECH32 PREFIX");
Err(TerraRustAPIError::Bech32DecodeErr)
}
}
pub fn address_from_public_key(public_key: &[u8]) -> Vec<u8> {
let mut hasher = Ripemd160::new();
let mut sha = Sha256::new();
let mut sha_result: [u8; 32] = [0; 32];
let mut ripe_result: [u8; 20] = [0; 20];
sha.input(public_key);
sha.result(&mut sha_result);
hasher.input(&sha_result);
hasher.result(&mut ripe_result);
let address: Vec<u8> = ripe_result.to_vec();
address
}
pub fn address_from_public_ed25519_key(
public_key: &[u8],
) -> Result<Vec<u8>, TerraRustAPIError> {
if public_key.len() != (32 + 5) {
Err(TerraRustAPIError::ConversionPrefixED25519(
public_key.len(),
hex::encode(public_key),
))
} else {
log::debug!(
"address_from_public_ed25519_key public key - {}",
hex::encode(public_key)
);
let mut sha = Sha256::new();
let mut sha_result: [u8; 32] = [0; 32];
sha.input(&public_key[5..]);
sha.result(&mut sha_result);
let address: Vec<u8> = sha_result[0..20].to_vec();
log::debug!(
"address_from_public_ed25519_key sha result - {}",
hex::encode(&address)
);
Ok(address)
}
}
pub fn account(&self) -> Result<String, TerraRustAPIError> {
match &self.raw_address {
Some(raw) => {
let data = encode("terra", raw.to_base32(), Variant::Bech32);
match data {
Ok(acc) => Ok(acc),
Err(_) => Err(TerraRustAPIError::Bech32DecodeErr),
}
}
None => Err(TerraRustAPIError::Implementation),
}
}
pub fn operator_address(&self) -> Result<String, TerraRustAPIError> {
match &self.raw_address {
Some(raw) => {
let data = encode("terravaloper", raw.to_base32(), Variant::Bech32);
match data {
Ok(acc) => Ok(acc),
Err(_) => Err(TerraRustAPIError::Bech32DecodeErr),
}
}
None => Err(TerraRustAPIError::Implementation),
}
}
pub fn application_public_key(&self) -> Result<String, TerraRustAPIError> {
match &self.raw_pub_key {
Some(raw) => {
let data = encode("terrapub", raw.to_base32(), Variant::Bech32);
match data {
Ok(acc) => Ok(acc),
Err(_) => Err(TerraRustAPIError::Bech32DecodeErr),
}
}
None => {
log::warn!("Missing Public Key. Can't continue");
Err(TerraRustAPIError::Implementation)
}
}
}
pub fn operator_address_public_key(&self) -> Result<String, TerraRustAPIError> {
match &self.raw_pub_key {
Some(raw) => {
let data = encode("terravaloperpub", raw.to_base32(), Variant::Bech32);
match data {
Ok(acc) => Ok(acc),
Err(_) => Err(TerraRustAPIError::Bech32DecodeErr),
}
}
None => Err(TerraRustAPIError::Implementation),
}
}
pub fn tendermint(&self) -> Result<String, TerraRustAPIError> {
match &self.raw_address {
Some(raw) => {
let data = encode("terravalcons", raw.to_base32(), Variant::Bech32);
match data {
Ok(acc) => Ok(acc),
Err(_) => Err(TerraRustAPIError::Bech32DecodeErr),
}
}
None => Err(TerraRustAPIError::Implementation),
}
}
pub fn tendermint_pubkey(&self) -> Result<String, TerraRustAPIError> {
match &self.raw_pub_key {
Some(raw) => {
let b32 = raw.to_base32();
let data = encode("terravalconspub", b32, Variant::Bech32);
match data {
Ok(acc) => Ok(acc),
Err(_) => Err(TerraRustAPIError::Bech32DecodeErr),
}
}
None => Err(TerraRustAPIError::Implementation),
}
}
}
#[cfg(test)]
mod tst {
use super::*;
#[allow(unused_imports)]
use dotenv::dotenv;
#[allow(unused_imports)]
use env_logger;
#[test]
pub fn tst_conv() -> anyhow::Result<()> {
let pub_key = PublicKey::from_account("terra1jnzv225hwl3uxc5wtnlgr8mwy6nlt0vztv3qqm")?;
assert_eq!(
&pub_key.account()?,
"terra1jnzv225hwl3uxc5wtnlgr8mwy6nlt0vztv3qqm"
);
assert_eq!(
&pub_key.operator_address()?,
"terravaloper1jnzv225hwl3uxc5wtnlgr8mwy6nlt0vztraasg"
);
assert_eq!(
&pub_key.tendermint()?,
"terravalcons1jnzv225hwl3uxc5wtnlgr8mwy6nlt0vzlswpuf"
);
let x = &pub_key.raw_address.unwrap();
assert_eq!(hex::encode(x), "94c4c52a9777e3c3628e5cfe819f6e26a7f5bd82");
Ok(())
}
#[test]
pub fn test_key_conversions() -> anyhow::Result<()> {
let pub_key = PublicKey::from_public_key(&hex::decode(
"02cf7ed0b5832538cd89b55084ce93399b186e381684b31388763801439cbdd20a",
)?);
assert_eq!(
&pub_key.operator_address()?,
"terravaloper1jnzv225hwl3uxc5wtnlgr8mwy6nlt0vztraasg"
);
assert_eq!(
&pub_key.account()?.to_string(),
"terra1jnzv225hwl3uxc5wtnlgr8mwy6nlt0vztv3qqm"
);
assert_eq!(
&pub_key.application_public_key()?,
"terrapub1addwnpepqt8ha594svjn3nvfk4ggfn5n8xd3sm3cz6ztxyugwcuqzsuuhhfq5nwzrf9"
);
assert_eq!(
&pub_key.tendermint_pubkey()?,
"terravalconspub1addwnpepqt8ha594svjn3nvfk4ggfn5n8xd3sm3cz6ztxyugwcuqzsuuhhfq5z3fguk"
);
let x = &pub_key.raw_address.unwrap();
assert_eq!(hex::encode(x), "94c4c52a9777e3c3628e5cfe819f6e26a7f5bd82");
let y = pub_key.raw_pub_key.unwrap();
assert_eq!(
hex::encode(y),
"eb5ae9872102cf7ed0b5832538cd89b55084ce93399b186e381684b31388763801439cbdd20a"
);
let valconspub_83 =
"terravalconspub1addwnpepqt8ha594svjn3nvfk4ggfn5n8xd3sm3cz6ztxyugwcuqzsuuhhfq5z3fguk";
let tendermint_pub_key = PublicKey::from_tendermint_key(valconspub_83)?;
assert_eq!(
&tendermint_pub_key.account()?.to_string(),
"terra1jnzv225hwl3uxc5wtnlgr8mwy6nlt0vztv3qqm"
);
assert_eq!(
&tendermint_pub_key.application_public_key()?,
"terrapub1addwnpepqt8ha594svjn3nvfk4ggfn5n8xd3sm3cz6ztxyugwcuqzsuuhhfq5nwzrf9"
);
assert_eq!(&tendermint_pub_key.tendermint_pubkey()?, valconspub_83);
let tendermint_pub_key_ed25519 = PublicKey::from_tendermint_key(
"terravalconspub1zcjduepqxrwvps0dn88x9s09h6nwrgrpv2vp5dz99309erlp0qmrx8y9ckmq49jx4n",
)?;
assert_eq!(
"terravalconspub1zcjduepqxrwvps0dn88x9s09h6nwrgrpv2vp5dz99309erlp0qmrx8y9ckmq49jx4n",
&tendermint_pub_key_ed25519.tendermint_pubkey()?
);
Ok(())
}
#[test]
pub fn test_tendermint() -> anyhow::Result<()> {
let secp256k1_public_key_str =
"02A1633CAFCC01EBFB6D78E39F687A1F0995C62FC95F51EAD10A02EE0BE551B5DC";
let seccp256k1_public_key =
PublicKey::from_public_key(&hex::decode(secp256k1_public_key_str)?);
assert_eq!(
"terrapub1addwnpepq2skx090esq7h7md0r3e76r6ruyet330e904r6k3pgpwuzl92x6actkch6g",
seccp256k1_public_key.application_public_key()?
);
let public_key = "4A25C6640A1F72B9C975338294EF51B6D1C33158BB6ECBA69FBC3FB5A33C9DCE";
let ed = Ed25519::from_bytes(&hex::decode(public_key)?)?;
let foo_v8 = PublicKey::pubkey_from_ed25519_public_key(&ed.to_bytes());
match encode("cosmosvalconspub", foo_v8.to_base32(),Variant::Bech32) {
Ok(cosmospub) => assert_eq!("cosmosvalconspub1zcjduepqfgjuveq2raetnjt4xwpffm63kmguxv2chdhvhf5lhslmtgeunh8qmf7exk", cosmospub),
Err(_) => assert!(false, "bad encoding"),
};
match encode("terravalconspub", foo_v8.to_base32(), Variant::Bech32) {
Ok(tendermint) => {
let ed_key = PublicKey::from_tendermint_key(&tendermint)?;
let foo = &ed_key.raw_pub_key.unwrap();
assert_eq!(public_key, hex::encode_upper(&foo[5..]));
}
Err(_) => assert!(false, "bad encoding"),
};
Ok(())
}
#[test]
pub fn test_proposer() -> anyhow::Result<()> {
let hex_str = "75161033EF6E116BB345F07910A493030B08AD12";
let cons_str = "terravalcons1w5tpqvl0dcgkhv697pu3pfynqv9s3tgj2d6q6l";
let cons_pub_str =
"terravalconspub1zcjduepqpxp3kxmn8yty9eh8a0e6tasdna04q7zsl88u7dyup7fv7t06pl9q342a8t";
let pk = PublicKey::from_tendermint_key(cons_pub_str)?;
assert_eq!(cons_str, pk.tendermint()?);
assert_eq!(hex_str, hex::encode_upper(&pk.raw_address.unwrap()));
let pk2 = PublicKey::from_tendermint_address(hex_str)?;
assert_eq!(cons_str, &pk2.tendermint()?);
Ok(())
}
}