use crate::errors::TerraRustAPIError;
use bitcoin::bech32::{decode, encode, u5, FromBase32, ToBase32};
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)]
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) -> anyhow::Result<PublicKey> {
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) -> anyhow::Result<PublicKey> {
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.into())
}
})
} 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::debug!("{:#?}", hex::encode(&vu8));
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: None,
})
} else {
eprintln!("{}", hex::encode(&vu8));
Err(TerraRustAPIError::ConversionED25519.into())
}
})
} else {
Err(TerraRustAPIError::ConversionLength(len).into())
}
}
pub fn from_operator_address(valoper_address: &str) -> anyhow::Result<PublicKey> {
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) -> anyhow::Result<PublicKey> {
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) -> anyhow::Result<Vec<u5>> {
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)
.into(),
)
}
}
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]) -> anyhow::Result<Vec<u8>> {
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.into())
}
}
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]) -> anyhow::Result<Vec<u8>> {
if public_key.len() != (32 + 5) {
Err(TerraRustAPIError::ConversionPrefixED25519(
public_key.len(),
hex::encode(public_key),
)
.into())
} else {
eprintln!("a_pub_ed_key {}", hex::encode(public_key));
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);
sha.result(&mut sha_result);
let address: Vec<u8> = sha_result[0..20].to_vec();
eprintln!("address_from_public_ed_key {}", hex::encode(&address));
log::debug!(
"address_from_public_ed25519_key sha result - {}",
hex::encode(&address)
);
Ok(address)
}
}
pub fn account(&self) -> anyhow::Result<String> {
match &self.raw_address {
Some(raw) => {
let data = encode("terra", raw.to_base32());
match data {
Ok(acc) => Ok(acc),
Err(_) => Err(TerraRustAPIError::Bech32DecodeErr.into()),
}
}
None => Err(TerraRustAPIError::Implementation.into()),
}
}
pub fn operator_address(&self) -> anyhow::Result<String> {
match &self.raw_address {
Some(raw) => {
let data = encode("terravaloper", raw.to_base32());
match data {
Ok(acc) => Ok(acc),
Err(_) => Err(TerraRustAPIError::Bech32DecodeErr.into()),
}
}
None => Err(TerraRustAPIError::Implementation.into()),
}
}
pub fn application_public_key(&self) -> anyhow::Result<String> {
match &self.raw_pub_key {
Some(raw) => {
let data = encode("terrapub", raw.to_base32());
match data {
Ok(acc) => Ok(acc),
Err(_) => Err(TerraRustAPIError::Bech32DecodeErr.into()),
}
}
None => {
log::warn!("Missing Public Key. Can't continue");
Err(TerraRustAPIError::Implementation.into())
}
}
}
pub fn operator_address_public_key(&self) -> anyhow::Result<String> {
match &self.raw_pub_key {
Some(raw) => {
let data = encode("terravaloperpub", raw.to_base32());
match data {
Ok(acc) => Ok(acc),
Err(_) => Err(TerraRustAPIError::Bech32DecodeErr.into()),
}
}
None => Err(TerraRustAPIError::Implementation.into()),
}
}
pub fn tendermint(&self) -> anyhow::Result<String> {
match &self.raw_address {
Some(raw) => {
let data = encode("terravalcons", raw.to_base32());
match data {
Ok(acc) => Ok(acc),
Err(_) => Err(TerraRustAPIError::Bech32DecodeErr.into()),
}
}
None => Err(TerraRustAPIError::Implementation.into()),
}
}
pub fn tendermint_pubkey(&self) -> anyhow::Result<String> {
match &self.raw_pub_key {
Some(raw) => {
let b32 = raw.to_base32();
let data = encode("terravalconspub", b32);
match data {
Ok(acc) => Ok(acc),
Err(_) => Err(TerraRustAPIError::Bech32DecodeErr.into()),
}
}
None => Err(TerraRustAPIError::Implementation.into()),
}
}
}
#[cfg(test)]
mod tst {
use super::*;
#[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());
let ed2: tendermint::PublicKey =
tendermint::PublicKey::from_raw_ed25519(&hex::decode(public_key)?).unwrap();
match encode("cosmosvalconspub", foo_v8.to_base32()) {
Ok(cosmospub) => assert_eq!("cosmosvalconspub1zcjduepqfgjuveq2raetnjt4xwpffm63kmguxv2chdhvhf5lhslmtgeunh8qmf7exk", cosmospub),
Err(_) => assert!(false, "bad encoding"),
};
assert_eq!(
"cosmosvalconspub1zcjduepqfgjuveq2raetnjt4xwpffm63kmguxv2chdhvhf5lhslmtgeunh8qmf7exk",
ed2.to_bech32("cosmosvalconspub")
);
match encode("terravalconspub", foo_v8.to_base32()) {
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(())
}
}