diff --git a/token-core/tcx-btc-kin/src/address.rs b/token-core/tcx-btc-kin/src/address.rs index b17658dd..2039120f 100644 --- a/token-core/tcx-btc-kin/src/address.rs +++ b/token-core/tcx-btc-kin/src/address.rs @@ -41,6 +41,7 @@ impl WIFDisplay for TypedPrivateKey { Ok(key.to_ss58check_with_version(&version)) } } + #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct BtcKinAddress { pub network: BtcKinNetwork, @@ -139,10 +140,13 @@ impl BtcKinAddress { fn bech32_network(bech32: &str) -> Option<&BtcKinNetwork> { let bech32_prefix = bech32.rfind('1').map(|sep| bech32.split_at(sep).0); - match bech32_prefix { - Some(prefix) => BtcKinNetwork::find_by_hrp(prefix), - None => None, + if bech32_prefix.is_some() { + let prefix = bech32_prefix.unwrap(); + if (!prefix.is_empty()) { + return BtcKinNetwork::find_by_hrp(prefix); + } } + return None; } fn decode_base58(addr: &str) -> result::Result, LibAddressError> { @@ -419,36 +423,36 @@ mod tests { let coin = coin_info_from_param("BITCOIN", "MAINNET", "P2WPKH", "").unwrap(); assert!(BtcKinAddress::is_valid( "3Js9bGaZSQCNLudeGRHL4NExVinc25RbuG", - &coin + &coin, )); let coin = coin_info_from_param("BITCOIN", "MAINNET", "NONE", "").unwrap(); assert!(BtcKinAddress::is_valid( "1Gx9QwpQBFnAjF27Uiz3ea2zYBDrLx31bw", - &coin + &coin, )); let coin = coin_info_from_param("BITCOIN", "MAINNET", "VERSION_0", "").unwrap(); assert!(BtcKinAddress::is_valid( "bc1qnfv46v0wtarc6n82dnehtvzj2gtnqzjhj5wxqj", - &coin + &coin, )); let coin = coin_info_from_param("LITECOIN", "MAINNET", "NONE", "").unwrap(); assert!(BtcKinAddress::is_valid( "Ldfdegx3hJygDuFDUA7Rkzjjx8gfFhP9DP", - &coin + &coin, )); let coin = coin_info_from_param("LITECOIN", "MAINNET", "P2WPKH", "").unwrap(); assert!(BtcKinAddress::is_valid( "MR5Hu9zXPX3o9QuYNJGft1VMpRP418QDfW", - &coin + &coin, )); let coin = coin_info_from_param("LITECOIN", "MAINNET", "P2WPKH", "").unwrap(); assert!(!BtcKinAddress::is_valid( "MR5Hu9zXPX3o9QuYNJGft1VMpRP418QDf", - &coin + &coin, )); let coin = coin_info_from_param("LITECOIN", "MAINNET", "P2WPKH", "").unwrap(); diff --git a/token-core/tcx-proto/src/params.proto b/token-core/tcx-proto/src/params.proto index 30a35b4d..a3c384a7 100644 --- a/token-core/tcx-proto/src/params.proto +++ b/token-core/tcx-proto/src/params.proto @@ -191,6 +191,7 @@ message DeriveSubAccountsParam { string segWit = 4; repeated string relativePaths = 5; string extendedPublicKey = 6; + string hrp = 7; } message DeriveSubAccountsResult { diff --git a/token-core/tcx/src/api.rs b/token-core/tcx/src/api.rs index 5dd309e9..f3d84dc1 100644 --- a/token-core/tcx/src/api.rs +++ b/token-core/tcx/src/api.rs @@ -550,6 +550,8 @@ pub struct DeriveSubAccountsParam { pub relative_paths: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, #[prost(string, tag = "6")] pub extended_public_key: ::prost::alloc::string::String, + #[prost(string, tag = "7")] + pub hrp: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/token-core/tcx/src/handler.rs b/token-core/tcx/src/handler.rs index e47cb19d..448132d5 100644 --- a/token-core/tcx/src/handler.rs +++ b/token-core/tcx/src/handler.rs @@ -1296,6 +1296,8 @@ pub fn derive_sub_accounts(data: &[u8]) -> Result> { ¶m.curve, )?; coin_info.derivation_path = relative_path.to_string(); + coin_info.hrp = param.hrp.to_string(); + let acc: Account = derive_sub_account(&xpub, &coin_info)?; let enc_xpub = encrypt_xpub(¶m.extended_public_key.to_string())?; @@ -1394,6 +1396,7 @@ mod tests { "BITCOIN".to_string(), "BITCOINCASH".to_string(), "LITECOIN".to_string(), + "DOGECOIN".to_string(), ] ); assert_eq!(decoded.curve, CurveType::SECP256k1); diff --git a/token-core/tcx/tests/derive_test.rs b/token-core/tcx/tests/derive_test.rs index 934233df..2abfd0a4 100644 --- a/token-core/tcx/tests/derive_test.rs +++ b/token-core/tcx/tests/derive_test.rs @@ -495,6 +495,7 @@ pub fn test_derive_btc_legacy_sub_accounts() { seg_wit: "NONE".to_string(), relative_paths: vec!["0/0".to_string(), "0/1".to_string(), "1/0".to_string()], extended_public_key: accounts.accounts[0].extended_public_key.to_string(), + hrp: "".to_string(), }; let result_bytes = derive_sub_accounts(&encode_message(params).unwrap()).unwrap(); @@ -536,6 +537,7 @@ pub fn test_derive_btc_p2wpkh_sub_accounts() { seg_wit: "P2WPKH".to_string(), relative_paths: vec!["0/0".to_string(), "0/1".to_string(), "1/0".to_string()], extended_public_key: accounts.accounts[0].extended_public_key.to_string(), + hrp: "".to_string(), }; let result_bytes = derive_sub_accounts(&encode_message(params).unwrap()).unwrap(); @@ -577,6 +579,7 @@ pub fn test_derive_eth_sub_accounts() { seg_wit: "".to_string(), relative_paths: vec!["0/0".to_string(), "0/1".to_string()], extended_public_key: accounts.accounts[0].extended_public_key.to_string(), + hrp: "".to_string(), }; let result_bytes = derive_sub_accounts(&encode_message(params).unwrap()).unwrap(); @@ -592,6 +595,65 @@ pub fn test_derive_eth_sub_accounts() { }) } +#[test] +#[serial] +pub fn test_derive_cosmos_sub_accounts() { + run_test(|| { + let derivation = Derivation { + chain_type: "COSMOS".to_string(), + path: "m/44'/118'/0'/0/0".to_string(), + network: "MAINNET".to_string(), + seg_wit: "".to_string(), + chain_id: "".to_string(), + curve: "secp256k1".to_string(), + hrp: "cosmos".to_string(), + }; + + let (_, accounts) = import_and_derive(derivation); + let params = DeriveSubAccountsParam { + chain_type: "COSMOS".to_string(), + curve: "secp256k1".to_string(), + network: "MAINNET".to_string(), + seg_wit: "".to_string(), + relative_paths: vec!["0/0".to_string(), "0/1".to_string()], + extended_public_key: accounts.accounts[0].extended_public_key.to_string(), + hrp: "cosmos".to_string(), + }; + + let result_bytes = derive_sub_accounts(&encode_message(params).unwrap()).unwrap(); + let result = DeriveSubAccountsResult::decode(result_bytes.as_slice()).unwrap(); + assert_eq!( + "cosmos1ajz9y0x3wekez7tz2td2j6l2dftn28v26dd992", + result.accounts[0].address + ); + assert_eq!( + "cosmos1nkujjlktqdue52xc0k09yzc7h3xswsfpl568zc", + result.accounts[1].address + ); + + let params = DeriveSubAccountsParam { + chain_type: "COSMOS".to_string(), + curve: "secp256k1".to_string(), + network: "MAINNET".to_string(), + seg_wit: "".to_string(), + relative_paths: vec!["0/0".to_string(), "0/1".to_string()], + extended_public_key: accounts.accounts[0].extended_public_key.to_string(), + hrp: "osmo".to_string(), + }; + + let result_bytes = derive_sub_accounts(&encode_message(params).unwrap()).unwrap(); + let result = DeriveSubAccountsResult::decode(result_bytes.as_slice()).unwrap(); + assert_eq!( + "osmo1ajz9y0x3wekez7tz2td2j6l2dftn28v2jk74nc", + result.accounts[0].address + ); + assert_eq!( + "osmo1nkujjlktqdue52xc0k09yzc7h3xswsfph0fh52", + result.accounts[1].address + ) + }) +} + #[test] #[serial] pub fn test_mnemonic_to_public() { diff --git a/token-core/tcx/tests/export_test.rs b/token-core/tcx/tests/export_test.rs index af788ca0..0d293f48 100644 --- a/token-core/tcx/tests/export_test.rs +++ b/token-core/tcx/tests/export_test.rs @@ -680,7 +680,8 @@ pub fn test_backup_private_key() { vec![ "BITCOIN".to_string(), "BITCOINCASH".to_string(), - "LITECOIN".to_string() + "LITECOIN".to_string(), + "DOGECOIN".to_string() ], import_result.identified_chain_types );