use crate::daemon::error::DaemonError;
use bitcoin::bech32::{decode, encode, u5, FromBase32, ToBase32, Variant};
pub use ed25519_dalek::PublicKey as Ed25519;
use ring::digest::{Context, SHA256};
use ripemd::{Digest as _, Ripemd160};
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::key::PublicKey) -> PublicKey {
let bpub_bytes = bpub.inner.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, prefix: &str) -> Result<PublicKey, DaemonError> {
PublicKey::check_prefix_and_length(prefix, acc_address, 44).and_then(|vu5| {
let vu8 =
Vec::from_base32(vu5.as_slice()).map_err(|source| DaemonError::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, DaemonError> {
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| {
DaemonError::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(DaemonError::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| {
DaemonError::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(DaemonError::ConversionED25519)
}
})
} else {
Err(DaemonError::ConversionLength(len))
}
}
pub fn from_tendermint_address(tendermint_hex_address: &str) -> Result<PublicKey, DaemonError> {
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(DaemonError::ConversionLengthED25519Hex(len))
}
}
pub fn from_operator_address(valoper_address: &str) -> Result<PublicKey, DaemonError> {
PublicKey::check_prefix_and_length("terravaloper", valoper_address, 51).and_then(|vu5| {
let vu8 =
Vec::from_base32(vu5.as_slice()).map_err(|source| DaemonError::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, DaemonError> {
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>, DaemonError> {
let (hrp, decoded_str, _) = decode(data).map_err(|source| DaemonError::Conversion {
key: data.into(),
source,
})?;
if hrp == prefix && data.len() == length {
Ok(decoded_str)
} else {
Err(DaemonError::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>, DaemonError> {
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(DaemonError::Bech32DecodeErr)
}
}
pub fn address_from_public_key(public_key: &[u8]) -> Vec<u8> {
let mut hasher = Ripemd160::new();
let sha_result = ring::digest::digest(&SHA256, public_key);
hasher.update(&sha_result.as_ref()[0..32]);
let ripe_result = hasher.finalize();
let address: Vec<u8> = ripe_result[0..20].to_vec();
address
}
pub fn address_from_public_ed25519_key(public_key: &[u8]) -> Result<Vec<u8>, DaemonError> {
if public_key.len() != (32 + 5) {
Err(DaemonError::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_result: [u8; 32] = [0; 32];
let sha_result = ring::digest::digest(&SHA256, &public_key[5..]);
let address: Vec<u8> = sha_result.as_ref()[0..20].to_vec();
log::debug!(
"address_from_public_ed25519_key sha result - {}",
hex::encode(&address)
);
Ok(address)
}
}
pub fn account(&self, prefix: &str) -> Result<String, DaemonError> {
match &self.raw_address {
Some(raw) => {
let data = encode(prefix, raw.to_base32(), Variant::Bech32);
match data {
Ok(acc) => Ok(acc),
Err(_) => Err(DaemonError::Bech32DecodeErr),
}
}
None => Err(DaemonError::Implementation),
}
}
pub fn operator_address(&self, prefix: &str) -> Result<String, DaemonError> {
match &self.raw_address {
Some(raw) => {
let data = encode(
&format!("{}{}", prefix, "valoper"),
raw.to_base32(),
Variant::Bech32,
);
match data {
Ok(acc) => Ok(acc),
Err(_) => Err(DaemonError::Bech32DecodeErr),
}
}
None => Err(DaemonError::Implementation),
}
}
pub fn application_public_key(&self, prefix: &str) -> Result<String, DaemonError> {
match &self.raw_pub_key {
Some(raw) => {
let data = encode(
&format!("{}{}", prefix, "pub"),
raw.to_base32(),
Variant::Bech32,
);
match data {
Ok(acc) => Ok(acc),
Err(_) => Err(DaemonError::Bech32DecodeErr),
}
}
None => {
log::warn!("Missing Public Key. Can't continue");
Err(DaemonError::Implementation)
}
}
}
pub fn operator_address_public_key(&self, prefix: &str) -> Result<String, DaemonError> {
match &self.raw_pub_key {
Some(raw) => {
let data = encode(
&format!("{}{}", prefix, "valoperpub"),
raw.to_base32(),
Variant::Bech32,
);
match data {
Ok(acc) => Ok(acc),
Err(_) => Err(DaemonError::Bech32DecodeErr),
}
}
None => Err(DaemonError::Implementation),
}
}
pub fn tendermint(&self, prefix: &str) -> Result<String, DaemonError> {
match &self.raw_address {
Some(raw) => {
let data = encode(
&format!("{}{}", prefix, "valcons"),
raw.to_base32(),
Variant::Bech32,
);
match data {
Ok(acc) => Ok(acc),
Err(_) => Err(DaemonError::Bech32DecodeErr),
}
}
None => Err(DaemonError::Implementation),
}
}
pub fn tendermint_pubkey(&self, prefix: &str) -> Result<String, DaemonError> {
match &self.raw_pub_key {
Some(raw) => {
let b32 = raw.to_base32();
let data = encode(&format!("{}{}", prefix, "valconspub"), b32, Variant::Bech32);
match data {
Ok(acc) => Ok(acc),
Err(_) => Err(DaemonError::Bech32DecodeErr),
}
}
None => Err(DaemonError::Implementation),
}
}
}
#[cfg(test)]
mod tst {
use super::*;
const PREFIX: &str = "terra";
#[test]
pub fn tst_conv() -> anyhow::Result<()> {
let pub_key =
PublicKey::from_account("terra1jnzv225hwl3uxc5wtnlgr8mwy6nlt0vztv3qqm", PREFIX)?;
assert_eq!(
&pub_key.account(PREFIX)?,
"terra1jnzv225hwl3uxc5wtnlgr8mwy6nlt0vztv3qqm"
);
assert_eq!(
&pub_key.operator_address(PREFIX)?,
"terravaloper1jnzv225hwl3uxc5wtnlgr8mwy6nlt0vztraasg"
);
assert_eq!(
&pub_key.tendermint(PREFIX)?,
"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(PREFIX)?,
"terravaloper1jnzv225hwl3uxc5wtnlgr8mwy6nlt0vztraasg"
);
assert_eq!(
&pub_key.account(PREFIX)?,
"terra1jnzv225hwl3uxc5wtnlgr8mwy6nlt0vztv3qqm"
);
assert_eq!(
&pub_key.application_public_key(PREFIX)?,
"terrapub1addwnpepqt8ha594svjn3nvfk4ggfn5n8xd3sm3cz6ztxyugwcuqzsuuhhfq5nwzrf9"
);
assert_eq!(
&pub_key.tendermint_pubkey(PREFIX)?,
"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(PREFIX)?,
"terra1jnzv225hwl3uxc5wtnlgr8mwy6nlt0vztv3qqm"
);
assert_eq!(
&tendermint_pub_key.application_public_key(PREFIX)?,
"terrapub1addwnpepqt8ha594svjn3nvfk4ggfn5n8xd3sm3cz6ztxyugwcuqzsuuhhfq5nwzrf9"
);
assert_eq!(
&tendermint_pub_key.tendermint_pubkey(PREFIX)?,
valconspub_83
);
let tendermint_pub_key_ed25519 = PublicKey::from_tendermint_key(
"terravalconspub1zcjduepqxrwvps0dn88x9s09h6nwrgrpv2vp5dz99309erlp0qmrx8y9ckmq49jx4n",
)?;
assert_eq!(
"terravalconspub1zcjduepqxrwvps0dn88x9s09h6nwrgrpv2vp5dz99309erlp0qmrx8y9ckmq49jx4n",
&tendermint_pub_key_ed25519.tendermint_pubkey(PREFIX)?
);
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(PREFIX)?
);
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(PREFIX)?);
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(PREFIX)?);
Ok(())
}
}