From d75f17834a758d70b49a6e34cdc64d35b8c1b976 Mon Sep 17 00:00:00 2001 From: Satoshi Otomakan Date: Wed, 15 Nov 2023 00:37:27 +0100 Subject: [PATCH] [Cosmos]: Expose bech32 address prefix via FFI * Implement missing TX compiler for Cosmos chains * TODO: Fix TWAnyAddressData --- rust/chains/tw_cosmos/src/entry.rs | 50 ++++++++++++------ rust/chains/tw_native_evmos/src/entry.rs | 51 +++++++++++++------ rust/chains/tw_native_injective/src/entry.rs | 47 ++++++++++++----- rust/tw_any_coin/src/any_address.rs | 10 +++- rust/tw_any_coin/src/ffi/tw_any_address.rs | 51 +++++++++++++++++++ .../tests/tw_any_address_ffi_tests.rs | 40 ++++++++++++++- rust/tw_bech32_address/src/bech32_prefix.rs | 22 ++++++++ rust/tw_bech32_address/src/lib.rs | 24 ++++++++- rust/tw_bitcoin/src/entry.rs | 12 ++++- rust/tw_coin_entry/src/coin_entry.rs | 8 +++ rust/tw_coin_entry/src/coin_entry_ext.rs | 36 +++---------- .../tw_coin_entry/src/common/compile_input.rs | 20 +++----- rust/tw_coin_entry/src/derivation.rs | 6 +++ rust/tw_cosmos_sdk/src/address.rs | 5 +- .../src/modules/compiler/tw_compiler.rs | 39 ++++++++------ .../src/modules/signer/tw_signer.rs | 4 +- .../src/test_utils/sign_utils.rs | 4 +- rust/tw_ethereum/src/entry.rs | 10 ++++ rust/tw_evm/src/modules/compiler.rs | 3 +- rust/tw_internet_computer/src/entry.rs | 9 ++++ rust/tw_ronin/src/entry.rs | 9 ++++ src/CoinEntry.cpp | 37 +++++++++----- src/Cosmos/Entry.cpp | 2 +- .../Cosmos/NativeEvmos/TWCoinTypeTests.cpp | 2 +- .../NativeInjective/TWCoinTypeTests.cpp | 2 +- 25 files changed, 371 insertions(+), 132 deletions(-) create mode 100644 rust/tw_bech32_address/src/bech32_prefix.rs diff --git a/rust/chains/tw_cosmos/src/entry.rs b/rust/chains/tw_cosmos/src/entry.rs index 993bb5a9d0c..ca2d84efcf7 100644 --- a/rust/chains/tw_cosmos/src/entry.rs +++ b/rust/chains/tw_cosmos/src/entry.rs @@ -4,6 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +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; @@ -11,9 +12,9 @@ use tw_coin_entry::error::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; -use tw_coin_entry::prefix::NoPrefix; -use tw_cosmos_sdk::address::{Address, CosmosAddress}; +use tw_cosmos_sdk::address::{Address, Bech32Prefix}; use tw_cosmos_sdk::context::StandardCosmosContext; +use tw_cosmos_sdk::modules::compiler::tw_compiler::TWTransactionCompiler; use tw_cosmos_sdk::modules::signer::tw_signer::TWSigner; use tw_keypair::tw; use tw_proto::Cosmos::Proto; @@ -22,7 +23,7 @@ use tw_proto::TxCompiler::Proto as CompilerProto; pub struct CosmosEntry; impl CoinEntry for CosmosEntry { - type AddressPrefix = NoPrefix; + type AddressPrefix = Bech32Prefix; type Address = Address; type SigningInput<'a> = Proto::SigningInput<'a>; type SigningOutput = Proto::SigningOutput<'static>; @@ -31,44 +32,63 @@ impl CoinEntry for CosmosEntry { type PlanBuilder = NoPlanBuilder; type MessageSigner = NoMessageSigner; + #[inline] fn parse_address( &self, coin: &dyn CoinContext, address: &str, - _prefix: Option, + prefix: Option, ) -> AddressResult { - Address::from_str_with_coin(coin, address) + Address::from_str_with_coin_and_prefix(coin, address.to_string(), prefix) } + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + Address::from_str(address) + } + + #[inline] fn derive_address( &self, coin: &dyn CoinContext, public_key: tw::PublicKey, _derivation: Derivation, - _prefix: Option, + prefix: Option, ) -> AddressResult { - Address::with_public_key_coin_context(coin, &public_key) + Address::with_public_key_coin_context(coin, &public_key, prefix) } + #[inline] fn sign(&self, coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::SigningOutput { TWSigner::::sign(coin, input) } + #[inline] fn preimage_hashes( &self, - _coin: &dyn CoinContext, - _input: Self::SigningInput<'_>, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, ) -> Self::PreSigningOutput { - todo!() + TWTransactionCompiler::::preimage_hashes(coin, input) } + #[inline] fn compile( &self, - _coin: &dyn CoinContext, - _input: Self::SigningInput<'_>, - _signatures: Vec, - _public_keys: Vec, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, ) -> Self::SigningOutput { - todo!() + TWTransactionCompiler::::compile( + coin, + input, + signatures, + public_keys, + ) } } diff --git a/rust/chains/tw_native_evmos/src/entry.rs b/rust/chains/tw_native_evmos/src/entry.rs index 54ff53b55f2..ab4b54ebdc3 100644 --- a/rust/chains/tw_native_evmos/src/entry.rs +++ b/rust/chains/tw_native_evmos/src/entry.rs @@ -5,6 +5,7 @@ // file LICENSE at the root of the source code distribution tree. use crate::context::NativeEvmosContext; +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; @@ -12,8 +13,9 @@ use tw_coin_entry::error::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; -use tw_coin_entry::prefix::NoPrefix; -use tw_cosmos_sdk::address::{Address, CosmosAddress}; +use tw_cosmos_sdk::address::{Address, Bech32Prefix}; +use tw_cosmos_sdk::context::StandardCosmosContext; +use tw_cosmos_sdk::modules::compiler::tw_compiler::TWTransactionCompiler; use tw_cosmos_sdk::modules::signer::tw_signer::TWSigner; use tw_keypair::tw; use tw_proto::Cosmos::Proto; @@ -22,7 +24,7 @@ use tw_proto::TxCompiler::Proto as CompilerProto; pub struct NativeEvmosEntry; impl CoinEntry for NativeEvmosEntry { - type AddressPrefix = NoPrefix; + type AddressPrefix = Bech32Prefix; type Address = Address; type SigningInput<'a> = Proto::SigningInput<'a>; type SigningOutput = Proto::SigningOutput<'static>; @@ -31,44 +33,63 @@ impl CoinEntry for NativeEvmosEntry { type PlanBuilder = NoPlanBuilder; type MessageSigner = NoMessageSigner; + #[inline] fn parse_address( &self, coin: &dyn CoinContext, address: &str, - _prefix: Option, + prefix: Option, ) -> AddressResult { - Address::from_str_with_coin(coin, address) + Address::from_str_with_coin_and_prefix(coin, address.to_string(), prefix) } + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + Address::from_str(address) + } + + #[inline] fn derive_address( &self, coin: &dyn CoinContext, public_key: tw::PublicKey, _derivation: Derivation, - _prefix: Option, + prefix: Option, ) -> AddressResult { - Address::with_public_key_coin_context(coin, &public_key) + Address::with_public_key_coin_context(coin, &public_key, prefix) } + #[inline] fn sign(&self, coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::SigningOutput { TWSigner::::sign(coin, input) } + #[inline] fn preimage_hashes( &self, - _coin: &dyn CoinContext, - _input: Self::SigningInput<'_>, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, ) -> Self::PreSigningOutput { - todo!() + TWTransactionCompiler::::preimage_hashes(coin, input) } + #[inline] fn compile( &self, - _coin: &dyn CoinContext, - _input: Self::SigningInput<'_>, - _signatures: Vec, - _public_keys: Vec, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, ) -> Self::SigningOutput { - todo!() + TWTransactionCompiler::::compile( + coin, + input, + signatures, + public_keys, + ) } } diff --git a/rust/chains/tw_native_injective/src/entry.rs b/rust/chains/tw_native_injective/src/entry.rs index e14012c468c..bf5ad0bd733 100644 --- a/rust/chains/tw_native_injective/src/entry.rs +++ b/rust/chains/tw_native_injective/src/entry.rs @@ -5,6 +5,7 @@ // file LICENSE at the root of the source code distribution tree. use crate::context::NativeInjectiveContext; +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; @@ -12,8 +13,9 @@ use tw_coin_entry::error::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; -use tw_coin_entry::prefix::NoPrefix; -use tw_cosmos_sdk::address::{Address, CosmosAddress}; +use tw_cosmos_sdk::address::{Address, Bech32Prefix, CosmosAddress}; +use tw_cosmos_sdk::context::StandardCosmosContext; +use tw_cosmos_sdk::modules::compiler::tw_compiler::TWTransactionCompiler; use tw_cosmos_sdk::modules::signer::tw_signer::TWSigner; use tw_keypair::tw; use tw_proto::Cosmos::Proto; @@ -22,7 +24,7 @@ use tw_proto::TxCompiler::Proto as CompilerProto; pub struct NativeInjectiveEntry; impl CoinEntry for NativeInjectiveEntry { - type AddressPrefix = NoPrefix; + type AddressPrefix = Bech32Prefix; type Address = Address; type SigningInput<'a> = Proto::SigningInput<'a>; type SigningOutput = Proto::SigningOutput<'static>; @@ -31,6 +33,7 @@ impl CoinEntry for NativeInjectiveEntry { type PlanBuilder = NoPlanBuilder; type MessageSigner = NoMessageSigner; + #[inline] fn parse_address( &self, coin: &dyn CoinContext, @@ -40,35 +43,53 @@ impl CoinEntry for NativeInjectiveEntry { Address::from_str_with_coin(coin, address) } + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + Address::from_str(address) + } + + #[inline] fn derive_address( &self, coin: &dyn CoinContext, public_key: tw::PublicKey, _derivation: Derivation, - _prefix: Option, + prefix: Option, ) -> AddressResult { - Address::with_public_key_coin_context(coin, &public_key) + Address::with_public_key_coin_context(coin, &public_key, prefix) } + #[inline] fn sign(&self, coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::SigningOutput { TWSigner::::sign(coin, input) } + #[inline] fn preimage_hashes( &self, - _coin: &dyn CoinContext, - _input: Self::SigningInput<'_>, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, ) -> Self::PreSigningOutput { - todo!() + TWTransactionCompiler::::preimage_hashes(coin, input) } + #[inline] fn compile( &self, - _coin: &dyn CoinContext, - _input: Self::SigningInput<'_>, - _signatures: Vec, - _public_keys: Vec, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, ) -> Self::SigningOutput { - todo!() + TWTransactionCompiler::::compile( + coin, + input, + signatures, + public_keys, + ) } } diff --git a/rust/tw_any_coin/src/any_address.rs b/rust/tw_any_coin/src/any_address.rs index 23fe3fe42e4..9359a4c7cb6 100644 --- a/rust/tw_any_coin/src/any_address.rs +++ b/rust/tw_any_coin/src/any_address.rs @@ -36,7 +36,8 @@ impl AnyAddress { prefix: Option, ) -> AddressResult { let (ctx, entry) = coin_dispatcher(coin).map_err(|_| AddressError::UnknownCoinType)?; - let address = entry.normalize_address(&ctx, address, prefix)?; + entry.validate_address(&ctx, address, prefix)?; + let address = entry.normalize_address(&ctx, address)?; Ok(AnyAddress { coin, address }) } @@ -56,8 +57,13 @@ impl AnyAddress { /// Returns underlying data (public key or key hash). #[inline] pub fn get_data(&self) -> AddressResult { + // TODO + println!( + "AnyAddress::get_data() coin={} address={}", + self.coin, self.address + ); let (ctx, entry) = coin_dispatcher(self.coin).map_err(|_| AddressError::UnknownCoinType)?; - entry.address_to_data(&ctx, &self.address, None) + entry.address_to_data(&ctx, &self.address) } /// Returns the address string representation. diff --git a/rust/tw_any_coin/src/ffi/tw_any_address.rs b/rust/tw_any_coin/src/ffi/tw_any_address.rs index 25aa2373a00..14367bc7aa1 100644 --- a/rust/tw_any_coin/src/ffi/tw_any_address.rs +++ b/rust/tw_any_coin/src/ffi/tw_any_address.rs @@ -8,6 +8,7 @@ use crate::any_address::AnyAddress; use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::prefix::AddressPrefix; use tw_keypair::ffi::pubkey::TWPublicKey; use tw_memory::ffi::tw_data::TWData; use tw_memory::ffi::tw_string::TWString; @@ -32,6 +33,28 @@ pub unsafe extern "C" fn tw_any_address_is_valid(string: *const TWString, coin: AnyAddress::is_valid(coin, string, None) } +/// Determines if the string is a valid Any address with the given hrp. +/// +/// \param string address to validate. +/// \param coin coin type of the address. +/// \param hrp explicit given hrp of the given address. +/// \return bool indicating if the address is valid. +#[no_mangle] +pub unsafe extern "C" fn tw_any_address_is_valid_bech32( + string: *const TWString, + coin: u32, + hrp: *const TWString, +) -> bool { + let string = try_or_false!(TWString::from_ptr_as_ref(string)); + let string = try_or_false!(string.as_str()); + + let hrp = try_or_false!(TWString::from_ptr_as_ref(hrp)); + let hrp = try_or_false!(hrp.as_str()); + + let prefix = AddressPrefix::Hrp(hrp.to_string()); + AnyAddress::is_valid(coin, string, Some(prefix)) +} + /// Creates an address from a string representation and a coin type. Must be deleted with `TWAnyAddressDelete` after use. /// /// \param string address to create. @@ -70,6 +93,34 @@ pub unsafe extern "C" fn tw_any_address_create_with_public_key_derivation( .unwrap_or_else(|_| std::ptr::null_mut()) } +/// Creates an bech32 address from a public key and a given hrp. +/// +/// \param public_key derivates the address from the public key. +/// \param coin coin type of the address. +/// \param hrp hrp of the address. +/// \return TWAnyAddress pointer or nullptr if public key is invalid. +#[no_mangle] +pub unsafe extern "C" fn tw_any_address_create_bech32_with_public_key( + public_key: *mut TWPublicKey, + coin: u32, + hrp: *const TWString, +) -> *mut TWAnyAddress { + let public_key = try_or_else!(TWPublicKey::from_ptr_as_ref(public_key), std::ptr::null_mut); + + let hrp = try_or_else!(TWString::from_ptr_as_ref(hrp), std::ptr::null_mut); + let hrp = try_or_else!(hrp.as_str(), std::ptr::null_mut); + + let prefix = AddressPrefix::Hrp(hrp.to_string()); + AnyAddress::with_public_key( + coin, + public_key.as_ref().clone(), + Derivation::default(), + Some(prefix), + ) + .map(|any_address| TWAnyAddress(any_address).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} + /// Deletes an address. /// /// \param address address to delete. 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 4618c552691..d6f5de08d72 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 @@ -5,8 +5,9 @@ // file LICENSE at the root of the source code distribution tree. use tw_any_coin::ffi::tw_any_address::{ - tw_any_address_create_with_public_key_derivation, tw_any_address_create_with_string, - tw_any_address_data, tw_any_address_description, tw_any_address_is_valid, + tw_any_address_create_bech32_with_public_key, tw_any_address_create_with_public_key_derivation, + tw_any_address_create_with_string, tw_any_address_data, tw_any_address_description, + tw_any_address_is_valid, tw_any_address_is_valid_bech32, }; use tw_any_coin::test_utils::TWAnyAddressHelper; use tw_coin_entry::derivation::Derivation; @@ -16,10 +17,12 @@ use tw_encoding::hex::DecodeHex; use tw_keypair::ffi::privkey::tw_private_key_get_public_key_by_type; use tw_keypair::test_utils::tw_private_key_helper::TWPrivateKeyHelper; use tw_keypair::test_utils::tw_public_key_helper::TWPublicKeyHelper; +use tw_keypair::tw::PublicKeyType; use tw_memory::test_utils::tw_data_helper::TWDataHelper; use tw_memory::test_utils::tw_string_helper::TWStringHelper; const ETHEREUM_COIN_TYPE: u32 = 60; +const OSMOSIS_COIN_TYPE: u32 = 10000118; #[test] fn test_any_address_derive() { @@ -213,3 +216,36 @@ fn test_any_address_get_data_eth() { let data = TWDataHelper::wrap(unsafe { tw_any_address_data(any_address.ptr()) }); assert_eq!(data.to_vec(), Some(addr.decode_hex().unwrap())); } + +#[test] +fn test_any_address_is_valid_bech32() { + let addr = "juno1mry47pkga5tdswtluy0m8teslpalkdq0gnn4mf"; + + let address_str = TWStringHelper::create(addr); + let hrp = TWStringHelper::create("juno"); + // Should be valid even though Osmosis chain has `osmo` default hrp. + let result = + unsafe { tw_any_address_is_valid_bech32(address_str.ptr(), OSMOSIS_COIN_TYPE, hrp.ptr()) }; + assert!(result); +} + +#[test] +fn test_any_address_create_bech32_with_public_key() { + let private_key = TWPrivateKeyHelper::with_hex( + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", + ); + let public_key = TWPublicKeyHelper::wrap(unsafe { + tw_private_key_get_public_key_by_type(private_key.ptr(), PublicKeyType::Secp256k1 as u32) + }); + let hrp = TWStringHelper::create("juno"); + + // Should be valid even though Osmosis chain has `osmo` default hrp. + let any_address = TWAnyAddressHelper::wrap(unsafe { + tw_any_address_create_bech32_with_public_key(public_key.ptr(), OSMOSIS_COIN_TYPE, hrp.ptr()) + }); + + let description = + TWStringHelper::wrap(unsafe { tw_any_address_description(any_address.ptr()) }); + let expected = "juno1ten42eesehw0ktddcp0fws7d3ycsqez3fksy86"; + assert_eq!(description.to_string(), Some(expected.to_string())); +} diff --git a/rust/tw_bech32_address/src/bech32_prefix.rs b/rust/tw_bech32_address/src/bech32_prefix.rs new file mode 100644 index 00000000000..8f6f418ed2c --- /dev/null +++ b/rust/tw_bech32_address/src/bech32_prefix.rs @@ -0,0 +1,22 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_coin_entry::error::AddressError; +use tw_coin_entry::prefix::AddressPrefix; + +pub struct Bech32Prefix { + pub hrp: String, +} + +impl TryFrom for Bech32Prefix { + type Error = AddressError; + + fn try_from(prefix: AddressPrefix) -> Result { + match prefix { + AddressPrefix::Hrp(hrp) => Ok(Bech32Prefix { hrp }), + } + } +} diff --git a/rust/tw_bech32_address/src/lib.rs b/rust/tw_bech32_address/src/lib.rs index f06d56eecb6..67c361f90a7 100644 --- a/rust/tw_bech32_address/src/lib.rs +++ b/rust/tw_bech32_address/src/lib.rs @@ -4,6 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +use crate::bech32_prefix::Bech32Prefix; use serde::{Serialize, Serializer}; use std::fmt; use std::str::FromStr; @@ -16,6 +17,8 @@ use tw_hash::H160; use tw_keypair::tw::{PrivateKey, PublicKey, PublicKeyType}; use tw_memory::Data; +pub mod bech32_prefix; + pub struct Bech32Address { hrp: String, key_hash: Data, @@ -62,8 +65,12 @@ impl Bech32Address { pub fn with_public_key_coin_context( coin: &dyn CoinContext, public_key: &PublicKey, + prefix: Option, ) -> AddressResult { - let hrp = coin.hrp().ok_or(AddressError::InvalidHrp)?; + let hrp = match prefix { + Some(Bech32Prefix { hrp }) => hrp, + None => coin.hrp().ok_or(AddressError::InvalidHrp)?, + }; let address_hasher = coin .address_hasher() .ok_or(AddressError::UnexpectedHasher)?; @@ -103,6 +110,21 @@ impl Bech32Address { }) } + pub fn from_str_with_coin_and_prefix( + coin: &dyn CoinContext, + address_str: String, + prefix: Option, + ) -> AddressResult + where + Self: Sized, + { + let hrp = match prefix { + Some(Bech32Prefix { hrp }) => hrp, + None => coin.hrp().ok_or(AddressError::InvalidHrp)?, + }; + Self::from_str_checked(&hrp, address_str) + } + pub fn key_hash(&self) -> &[u8] { &self.key_hash } diff --git a/rust/tw_bitcoin/src/entry.rs b/rust/tw_bitcoin/src/entry.rs index 0809b39f315..81226eb32ca 100644 --- a/rust/tw_bitcoin/src/entry.rs +++ b/rust/tw_bitcoin/src/entry.rs @@ -49,12 +49,22 @@ impl CoinEntry for BitcoinEntry { #[inline] fn parse_address( &self, - _coin: &dyn CoinContext, + coin: &dyn CoinContext, address: &str, _prefix: Option, + ) -> AddressResult { + self.parse_address_unchecked(coin, address) + } + + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, ) -> AddressResult { let address = bitcoin::address::Address::from_str(address) .map_err(|_| AddressError::FromHexError)? + // TODO Bitcoin address should not check the network in `Self::parse_address_unchecked`. .require_network(bitcoin::Network::Bitcoin) .map_err(|_| AddressError::InvalidInput)?; diff --git a/rust/tw_coin_entry/src/coin_entry.rs b/rust/tw_coin_entry/src/coin_entry.rs index 195efe7263b..70c9d24077d 100644 --- a/rust/tw_coin_entry/src/coin_entry.rs +++ b/rust/tw_coin_entry/src/coin_entry.rs @@ -46,6 +46,14 @@ pub trait CoinEntry { prefix: Option, ) -> AddressResult; + /// Tries to parse `Self::Address` from the given `address` string by `coin` type. + /// Please note that this method does not check if the address belongs to the given chain. + fn parse_address_unchecked( + &self, + coin: &dyn CoinContext, + address: &str, + ) -> AddressResult; + /// Derives an address associated with the given `public_key` by `coin` context, `derivation` and address `prefix`. fn derive_address( &self, diff --git a/rust/tw_coin_entry/src/coin_entry_ext.rs b/rust/tw_coin_entry/src/coin_entry_ext.rs index 68caa9fe6e7..d3daf007c4a 100644 --- a/rust/tw_coin_entry/src/coin_entry_ext.rs +++ b/rust/tw_coin_entry/src/coin_entry_ext.rs @@ -29,12 +29,7 @@ pub trait CoinEntryExt { ) -> AddressResult<()>; /// Validates and normalizes the given `address`. - fn normalize_address( - &self, - coin: &dyn CoinContext, - address: &str, - prefix: Option, - ) -> AddressResult; + fn normalize_address(&self, coin: &dyn CoinContext, address: &str) -> AddressResult; /// Derives an address associated with the given `public_key` by `coin` context, `derivation` and address `prefix`. fn derive_address( @@ -46,12 +41,7 @@ pub trait CoinEntryExt { ) -> AddressResult; /// Returns underlying data (public key or key hash). - fn address_to_data( - &self, - coin: &dyn CoinContext, - address: &str, - prefix: Option, - ) -> AddressResult; + fn address_to_data(&self, coin: &dyn CoinContext, address: &str) -> AddressResult; /// Signs a transaction declared as the given `input`. fn sign(&self, coin: &dyn CoinContext, input: &[u8]) -> ProtoResult; @@ -110,16 +100,11 @@ where self.parse_address(coin, address, prefix).map(|_| ()) } - fn normalize_address( - &self, - coin: &dyn CoinContext, - address: &str, - prefix: Option, - ) -> AddressResult { - let prefix = prefix.map(T::AddressPrefix::try_from).transpose()?; + fn normalize_address(&self, coin: &dyn CoinContext, address: &str) -> AddressResult { // Parse the address and display it. // Please note that `Self::Address::to_string()` returns a normalize address. - ::parse_address(self, coin, address, prefix).map(|addr| addr.to_string()) + ::parse_address_unchecked(self, coin, address) + .map(|addr| addr.to_string()) } fn derive_address( @@ -136,15 +121,8 @@ where .map(|addr| addr.to_string()) } - fn address_to_data( - &self, - coin: &dyn CoinContext, - address: &str, - prefix: Option, - ) -> AddressResult { - let prefix = prefix.map(T::AddressPrefix::try_from).transpose()?; - - self.parse_address(coin, address, prefix) + fn address_to_data(&self, coin: &dyn CoinContext, address: &str) -> AddressResult { + self.parse_address_unchecked(coin, address) .map(|addr| addr.data()) } diff --git a/rust/tw_coin_entry/src/common/compile_input.rs b/rust/tw_coin_entry/src/common/compile_input.rs index 317c1f3b5f7..fc3dfa5f5fb 100644 --- a/rust/tw_coin_entry/src/common/compile_input.rs +++ b/rust/tw_coin_entry/src/common/compile_input.rs @@ -6,18 +6,13 @@ use crate::coin_entry::{PublicKeyBytes, SignatureBytes}; use crate::error::{SigningError, SigningErrorType, SigningResult}; -use tw_keypair::KeyPairError; -pub struct SingleSignaturePubkey { - pub signature: Signature, - pub public_key: PublicKey, +pub struct SingleSignaturePubkey { + pub signature: SignatureBytes, + pub public_key: PublicKeyBytes, } -impl SingleSignaturePubkey -where - Signature: for<'a> TryFrom<&'a [u8], Error = KeyPairError>, - PublicKey: for<'a> TryFrom<&'a [u8], Error = KeyPairError>, -{ +impl SingleSignaturePubkey { pub fn from_sign_pubkey_list( signatures: Vec, public_keys: Vec, @@ -26,18 +21,15 @@ where return Err(SigningError(SigningErrorType::Error_no_support_n2n)); } - let signature_data = signatures + let signature = signatures .into_iter() .next() .ok_or(SigningError(SigningErrorType::Error_signatures_count))?; - let public_key_data = public_keys + let public_key = public_keys .into_iter() .next() .ok_or(SigningError(SigningErrorType::Error_invalid_params))?; - let signature = Signature::try_from(signature_data.as_slice())?; - let public_key = PublicKey::try_from(public_key_data.as_slice())?; - Ok(SingleSignaturePubkey { signature, public_key, diff --git a/rust/tw_coin_entry/src/derivation.rs b/rust/tw_coin_entry/src/derivation.rs index 3eb0f6c0b70..4e9ccd93a5d 100644 --- a/rust/tw_coin_entry/src/derivation.rs +++ b/rust/tw_coin_entry/src/derivation.rs @@ -20,3 +20,9 @@ impl Derivation { } } } + +impl Default for Derivation { + fn default() -> Self { + Derivation::Default + } +} diff --git a/rust/tw_cosmos_sdk/src/address.rs b/rust/tw_cosmos_sdk/src/address.rs index df554ad5b90..4f141b2a1dd 100644 --- a/rust/tw_cosmos_sdk/src/address.rs +++ b/rust/tw_cosmos_sdk/src/address.rs @@ -10,6 +10,7 @@ use tw_coin_entry::coin_context::CoinContext; use tw_coin_entry::error::{AddressError, AddressResult}; pub type Address = tw_bech32_address::Bech32Address; +pub type Bech32Prefix = tw_bech32_address::bech32_prefix::Bech32Prefix; pub trait CosmosAddress: FromStr + Serialize + ToString { fn from_str_with_coin(coin: &dyn CoinContext, addr: &str) -> AddressResult @@ -22,7 +23,7 @@ impl CosmosAddress for Address { where Self: Sized, { - let hrp = coin.hrp().ok_or(AddressError::InvalidHrp)?; - Address::from_str_checked(&hrp, addr.to_string()) + let prefix = None; + Address::from_str_with_coin_and_prefix(coin, addr.to_string(), prefix) } } diff --git a/rust/tw_cosmos_sdk/src/modules/compiler/tw_compiler.rs b/rust/tw_cosmos_sdk/src/modules/compiler/tw_compiler.rs index 9bb7fd4115a..4ed45873dcb 100644 --- a/rust/tw_cosmos_sdk/src/modules/compiler/tw_compiler.rs +++ b/rust/tw_cosmos_sdk/src/modules/compiler/tw_compiler.rs @@ -16,6 +16,7 @@ use std::borrow::Cow; use std::marker::PhantomData; use tw_coin_entry::coin_context::CoinContext; use tw_coin_entry::coin_entry::{PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::common::compile_input::SingleSignaturePubkey; use tw_coin_entry::error::{SigningError, SigningErrorType, SigningResult}; use tw_coin_entry::signing_output_error; use tw_proto::Cosmos::Proto; @@ -42,15 +43,13 @@ impl TWTransactionCompiler { pub fn compile( coin: &dyn CoinContext, input: Proto::SigningInput<'_>, - signature_data: SignatureBytes, - public_key: PublicKeyBytes, + signatures: Vec, + public_keys: Vec, ) -> Proto::SigningOutput<'static> { match input.signing_mode { - Proto::SigningMode::JSON => { - Self::compile_as_json(coin, input, signature_data, public_key) - }, + Proto::SigningMode::JSON => Self::compile_as_json(coin, input, signatures, public_keys), Proto::SigningMode::Protobuf => { - Self::compile_as_protobuf(coin, input, signature_data, public_key) + Self::compile_as_protobuf(coin, input, signatures, public_keys) }, } .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) @@ -98,23 +97,27 @@ impl TWTransactionCompiler { pub fn compile_as_protobuf( coin: &dyn CoinContext, mut input: Proto::SigningInput<'_>, - signature_data: SignatureBytes, - public_key: PublicKeyBytes, + signatures: Vec, + public_keys: Vec, ) -> SigningResult> { + let SingleSignaturePubkey { + signature, + public_key, + } = SingleSignaturePubkey::from_sign_pubkey_list(signatures, public_keys)?; let public_key = Context::PublicKey::from_bytes(coin, &public_key)?; let signed_tx_raw = match TxBuilder::::try_sign_direct_args(&input) { // If there was a `SignDirect` message in the signing input, generate the `TxRaw` directly. Ok(Some(sign_direct_args)) => ProtobufSerializer::::build_direct_signed_tx( &sign_direct_args, - signature_data.clone(), + signature.clone(), ), // Otherwise, generate the `TxRaw` by using `TxBuilder`. _ => { // Set the public key. It will be used to construct a signer info. input.public_key = Cow::from(public_key.to_bytes()); let unsigned_tx = TxBuilder::::unsigned_tx_from_proto(coin, &input)?; - let signed_tx = unsigned_tx.into_signed(signature_data.clone()); + let signed_tx = unsigned_tx.into_signed(signature.clone()); ProtobufSerializer::build_signed_tx(&signed_tx)? }, @@ -124,12 +127,12 @@ impl TWTransactionCompiler { let broadcast_tx = BroadcastMsg::raw(broadcast_mode, &signed_tx_raw).to_json_string(); let signature_json = - JsonSerializer::::serialize_signature(&public_key, signature_data.clone()); + JsonSerializer::::serialize_signature(&public_key, signature.clone()); let signature_json = serde_json::to_string(&[signature_json]) .map_err(|_| SigningError(SigningErrorType::Error_internal))?; Ok(Proto::SigningOutput { - signature: Cow::from(signature_data), + signature: Cow::from(signature), signature_json: Cow::from(signature_json), serialized: Cow::from(broadcast_tx), ..Proto::SigningOutput::default() @@ -139,15 +142,19 @@ impl TWTransactionCompiler { pub fn compile_as_json( coin: &dyn CoinContext, mut input: Proto::SigningInput<'_>, - signature_data: SignatureBytes, - public_key: PublicKeyBytes, + signatures: Vec, + public_keys: Vec, ) -> SigningResult> { + let SingleSignaturePubkey { + signature, + public_key, + } = SingleSignaturePubkey::from_sign_pubkey_list(signatures, public_keys)?; let public_key = Context::PublicKey::from_bytes(coin, &public_key)?; // Set the public key. It will be used to construct a signer info. input.public_key = Cow::from(public_key.to_bytes()); let unsigned_tx = TxBuilder::::unsigned_tx_from_proto(coin, &input)?; - let signed_tx = unsigned_tx.into_signed(signature_data.clone()); + let signed_tx = unsigned_tx.into_signed(signature.clone()); let signed_tx_json = JsonSerializer::build_signed_tx(&signed_tx)?; @@ -158,7 +165,7 @@ impl TWTransactionCompiler { .map_err(|_| SigningError(SigningErrorType::Error_internal))?; Ok(Proto::SigningOutput { - signature: Cow::from(signature_data), + signature: Cow::from(signature), signature_json: Cow::from(signature_json), json: Cow::from(broadcast_tx), ..Proto::SigningOutput::default() diff --git a/rust/tw_cosmos_sdk/src/modules/signer/tw_signer.rs b/rust/tw_cosmos_sdk/src/modules/signer/tw_signer.rs index dca48e798c1..8f63310be5a 100644 --- a/rust/tw_cosmos_sdk/src/modules/signer/tw_signer.rs +++ b/rust/tw_cosmos_sdk/src/modules/signer/tw_signer.rs @@ -48,8 +48,8 @@ impl TWSigner { let compile_output = TWTransactionCompiler::::compile( coin, input, - signature_data, - public_key.to_bytes(), + vec![signature_data], + vec![public_key.to_bytes()], ); if compile_output.error != SigningErrorType::OK { diff --git a/rust/tw_cosmos_sdk/src/test_utils/sign_utils.rs b/rust/tw_cosmos_sdk/src/test_utils/sign_utils.rs index 42989c7eeb4..3e439ad4dd1 100644 --- a/rust/tw_cosmos_sdk/src/test_utils/sign_utils.rs +++ b/rust/tw_cosmos_sdk/src/test_utils/sign_utils.rs @@ -141,8 +141,8 @@ fn test_compile_impl(test_input: TestCompileInput<'_>) { let compile_output = TWTransactionCompiler::::compile( test_input.coin, test_input.input, - test_input.signature.decode_hex().unwrap(), - public_key, + vec![test_input.signature.decode_hex().unwrap()], + vec![public_key], ); assert_eq!(compile_output.error, SigningError::OK); diff --git a/rust/tw_ethereum/src/entry.rs b/rust/tw_ethereum/src/entry.rs index cb9a1b4fb48..f69893fd63f 100644 --- a/rust/tw_ethereum/src/entry.rs +++ b/rust/tw_ethereum/src/entry.rs @@ -46,6 +46,16 @@ impl CoinEntry for EthereumEntry { Address::from_str(address) } + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + Address::from_str(address) + } + + #[inline] fn derive_address( &self, _coin: &dyn CoinContext, diff --git a/rust/tw_evm/src/modules/compiler.rs b/rust/tw_evm/src/modules/compiler.rs index 4d5066874b4..23598eca7aa 100644 --- a/rust/tw_evm/src/modules/compiler.rs +++ b/rust/tw_evm/src/modules/compiler.rs @@ -64,7 +64,8 @@ impl Compiler { let SingleSignaturePubkey { signature, public_key: _, - } = SingleSignaturePubkey::::from_sign_pubkey_list(signatures, public_keys)?; + } = SingleSignaturePubkey::from_sign_pubkey_list(signatures, public_keys)?; + let signature = secp256k1::Signature::from_bytes(&signature)?; let chain_id = U256::from_big_endian_slice(&input.chain_id)?; diff --git a/rust/tw_internet_computer/src/entry.rs b/rust/tw_internet_computer/src/entry.rs index a9542fbb8ea..6fb47ba161c 100644 --- a/rust/tw_internet_computer/src/entry.rs +++ b/rust/tw_internet_computer/src/entry.rs @@ -53,6 +53,15 @@ impl CoinEntry for InternetComputerEntry { Self::Address::from_str(address) } + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + Self::Address::from_str(address) + } + #[inline] fn derive_address( &self, diff --git a/rust/tw_ronin/src/entry.rs b/rust/tw_ronin/src/entry.rs index 976a08e0eca..36bb8fb96b2 100644 --- a/rust/tw_ronin/src/entry.rs +++ b/rust/tw_ronin/src/entry.rs @@ -46,6 +46,15 @@ impl CoinEntry for RoninEntry { Address::from_str(address) } + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + Address::from_str(address) + } + fn derive_address( &self, _coin: &dyn CoinContext, diff --git a/src/CoinEntry.cpp b/src/CoinEntry.cpp index 56ba53c3f95..a4ef29f04e8 100644 --- a/src/CoinEntry.cpp +++ b/src/CoinEntry.cpp @@ -33,12 +33,16 @@ byte getFromPrefixPkhOrDefault(const PrefixVariant &prefix, TWCoinType coin) { } bool validateAddressRust(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) { - if (!std::holds_alternative(addressPrefix)) { - throw std::invalid_argument("`Rust::tw_any_address_is_valid_bech32`, `Rust::tw_any_address_is_valid_ss58` are not supported yet"); - } - Rust::TWStringWrapper addressStr = address; - return Rust::tw_any_address_is_valid(addressStr.get(), static_cast(coin)); + + if (std::holds_alternative(addressPrefix)) { + return Rust::tw_any_address_is_valid(addressStr.get(), static_cast(coin)); + } else if (const auto* hrpPrefix = std::get_if(&addressPrefix); hrpPrefix) { + Rust::TWStringWrapper hrpStr = std::string(*hrpPrefix); + return Rust::tw_any_address_is_valid_bech32(addressStr.get(), static_cast(coin), hrpStr.get()); + } else { + throw std::invalid_argument("`Rust::tw_any_address_is_valid_ss58`, `Rust::tw_any_address_create_with_public_key_filecoin_address_type` are not supported yet"); + } } std::string normalizeAddressRust(TWCoinType coin, const std::string& address) { @@ -55,12 +59,6 @@ std::string normalizeAddressRust(TWCoinType coin, const std::string& address) { } std::string deriveAddressRust(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) { - if (!std::holds_alternative(addressPrefix)) { - throw std::invalid_argument("`Rust::tw_any_address_create_bech32_with_public_key`, " - "`Rust::tw_any_address_create_ss58_with_public_key`, " - "`Rust::tw_any_address_create_with_public_key_filecoin_address_type` are not supported yet"); - } - auto *twPublicKeyRaw = Rust::tw_public_key_create_with_data(publicKey.bytes.data(), publicKey.bytes.size(), static_cast(publicKey.type)); @@ -69,9 +67,20 @@ std::string deriveAddressRust(TWCoinType coin, const PublicKey& publicKey, TWDer return {}; } - auto *anyAddressRaw = Rust::tw_any_address_create_with_public_key_derivation(twPublicKey.get(), - static_cast(coin), - static_cast(derivation)); + Rust::TWAnyAddress* anyAddressRaw = nullptr; + if (std::holds_alternative(addressPrefix)) { + anyAddressRaw = Rust::tw_any_address_create_with_public_key_derivation(twPublicKey.get(), + static_cast(coin), + static_cast(derivation)); + } else if (const auto* hrpPrefix = std::get_if(&addressPrefix); hrpPrefix) { + Rust::TWStringWrapper hrpStr = std::string(*hrpPrefix); + anyAddressRaw = Rust::tw_any_address_create_bech32_with_public_key(twPublicKey.get(), + static_cast(coin), + hrpStr.get()); + } else { + throw std::invalid_argument("`Rust::tw_any_address_is_valid_ss58`, `Rust::tw_any_address_create_with_public_key_filecoin_address_type` are not supported yet"); + } + auto anyAddress = Rust::wrapTWAnyAddress(anyAddressRaw); if (!anyAddress) { return {}; diff --git a/src/Cosmos/Entry.cpp b/src/Cosmos/Entry.cpp index 56f06d347c2..264aa2295eb 100644 --- a/src/Cosmos/Entry.cpp +++ b/src/Cosmos/Entry.cpp @@ -24,7 +24,7 @@ std::string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, [[ return deriveAddressRust(coin, publicKey, derivation, addressPrefix); } -Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { +Data Entry::addressToData(TWCoinType coin, const std::string& address) const { return addressToDataRust(coin, address); } diff --git a/tests/chains/Cosmos/NativeEvmos/TWCoinTypeTests.cpp b/tests/chains/Cosmos/NativeEvmos/TWCoinTypeTests.cpp index 822897296e8..2713fcbba7e 100644 --- a/tests/chains/Cosmos/NativeEvmos/TWCoinTypeTests.cpp +++ b/tests/chains/Cosmos/NativeEvmos/TWCoinTypeTests.cpp @@ -20,7 +20,7 @@ TEST(TWEvmosCoinType, TWCoinTypeNativeEvmos) { auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeNativeEvmos)); ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeNativeEvmos), 18); - ASSERT_EQ(TWBlockchainCosmos, TWCoinTypeBlockchain(TWCoinTypeNativeEvmos)); + ASSERT_EQ(TWBlockchainNativeEvmos, TWCoinTypeBlockchain(TWCoinTypeNativeEvmos)); assertStringsEqual(symbol, "EVMOS"); assertStringsEqual(txUrl, "https://mintscan.io/evmos/txs/A16C211C83AD1E684DE46F694FAAC17D8465C864BD7385A81EC062CDE0638811"); diff --git a/tests/chains/Cosmos/NativeInjective/TWCoinTypeTests.cpp b/tests/chains/Cosmos/NativeInjective/TWCoinTypeTests.cpp index 0b152ca68be..f92a2f9ca7e 100644 --- a/tests/chains/Cosmos/NativeInjective/TWCoinTypeTests.cpp +++ b/tests/chains/Cosmos/NativeInjective/TWCoinTypeTests.cpp @@ -28,7 +28,7 @@ TEST(TWNativeInjectiveCoinType, TWCoinType) { assertStringsEqual(name, "Native Injective"); assertStringsEqual(symbol, "INJ"); ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); - ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainCosmos); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainNativeInjective); ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); assertStringsEqual(chainId, "injective-1");