diff --git a/rust/tw_any_coin/tests/tw_any_address_ffi_tests.rs b/rust/tw_any_coin/tests/tw_any_address_ffi_tests.rs index e45e4d2b10d..856c54172a1 100644 --- a/rust/tw_any_coin/tests/tw_any_address_ffi_tests.rs +++ b/rust/tw_any_coin/tests/tw_any_address_ffi_tests.rs @@ -34,7 +34,7 @@ fn test_any_address_derive() { // TODO match `CoinType` when it's generated. let expected_address = match coin.blockchain { - BlockchainType::Aptos => "", + BlockchainType::Aptos => "0x9006fa46f038224e8004bdda97f2e7a60c2c3d135bce7cb15541e5c0aae907a4", // By default, Bitcoin will return a P2PKH address. BlockchainType::Bitcoin => "19cAJn4Ms8jodBBGtroBNNpCZiHAWGAq7X", BlockchainType::Ethereum => "0xAc1ec44E4f0ca7D172B7803f6836De87Fb72b309", @@ -111,9 +111,8 @@ fn test_any_address_is_valid_coin() { "0xeeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b", "eeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b", "19aadeca9388e009d136245b9a67423f3eee242b03142849eb4f81a4a409e59c", - "777821c78442e17d82c3d7a371f42de7189e4248e529fe6eee6bca40ddbb", "0x777821c78442e17d82c3d7a371f42de7189e4248e529fe6eee6bca40ddbb", - "eeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175" // Too short automatically padded + "0xeeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175" ], BlockchainType::Bitcoin => vec![ "1MrZNGN7mfWZiZNQttrzHjfw72jnJC2JNx", diff --git a/rust/tw_aptos/src/address.rs b/rust/tw_aptos/src/address.rs index 080c28a9d97..17e699a923b 100644 --- a/rust/tw_aptos/src/address.rs +++ b/rust/tw_aptos/src/address.rs @@ -9,13 +9,33 @@ use std::str::FromStr; use move_core_types::account_address::{AccountAddress, AccountAddressParseError}; use tw_coin_entry::coin_entry::CoinAddress; use tw_coin_entry::error::AddressError; +use tw_keypair::ed25519; use tw_memory::Data; +use tw_hash::sha3::sha3_256; + + +#[derive(Debug)] +#[repr(u8)] +pub enum Scheme { + Ed25519 = 0, +} #[derive(Clone, Copy, Debug, PartialEq)] pub struct Address { addr: AccountAddress, } +impl Address { + /// Initializes an address with a `ed25519` public key. + pub fn with_ed25519_pubkey(pubkey: &ed25519::sha512::PublicKey) -> Result { + let mut to_hash = pubkey.as_slice().to_vec(); + to_hash.push(Scheme::Ed25519 as u8); + let hashed = sha3_256(to_hash.as_slice()); + let addr = AccountAddress::from_bytes(hashed).map_err(from_account_error)?; + Ok(Address{addr}) + } +} + impl Display for Address { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.addr.to_hex_literal()) @@ -38,7 +58,52 @@ impl FromStr for Address { type Err = AddressError; fn from_str(s: &str) -> Result { - let addr = AccountAddress::from_str(s).map_err(from_account_error)?; + const NUM_CHARS: usize = AccountAddress::LENGTH * 2; + let mut has_0x = false; + let mut working = s.trim(); + + // Checks if it has a 0x at the beginning, which is okay + if working.starts_with("0x") { + has_0x = true; + working = &working[2..]; + } + + if working.len() > NUM_CHARS { + return Err(AddressError::InvalidInput) + } else if !has_0x && working.len() < NUM_CHARS { + return Err(AddressError::InvalidInput) + } + + if !working.chars().all(|c| char::is_ascii_hexdigit(&c)) { + return Err(AddressError::InvalidInput) + } + + let addr = if has_0x { + AccountAddress::from_hex_literal(s.trim()) + } else { + AccountAddress::from_str(s.trim()) + }.map_err(from_account_error)?; + Ok(Address{addr}) } +} + +#[cfg(test)] +mod tests { + use super::*; + use tw_keypair::ed25519::sha512::PrivateKey; + + #[test] + fn test_from_public_key() { + let private = PrivateKey::try_from( + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", + ) + .unwrap(); + let public = private.public(); + let addr = Address::with_ed25519_pubkey(&public); + assert_eq!( + addr.unwrap().to_string(), + "0x9006fa46f038224e8004bdda97f2e7a60c2c3d135bce7cb15541e5c0aae907a4" + ); + } } \ No newline at end of file diff --git a/rust/tw_aptos/src/entry.rs b/rust/tw_aptos/src/entry.rs index d8a348f4571..a01e5417662 100644 --- a/rust/tw_aptos/src/entry.rs +++ b/rust/tw_aptos/src/entry.rs @@ -9,7 +9,7 @@ use std::str::FromStr; use tw_coin_entry::coin_context::CoinContext; use tw_coin_entry::coin_entry::{CoinEntry, PublicKeyBytes, SignatureBytes}; use tw_coin_entry::derivation::Derivation; -use tw_coin_entry::error::{AddressResult}; +use tw_coin_entry::error::{AddressError, AddressResult}; use tw_coin_entry::modules::json_signer::NoJsonSigner; use tw_coin_entry::modules::message_signer::NoMessageSigner; use tw_coin_entry::modules::plan_builder::NoPlanBuilder; @@ -51,7 +51,10 @@ impl CoinEntry for AptosEntry { _derivation: Derivation, _prefix: Option, ) -> AddressResult { - todo!() + let public_key = public_key + .to_ed25519() + .ok_or(AddressError::PublicKeyTypeMismatch)?; + Address::with_ed25519_pubkey(public_key) } #[inline] diff --git a/rust/tw_keypair/src/tw/public.rs b/rust/tw_keypair/src/tw/public.rs index e8a31f0fc20..46cb0e4f7e9 100644 --- a/rust/tw_keypair/src/tw/public.rs +++ b/rust/tw_keypair/src/tw/public.rs @@ -135,4 +135,13 @@ impl PublicKey { _ => None, } } + + pub fn to_ed25519(&self) -> Option<&ed25519::sha512::PublicKey> { + match self { + PublicKey::Ed25519(ed25519) => { + Some(ed25519) + }, + _ => None, + } + } }