diff --git a/imkey-core/ikc-common/src/path.rs b/imkey-core/ikc-common/src/path.rs index d90d044c..01d932d6 100644 --- a/imkey-core/ikc-common/src/path.rs +++ b/imkey-core/ikc-common/src/path.rs @@ -6,7 +6,7 @@ pub fn check_path_validity(path: &str) -> Result<()> { //check depth and length let strings: Vec<&str> = path.split("/").collect(); let depth = strings.len(); - if depth < 2 || depth > 10 { + if depth < 3 || depth > 10 { return Err(CommonError::ImkeyPathIllegal.into()); } Ok(()) @@ -16,7 +16,7 @@ pub fn check_path_max_five_depth(path: &str) -> Result<()> { //check depth and length let strings: Vec<&str> = path.split("/").collect(); let depth = strings.len(); - if depth < 2 || depth > 6 { + if depth < 3 || depth > 6 { return Err(CommonError::ImkeyPathIllegal.into()); } Ok(()) diff --git a/imkey-core/ikc-proto/src/api.proto b/imkey-core/ikc-proto/src/api.proto index 3584e102..66a70a6e 100644 --- a/imkey-core/ikc-proto/src/api.proto +++ b/imkey-core/ikc-proto/src/api.proto @@ -141,4 +141,26 @@ message DeriveSubAccountsParam { message DeriveSubAccountsResult { repeated AccountResponse accounts = 1; +} + +message GetExtendedPublicKeysParam { + repeated PublicKeyDerivation derivations = 1; +} + +message GetExtendedPublicKeysResult { + repeated string extendedPublicKeys = 1; +} + +message PublicKeyDerivation { + string chainType = 1; + string path = 2; + string curve = 3; +} + +message GetPublicKeysParam { + repeated PublicKeyDerivation derivations = 1; +} + +message GetPublicKeysResult { + repeated string publicKeys = 1; } \ No newline at end of file diff --git a/imkey-core/ikc-proto/src/btc.proto b/imkey-core/ikc-proto/src/btc.proto index 076b4965..cf4b9c1e 100644 --- a/imkey-core/ikc-proto/src/btc.proto +++ b/imkey-core/ikc-proto/src/btc.proto @@ -32,14 +32,3 @@ message BtcTxOutput { string txHash = 2; string wtxHash = 3; } - -message BtcXpubReq { - string network = 1; - string path = 2; -} - -message BtcXpubRes { - string xpub = 1; -} - - diff --git a/imkey-core/ikc-wallet/coin-bitcoin/src/btcapi.rs b/imkey-core/ikc-wallet/coin-bitcoin/src/btcapi.rs index 2bca90ae..f9217c83 100644 --- a/imkey-core/ikc-wallet/coin-bitcoin/src/btcapi.rs +++ b/imkey-core/ikc-wallet/coin-bitcoin/src/btcapi.rs @@ -56,17 +56,3 @@ pub struct BtcTxOutput { #[prost(string, tag = "3")] pub wtx_hash: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct BtcXpubReq { - #[prost(string, tag = "1")] - pub network: ::prost::alloc::string::String, - #[prost(string, tag = "2")] - pub path: ::prost::alloc::string::String, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct BtcXpubRes { - #[prost(string, tag = "1")] - pub xpub: ::prost::alloc::string::String, -} diff --git a/imkey-core/ikc-wallet/coin-substrate/src/address.rs b/imkey-core/ikc-wallet/coin-substrate/src/address.rs index b5a5dd7f..3c2625d4 100644 --- a/imkey-core/ikc-wallet/coin-substrate/src/address.rs +++ b/imkey-core/ikc-wallet/coin-substrate/src/address.rs @@ -67,7 +67,7 @@ impl SubstrateAddress { return Err(CoinError::ImkeySignatureVerifyFail.into()); } - Ok(pubkey.to_string()) + Ok(pubkey.to_lowercase()) } pub fn get_address(path: &str, address_type: &AddressType) -> Result { diff --git a/imkey-core/ikc/src/api.rs b/imkey-core/ikc/src/api.rs index 6732719b..939d8e34 100644 --- a/imkey-core/ikc/src/api.rs +++ b/imkey-core/ikc/src/api.rs @@ -235,3 +235,37 @@ pub struct DeriveSubAccountsResult { #[prost(message, repeated, tag = "1")] pub accounts: ::prost::alloc::vec::Vec, } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetExtendedPublicKeysParam { + #[prost(message, repeated, tag = "1")] + pub derivations: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetExtendedPublicKeysResult { + #[prost(string, repeated, tag = "1")] + pub extended_public_keys: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PublicKeyDerivation { + #[prost(string, tag = "1")] + pub chain_type: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub path: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub curve: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetPublicKeysParam { + #[prost(message, repeated, tag = "1")] + pub derivations: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetPublicKeysResult { + #[prost(string, repeated, tag = "1")] + pub public_keys: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} diff --git a/imkey-core/ikc/src/btc_address.rs b/imkey-core/ikc/src/btc_address.rs index 09d0e353..90af7762 100644 --- a/imkey-core/ikc/src/btc_address.rs +++ b/imkey-core/ikc/src/btc_address.rs @@ -5,19 +5,9 @@ use crate::error_handling::Result; use crate::message_handler::encode_message; use bitcoin::network::constants::Network; use coin_bitcoin::address::BtcAddress; -use coin_bitcoin::btcapi::{BtcXpubReq, BtcXpubRes}; use ikc_common::utility::network_convert; use prost::Message; -pub fn get_btc_xpub(data: &[u8]) -> Result> { - let input: BtcXpubReq = BtcXpubReq::decode(data).expect("imkey_illegal_param"); - let network = network_convert(input.network.as_ref()); - let xpub = BtcAddress::get_xpub(network, input.path.as_ref())?; - - let address_message = BtcXpubRes { xpub }; - encode_message(address_message) -} - pub fn get_address(param: &AddressParam) -> Result> { let network = network_convert(param.network.as_ref()); let account_path = param.path.to_string(); diff --git a/imkey-core/ikc/src/handler.rs b/imkey-core/ikc/src/handler.rs index d86359b9..fdc84b0c 100644 --- a/imkey-core/ikc/src/handler.rs +++ b/imkey-core/ikc/src/handler.rs @@ -1,12 +1,14 @@ use crate::api::{ AccountResponse, DeriveAccountsParam, DeriveAccountsResult, DeriveSubAccountsParam, - DeriveSubAccountsResult, + DeriveSubAccountsResult, GetExtendedPublicKeysParam, GetExtendedPublicKeysResult, + GetPublicKeysParam, GetPublicKeysResult, }; use crate::message_handler::encode_message; use crate::Result; use anyhow::anyhow; use bitcoin::hashes::hex::ToHex; use bitcoin::util::bip32::ExtendedPubKey; +use bitcoin::Network; use coin_bch::address::BchAddress; use coin_bitcoin::address::BtcAddress; use coin_btc_fork::address::BtcForkAddress; @@ -22,7 +24,7 @@ use ikc_common::curve::CurveType; use ikc_common::path::get_account_path; use ikc_common::utility::{ encrypt_xpub, extended_pub_key_derive, from_ss58check_with_version, get_xpub_prefix, - network_convert, to_ss58check_with_version, uncompress_pubkey_2_compress, + hex_to_bytes, network_convert, to_ss58check_with_version, uncompress_pubkey_2_compress, }; use prost::Message; use std::str::FromStr; @@ -168,6 +170,7 @@ pub(crate) fn derive_sub_accounts(data: &[u8]) -> Result> { let ext_pub_key = extended_pub_key_derive(&xpub.0, &relative_path)?; let pub_key_uncompressed = ext_pub_key.public_key.serialize_uncompressed().to_vec(); account.public_key = format!("0x{}", ext_pub_key.public_key.serialize().to_hex()); + account.path = relative_path; let address = match param.chain_type.as_str() { "ETHEREUM" => EthAddress::from_pub_key(pub_key_uncompressed)?, "BITCOIN" | "LITECOIN" => { @@ -195,6 +198,51 @@ pub(crate) fn derive_sub_accounts(data: &[u8]) -> Result> { }) } +pub(crate) fn get_extended_public_keys(data: &[u8]) -> Result> { + let param: GetExtendedPublicKeysParam = GetExtendedPublicKeysParam::decode(data)?; + let mut extended_public_keys = vec![]; + for public_key_derivation in param.derivations.iter() { + // if "".eq(&public_key_derivation.path) || &public_key_derivation.path.split("/") { } + let extended_public_key = match public_key_derivation.curve.as_str() { + "secp256k1" => { + BtcAddress::get_xpub(Network::Bitcoin, public_key_derivation.path.as_str())? + } + _ => return Err(anyhow!("unsupported_curve_type")), + }; + extended_public_keys.push(extended_public_key); + } + encode_message(GetExtendedPublicKeysResult { + extended_public_keys, + }) +} + +pub(crate) fn get_public_keys(data: &[u8]) -> Result> { + let param: GetPublicKeysParam = GetPublicKeysParam::decode(data)?; + let mut public_keys = vec![]; + for public_key_derivation in param.derivations.iter() { + let mut public_key = match public_key_derivation.curve.as_str() { + "secp256k1" => uncompress_pubkey_2_compress(&BtcAddress::get_pub_key( + public_key_derivation.path.as_str(), + )?), + "ed25519" => SubstrateAddress::get_public_key( + public_key_derivation.path.as_str(), + &AddressType::Polkadot, + )?, + _ => return Err(anyhow!("unsupported_curve_type")), + }; + if !public_key.starts_with("0x") { + public_key = format!("0x{}", public_key); + }; + let public_key = match public_key_derivation.chain_type.as_str() { + "EOS" => EosPubkey::from_pub_key(hex_to_bytes(&public_key)?.as_slice())?, + _ => public_key, + }; + public_keys.push(public_key); + } + + encode_message(GetPublicKeysResult { public_keys }) +} + #[cfg(test)] mod test { use crate::api::derive_accounts_param::Derivation; diff --git a/imkey-core/ikc/src/lib.rs b/imkey-core/ikc/src/lib.rs index 830a86d5..a4a6fd3f 100644 --- a/imkey-core/ikc/src/lib.rs +++ b/imkey-core/ikc/src/lib.rs @@ -41,7 +41,7 @@ pub mod tezos_signer; extern crate lazy_static; extern crate anyhow; use crate::error_handling::{landingpad, LAST_ERROR}; -use crate::handler::derive_accounts; +use crate::handler::{derive_accounts, get_extended_public_keys, get_public_keys}; use crate::message_handler::encode_message; use ikc_transport::message; @@ -154,17 +154,7 @@ pub unsafe extern "C" fn call_imkey_api(hex_str: *const c_char) -> *const c_char }), "derive_accounts" => landingpad(|| derive_accounts(&action.param.unwrap().value)), "derive_sub_accounts" => landingpad(|| derive_sub_accounts(&action.param.unwrap().value)), - "get_pub_key" => landingpad(|| { - let param: PubKeyParam = PubKeyParam::decode(action.param.unwrap().value.as_slice()) - .expect("imkey_illegal_param"); - match param.chain_type.as_str() { - "EOS" => eos_pubkey::get_eos_pubkey(¶m), - "TEZOS" => tezos_address::get_pub_key(¶m), - "COSMOS" => cosmos_address::get_cosmos_pub_key(¶m), - _ => Err(anyhow!("get_pub_key unsupported_chain")), - } - }), - + "get_public_keys" => landingpad(|| get_public_keys(&action.param.unwrap().value)), "register_pub_key" => landingpad(|| { let param: PubKeyParam = PubKeyParam::decode(action.param.unwrap().value.as_slice()) .expect("imkey_illegal_param"); @@ -270,8 +260,9 @@ pub unsafe extern "C" fn call_imkey_api(hex_str: *const c_char) -> *const c_char } }), - "btc_get_xpub" => landingpad(|| btc_address::get_btc_xpub(&action.param.unwrap().value)), - + "get_extended_public_keys" => { + landingpad(|| get_extended_public_keys(&action.param.unwrap().value)) + } _ => landingpad(|| Err(anyhow!("unsupported_method"))), }; match reply { @@ -313,6 +304,8 @@ mod tests { use crate::api::derive_accounts_param::Derivation; use crate::api::{ DeriveAccountsParam, DeriveAccountsResult, DeriveSubAccountsParam, DeriveSubAccountsResult, + GetExtendedPublicKeysParam, GetExtendedPublicKeysResult, GetPublicKeysParam, + GetPublicKeysResult, PublicKeyDerivation, }; use ikc_device::deviceapi::{BindAcquireReq, BindCheckRes}; @@ -1401,6 +1394,268 @@ mod tests { ); } + #[test] + fn test_get_extended_public_keys() { + connect_and_bind(); + + let derivations = vec![ + PublicKeyDerivation { + chain_type: "BITCOIN".to_string(), + path: "m/44'/145'/0'/0/0".to_string(), + curve: "secp256k1".to_string(), + }, + PublicKeyDerivation { + chain_type: "ETHEREUM".to_string(), + path: "m/44'/60'/0'/0/0".to_string(), + curve: "secp256k1".to_string(), + }, + PublicKeyDerivation { + chain_type: "BITCOIN".to_string(), + path: "m/0/1".to_string(), + curve: "secp256k1".to_string(), + }, + PublicKeyDerivation { + chain_type: "BITCOIN".to_string(), + path: "m/44'/145'/0'".to_string(), + curve: "secp256k1".to_string(), + }, + ]; + let param = GetExtendedPublicKeysParam { derivations }; + let action: ImkeyAction = ImkeyAction { + method: "get_extended_public_keys".to_string(), + param: Some(::prost_types::Any { + type_url: "get_extended_public_keys".to_string(), + value: encode_message(param).unwrap(), + }), + }; + let action = hex::encode(encode_message(action).unwrap()); + let ret_hex = unsafe { _to_str(call_imkey_api(_to_c_char(action.as_str()))) }; + let ret_bytes = hex::decode(ret_hex).unwrap(); + let extended_public_key: GetExtendedPublicKeysResult = + GetExtendedPublicKeysResult::decode(ret_bytes.as_slice()).unwrap(); + assert_eq!(extended_public_key.extended_public_keys[0], "xpub6GZjFnyumLtEwC4KQkigvc3vXJdZvy71QxHTsFQQv1YtEUWNEwynKWsK2LBFZNLWdTk3w1Y9cRv4NN7V2pnDBoWgH3PkVE9r9Q2kSQL2zkH"); + assert_eq!(extended_public_key.extended_public_keys[1], "xpub6FmdMKZ36pLzf1iF7DLCzKtZms33cZ6mVjvBSy2dCPugFCH23cS3jgHfQ9PKmxs989ZyiyALuADMtLokCzpw7Fi35ap4uybfQAY5WVakan7"); + assert_eq!(extended_public_key.extended_public_keys[2], "xpub6AQmexrYd5utZNmD9Gnf4CjrzJ4kuvaxacLyuSD5sA34g4oKuzBpX5rhAZrCZoxkcqWLVyWSz1rEh5ECs4PDRN16PLfNKFftxm48y6zsWX3"); + assert_eq!(extended_public_key.extended_public_keys[3], "xpub6Bmkv3mmRZZWoFSBdj9vDMqR2PCPSP6DEj8u3bBuv44g3Ncnro6cPVqZAw6wTEcxHQuodkuJG4EmAinqrrRXGsN3HHnRRMtAvzfYTiBATV1"); + } + + #[test] + fn test_get_extended_public_keys_error_case() { + connect_and_bind(); + + let test_data = vec![ + vec![PublicKeyDerivation { + chain_type: "POLKADOT".to_string(), + path: "m/44'/354'/0'/0'/0'".to_string(), + curve: "ed25519".to_string(), + }], + vec![PublicKeyDerivation { + chain_type: "BITCOIN".to_string(), + path: "".to_string(), + curve: "secp256k1".to_string(), + }], + vec![PublicKeyDerivation { + chain_type: "BITCOIN".to_string(), + path: "m/0".to_string(), + curve: "secp256k1".to_string(), + }], + ]; + for i in 0..test_data.len() { + let param = GetExtendedPublicKeysParam { + derivations: test_data[i].clone(), + }; + let action: ImkeyAction = ImkeyAction { + method: "get_extended_public_keys".to_string(), + param: Some(::prost_types::Any { + type_url: "get_extended_public_keys".to_string(), + value: encode_message(param).unwrap(), + }), + }; + let action = hex::encode(encode_message(action).unwrap()); + let ret_hex = unsafe { _to_str(call_imkey_api(_to_c_char(action.as_str()))) }; + let err = unsafe { _to_str(imkey_get_last_err_message()) }; + assert!(!err.is_empty()); + let error_ret: ErrorResponse = + ErrorResponse::decode(hex::decode(err).unwrap().as_slice()).unwrap(); + match i { + 0 => { + assert_eq!(error_ret.error, "unsupported_curve_type"); + } + 1 => { + assert_eq!(error_ret.error, "imkey_path_illegal"); + } + 2 => { + assert_eq!(error_ret.error, "imkey_path_illegal"); + } + _ => {} + }; + } + } + + #[test] + fn test_get_public_keys() { + connect_and_bind(); + + let derivations = vec![ + PublicKeyDerivation { + chain_type: "BITCOIN".to_string(), + path: "m/44'/145'/0'".to_string(), + curve: "secp256k1".to_string(), + }, + PublicKeyDerivation { + chain_type: "ETHEREUM".to_string(), + path: "m/44'/60'/0'".to_string(), + curve: "secp256k1".to_string(), + }, + PublicKeyDerivation { + chain_type: "EOS".to_string(), + path: "m/44'/194'/0'".to_string(), + curve: "secp256k1".to_string(), + }, + PublicKeyDerivation { + chain_type: "COSMOS".to_string(), + path: "m/44'/118'/0'".to_string(), + curve: "secp256k1".to_string(), + }, + PublicKeyDerivation { + chain_type: "BITCOINCASH".to_string(), + path: "m/44'/145'/0'".to_string(), + curve: "secp256k1".to_string(), + }, + PublicKeyDerivation { + chain_type: "LITECOIN".to_string(), + path: "m/44'/2'/0'".to_string(), + curve: "secp256k1".to_string(), + }, + PublicKeyDerivation { + chain_type: "TRON".to_string(), + path: "m/44'/195'/0'".to_string(), + curve: "secp256k1".to_string(), + }, + PublicKeyDerivation { + chain_type: "NERVOS".to_string(), + path: "m/44'/309'/0'".to_string(), + curve: "secp256k1".to_string(), + }, + PublicKeyDerivation { + chain_type: "POLKADOT".to_string(), + path: "m/44'/354'/0'".to_string(), + curve: "ed25519".to_string(), + }, + PublicKeyDerivation { + chain_type: "KUSAMA".to_string(), + path: "m/44'/434'/0'".to_string(), + curve: "ed25519".to_string(), + }, + PublicKeyDerivation { + chain_type: "FILECOIN".to_string(), + path: "m/44'/461'/0'".to_string(), + curve: "secp256k1".to_string(), + }, + PublicKeyDerivation { + chain_type: "BITCOIN".to_string(), + path: "m/0/0".to_string(), + curve: "secp256k1".to_string(), + }, + PublicKeyDerivation { + chain_type: "POLKADOT".to_string(), + path: "m/0'/0'".to_string(), + curve: "ed25519".to_string(), + }, + ]; + let param = GetPublicKeysParam { derivations }; + let action: ImkeyAction = ImkeyAction { + method: "get_public_keys".to_string(), + param: Some(::prost_types::Any { + type_url: "get_public_keys".to_string(), + value: encode_message(param).unwrap(), + }), + }; + let action = hex::encode(encode_message(action).unwrap()); + let ret_hex = unsafe { _to_str(call_imkey_api(_to_c_char(action.as_str()))) }; + let ret_bytes = hex::decode(ret_hex).unwrap(); + let result: GetPublicKeysResult = + GetPublicKeysResult::decode(ret_bytes.as_slice()).unwrap(); + assert_eq!( + result.public_keys[0], + "0x0303f2f84851514bf2f40a46b5bb9dbf4e5913fbacde1a96968cda08f9fd882caa" + ); + assert_eq!( + result.public_keys[1], + "0x03f3175613d999d15e6fde436825a3cc2c568f8f5082275f06eb4bd6e561f503ac" + ); + assert_eq!( + result.public_keys[2], + "EOS7ik8DKrvmBBKZHePgRSJiFhKoG1r3w8wNBwtRE9rTntW8yYmSk" + ); + assert_eq!( + result.public_keys[3], + "0x03cca080a087467a703b01a1f87a65f3e4e566508c6f85f5582a6973a77c80c35e" + ); + assert_eq!( + result.public_keys[4], + "0x0303f2f84851514bf2f40a46b5bb9dbf4e5913fbacde1a96968cda08f9fd882caa" + ); + assert_eq!( + result.public_keys[5], + "0x02c7709248e6205fefa7366efb0269021f1f2f1e04fdc334fe7c7fd2628d7451e8" + ); + assert_eq!( + result.public_keys[6], + "0x03349ff19e96c1aa7f568e493f85fa506320410245b4e69146bb0d3d8b5df3b901" + ); + assert_eq!( + result.public_keys[7], + "0x03ad9d0e2d9181e23c7075a56ed4f10e249aaf38a2bb7aa0cb604f8b768ea84b86" + ); + assert_eq!( + result.public_keys[8], + "0x2d9aecea337e9eee9d9a86f2d81aadafa88557fe5fb49efa187ce8ca3bc4e2a2" + ); + assert_eq!( + result.public_keys[9], + "0x5fcd1bec698400671d396c7f3507441a9b62340731b53aebf0a58c57512b5c45" + ); + assert_eq!( + result.public_keys[10], + "0x02611325073f61ae5feb6c8dce96857d007cdb765937e53e43e6f91374dac62edb" + ); + assert_eq!( + result.public_keys[11], + "0x0330f3b39c1a4278db118d2a8e8cd1fd5bd574d6fac43040f5ec514ad6cc776892" + ); + assert_eq!( + result.public_keys[12], + "0x4f3f24c064893a591d5a5b31990de9d12ed9da0c8650bcf98ede27e3da141401" + ); + } + + #[test] + fn test_get_public_keys_error_case() { + connect_and_bind(); + + let derivations = vec![PublicKeyDerivation { + chain_type: "POLKADOT".to_string(), + path: "m/44'/354'/0'/0'/0'".to_string(), + curve: "sr25519".to_string(), + }]; + let param = GetPublicKeysParam { derivations }; + let action: ImkeyAction = ImkeyAction { + method: "get_public_keys".to_string(), + param: Some(::prost_types::Any { + type_url: "get_public_keys".to_string(), + value: encode_message(param).unwrap(), + }), + }; + let action = hex::encode(encode_message(action).unwrap()); + let _ = unsafe { _to_str(call_imkey_api(_to_c_char(action.as_str()))) }; + let err = unsafe { _to_str(imkey_get_last_err_message()) }; + assert!(!err.is_empty()); + let error_ret: ErrorResponse = + ErrorResponse::decode(hex::decode(err).unwrap().as_slice()).unwrap(); + assert_eq!(error_ret.error, "unsupported_curve_type"); + } + fn derive_account(derivation: Derivation) -> DeriveAccountsResult { connect_and_bind();