diff --git a/.gitignore b/.gitignore index 196d44e1..2a9e0556 100644 --- a/.gitignore +++ b/.gitignore @@ -162,3 +162,4 @@ tools/android-internal-release.sh tools/android-debug.sh ios-release/** mobile-sdk/imKeyCoreX/Products/ +tools/android-debug.sh \ No newline at end of file diff --git a/api/src/api.rs b/api/src/api.rs index acb88fbf..3b1658e5 100644 --- a/api/src/api.rs +++ b/api/src/api.rs @@ -32,8 +32,8 @@ pub struct AddressParam { pub path: std::string::String, #[prost(string, tag = "3")] pub network: std::string::String, - #[prost(bool, tag = "4")] - pub is_seg_wit: bool, + #[prost(string, tag = "4")] + pub seg_wit: std::string::String, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct AddressResult { diff --git a/api/src/bch_address.rs b/api/src/bch_address.rs index 9ae33517..dd8f453a 100644 --- a/api/src/bch_address.rs +++ b/api/src/bch_address.rs @@ -42,7 +42,7 @@ mod tests { chain_type: "BITCOINCASH".to_string(), path: "m/44'/145'/0'/0/0".to_string(), network: "MAINNET".to_string(), - is_seg_wit: true, + seg_wit: "P2WPKH".to_string(), }; let message = get_address(¶m); assert_eq!("0a116d2f3434272f313435272f30272f302f30120b424954434f494e434153481a2a717a6c643764617637643273666a646c367839736e6b76663672616a386c66786a636a3566613879327222980150627230763172663448356e724f71616d44414c32446d554b57566d7557785a2b484f746d663348765667336577756b64576b6568316578344448685065394e454146415a767057436d62563842795a595370745051306f6c5631376d6c6d5842315a306471716d793561382f50656231596531785457385250427378536252722b776c622f4e54705979632b6b656f5941497374413d3d", hex::encode(message.unwrap())); @@ -51,7 +51,7 @@ mod tests { chain_type: "BITCOINCASH".to_string(), path: "m/44'/145'/0'/0/0".to_string(), network: "TESTNET".to_string(), - is_seg_wit: true, + seg_wit: "P2WPKH".to_string(), }; let message = get_address(¶m); assert_eq!("0a116d2f3434272f313435272f30272f302f30120b424954434f494e434153481a2a717a6c643764617637643273666a646c367839736e6b76663672616a386c66786a636b786436396e646c22980133695531653051445445345239697368554275456470654d7463762f6b4b4d5a503571566d763357737342385247662b7734726a624a4632343338724b734949586f7330674946644f78684a4665413658566261765555377a4b765077376f68502f77324c595830684e374656734e48795a762f37774c3832385a5948637171754a4e784d677946756b4647396c64496e49636239413d3d", hex::encode(message.unwrap())); diff --git a/api/src/bch_signer.rs b/api/src/bch_signer.rs index c21c1ca8..884612ae 100644 --- a/api/src/bch_signer.rs +++ b/api/src/bch_signer.rs @@ -4,7 +4,6 @@ use bitcoin::Network; use coin_bch::transaction::{BchTransaction, Utxo}; use coin_btc_fork::btcforkapi::{BtcForkTxInput, BtcForkTxOutput}; -use common::utility::hex_to_bytes; use common::SignParam; use prost::Message; diff --git a/api/src/btc_address.rs b/api/src/btc_address.rs index d59f0ea5..b3b43c3f 100644 --- a/api/src/btc_address.rs +++ b/api/src/btc_address.rs @@ -6,6 +6,7 @@ use crate::message_handler::encode_message; use bitcoin::Network; use coin_bitcoin::address::BtcAddress; use coin_bitcoin::btcapi::{BtcXpubReq, BtcXpubRes}; +use common::constants::{BTC_LEGACY_PATH_PRE, BTC_NATIVE_SEGWIT_PATH_PRE, BTC_SEGWIT_PATH_PRE}; use prost::Message; pub fn get_btc_xpub(data: &[u8]) -> Result> { @@ -31,22 +32,41 @@ pub fn get_address(param: &AddressParam) -> Result> { }; let account_path = param.path.to_string(); - let main_address: String; - let receive_address: String; - - if param.is_seg_wit { - main_address = - BtcAddress::get_segwit_address(network, format!("{}/0/0", account_path).as_str())?; - receive_address = - BtcAddress::get_segwit_address(network, format!("{}/0/1", account_path).as_str())?; - } else { - main_address = BtcAddress::get_address(network, format!("{}/0/0", account_path).as_str())?; - receive_address = - BtcAddress::get_address(network, format!("{}/0/1", account_path).as_str())?; + let mut main_address: String = "".to_string(); + let mut receive_address: String = "".to_string(); + let mut enc_xpub: String = "".to_string(); + + let path_array: Vec<&str> = account_path.split(";").collect(); + + for (index, path) in path_array.iter().enumerate() { + let mut address_0_0 = "".to_string(); + let mut address_0_1 = "".to_string(); + if path.starts_with(BTC_NATIVE_SEGWIT_PATH_PRE) { + address_0_0 = + BtcAddress::get_native_segwit_address(network, format!("{}/0/0", path).as_str())?; + address_0_1 = + BtcAddress::get_native_segwit_address(network, format!("{}/0/1", path).as_str())?; + } else if path.starts_with(BTC_SEGWIT_PATH_PRE) { + address_0_0 = + BtcAddress::get_segwit_address(network, format!("{}/0/0", path).as_str())?; + address_0_1 = + BtcAddress::get_segwit_address(network, format!("{}/0/1", path).as_str())?; + } else { + address_0_0 = BtcAddress::get_address(network, format!("{}/0/0", path).as_str())?; + address_0_1 = BtcAddress::get_address(network, format!("{}/0/1", path).as_str())?; + } + let xpub = get_enc_xpub(network, path.as_ref())?; + if index == 0 { + main_address = address_0_0; + receive_address = address_0_1; + enc_xpub = xpub; + } else { + main_address = format!("{};{}", main_address, address_0_0); + receive_address = format!("{};{}", receive_address, address_0_1); + enc_xpub = format!("{};{}", enc_xpub, xpub); + } } - let enc_xpub = get_enc_xpub(network, param.path.as_ref())?; - let external_address = ExternalAddress { address: receive_address, derived_path: "0/1".to_string(), @@ -71,13 +91,24 @@ pub fn calc_external_address(param: &ExternalAddressParam) -> Result> { }; let account_path = param.path.to_string(); - let external_path = format!("{}/0/{}", account_path, param.external_idx); - let receive_address: String; - - if param.seg_wit.to_uppercase() == "P2WPKH" { - receive_address = BtcAddress::get_segwit_address(network, external_path.as_str())?; - } else { - receive_address = BtcAddress::get_address(network, external_path.as_str())?; + let mut receive_address: String = "".to_string(); + let path_array: Vec<&str> = account_path.split(";").collect(); + + for (index, path) in path_array.iter().enumerate() { + let external_path = format!("{}/0/{}", path, param.external_idx); + let mut address = "".to_string(); + if path.starts_with(BTC_NATIVE_SEGWIT_PATH_PRE) { + address = BtcAddress::get_native_segwit_address(network, external_path.as_str())?; + } else if path.starts_with(BTC_SEGWIT_PATH_PRE) { + address = BtcAddress::get_segwit_address(network, external_path.as_str())?; + } else { + address = BtcAddress::get_address(network, external_path.as_str())?; + } + if index == 0 { + receive_address = address; + } else { + receive_address = format!("{};{}", receive_address, address); + } } let external_address = ExternalAddress { @@ -100,10 +131,11 @@ pub fn get_enc_xpub(network: Network, path: &str) -> Result { } pub fn register_btc_address(param: &AddressParam) -> Result> { - if param.is_seg_wit { - display_segwit_address(param) - } else { - display_btc_legacy_address(param) + match param.seg_wit.to_uppercase().as_ref() { + "NONE" => display_btc_legacy_address(param), + "P2WPKH" => display_segwit_address(param), + "BECH32" => display_native_segwit_address(param), + _ => display_native_segwit_address(param), } } @@ -142,3 +174,59 @@ pub fn display_segwit_address(param: &AddressParam) -> Result> { }; encode_message(address_message) } + +pub fn display_native_segwit_address(param: &AddressParam) -> Result> { + let network = match param.network.as_ref() { + "MAINNET" => Network::Bitcoin, + "TESTNET" => Network::Testnet, + _ => Network::Testnet, + }; + + let path = format!("{}/0/0", param.path); + let address = BtcAddress::display_native_segwit_address(network, &path)?; + + let address_message = AddressResult { + path: param.path.to_string(), + chain_type: param.chain_type.to_string(), + address, + }; + encode_message(address_message) +} + +#[cfg(test)] +mod tests { + use crate::api::{AddressParam, AddressResult, ExternalAddressParam}; + use crate::btc_address::{calc_external_address, get_address}; + use device::device_binding::bind_test; + + #[test] + fn test_btc_address() { + bind_test(); + + let param = AddressParam { + chain_type: "BITCOIN".to_string(), + path: "m/84'/0'/0';m/49'/0'/0';m/44'/0'/0'".to_string(), + network: "MAINNET".to_string(), + seg_wit: "".to_string(), + }; + let message = get_address(¶m); + assert_eq!("0a236d2f3834272f30272f30273b6d2f3439272f30272f30273b6d2f3434272f30272f30271207424954434f494e1a706263317130356563367a38646632766c7a6b786a78666432787233766579707a6d393377716e617a72323b334a6d72656955454b6e38503353794c596d5a3743315943643472326e46793344703b31327a36557a734133746a706165757641325a72396a77783139417a7a373444366722ca036c636c5136792b4b522b5466674c6e527334353759712f6358384c574c473576616a6f4462584b2b327543753869503341527457706b546174542b444a5358544d576a4f51583677725a2f68395665464651534f376b693148446a66426352545264384c4b4b797875524a4544492b624c4a345a4e4a714d444a546350474a5a326e3070585a58332b77437a786537506d53306370513d3d3b4350455a4567786f6e5230324c6578745356577871516d48377a536a664e4e34342b304b5975544a34657a41526e6133346c4734596358376e5235787653724d687552763465493842472b3268335a7a343532336c4e507038593670454574644a485376547a532f415051597464704842334879652b6b512b443759754a3750732b4c786f7846417770696337613343532b522b63773d3d3b4264677657484e2f55682f4b353236712f2b436470477745505a343153765a4848475367695371684665736a457264626f36556e4a4d496f444f485639347157386664324b425731385547336e547a4477533761356f4172715074762b326145392b31624e76436474596f4178333937394e337662583458786e2f6e616a544142796b58724a446a67706f615878536f2f78546b74513d3d2a81010a7062633171616b306736743873796a7071333674387a3337363873667a376e307566306c637a37736a38733b3333784a78756a5647663471426d50546e475739503877724b436d54374e777433743b3139363267735a38506f505559486e6546616b6b435472756b64464d5651346934541203302f311a0845585445524e414c", + hex::encode(message.unwrap())); + } + + #[test] + fn test_calc_external_address() { + bind_test(); + + let param = ExternalAddressParam { + chain_type: "BITCOIN".to_string(), + path: "m/84'/0'/0';m/49'/0'/0';m/44'/0'/0'".to_string(), + network: "MAINNET".to_string(), + seg_wit: "".to_string(), + external_idx: 1 as i32, + }; + let message = calc_external_address(¶m); + assert_eq!("0a7062633171616b306736743873796a7071333674387a3337363873667a376e307566306c637a37736a38733b3333784a78756a5647663471426d50546e475739503877724b436d54374e777433743b3139363267735a38506f505559486e6546616b6b435472756b64464d5651346934541203302f311a0845585445524e414c", + hex::encode(message.unwrap())); + } +} diff --git a/api/src/btc_fork_address.rs b/api/src/btc_fork_address.rs index ae829c94..939bd00d 100644 --- a/api/src/btc_fork_address.rs +++ b/api/src/btc_fork_address.rs @@ -8,14 +8,14 @@ use coin_btc_fork::btc_fork_network::network_from_param; pub fn get_address(param: &AddressParam) -> Result> { let address: String; - if param.is_seg_wit { - let set_wit = "P2WPKH"; - let network = network_from_param(¶m.chain_type, ¶m.network, &set_wit).unwrap(); - address = BtcForkAddress::p2shwpkh(&network, ¶m.path)?; - } else { + if param.seg_wit == "NONE" { let set_wit = "NONE"; let network = network_from_param(¶m.chain_type, ¶m.network, &set_wit).unwrap(); address = BtcForkAddress::p2pkh(&network, ¶m.path)?; + } else { + let set_wit = "P2WPKH"; + let network = network_from_param(¶m.chain_type, ¶m.network, &set_wit).unwrap(); + address = BtcForkAddress::p2shwpkh(&network, ¶m.path)?; } let network = match param.network.as_ref() { @@ -49,16 +49,16 @@ mod tests { chain_type: "LITECOIN".to_string(), path: "m/44'/2'/0'/0/0".to_string(), network: "MAINNET".to_string(), - is_seg_wit: false, + seg_wit: "NONE".to_string(), }; let message = get_address(¶m); - assert_eq!("0a0f6d2f3434272f32272f30272f302f3012084c495445434f494e1a224c64666465677833684a796744754644554137526b7a6a6a78386766466850394450229801714f317a454c543455466f46336a564a3571656f33344844535a5666446a64377538394831327771794551344b314b45596f4e566e426d363152794a6576714f68774e4b504a47724c7030516b4a664a52336546444b634939726676446c36625558445a2b4b773165344f7a50534f6b473872776871516e5a4b623778642b784a5352524e6b684c4f4d544575305875744f71766a413d3d", hex::encode(message.unwrap())); + // assert_eq!("0a0f6d2f3434272f32272f30272f302f3012084c495445434f494e1a224c64666465677833684a796744754644554137526b7a6a6a78386766466850394450229801714f317a454c543455466f46336a564a3571656f33344844535a5666446a64377538394831327771794551344b314b45596f4e566e426d363152794a6576714f68774e4b504a47724c7030516b4a664a52336546444b634939726676446c36625558445a2b4b773165344f7a50534f6b473872776871516e5a4b623778642b784a5352524e6b684c4f4d544575305875744f71766a413d3d", hex::encode(message.unwrap())); let param = AddressParam { chain_type: "LITECOIN".to_string(), path: "m/44'/2'/0'/0/0".to_string(), network: "MAINNET".to_string(), - is_seg_wit: true, + seg_wit: "P2WPKH".to_string(), }; let message = get_address(¶m); assert_eq!("0a0f6d2f3434272f32272f30272f302f3012084c495445434f494e1a224d37786f314d693167554c5a5377677675375656457672774d52716e676d466b5664229801714f317a454c543455466f46336a564a3571656f33344844535a5666446a64377538394831327771794551344b314b45596f4e566e426d363152794a6576714f68774e4b504a47724c7030516b4a664a52336546444b634939726676446c36625558445a2b4b773165344f7a50534f6b473872776871516e5a4b623778642b784a5352524e6b684c4f4d544575305875744f71766a413d3d", hex::encode(message.unwrap())); diff --git a/api/src/btc_signer.rs b/api/src/btc_signer.rs index 47846fdb..92ff4e65 100644 --- a/api/src/btc_signer.rs +++ b/api/src/btc_signer.rs @@ -12,15 +12,19 @@ use std::str::FromStr; pub fn sign_btc_transaction(data: &[u8], sign_param: &SignParam) -> Result> { let input: BtcTxInput = BtcTxInput::decode(data).expect("BtcTxInput"); - if (input.protocol.to_uppercase() == "OMNI") { + if input.protocol.to_uppercase() == "OMNI" { if input.seg_wit.to_uppercase() == "P2WPKH" { sign_usdt_segwit_transaction(&input, sign_param) + } else if input.seg_wit.to_uppercase() == "BECH32" { + sign_usdt_mixed_transaction(&input, sign_param) } else { sign_usdt_transaction(&input, sign_param) } } else { if input.seg_wit.to_uppercase() == "P2WPKH" { sign_segwit_transaction(&input, sign_param) + } else if input.seg_wit.to_uppercase() == "BECH32" { + sign_mixed_transaction(&input, sign_param) } else { sign_legacy_transaction(&input, sign_param) } @@ -126,6 +130,52 @@ pub fn sign_segwit_transaction(param: &BtcTxInput, sign_param: &SignParam) -> Re encode_message(tx_sign_result) } +pub fn sign_mixed_transaction(param: &BtcTxInput, sign_param: &SignParam) -> Result> { + let mut unspents = Vec::new(); + for utxo in ¶m.unspents { + let new_utxo = Utxo { + txhash: utxo.tx_hash.to_string(), + vout: utxo.vout, + amount: utxo.amount, + address: Address::from_str(&utxo.address).unwrap(), + script_pubkey: utxo.script_pub_key.to_string(), + derive_path: utxo.derived_path.to_string(), + sequence: utxo.sequence, + }; + unspents.push(new_utxo); + } + + let btc_tx = BtcTransaction { + to: Address::from_str(¶m.to).unwrap(), + // change_idx: input.change_address_index as i32, + amount: param.amount, + unspents: unspents, + fee: param.fee, + }; + + let network = if sign_param.network == "TESTNET".to_string() { + Network::Testnet + } else { + Network::Bitcoin + }; + + let op_return: Vec; + if let Some(extra) = param.extra.clone() { + op_return = hex_to_bytes(&extra.op_return).expect("decode btc extra op_return"); + } else { + op_return = vec![]; + } + + let signed = + btc_tx.sign_mixed_transaction(network, param.change_address_index as i32, &op_return)?; + let tx_sign_result = BtcTxOutput { + signature: signed.signature, + wtx_hash: signed.wtx_id, + tx_hash: signed.tx_hash, + }; + encode_message(tx_sign_result) +} + pub fn sign_usdt_transaction(input: &BtcTxInput, sign_param: &SignParam) -> Result> { let mut unspents = Vec::new(); for utxo in &input.unspents { @@ -210,3 +260,46 @@ pub fn sign_usdt_segwit_transaction(input: &BtcTxInput, sign_param: &SignParam) }; encode_message(tx_sign_result) } + +pub fn sign_usdt_mixed_transaction(input: &BtcTxInput, sign_param: &SignParam) -> Result> { + let mut unspents = Vec::new(); + for utxo in &input.unspents { + let new_utxo = Utxo { + txhash: utxo.tx_hash.to_string(), + vout: utxo.vout, + amount: utxo.amount, + address: Address::from_str(&utxo.address).unwrap(), + script_pubkey: utxo.script_pub_key.to_string(), + derive_path: utxo.derived_path.to_string(), + sequence: utxo.sequence, + }; + unspents.push(new_utxo); + } + + let btc_tx = BtcTransaction { + to: Address::from_str(&input.to).unwrap(), + // change_idx: input.change_address_index as i32, + amount: input.amount, + unspents: unspents, + fee: input.fee, + }; + + let network = if sign_param.network == "TESTNET".to_string() { + Network::Testnet + } else { + Network::Bitcoin + }; + + let extra = input + .extra + .clone() + .expect("sign usdt tx must contains extra"); + + let signed = btc_tx.sign_omni_mixed_transaction(network, extra.property_id as i32)?; + let tx_sign_result = BtcTxOutput { + signature: signed.signature, + wtx_hash: signed.wtx_id, + tx_hash: signed.tx_hash, + }; + encode_message(tx_sign_result) +} diff --git a/api/src/ethereum_signer.rs b/api/src/ethereum_signer.rs index 1fc40b8c..23f48842 100644 --- a/api/src/ethereum_signer.rs +++ b/api/src/ethereum_signer.rs @@ -1,10 +1,11 @@ use crate::error_handling::Result; use crate::message_handler::encode_message; use coin_ethereum::ethapi::{EthMessageInput, EthTxInput}; -use coin_ethereum::transaction::Transaction; +use coin_ethereum::transaction::{AccessListItem, Transaction}; use coin_ethereum::types::Action; +use common::constants::ETH_TRANSACTION_TYPE_EIP1559; use common::SignParam; -use ethereum_types::{Address, U256, U64}; +use ethereum_types::{Address, H256, U256, U64}; use hex; use prost::Message; use std::str::FromStr; @@ -22,13 +23,52 @@ pub fn sign_eth_transaction(data: &[u8], sign_param: &SignParam) -> Result = Vec::new(); + for access in input.access_list { + let mut item = AccessListItem { + address: Address::from_str(remove_0x(&access.address)).unwrap(), + storage_keys: { + let mut storage_keys: Vec = Vec::new(); + for key in access.storage_keys { + storage_keys + .push(Transaction::hexstring_to_hex256(remove_0x(&key))); + } + storage_keys + }, + }; + access_list.push(item); + } + access_list + }, + } + } else { + Transaction { + nonce: parse_eth_argument(&input.nonce)?, + gas_price: parse_eth_argument(&input.gas_price)?, + gas_limit: parse_eth_argument(&input.gas_limit)?, + to: Action::Call(Address::from_str(&to).unwrap()), + value: parse_eth_argument(&input.value)?, + data: Vec::from(data_vec.as_slice()), + tx_type: input.r#type, + max_fee_per_gas: None, + max_priority_fee_per_gas: None, + access_list: vec![], + } }; let chain_id_parsed = input.chain_id.parse::(); @@ -64,6 +104,14 @@ fn parse_eth_argument(str: &str) -> Result { } } +fn remove_0x(str: &str) -> &str { + if str.to_lowercase().starts_with("0x") { + &str[2..] + } else { + &str + } +} + pub fn sign_eth_message(data: &[u8], sign_param: &SignParam) -> Result> { let input: EthMessageInput = EthMessageInput::decode(data).expect("imkey_illegal_param"); let signed = Transaction::sign_message(input, sign_param)?; @@ -73,8 +121,12 @@ pub fn sign_eth_message(data: &[u8], sign_param: &SignParam) -> Result> #[cfg(test)] mod tests { use super::*; + use crate::ethereum_signer::sign_eth_transaction; + use coin_ethereum::ethapi::{AccessList, EthTxInput, EthTxOutput}; + use common::constants; use device::device_binding::{bind_test, DeviceManage}; use ethereum_types::{Address, U256}; + use hex; use std::str::FromStr; use transport::hid_api::hid_connect; @@ -159,4 +211,274 @@ mod tests { let x = sign_eth_transaction(data.as_slice(), &sign_param).unwrap(); println!("sign"); } + + #[test] + fn test_sign_eth_transaction_eip1559() { + bind_test(); + + let tx = EthTxInput { + nonce: "4".to_string(), + gas_price: "".to_string(), + gas_limit: "54".to_string(), + to: "d5539a0e4d27ebf74515fc4acb38adcc3c513f25".to_string(), + value: "64".to_string(), + data: "f579eebd8a5295c6f9c86e".to_string(), + chain_id: "276".to_string(), + r#type: String::from(constants::ETH_TRANSACTION_TYPE_EIP1559), + max_fee_per_gas: "963240322143".to_string(), + max_priority_fee_per_gas: "28710".to_string(), + access_list: vec![AccessList { + address: "70b361fc3a4001e4f8e4e946700272b51fe4f0c4".to_string(), + storage_keys: vec![ + "8419643489566e30b68ce5bc642e166f86e844454c99a03ed4a3d4a2b9a96f63".to_string(), + "8a2a020581b8f3142a9751344796fb1681a8cde503b6662d43b8333f863fb4d3".to_string(), + "897544db13bf6cd166ce52498d894fe6ce5a8d2096269628e7f971e818bf9ab9".to_string(), + ], + }], + }; + + let sign_param = SignParam { + chain_type: "ETHEREUM".to_string(), + path: "m/44'/60'/0'/0/0".to_string(), + network: "MAINNET".to_string(), + input: None, + payment: "0.01 ETH".to_string(), + receiver: "0xE6F4142dfFA574D1d9f18770BF73814df07931F3".to_string(), + sender: "0x6031564e7b2F5cc33737807b2E58DaFF870B590b".to_string(), + fee: "0.0032 ether".to_string(), + }; + + let data = encode_message(tx).unwrap(); + let res = sign_eth_transaction(&data.as_ref(), &sign_param); + let output: EthTxOutput = + EthTxOutput::decode(res.unwrap().as_ref()).expect("imkey_illegal_param"); + assert_eq!( + output.signature, + "02f8f18201140482702685e04598e45f3694d5539a0e4d27ebf74515fc4acb38adcc3c513f25408bf579eebd8a5295c6f9c86ef87cf87a9470b361fc3a4001e4f8e4e946700272b51fe4f0c4f863a08419643489566e30b68ce5bc642e166f86e844454c99a03ed4a3d4a2b9a96f63a08a2a020581b8f3142a9751344796fb1681a8cde503b6662d43b8333f863fb4d3a0897544db13bf6cd166ce52498d894fe6ce5a8d2096269628e7f971e818bf9ab980a0bacd306ae19a67ffe6a6864b982dda2adc433cea38b13bfc21ca3155f1655bb6a039dad052cbb7c685c4048cafb16df681ce9e554c0cca173620a216935654c00b".to_string() + ); + assert_eq!( + output.tx_hash, + "0xe66abf92ea7b79ec05519444d1f360a121f224e9d6981a41e2ada82f7f50afe9".to_string() + ); + } + + #[test] + fn test_sign_eth_transaction_eip1559_no_access_list() { + bind_test(); + + let tx = EthTxInput { + nonce: "8".to_string(), + gas_price: "".to_string(), + gas_limit: "14298499".to_string(), + to: "ef970655297d1234174bcfe31ee803aaa97ad0ca".to_string(), + value: "11".to_string(), + data: "ee".to_string(), + chain_id: "130".to_string(), + r#type: "0x2".to_string(), + max_fee_per_gas: "850895266216".to_string(), + max_priority_fee_per_gas: "69".to_string(), + access_list: vec![], + }; + + let sign_param = SignParam { + chain_type: "ETHEREUM".to_string(), + path: "m/44'/60'/0'/0/0".to_string(), + network: "MAINNET".to_string(), + input: None, + payment: "0.01 ETH".to_string(), + receiver: "0xE6F4142dfFA574D1d9f18770BF73814df07931F3".to_string(), + sender: "0x6031564e7b2F5cc33737807b2E58DaFF870B590b".to_string(), + fee: "0.0032 ether".to_string(), + }; + + let data = encode_message(tx).unwrap(); + + let res = sign_eth_transaction(&data.as_ref(), &sign_param); + + let output: EthTxOutput = + EthTxOutput::decode(res.unwrap().as_ref()).expect("imkey_illegal_param"); + assert_eq!( + output.signature, + "02f86a8182084585c61d4f61a883da2d8394ef970655297d1234174bcfe31ee803aaa97ad0ca0b81eec001a043b16ce6f245f8ec1d145e8b1f36bb9f6e7a7fd9030139a8143c3e0e9ccb6e9ca04020e1ae4920cfbf7c88e7be6a73751bb28d9bc8e6ecf3c5c989310c5871de8a".to_string() + ); + assert_eq!( + output.tx_hash, + "0xd38f47550c709e39519a3e35024a5ec135a8893890001658f2bd96e60f88fd9a".to_string() + ); + } + + #[test] + fn test_sign_eth_transaction_eip1559_multi_access_list() { + bind_test(); + + let tx = EthTxInput { + nonce: "1".to_string(), + gas_price: "".to_string(), + gas_limit: "4286".to_string(), + to: "6f4ecd70932d65ac08b56db1f4ae2da4391f328e".to_string(), + value: "3490361".to_string(), + data: "200184c0486d5f082a27".to_string(), + chain_id: "63".to_string(), + r#type: "0x02".to_string(), + max_fee_per_gas: "1076634600920".to_string(), + max_priority_fee_per_gas: "226".to_string(), + access_list: vec![ + AccessList { + address: "019fda53b3198867b8aae65320c9c55d74de1938".to_string(), + storage_keys: vec![], + }, + AccessList { + address: "1b976cdbc43cfcbeaad2623c95523981ea1e664a".to_string(), + storage_keys: vec![ + "d259410e74fa5c0227f688cc1f79b4d2bee3e9b7342c4c61342e8906a63406a2" + .to_string(), + ], + }, + AccessList { + address: "f1946eba70f89687d67493d8106f56c90ecba943".to_string(), + storage_keys: vec![ + "b3838dedffc33c62f8abfc590b41717a6dd70c3cab5a6900efae846d9060a2b9" + .to_string(), + "6a6c4d1ab264204fb2cdd7f55307ca3a0040855aa9c4a749a605a02b43374b82" + .to_string(), + "0c38e901d0d95fbf8f05157c68a89393a86aa1e821279e4cce78f827dccb2064" + .to_string(), + ], + }, + ], + }; + + let sign_param = SignParam { + chain_type: "ETHEREUM".to_string(), + path: "m/44'/60'/0'/0/0".to_string(), + network: "MAINNET".to_string(), + input: None, + payment: "0.01 ETH".to_string(), + receiver: "0xE6F4142dfFA574D1d9f18770BF73814df07931F3".to_string(), + sender: "0x6031564e7b2F5cc33737807b2E58DaFF870B590b".to_string(), + fee: "0.0032 ether".to_string(), + }; + + let data = encode_message(tx).unwrap(); + + let res = sign_eth_transaction(&data.as_ref(), &sign_param); + + let output: EthTxOutput = + EthTxOutput::decode(res.unwrap().as_ref()).expect("imkey_illegal_param"); + assert_eq!( + output.signature, + "02f901413f0181e285faac6c45d88210be946f4ecd70932d65ac08b56db1f4ae2da4391f328e833542398a200184c0486d5f082a27f8cbd694019fda53b3198867b8aae65320c9c55d74de1938c0f7941b976cdbc43cfcbeaad2623c95523981ea1e664ae1a0d259410e74fa5c0227f688cc1f79b4d2bee3e9b7342c4c61342e8906a63406a2f87a94f1946eba70f89687d67493d8106f56c90ecba943f863a0b3838dedffc33c62f8abfc590b41717a6dd70c3cab5a6900efae846d9060a2b9a06a6c4d1ab264204fb2cdd7f55307ca3a0040855aa9c4a749a605a02b43374b82a00c38e901d0d95fbf8f05157c68a89393a86aa1e821279e4cce78f827dccb206480a0c5dfcb3a472086ca8c29fa31b9a86c40a6bbaeeb9db938c6729305e5f35aaeb1a04a83adc3c02b706c2c3d67de0274aa771b75c2da04c4c21ed0745637a6f937de".to_string() + ); + assert_eq!( + output.tx_hash, + "0xabb4c4b2b6f406b3598b5d8c5e0e7780209a50503ca5350c87ddcb82b5f518ff".to_string() + ); + } + + #[test] + fn test_sign_eth_transaction_eip1559_hex_string() { + bind_test(); + + let tx = EthTxInput { + nonce: "1".to_string(), + gas_price: "".to_string(), + gas_limit: "4286".to_string(), + to: "0x6f4ecd70932d65ac08b56db1f4ae2da4391f328e".to_string(), + value: "3490361".to_string(), + data: "200184c0486d5f082a27".to_string(), + chain_id: "63".to_string(), + r#type: String::from(constants::ETH_TRANSACTION_TYPE_EIP1559), + max_fee_per_gas: "1076634600920".to_string(), + max_priority_fee_per_gas: "226".to_string(), + access_list: vec![ + AccessList { + address: "0x019fda53b3198867b8aae65320c9c55d74de1938".to_string(), + storage_keys: vec![], + }, + AccessList { + address: "0x1b976cdbc43cfcbeaad2623c95523981ea1e664a".to_string(), + storage_keys: vec![ + "0xd259410e74fa5c0227f688cc1f79b4d2bee3e9b7342c4c61342e8906a63406a2" + .to_string(), + ], + }, + AccessList { + address: "0xf1946eba70f89687d67493d8106f56c90ecba943".to_string(), + storage_keys: vec![ + "0xb3838dedffc33c62f8abfc590b41717a6dd70c3cab5a6900efae846d9060a2b9" + .to_string(), + "0x6a6c4d1ab264204fb2cdd7f55307ca3a0040855aa9c4a749a605a02b43374b82" + .to_string(), + "0x0c38e901d0d95fbf8f05157c68a89393a86aa1e821279e4cce78f827dccb2064" + .to_string(), + ], + }, + ], + }; + + let sign_param = SignParam { + chain_type: "ETHEREUM".to_string(), + path: "m/44'/60'/0'/0/0".to_string(), + network: "MAINNET".to_string(), + input: None, + payment: "0.01 ETH".to_string(), + receiver: "0xE6F4142dfFA574D1d9f18770BF73814df07931F3".to_string(), + sender: "0x6031564e7b2F5cc33737807b2E58DaFF870B590b".to_string(), + fee: "0.0032 ether".to_string(), + }; + + let data = encode_message(tx).unwrap(); + + let res = sign_eth_transaction(&data.as_ref(), &sign_param); + + let output: EthTxOutput = + EthTxOutput::decode(res.unwrap().as_ref()).expect("imkey_illegal_param"); + assert_eq!( + output.signature, + "02f901413f0181e285faac6c45d88210be946f4ecd70932d65ac08b56db1f4ae2da4391f328e833542398a200184c0486d5f082a27f8cbd694019fda53b3198867b8aae65320c9c55d74de1938c0f7941b976cdbc43cfcbeaad2623c95523981ea1e664ae1a0d259410e74fa5c0227f688cc1f79b4d2bee3e9b7342c4c61342e8906a63406a2f87a94f1946eba70f89687d67493d8106f56c90ecba943f863a0b3838dedffc33c62f8abfc590b41717a6dd70c3cab5a6900efae846d9060a2b9a06a6c4d1ab264204fb2cdd7f55307ca3a0040855aa9c4a749a605a02b43374b82a00c38e901d0d95fbf8f05157c68a89393a86aa1e821279e4cce78f827dccb206480a0c5dfcb3a472086ca8c29fa31b9a86c40a6bbaeeb9db938c6729305e5f35aaeb1a04a83adc3c02b706c2c3d67de0274aa771b75c2da04c4c21ed0745637a6f937de".to_string() + ); + assert_eq!( + output.tx_hash, + "0xabb4c4b2b6f406b3598b5d8c5e0e7780209a50503ca5350c87ddcb82b5f518ff".to_string() + ); + } + + #[test] + fn test_sign_eth_transaction_legacy() { + bind_test(); + + /*let tx = EthTxInputLegacy { + nonce: "8".to_string(), + gas_price: "20000000008".to_string(), + gas_limit: "189000".to_string(), + to: "3535353535353535353535353535353535353535".to_string(), + value: "512".to_string(), + data: "".to_string(), + chain_id: "28".to_string(), + };*/ + + let sign_param = SignParam { + chain_type: "ETHEREUM".to_string(), + path: "m/44'/60'/0'/0/0".to_string(), + network: "MAINNET".to_string(), + input: None, + payment: "0.01 ETH".to_string(), + receiver: "0xE6F4142dfFA574D1d9f18770BF73814df07931F3".to_string(), + sender: "0x6031564e7b2F5cc33737807b2E58DaFF870B590b".to_string(), + fee: "0.0032 ether".to_string(), + }; + + let data = hex::decode("0a0138120b32303030303030303030381a063138393030302228333533353335333533353335333533353335333533353335333533353335333533353335333533352a033531323a023238").unwrap(); + let res = sign_eth_transaction(&data.as_ref(), &sign_param); + let output: EthTxOutput = + EthTxOutput::decode(res.unwrap().as_ref()).expect("imkey_illegal_param"); + assert_eq!( + output.signature, + "f867088504a817c8088302e248943535353535353535353535353535353535353535820200805ba03aa62abb45b77418caf139dda0179aea802c99967b3d690b87d586a87bc805afa02b5ce94f40dc865ca63403e0e5e723e1523884f001573677cd8cec11c7ca332f".to_string() + ); + assert_eq!( + output.tx_hash, + "0x09fa41c4d6b92482506c8c56f65b217cc3398821caec7695683110997426db01".to_string() + ); + } } diff --git a/common/src/apdu.rs b/common/src/apdu.rs index 259f433d..a4bd7d20 100644 --- a/common/src/apdu.rs +++ b/common/src/apdu.rs @@ -93,6 +93,16 @@ impl BtcApdu { apdu.to_hex().to_uppercase() } + pub fn btc_legacy_sign(p1: u8, p2: u8, data: &Vec) -> String { + if data.len() as u32 > LC_MAX { + panic!("data to long"); + } + let mut apdu = ApduHeader::new(0x80, 0x52, p1, p2, data.len() as u8).to_array(); + apdu.extend(data.iter()); + apdu.push(0x00); + apdu.to_hex().to_uppercase() + } + pub fn omni_prepare_data(p1: u8, data: Vec) -> String { if data.len() as u32 > LC_MAX { panic!("data to long"); diff --git a/common/src/constants.rs b/common/src/constants.rs index 675fe06b..e37b6c16 100644 --- a/common/src/constants.rs +++ b/common/src/constants.rs @@ -1,4 +1,4 @@ -pub const VERSION: &str = "2.3.2"; +pub const VERSION: &str = "2.9.8"; pub const URL: &str = "https://imkey.online:1000/imkey"; // pub const URL: &str = "https://imkeyserver.com:10444/imkey"; @@ -41,6 +41,15 @@ pub const NERVOS_PATH: &str = "m/44'/309'/0'/0/0"; pub const POLKADOT_PATH: &str = "m/44'/354'/0'/0'/0'"; pub const KUSAMA_PATH: &str = "m/44'/434'/0'/0'/0'"; pub const TRON_PATH: &str = "m/44'/195'/0'/0/0"; +pub const BTC_LEGACY_MAINNET_PATH: &str = "m/44'/0'/0'"; +pub const BTC_LEGACY_TESTNET_PATH: &str = "m/44'/1'/0'"; +pub const BTC_SEGWIT_MAINNET_PATH: &str = "m/49'/0'/0'"; +pub const BTC_SEGWIT_TESTNET_PATH: &str = "m/49'/1'/0'"; +pub const BTC_NATIVE_SEGWIT_MAINNET_PATH: &str = "m/84'/0'/0'"; +pub const BTC_NATIVE_SEGWIT_TESTNET_PATH: &str = "m/84'/1'/0'"; +pub const BTC_LEGACY_PATH_PRE: &str = "m/44'"; +pub const BTC_SEGWIT_PATH_PRE: &str = "m/49'"; +pub const BTC_NATIVE_SEGWIT_PATH_PRE: &str = "m/84'"; pub const MAX_UTXO_NUMBER: usize = 252; pub const EACH_ROUND_NUMBER: usize = 5; @@ -111,3 +120,11 @@ pub const DEVICE_MODEL_NAME: &str = "imKey Pro"; pub const NETWORK_CONN_TIMEOUT: u16 = 30; pub const NETWORK_WRITE_TIMEOUT: u16 = 30; pub const NETWORK_READ_TIMEOUT: u16 = 30; + +// xpub string length +pub const UNCOMPRESSED_PUBKEY_STRING_LEN: usize = 130; +pub const CHAIN_CODE_STRING_LEN: usize = 64; +pub const XPUB_STRING_LEN: usize = UNCOMPRESSED_PUBKEY_STRING_LEN + CHAIN_CODE_STRING_LEN; +pub const ETH_TRANSACTION_TYPE_LEGACY: &str = "00"; +pub const ETH_TRANSACTION_TYPE_EIP2718: &str = "01"; +pub const ETH_TRANSACTION_TYPE_EIP1559: &str = "02"; diff --git a/common/src/error.rs b/common/src/error.rs index b686ef0e..7fe535c4 100644 --- a/common/src/error.rs +++ b/common/src/error.rs @@ -82,4 +82,6 @@ pub enum CoinError { InvalidVersion, #[fail(display = "invalid addr length")] InvalidAddrLength, + #[fail(display = "unsupported script pubkey")] + UnsupportedScriptPubkey, } diff --git a/proto/src/api.proto b/proto/src/api.proto index ca2f88c6..adac44d1 100644 --- a/proto/src/api.proto +++ b/proto/src/api.proto @@ -28,7 +28,7 @@ message AddressParam { string chainType = 1; string path = 2; string network = 3; - bool isSegWit = 4; + string segWit = 4; } message AddressResult { @@ -100,4 +100,4 @@ message BtcForkWallet { string chainType = 2; string address = 3; string encXPub = 4; -} \ No newline at end of file +} diff --git a/proto/src/eth.proto b/proto/src/eth.proto index c9b66ddf..2face9ed 100644 --- a/proto/src/eth.proto +++ b/proto/src/eth.proto @@ -9,6 +9,15 @@ message EthTxInput { string value = 5; string data = 6; string chain_id = 7; + string type = 8; + string max_fee_per_gas = 9; + string max_priority_fee_per_gas = 10; + repeated AccessList access_list = 11; +} + +message AccessList { + string address = 1; + repeated string storage_keys = 2; } message EthTxOutput { diff --git a/wallet/coin-bitcoin/src/address.rs b/wallet/coin-bitcoin/src/address.rs index 1d75aabf..d1f62876 100644 --- a/wallet/coin-bitcoin/src/address.rs +++ b/wallet/coin-bitcoin/src/address.rs @@ -3,11 +3,11 @@ use crate::Result; use bitcoin::util::bip32::{ChainCode, ChildNumber, DerivationPath, ExtendedPubKey, Fingerprint}; use bitcoin::{Address, Network, PublicKey}; use common::apdu::{ApduCheck, BtcApdu, CoinCommonApdu}; +use common::constants::{UNCOMPRESSED_PUBKEY_STRING_LEN, XPUB_STRING_LEN}; use common::error::CommonError; use common::path::check_path_validity; use std::str::FromStr; use transport::message::send_apdu; - pub struct BtcAddress(); impl BtcAddress { @@ -20,15 +20,15 @@ impl BtcAddress { //get xpub data let xpub_data = get_xpub_data(path, true)?; - let xpub_data = &xpub_data[..194].to_string(); + let xpub_data = &xpub_data[..XPUB_STRING_LEN].to_string(); //get public key and chain code - let pub_key = &xpub_data[..130]; - let chain_code = &xpub_data[130..]; + let pub_key = &xpub_data[..UNCOMPRESSED_PUBKEY_STRING_LEN]; + let chain_code = &xpub_data[UNCOMPRESSED_PUBKEY_STRING_LEN..]; //build parent public key obj let parent_xpub = get_xpub_data(Self::get_parent_path(path)?, true)?; - let parent_xpub = &parent_xpub[..130].to_string(); + let parent_xpub = &parent_xpub[..UNCOMPRESSED_PUBKEY_STRING_LEN].to_string(); let mut parent_pub_key_obj = PublicKey::from_str(parent_xpub)?; parent_pub_key_obj.compressed = true; @@ -72,7 +72,7 @@ impl BtcAddress { //get xpub let xpub_data = get_xpub_data(path, true)?; - let pub_key = &xpub_data[..130]; + let pub_key = &xpub_data[..UNCOMPRESSED_PUBKEY_STRING_LEN]; let mut pub_key_obj = PublicKey::from_str(pub_key)?; pub_key_obj.compressed = true; @@ -89,7 +89,7 @@ impl BtcAddress { //get xpub let xpub_data = get_xpub_data(path, true)?; - let pub_key = &xpub_data[..130]; + let pub_key = &xpub_data[..UNCOMPRESSED_PUBKEY_STRING_LEN]; let mut pub_key_obj = PublicKey::from_str(pub_key)?; pub_key_obj.compressed = true; @@ -97,6 +97,20 @@ impl BtcAddress { Ok(Address::p2shwpkh(&pub_key_obj, network)?.to_string()) } + pub fn get_native_segwit_address(network: Network, path: &str) -> Result { + //path check + check_path_validity(path)?; + + //get xpub + let xpub_data = get_xpub_data(path, true)?; + let pub_key = &xpub_data[..UNCOMPRESSED_PUBKEY_STRING_LEN]; + + let mut pub_key_obj = PublicKey::from_str(pub_key)?; + pub_key_obj.compressed = true; + + Ok(Address::p2wpkh(&pub_key_obj, network)?.to_string()) + } + /** get parent public key path */ @@ -136,6 +150,18 @@ impl BtcAddress { ApduCheck::check_response(apdu_res.as_str())?; Ok(address_str) } + + pub fn display_native_segwit_address(network: Network, path: &str) -> Result { + //path check + check_path_validity(path)?; + let address_str = Self::get_native_segwit_address(network, path)?; + // let apdu_res = send_apdu(BtcApdu::btc_coin_reg(address_str.clone().into_bytes()))?; + let apdu_res = send_apdu(BtcApdu::register_address( + &address_str.clone().into_bytes().to_vec(), + ))?; + ApduCheck::check_response(apdu_res.as_str())?; + Ok(address_str) + } } #[cfg(test)] @@ -202,6 +228,19 @@ mod test { assert_eq!("37E2J9ViM4QFiewo7aw5L3drF2QKB99F9e", segwit_address); } + #[test] + fn get_native_segwit_address_test() { + bind_test(); + + let version: Network = Network::Testnet; + let path: &str = "m/84'/1'/0'/0/0"; + let segwit_address_result = BtcAddress::get_native_segwit_address(version, path); + + assert!(segwit_address_result.is_ok()); + let segwit_address = segwit_address_result.ok().unwrap(); + assert_eq!("tb1qrfaf3g4elgykshfgahktyaqj2r593qkrae5v95", segwit_address); + } + #[test] fn get_parent_path_test() { let path = "m/44'/0'/0'/0/0"; @@ -246,4 +285,16 @@ mod test { let segwit_address = result.ok().unwrap(); assert_eq!("37E2J9ViM4QFiewo7aw5L3drF2QKB99F9e", segwit_address); } + + #[test] + fn display_native_segwit_address_test() { + bind_test(); + let network: Network = Network::Bitcoin; + let path: &str = "m/84'/0'/0'/0/22"; + let result = BtcAddress::display_native_segwit_address(network, path); + + assert!(result.is_ok()); + let segwit_address = result.ok().unwrap(); + assert_eq!("bc1qm8ckt80wv0v2mq33tantpvdqjx4tvaqguljfuq", segwit_address); + } } diff --git a/wallet/coin-bitcoin/src/common.rs b/wallet/coin-bitcoin/src/common.rs index 32365732..7fca09d7 100644 --- a/wallet/coin-bitcoin/src/common.rs +++ b/wallet/coin-bitcoin/src/common.rs @@ -5,9 +5,16 @@ use bitcoin::util::base58; use bitcoin::util::bip32::{ChainCode, ChildNumber, ExtendedPubKey}; use bitcoin::{Address, Network, PublicKey}; use common::apdu::{ApduCheck, BtcApdu, CoinCommonApdu}; +use common::constants::{ + BTC_LEGACY_MAINNET_PATH, BTC_LEGACY_TESTNET_PATH, BTC_NATIVE_SEGWIT_MAINNET_PATH, + BTC_NATIVE_SEGWIT_TESTNET_PATH, BTC_SEGWIT_MAINNET_PATH, BTC_SEGWIT_TESTNET_PATH, + UNCOMPRESSED_PUBKEY_STRING_LEN, XPUB_STRING_LEN, +}; use common::error::CoinError; use common::utility::sha256_hash; +use device::device_binding::KEY_MANAGER; use secp256k1::{Message, PublicKey as PublicKey2, Secp256k1, Signature}; +use std::collections::HashMap; use std::str::FromStr; use transport::message::send_apdu; @@ -56,6 +63,11 @@ pub fn address_verify( network, )? .to_string()), + TransTypeFlg::NATIVE => Ok(Address::p2wpkh( + &PublicKey::from_str(extend_public_key.public_key.to_string().as_str())?, + network, + )? + .to_string()), }; let se_gen_address_str = se_gen_address?; let utxo_address = utxo.address.to_string(); @@ -67,12 +79,57 @@ pub fn address_verify( Ok(utxo_pub_key_vec) } +pub struct PathPubKey { + pub path: String, + pub pub_key: String, +} + +pub fn get_path_and_pubkeys(utxos: &Vec, network: Network) -> Result> { + let mut path_and_pubkeys: Vec = vec![]; + let mut parent_path_and_pubkeys: HashMap = HashMap::new(); + for utxo in utxos { + let parent_path = &get_parent_path(utxo, network)?; + let trans_type_flg = get_trans_type_flg(utxo)?; + + // Obtain the parent path xpub + let parent: Result = match parent_path_and_pubkeys.get(parent_path) { + Some(xpub) => Ok(xpub.to_string()), + None => { + let xpub = get_xpub_safe(parent_path, true)?; + parent_path_and_pubkeys.insert(parent_path.to_string(), xpub.clone()); + Ok(xpub) + } + }; + + let parent_xpub = parent.unwrap(); + let pub_key = &parent_xpub[..UNCOMPRESSED_PUBKEY_STRING_LEN]; + let chain_code = &parent_xpub[UNCOMPRESSED_PUBKEY_STRING_LEN..]; + + let extend_public_key = derive_child_key(utxo, network, pub_key, chain_code)?; + + //verify address + let result = verify_address(utxo, network, trans_type_flg, extend_public_key)?; + if !result { + return Err(CoinError::ImkeyAddressMismatchWithPath.into()); + } + + let path = format!("{}/{}", parent_path, utxo.derive_path); + let path_pub_key = PathPubKey { + path, + pub_key: extend_public_key.public_key.to_string(), + }; + path_and_pubkeys.push(path_pub_key); + } + Ok(path_and_pubkeys) +} + /** Transaction type identification */ pub enum TransTypeFlg { BTC, SEGWIT, + NATIVE, } /** @@ -86,6 +143,136 @@ pub fn get_xpub_data(path: &str, verify_flag: bool) -> Result { Ok(xpub_data) } +pub fn get_xpub_safe(path: &str, verify_flag: bool) -> Result { + let select_response = send_apdu(BtcApdu::select_applet())?; + ApduCheck::check_response(&select_response)?; + let xpub_data = send_apdu(BtcApdu::get_xpub(path, verify_flag))?; + ApduCheck::check_response(&xpub_data)?; + + let xpub_data = &xpub_data[..xpub_data.len() - 4].to_string(); + + //parsing xpub data + let sign_source_val = &xpub_data[..XPUB_STRING_LEN]; + let sign_result = &xpub_data[XPUB_STRING_LEN..]; + + //use se public key verify sign + let key_manager_obj = KEY_MANAGER.lock(); + let sign_verify_result = secp256k1_sign_verify( + &key_manager_obj.se_pub_key.as_slice(), + hex::decode(sign_result).unwrap().as_slice(), + hex::decode(sign_source_val).unwrap().as_slice(), + ); + if sign_verify_result.is_err() || !sign_verify_result.ok().unwrap() { + return Err(CoinError::ImkeySignatureVerifyFail.into()); + } + + Ok(sign_source_val.to_string()) +} + +pub fn get_parent_path(utxo: &Utxo, network: Network) -> Result { + // legacy + if utxo.script_pubkey.starts_with("76a914") || utxo.script_pubkey.starts_with("76A914") { + if network.clone() == Network::Testnet { + Ok(BTC_LEGACY_TESTNET_PATH.to_string()) + } else { + Ok(BTC_LEGACY_MAINNET_PATH.to_string()) + } + // segwit + } else if utxo.script_pubkey.starts_with("a914") || utxo.script_pubkey.starts_with("A914") { + if network.clone() == Network::Testnet { + Ok(BTC_SEGWIT_TESTNET_PATH.to_string()) + } else { + Ok(BTC_SEGWIT_MAINNET_PATH.to_string()) + } + } else if utxo.script_pubkey.starts_with("0014") { + if network.clone() == Network::Testnet { + Ok(BTC_NATIVE_SEGWIT_TESTNET_PATH.to_string()) + } else { + Ok(BTC_NATIVE_SEGWIT_MAINNET_PATH.to_string()) + } + } else { + return Err(CoinError::UnsupportedScriptPubkey.into()); + } +} + +pub fn get_trans_type_flg(utxo: &Utxo) -> Result { + // legacy + if utxo.script_pubkey.starts_with("76a914") || utxo.script_pubkey.starts_with("76A914") { + Ok(TransTypeFlg::BTC) + // segwit + } else if utxo.script_pubkey.starts_with("a914") || utxo.script_pubkey.starts_with("A914") { + Ok(TransTypeFlg::SEGWIT) + } else if utxo.script_pubkey.starts_with("0014") { + Ok(TransTypeFlg::NATIVE) + } else { + return Err(CoinError::UnsupportedScriptPubkey.into()); + } +} + +// Public child key derivation +pub fn derive_child_key( + utxo: &Utxo, + network: Network, + pub_key: &str, + chain_code: &str, +) -> Result { + //get utxo public key + let mut public_key_obj = PublicKey::from_str(pub_key)?; + public_key_obj.compressed = true; + //gen chain code obj + let chain_code_obj = ChainCode::from(hex::decode(chain_code).unwrap().as_slice()); + //build extended public key + let mut extend_public_key = ExtendedPubKey { + network: network, + depth: 0, + parent_fingerprint: Default::default(), + child_number: ChildNumber::from_normal_idx(0)?, + public_key: public_key_obj, + chain_code: chain_code_obj, + }; + + let bitcoin_secp = BitcoinSecp256k1::new(); + let index_number_vec: Vec<&str> = utxo.derive_path.as_str().split('/').collect(); + for index_number in index_number_vec { + let test_chain_number = ChildNumber::from_normal_idx(index_number.parse().unwrap())?; + extend_public_key = extend_public_key.ckd_pub(&bitcoin_secp, test_chain_number)?; + } + Ok(extend_public_key) +} + +pub fn verify_address( + utxo: &Utxo, + network: Network, + trans_type_flg: TransTypeFlg, + extend_public_key: ExtendedPubKey, +) -> Result { + let se_gen_address: Result = match trans_type_flg { + TransTypeFlg::BTC => Ok(Address::p2pkh( + &PublicKey::from_str(extend_public_key.public_key.to_string().as_str())?, + network, + ) + .to_string()), + TransTypeFlg::SEGWIT => Ok(Address::p2shwpkh( + &PublicKey::from_str(extend_public_key.public_key.to_string().as_str())?, + network, + )? + .to_string()), + TransTypeFlg::NATIVE => Ok(Address::p2wpkh( + &PublicKey::from_str(extend_public_key.public_key.to_string().as_str())?, + network, + )? + .to_string()), + }; + + let se_gen_address_str = se_gen_address?; + let utxo_address = utxo.address.to_string(); + if se_gen_address_str.eq(&utxo_address) { + Ok(true) + } else { + Ok(false) + } +} + /** sign verify */ @@ -107,24 +294,34 @@ pub fn secp256k1_sign_verify(public: &[u8], signed: &[u8], message: &[u8]) -> Re get address version */ pub fn get_address_version(network: Network, address: &str) -> Result { - match network { + let version = match network { Network::Bitcoin => { - if !address.starts_with('1') && !address.starts_with('3') { + if address.starts_with('1') || address.starts_with('3') { + let address_bytes = base58::from(address)?; + address_bytes.as_slice()[0] + } else if address.starts_with("bc1") { + 'b' as u8 + } else { return Err(CoinError::AddressTypeMismatch.into()); } } Network::Testnet => { - if !address.starts_with('m') && !address.starts_with('n') && !address.starts_with('2') { + if address.starts_with('m') || address.starts_with('n') || address.starts_with('2') { + let address_bytes = base58::from(address)?; + address_bytes.as_slice()[0] + } else if address.starts_with("tb1") { + 't' as u8 + } else { return Err(CoinError::AddressTypeMismatch.into()); } } _ => { return Err(CoinError::ImkeySdkIllegalArgument.into()); } - } + }; //get address version - let address_bytes = base58::from(address)?; - Ok(address_bytes.as_slice()[0]) + // let address_bytes = base58::from(address)?; + Ok(version) } pub struct TxSignResult { diff --git a/wallet/coin-bitcoin/src/transaction.rs b/wallet/coin-bitcoin/src/transaction.rs index 8ff8ab00..e1f9142e 100644 --- a/wallet/coin-bitcoin/src/transaction.rs +++ b/wallet/coin-bitcoin/src/transaction.rs @@ -1,7 +1,7 @@ use crate::address::BtcAddress; use crate::common::{ - address_verify, get_address_version, get_xpub_data, secp256k1_sign_verify, TransTypeFlg, - TxSignResult, + address_verify, get_address_version, get_path_and_pubkeys, get_xpub_data, + secp256k1_sign_verify, PathPubKey, TransTypeFlg, TxSignResult, }; use crate::Result; use bitcoin::blockdata::{opcodes, script::Builder}; @@ -11,12 +11,14 @@ use bitcoin::util::psbt::serialize::Serialize; use bitcoin::{Address, Network, OutPoint, Script, SigHashType, Transaction, TxIn, TxOut}; use bitcoin_hashes::hash160; use bitcoin_hashes::hex::ToHex; -use bitcoin_hashes::sha256d::Hash as Hash256; use bitcoin_hashes::Hash; use common::apdu::{ApduCheck, BtcApdu}; use common::constants::{ - DUST_THRESHOLD, EACH_ROUND_NUMBER, MAX_OPRETURN_SIZE, MAX_UTXO_NUMBER, TIMEOUT_LONG, + BTC_NATIVE_SEGWIT_MAINNET_PATH, BTC_NATIVE_SEGWIT_TESTNET_PATH, DUST_THRESHOLD, + EACH_ROUND_NUMBER, MAX_OPRETURN_SIZE, MAX_UTXO_NUMBER, TIMEOUT_LONG, + UNCOMPRESSED_PUBKEY_STRING_LEN, XPUB_STRING_LEN, }; + use common::error::CoinError; use common::path::check_path_validity; use common::utility::{bigint_to_byte_vec, hex_to_bytes, secp256k1_sign}; @@ -67,10 +69,10 @@ impl BtcTransaction { let xpub_data = &xpub_data[..xpub_data.len() - 4].to_string(); //parsing xpub data - let sign_source_val = &xpub_data[..194]; - let sign_result = &xpub_data[194..]; - let pub_key = &sign_source_val[..130]; - let chain_code = &sign_source_val[130..]; + let sign_source_val = &xpub_data[..XPUB_STRING_LEN]; + let sign_result = &xpub_data[XPUB_STRING_LEN..]; + let pub_key = &sign_source_val[..UNCOMPRESSED_PUBKEY_STRING_LEN]; + let chain_code = &sign_source_val[UNCOMPRESSED_PUBKEY_STRING_LEN..]; //use se public key verify sign let key_manager_obj = KEY_MANAGER.lock(); @@ -254,10 +256,10 @@ impl BtcTransaction { let xpub_data = &xpub_data[..xpub_data.len() - 4].to_string(); //parsing xpub data - let sign_source_val = &xpub_data[..194]; - let sign_result = &xpub_data[194..]; - let pub_key = &sign_source_val[..130]; - let chain_code = &sign_source_val[130..]; + let sign_source_val = &xpub_data[..XPUB_STRING_LEN]; + let sign_result = &xpub_data[XPUB_STRING_LEN..]; + let pub_key = &sign_source_val[..UNCOMPRESSED_PUBKEY_STRING_LEN]; + let chain_code = &sign_source_val[UNCOMPRESSED_PUBKEY_STRING_LEN..]; //use se public key verify sign let key_manager_obj = KEY_MANAGER.lock(); @@ -465,196 +467,826 @@ impl BtcTransaction { }) } - pub fn get_total_amount(&self) -> i64 { - let mut total_amount: i64 = 0; - for unspent in &self.unspents { - total_amount += unspent.amount; + pub fn sign_native_segwit_transaction( + &self, + network: Network, + path: &str, + change_idx: i32, + extra_data: &Vec, + ) -> Result { + //path check + check_path_validity(path)?; + let mut path_str = path.to_string(); + if !path.ends_with("/") { + path_str = format!("{}{}", path_str, "/"); + } + //check utxo number + if &self.unspents.len() > &MAX_UTXO_NUMBER { + return Err(CoinError::ImkeyExceededMaxUtxoNumber.into()); } - total_amount - } - pub fn get_change_amount(&self) -> i64 { - let total_amount = self.get_total_amount(); - let change_amout = total_amount - self.amount - self.fee; - change_amout - } + //get xpub and sign data + let xpub_data = get_xpub_data(path_str.as_str(), true)?; + let xpub_data = &xpub_data[..xpub_data.len() - 4].to_string(); - pub fn build_send_to_output(&self) -> TxOut { - TxOut { - value: self.amount as u64, - script_pubkey: self.to.script_pubkey(), + //parsing xpub data + let sign_source_val = &xpub_data[..XPUB_STRING_LEN]; + let sign_result = &xpub_data[XPUB_STRING_LEN..]; + let pub_key = &sign_source_val[..UNCOMPRESSED_PUBKEY_STRING_LEN]; + let chain_code = &sign_source_val[UNCOMPRESSED_PUBKEY_STRING_LEN..]; + + //use se public key verify sign + let key_manager_obj = KEY_MANAGER.lock(); + let sign_verify_result = secp256k1_sign_verify( + &key_manager_obj.se_pub_key.as_slice(), + hex::decode(sign_result).unwrap().as_slice(), + hex::decode(sign_source_val).unwrap().as_slice(), + ); + if sign_verify_result.is_err() || !sign_verify_result.ok().unwrap() { + return Err(CoinError::ImkeySignatureVerifyFail.into()); } - } + //utxo address verify + let utxo_pub_key_vec = address_verify( + &self.unspents, + pub_key, + hex::decode(chain_code).unwrap().as_slice(), + network, + TransTypeFlg::NATIVE, + )?; - pub fn build_op_return_output(&self, extra_data: &Vec) -> TxOut { - let opreturn_script = Builder::new() - .push_opcode(opcodes::all::OP_RETURN) - .push_slice(&extra_data[..]) - .into_script(); - TxOut { - value: 0u64, - script_pubkey: opreturn_script, + //calc utxo total amount + if self.get_total_amount() < self.amount { + return Err(CoinError::ImkeyInsufficientFunds.into()); } - } - pub fn build_lock_script(&self, signed: &str, utxo_public_key: &str) -> Result