use crate::util::to_arr;
use hex;
use std::{fmt, ops, str::FromStr};
use crate::{EthereumPrivateKey, keccak256};
use bitcoin::bip32::Xpriv as Bitcoin_Xpriv;
use std::convert::TryFrom;
use crate::error::VaultError;
use secp256k1::PublicKey;
use crate::convert::error::ConversionError;
pub const ETHEREUM_ADDRESS_BYTES: usize = 20;
#[derive(Clone, Copy, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct EthereumAddress(pub [u8; ETHEREUM_ADDRESS_BYTES]);
impl EthereumAddress {
pub fn try_from(data: &[u8]) -> Result<Self, ConversionError> {
if data.len() != ETHEREUM_ADDRESS_BYTES {
return Err(ConversionError::InvalidLength);
}
Ok(EthereumAddress(to_arr(data)))
}
}
impl ops::Deref for EthereumAddress {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<[u8; ETHEREUM_ADDRESS_BYTES]> for EthereumAddress {
fn from(bytes: [u8; ETHEREUM_ADDRESS_BYTES]) -> Self {
EthereumAddress(bytes)
}
}
impl AsRef<[u8]> for EthereumAddress {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl FromStr for EthereumAddress {
type Err = ConversionError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() != ETHEREUM_ADDRESS_BYTES * 2 && !s.starts_with("0x") {
return Err(ConversionError::InvalidLength);
}
let value = if s.starts_with("0x") {
s.split_at(2).1
} else {
s
};
EthereumAddress::try_from(hex::decode(value)?.as_slice())
}
}
impl fmt::Display for EthereumAddress {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "0x{}", hex::encode(self.0))
}
}
impl fmt::Debug for EthereumAddress {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "0x{}", hex::encode(self.0))
}
}
impl TryFrom<Bitcoin_Xpriv> for EthereumPrivateKey {
type Error = VaultError;
fn try_from(value: Bitcoin_Xpriv) -> Result<Self, Self::Error> {
EthereumPrivateKey::try_from(value.private_key.secret_bytes().as_ref())
.map_err(|_| VaultError::InvalidPrivateKey)
}
}
impl From<PublicKey> for EthereumAddress {
fn from(value: PublicKey) -> Self {
let hash = keccak256(&value.serialize_uncompressed()[1..] );
EthereumAddress(to_arr(&hash[12..]))
}
}
impl From<bitcoin::PublicKey> for EthereumAddress {
fn from(value: bitcoin::PublicKey) -> Self {
let hash = keccak256(&value.inner.serialize_uncompressed()[1..] );
EthereumAddress(to_arr(&hash[12..]))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn should_display_zero_address() {
assert_eq!(
EthereumAddress::default().to_string(),
"0x0000000000000000000000000000000000000000"
);
}
#[test]
fn should_display_real_address() {
let addr = EthereumAddress([
0x0e, 0x7c, 0x04, 0x51, 0x10, 0xb8, 0xdb, 0xf2, 0x97, 0x65, 0x04, 0x73, 0x80, 0x89,
0x89, 0x19, 0xc5, 0xcb, 0x56, 0xf4,
]);
assert_eq!(
addr.to_string(),
"0x0e7c045110b8dbf29765047380898919c5cb56f4"
);
}
#[test]
fn should_parse_real_address() {
let addr = EthereumAddress([
0x0e, 0x7c, 0x04, 0x51, 0x10, 0xb8, 0xdb, 0xf2, 0x97, 0x65, 0x04, 0x73, 0x80, 0x89,
0x89, 0x19, 0xc5, 0xcb, 0x56, 0xf4,
]);
assert_eq!(
"0x0e7c045110b8dbf29765047380898919c5cb56f4"
.parse::<EthereumAddress>()
.unwrap(),
addr
);
}
#[test]
fn should_parse_real_address_without_prefix() {
let addr = EthereumAddress([
0x0e, 0x7c, 0x04, 0x51, 0x10, 0xb8, 0xdb, 0xf2, 0x97, 0x65, 0x04, 0x73, 0x80, 0x89,
0x89, 0x19, 0xc5, 0xcb, 0x56, 0xf4,
]);
assert_eq!(
"0e7c045110b8dbf29765047380898919c5cb56f4"
.parse::<EthereumAddress>()
.unwrap(),
addr
);
}
#[test]
fn should_catch_wrong_address_encoding() {
assert!("0x___c045110b8dbf29765047380898919c5cb56f4"
.parse::<EthereumAddress>()
.is_err());
}
#[test]
fn should_catch_wrong_address_insufficient_length() {
assert!("0x0e7c045110b8dbf297650473808989"
.parse::<EthereumAddress>()
.is_err());
}
#[test]
fn should_catch_wrong_address_excess_length() {
assert!("0x0e7c045110b8dbf29765047380898919c5cb56f400000000"
.parse::<EthereumAddress>()
.is_err());
}
#[test]
fn should_catch_wrong_address_prefix() {
assert!("0_0e7c045110b8dbf29765047380898919c5cb56f4"
.parse::<EthereumAddress>()
.is_err());
}
#[test]
fn should_catch_missing_address_prefix() {
assert!("_".parse::<EthereumAddress>().is_err());
}
#[test]
fn should_catch_empty_address_string() {
assert!("".parse::<EthereumAddress>().is_err());
}
}