diff --git a/imkey-core/ikc-common/src/apdu.rs b/imkey-core/ikc-common/src/apdu.rs index 2fb5aeab..a2b01cd2 100644 --- a/imkey-core/ikc-common/src/apdu.rs +++ b/imkey-core/ikc-common/src/apdu.rs @@ -93,6 +93,21 @@ impl BtcApdu { apdu.to_hex().to_uppercase() } + pub fn btc_taproot_sign(last_one: bool, data: Vec) -> String { + if data.len() as u32 > LC_MAX { + panic!("data to long"); + } + + let mut apdu = match last_one { + true => ApduHeader::new(0x80, 0x40, 0x80, 0x00, data.len() as u8).to_array(), + _ => ApduHeader::new(0x80, 0x40, 0x00, 0x00, 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"); @@ -111,6 +126,58 @@ impl BtcApdu { data.extend(name); Apdu::register_address(0x37, &data) } + + pub fn btc_single_utxo_sign_prepare(ins: u8, data: &Vec) -> Vec { + let mut apdu_vec = Vec::new(); + let apdu_number = (data.len() - 1) / LC_MAX as usize + 1; + for index in 0..apdu_number { + if index == 0 && index == apdu_number - 1 { + let length = if data.len() % LC_MAX as usize == 0 { + LC_MAX + } else { + (data.len() % LC_MAX as usize) as u32 + }; + let mut temp_apdu_vec = + ApduHeader::new(0x80, ins, 0x00, 0x80, length as u8).to_array(); + temp_apdu_vec.extend_from_slice(&data[index * LC_MAX as usize..]); + apdu_vec.push(hex::encode_upper(temp_apdu_vec)); + } else if index == 0 && index != apdu_number - 1 { + let mut temp_apdu_vec = + ApduHeader::new(0x80, ins, 0x00, 0x00, LC_MAX as u8).to_array(); + temp_apdu_vec.extend_from_slice( + &data[index * LC_MAX as usize..((index + 1) * LC_MAX as usize) as usize], + ); + apdu_vec.push(hex::encode_upper(temp_apdu_vec)); + } else if index != 0 && index != apdu_number - 1 { + let mut temp_apdu_vec = + ApduHeader::new(0x80, ins, 0x80, 0x00, LC_MAX as u8).to_array(); + temp_apdu_vec.extend_from_slice( + &data[index * LC_MAX as usize..((index + 1) * LC_MAX as usize) as usize], + ); + apdu_vec.push(hex::encode_upper(temp_apdu_vec)); + } else if index != 0 && index == apdu_number - 1 { + let length = if data.len() % LC_MAX as usize == 0 { + LC_MAX + } else { + (data.len() % LC_MAX as usize) as u32 + }; + let mut temp_apdu_vec = + ApduHeader::new(0x80, ins, 0x80, 0x80, length as u8).to_array(); + temp_apdu_vec.extend_from_slice(&data[index * LC_MAX as usize..]); + apdu_vec.push(hex::encode_upper(temp_apdu_vec)); + } + } + return apdu_vec; + } + + pub fn btc_single_utxo_sign(index: u8, hash_type: u8, path: &str) -> String { + let path_bytes = path.as_bytes(); + let mut apdu = + ApduHeader::new(0x80, 0x45, index, hash_type, path_bytes.len() as u8).to_array(); + apdu.extend(path_bytes.iter()); + apdu.push(0x00); + apdu.to_hex().to_uppercase() + } } pub struct EthApdu(); diff --git a/imkey-core/ikc-common/src/constants.rs b/imkey-core/ikc-common/src/constants.rs index c3ec85c2..37046393 100644 --- a/imkey-core/ikc-common/src/constants.rs +++ b/imkey-core/ikc-common/src/constants.rs @@ -1,6 +1,6 @@ pub const VERSION: &str = "2.15.2"; -pub const URL: &str = "https://imkey.online:1000/imkey"; -// pub const URL: &str = "https://imkeyserver.com:10444/imkey"; +// pub const URL: &str = "https://imkey.online:1000/imkey"; +pub const URL: &str = "https://imkeyserver.com:10444/imkey"; pub const TSM_ACTION_SE_SECURE_CHECK: &str = "/seSecureCheck"; pub const TSM_ACTION_APP_DOWNLOAD: &str = "/appDownload"; @@ -44,11 +44,11 @@ pub const TRON_PATH: &str = "m/44'/195'/0'/0/0"; pub const MAX_UTXO_NUMBER: usize = 252; pub const EACH_ROUND_NUMBER: usize = 5; -pub const DUST_THRESHOLD: i64 = 2730; -pub const MIN_NONDUST_OUTPUT: i64 = 546; +pub const DUST_THRESHOLD: u64 = 2730; +pub const MIN_NONDUST_OUTPUT: u64 = 546; // max op return size pub const MAX_OPRETURN_SIZE: usize = 80; -pub const BTC_FORK_DUST: i64 = 546; +pub const BTC_FORK_DUST: u64 = 546; // imkey device status pub const IMKEY_DEV_STATUS_INACTIVATED: &str = "inactivated"; diff --git a/imkey-core/ikc-common/src/error.rs b/imkey-core/ikc-common/src/error.rs index d86f07ae..6f9611c8 100644 --- a/imkey-core/ikc-common/src/error.rs +++ b/imkey-core/ikc-common/src/error.rs @@ -88,4 +88,6 @@ pub enum CoinError { InvalidVersion, #[error("invalid addr length")] InvalidAddrLength, + #[error("invalid_utxo")] + InvalidUtxo, } diff --git a/imkey-core/ikc-common/src/utility.rs b/imkey-core/ikc-common/src/utility.rs index bf789174..af52b25f 100644 --- a/imkey-core/ikc-common/src/utility.rs +++ b/imkey-core/ikc-common/src/utility.rs @@ -65,7 +65,7 @@ pub fn secp256k1_sign_verify(public: &[u8], signed: &[u8], message: &[u8]) -> Re .is_ok()) } -pub fn bigint_to_byte_vec(val: i64) -> Vec { +pub fn bigint_to_byte_vec(val: u64) -> Vec { let mut return_data = BigInt::from(val).to_signed_bytes_be(); while return_data.len() < 8 { return_data.insert(0, 0x00); diff --git a/imkey-core/ikc-device/src/device_binding.rs b/imkey-core/ikc-device/src/device_binding.rs index f6f1e217..5742e075 100644 --- a/imkey-core/ikc-device/src/device_binding.rs +++ b/imkey-core/ikc-device/src/device_binding.rs @@ -237,7 +237,7 @@ pub fn bind_test() { // pub const TEST_KEY_PATH: &str = "/tmp/"; // pub const TEST_BIND_CODE: &str = "MCYNK5AH"; pub const TEST_KEY_PATH: &str = "/tmp/"; -pub const TEST_BIND_CODE: &str = "7FVRAJJ7"; +pub const TEST_BIND_CODE: &str = "FT2Z3LT2"; #[cfg(test)] mod test { diff --git a/imkey-core/ikc-proto/src/api.proto b/imkey-core/ikc-proto/src/api.proto index 66a70a6e..b1ac8b16 100644 --- a/imkey-core/ikc-proto/src/api.proto +++ b/imkey-core/ikc-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 { diff --git a/imkey-core/ikc-proto/src/btc.proto b/imkey-core/ikc-proto/src/btc.proto index cf4b9c1e..9c12d383 100644 --- a/imkey-core/ikc-proto/src/btc.proto +++ b/imkey-core/ikc-proto/src/btc.proto @@ -3,8 +3,8 @@ package btcapi; message Utxo { string tx_hash = 1; - int32 vout = 2; - int64 amount = 3; + uint32 vout = 2; + uint64 amount = 3; string address = 4; string script_pubKey = 5; string derived_path = 6; @@ -18,13 +18,13 @@ message BtcTxExtra { } message BtcTxInput { string to = 1; - int64 amount = 2; - int64 fee = 3; - uint32 change_address_index = 4; + uint64 amount = 2; + uint64 fee = 3; + optional uint32 change_address_index = 4; repeated Utxo unspents = 5; string segWit = 6; string protocol = 7; - BtcTxExtra extra = 8; + optional BtcTxExtra extra = 8; } message BtcTxOutput { diff --git a/imkey-core/ikc-proto/src/btcfork.proto b/imkey-core/ikc-proto/src/btcfork.proto index faee7d01..f4160478 100644 --- a/imkey-core/ikc-proto/src/btcfork.proto +++ b/imkey-core/ikc-proto/src/btcfork.proto @@ -3,8 +3,8 @@ package btcforkapi; message Utxo { string txHash = 1; - int32 vout = 2; - int64 amount = 3; + uint32 vout = 2; + uint64 amount = 3; string address = 4; string scriptPubKey = 5; string derivedPath = 6; @@ -13,9 +13,9 @@ message Utxo { message BtcForkTxInput { string to = 1; - int64 amount = 2; + uint64 amount = 2; repeated Utxo unspents = 3; - int64 fee = 4; + uint64 fee = 4; uint32 changeAddressIndex = 5; string changeAddress = 6; string segWit = 7; diff --git a/imkey-core/ikc-wallet/coin-bch/src/transaction.rs b/imkey-core/ikc-wallet/coin-bch/src/transaction.rs index 7ded7094..23812d2b 100644 --- a/imkey-core/ikc-wallet/coin-bch/src/transaction.rs +++ b/imkey-core/ikc-wallet/coin-bch/src/transaction.rs @@ -25,8 +25,8 @@ use std::str::FromStr; #[derive(Clone)] pub struct Utxo { pub txhash: String, - pub vout: i32, - pub amount: i64, + pub vout: u32, + pub amount: u64, pub address: String, pub script_pubkey: String, pub derive_path: String, @@ -35,9 +35,9 @@ pub struct Utxo { pub struct BchTransaction { pub to: String, - pub amount: i64, + pub amount: u64, pub unspents: Vec, - pub fee: i64, + pub fee: u64, } impl BchTransaction { @@ -278,15 +278,15 @@ impl BchTransaction { }) } - pub fn get_total_amount(&self) -> i64 { - let mut total_amount: i64 = 0; + pub fn get_total_amount(&self) -> u64 { + let mut total_amount = 0; for unspent in &self.unspents { total_amount += unspent.amount; } total_amount } - pub fn get_change_amount(&self) -> i64 { + pub fn get_change_amount(&self) -> u64 { let total_amount = self.get_total_amount(); let change_amout = total_amount - self.amount - self.fee; change_amout diff --git a/imkey-core/ikc-wallet/coin-bitcoin/src/address.rs b/imkey-core/ikc-wallet/coin-bitcoin/src/address.rs index dcb091f0..c91543a2 100644 --- a/imkey-core/ikc-wallet/coin-bitcoin/src/address.rs +++ b/imkey-core/ikc-wallet/coin-bitcoin/src/address.rs @@ -1,13 +1,16 @@ use crate::common::get_xpub_data; use crate::Result; use bitcoin::network::constants::Network; +use bitcoin::schnorr::UntweakedPublicKey; use bitcoin::util::bip32::{ChainCode, ChildNumber, DerivationPath, ExtendedPubKey, Fingerprint}; use bitcoin::{Address, PublicKey}; +use bitcoin_hashes::{hash160, Hash}; use ikc_common::apdu::{ApduCheck, BtcApdu, CoinCommonApdu}; use ikc_common::error::CommonError; use ikc_common::path::check_path_validity; +use ikc_common::utility::hex_to_bytes; use ikc_transport::message::send_apdu; -use secp256k1::PublicKey as Secp256k1PublicKey; +use secp256k1::{PublicKey as Secp256k1PublicKey, Secp256k1}; use std::str::FromStr; pub struct BtcAddress(); @@ -17,34 +20,24 @@ impl BtcAddress { get btc xpub by path */ pub fn get_xpub(network: Network, path: &str) -> Result { - //path check check_path_validity(path)?; - //get xpub data let xpub_data = get_xpub_data(path, true)?; let xpub_data = &xpub_data[..194].to_string(); - //get public key and chain code let pub_key = &xpub_data[..130]; let chain_code = &xpub_data[130..]; - //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 mut parent_pub_key_obj = PublicKey::from_str(parent_xpub)?; - // parent_pub_key_obj.compressed = true; - let parent_pub_key_obj = Secp256k1PublicKey::from_str(parent_xpub)?; //TODO 是否是压缩公钥 + let parent_pub_key_obj = Secp256k1PublicKey::from_str(parent_xpub)?; - //build child public key obj - // let mut pub_key_obj = PublicKey::from_str(pub_key)?; - // pub_key_obj.compressed = true; - let pub_key_obj = Secp256k1PublicKey::from_str(pub_key)?; //TODO 是否是压缩公钥 + let pub_key_obj = Secp256k1PublicKey::from_str(pub_key)?; - //get parent public key fingerprint let chain_code_obj = ChainCode::from(hex::decode(chain_code).unwrap().as_slice()); let parent_ext_pub_key = ExtendedPubKey { - network: network, - depth: 0 as u8, + network, + depth: 0u8, parent_fingerprint: Fingerprint::default(), child_number: ChildNumber::from_normal_idx(0).unwrap(), public_key: parent_pub_key_obj, @@ -56,21 +49,20 @@ impl BtcAddress { let chain_code_obj = ChainCode::from(hex::decode(chain_code).unwrap().as_slice()); let chain_number_vec: Vec = DerivationPath::from_str(path)?.into(); let extend_public_key = ExtendedPubKey { - network: network, + network, depth: chain_number_vec.len() as u8, parent_fingerprint: fingerprint_obj, child_number: *chain_number_vec.get(chain_number_vec.len() - 1).unwrap(), public_key: pub_key_obj, chain_code: chain_code_obj, }; - //get and return xpub Ok(extend_public_key.to_string()) } /** get btc address by path */ - pub fn get_address(network: Network, path: &str) -> Result { + pub fn p2pkh(network: Network, path: &str) -> Result { //path check check_path_validity(path)?; @@ -87,7 +79,7 @@ impl BtcAddress { /** get segwit address by path */ - pub fn get_segwit_address(network: Network, path: &str) -> Result { + pub fn p2shwpkh(network: Network, path: &str) -> Result { //path check check_path_validity(path)?; @@ -101,6 +93,29 @@ impl BtcAddress { Ok(Address::p2shwpkh(&pub_key_obj, network)?.to_string()) } + pub fn p2wpkh(network: Network, path: &str) -> Result { + check_path_validity(path)?; + + let xpub_data = get_xpub_data(path, true)?; + let pub_key = &xpub_data[..130]; + let mut pub_key_obj = PublicKey::from_str(pub_key)?; + pub_key_obj.compressed = true; + + Ok(Address::p2wpkh(&pub_key_obj, network)?.to_string()) + } + + pub fn p2tr(network: Network, path: &str) -> Result { + check_path_validity(path)?; + + let xpub_data = get_xpub_data(path, true)?; + let pub_key = &xpub_data[..130]; + let untweak_pub_key = + UntweakedPublicKey::from(secp256k1::PublicKey::from_slice(&hex_to_bytes(&pub_key)?)?); + + let secp256k1 = Secp256k1::new(); + Ok(Address::p2tr(&secp256k1, untweak_pub_key, None, network).to_string()) + } + pub fn get_pub_key(path: &str) -> Result { //path check check_path_validity(path)?; @@ -128,28 +143,48 @@ impl BtcAddress { Ok(&path[..end_flg]) } - pub fn display_address(network: Network, path: &str) -> Result { - //path check + pub fn display_address(network: Network, path: &str, seg_wit: &str) -> Result { check_path_validity(path)?; - let address_str = Self::get_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(), - ))?; + + let address = match seg_wit { + "P2WPKH" => Self::p2shwpkh(network, path)?, + "VERSION_0" => Self::p2wpkh(network, path)?, + "VERSION_1" => Self::p2tr(network, path)?, + _ => Self::p2pkh(network, path)?, + }; + + let apdu_res = send_apdu(BtcApdu::register_address(&address.as_bytes()))?; ApduCheck::check_response(apdu_res.as_str())?; - Ok(address_str) + Ok(address) } - pub fn display_segwit_address(network: Network, path: &str) -> Result { - //path check - check_path_validity(path)?; - let address_str = Self::get_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) + // pub fn display_segwit_address(network: Network, path: &str) -> Result { + // check_path_validity(path)?; + // let address_str = Self::p2shwpkh(network, path)?; + // 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) + // } + + pub fn from_public_key(public_key: &str, network: Network, seg_wit: &str) -> Result { + let mut pub_key_obj = PublicKey::from_str(public_key)?; + pub_key_obj.compressed = true; + let address = match seg_wit { + "P2WPKH" => Address::p2shwpkh(&pub_key_obj, network)?.to_string(), + "VERSION_0" => Address::p2wpkh(&pub_key_obj, network)?.to_string(), + "VERSION_1" => { + let untweak_pub_key = UntweakedPublicKey::from(secp256k1::PublicKey::from_slice( + &hex_to_bytes(&public_key)?, + )?); + let secp256k1 = Secp256k1::new(); + Address::p2tr(&secp256k1, untweak_pub_key, None, network).to_string() + } + _ => Address::p2pkh(&pub_key_obj, network).to_string(), + }; + + Ok(address) } } @@ -192,12 +227,12 @@ mod test { } #[test] - fn get_address_test() { + fn p2pkh_test() { bind_test(); let version: Network = Network::Bitcoin; let path: &str = "m/44'/0'/0'/0/0"; - let get_btc_address_result = BtcAddress::get_address(version, path); + let get_btc_address_result = BtcAddress::p2pkh(version, path); assert!(get_btc_address_result.is_ok()); let btc_address = get_btc_address_result.ok().unwrap(); @@ -205,18 +240,45 @@ mod test { } #[test] - fn get_segwit_address_test() { + fn p2shwpkh_address_test() { bind_test(); let version: Network = Network::Bitcoin; let path: &str = "m/49'/0'/0'/0/22"; - let segwit_address_result = BtcAddress::get_segwit_address(version, path); + let segwit_address_result = BtcAddress::p2shwpkh(version, path); assert!(segwit_address_result.is_ok()); let segwit_address = segwit_address_result.ok().unwrap(); assert_eq!("37E2J9ViM4QFiewo7aw5L3drF2QKB99F9e", segwit_address); } + #[test] + fn p2wpkh_address_test() { + bind_test(); + + let network: Network = Network::Bitcoin; + let path: &str = "m/49'/0'/0'/0/22"; + let segwit_address_result = BtcAddress::p2wpkh(network, path); + + assert!(segwit_address_result.is_ok()); + let segwit_address = segwit_address_result.ok().unwrap(); + assert_eq!("bc1qe74h3vkdcj94uph4wdpk48nlqjdy42y87mdm7q", segwit_address); + } + #[test] + fn p2tr_address_test() { + bind_test(); + + let network: Network = Network::Bitcoin; + let path: &str = "m/49'/0'/0'/0/22"; + let segwit_address_result = BtcAddress::p2tr(network, path); + + assert!(segwit_address_result.is_ok()); + let segwit_address = segwit_address_result.ok().unwrap(); + assert_eq!( + "bc1ph40wj9vl3kwhxq747wxkcr63e4r3uaryagpetnkey4zqhucmjfzse24jrd", + segwit_address + ); + } #[test] fn get_parent_path_test() { let path = "m/44'/0'/0'/0/0"; @@ -243,7 +305,7 @@ mod test { bind_test(); let version: Network = Network::Bitcoin; let path: &str = "m/44'/0'/0'/0/0"; - let result = BtcAddress::display_address(version, path); + let result = BtcAddress::display_address(version, path, "NONE"); assert!(result.is_ok()); let btc_address = result.ok().unwrap(); @@ -255,7 +317,7 @@ mod test { bind_test(); let network: Network = Network::Bitcoin; let path: &str = "m/49'/0'/0'/0/22"; - let result = BtcAddress::display_segwit_address(network, path); + let result = BtcAddress::display_address(network, path, "P2WPKH"); assert!(result.is_ok()); let segwit_address = result.ok().unwrap(); diff --git a/imkey-core/ikc-wallet/coin-bitcoin/src/btcapi.rs b/imkey-core/ikc-wallet/coin-bitcoin/src/btcapi.rs index f9217c83..0217ac6e 100644 --- a/imkey-core/ikc-wallet/coin-bitcoin/src/btcapi.rs +++ b/imkey-core/ikc-wallet/coin-bitcoin/src/btcapi.rs @@ -3,10 +3,10 @@ pub struct Utxo { #[prost(string, tag = "1")] pub tx_hash: ::prost::alloc::string::String, - #[prost(int32, tag = "2")] - pub vout: i32, - #[prost(int64, tag = "3")] - pub amount: i64, + #[prost(uint32, tag = "2")] + pub vout: u32, + #[prost(uint64, tag = "3")] + pub amount: u64, #[prost(string, tag = "4")] pub address: ::prost::alloc::string::String, #[prost(string, tag = "5")] @@ -31,12 +31,12 @@ pub struct BtcTxExtra { pub struct BtcTxInput { #[prost(string, tag = "1")] pub to: ::prost::alloc::string::String, - #[prost(int64, tag = "2")] - pub amount: i64, - #[prost(int64, tag = "3")] - pub fee: i64, - #[prost(uint32, tag = "4")] - pub change_address_index: u32, + #[prost(uint64, tag = "2")] + pub amount: u64, + #[prost(uint64, tag = "3")] + pub fee: u64, + #[prost(uint32, optional, tag = "4")] + pub change_address_index: ::core::option::Option, #[prost(message, repeated, tag = "5")] pub unspents: ::prost::alloc::vec::Vec, #[prost(string, tag = "6")] diff --git a/imkey-core/ikc-wallet/coin-bitcoin/src/common.rs b/imkey-core/ikc-wallet/coin-bitcoin/src/common.rs index d269bb86..50aeb447 100644 --- a/imkey-core/ikc-wallet/coin-bitcoin/src/common.rs +++ b/imkey-core/ikc-wallet/coin-bitcoin/src/common.rs @@ -1,8 +1,9 @@ use crate::transaction::Utxo; use crate::Result; +use bitcoin::schnorr::UntweakedPublicKey; use bitcoin::util::base58; use bitcoin::util::bip32::{ChainCode, ChildNumber, ExtendedPubKey}; -use bitcoin::{Address, Network, PublicKey}; +use bitcoin::{Address, AddressType, Network, PublicKey}; use ikc_common::apdu::{ApduCheck, BtcApdu, CoinCommonApdu}; use ikc_common::error::CoinError; use ikc_common::utility::{hex_to_bytes, sha256_hash}; @@ -13,43 +14,41 @@ use std::str::FromStr; /** utxo address verify */ -pub fn address_verify( - utxos: &Vec, - network: Network, - trans_type_flg: TransTypeFlg, -) -> Result> { +pub fn address_verify(utxos: &Vec, network: Network) -> Result> { let mut utxo_pub_key_vec: Vec = vec![]; for utxo in utxos { - //get xpub and sign data let xpub_data = get_xpub_data(&utxo.derive_path, false)?; //parsing xpub data - let public_key = &xpub_data[..130]; + let derive_pub_key = &xpub_data[..130]; let chain_code = &xpub_data[130..194]; let mut extend_public_key = ExtendedPubKey { network, depth: 0, parent_fingerprint: Default::default(), child_number: ChildNumber::from_normal_idx(0)?, - public_key: Secp256k1PublicKey::from_str(public_key)?, + public_key: Secp256k1PublicKey::from_str(derive_pub_key)?, chain_code: ChainCode::from(hex_to_bytes(chain_code)?.as_slice()), }; - //verify address - 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()), + let public_key = PublicKey::from_str(&extend_public_key.public_key.to_string())?; + let script_pubkey = utxo.address.script_pubkey(); + let mut se_gen_address = "".to_string(); + if script_pubkey.is_p2pkh() { + se_gen_address = Address::p2pkh(&public_key, network).to_string(); + } else if script_pubkey.is_p2sh() { + se_gen_address = Address::p2shwpkh(&public_key, network)?.to_string(); + } else if script_pubkey.is_v0_p2wpkh() { + se_gen_address = Address::p2wpkh(&public_key, network)?.to_string(); + } else if script_pubkey.is_v1_p2tr() { + let untweak_pub_key = UntweakedPublicKey::from(secp256k1::PublicKey::from_slice( + &hex_to_bytes(&derive_pub_key)?, + )?); + let secp256k1 = Secp256k1::new(); + se_gen_address = Address::p2tr(&secp256k1, untweak_pub_key, None, network).to_string(); + } else { + return Err(CoinError::InvalidAddress.into()); }; - let se_gen_address_str = se_gen_address?; - let utxo_address = utxo.address.to_string(); - if !se_gen_address_str.eq(&utxo_address) { + if !se_gen_address.eq(&utxo.address.to_string()) { return Err(CoinError::ImkeyAddressMismatchWithPath.into()); } utxo_pub_key_vec.push(extend_public_key.public_key.to_string()); @@ -57,14 +56,6 @@ pub fn address_verify( Ok(utxo_pub_key_vec) } -/** -Transaction type identification -*/ -pub enum TransTypeFlg { - BTC, - SEGWIT, -} - /** get xpub */ @@ -99,24 +90,32 @@ 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') { - return Err(CoinError::AddressTypeMismatch.into()); + 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::InvalidAddress.into()); } } Network::Testnet => { - if !address.starts_with('m') && !address.starts_with('n') && !address.starts_with('2') { - return Err(CoinError::AddressTypeMismatch.into()); + 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::InvalidAddress.into()); } } _ => { return Err(CoinError::ImkeySdkIllegalArgument.into()); } - } - //get address version - let address_bytes = base58::from(address)?; - Ok(address_bytes.as_slice()[0]) + }; + Ok(version) } pub struct TxSignResult { diff --git a/imkey-core/ikc-wallet/coin-bitcoin/src/transaction.rs b/imkey-core/ikc-wallet/coin-bitcoin/src/transaction.rs index 6af419b1..6721e1ba 100644 --- a/imkey-core/ikc-wallet/coin-bitcoin/src/transaction.rs +++ b/imkey-core/ikc-wallet/coin-bitcoin/src/transaction.rs @@ -1,33 +1,35 @@ use crate::address::BtcAddress; -use crate::common::{address_verify, get_address_version, TransTypeFlg, TxSignResult}; +use crate::common::{address_verify, get_address_version, TxSignResult}; use crate::Result; use bitcoin::blockdata::{opcodes, script::Builder}; use bitcoin::consensus::{serialize, Encodable}; use bitcoin::hashes::hex::FromHex; +use bitcoin::psbt::serialize::Serialize; +use bitcoin::schnorr::{TapTweak, UntweakedPublicKey}; +use bitcoin::util::taproot::TapTweakHash; use bitcoin::{ - Address, EcdsaSighashType, Network, OutPoint, PackedLockTime, Script, Sequence, Transaction, - TxIn, TxOut, Witness, + Address, EcdsaSighashType, Network, OutPoint, PackedLockTime, SchnorrSighashType, Script, + Sequence, Transaction, TxIn, TxOut, WPubkeyHash, Witness, }; use bitcoin_hashes::hash160; use bitcoin_hashes::hex::ToHex; use bitcoin_hashes::Hash; use ikc_common::apdu::{ApduCheck, BtcApdu}; -use ikc_common::constants::{ - DUST_THRESHOLD, EACH_ROUND_NUMBER, MAX_OPRETURN_SIZE, MAX_UTXO_NUMBER, TIMEOUT_LONG, -}; +use ikc_common::constants::{MAX_OPRETURN_SIZE, MAX_UTXO_NUMBER, MIN_NONDUST_OUTPUT, TIMEOUT_LONG}; use ikc_common::error::CoinError; -use ikc_common::path::check_path_validity; +use ikc_common::path::{check_path_validity, get_account_path}; use ikc_common::utility::{bigint_to_byte_vec, hex_to_bytes, secp256k1_sign}; use ikc_device::device_binding::KEY_MANAGER; use ikc_transport::message::{send_apdu, send_apdu_timeout}; use secp256k1::ecdsa::Signature; +use std::borrow::Borrow; use std::str::FromStr; #[derive(Clone)] pub struct Utxo { pub txhash: String, - pub vout: i32, - pub amount: i64, + pub vout: u32, + pub amount: u64, pub address: Address, pub script_pubkey: String, pub derive_path: String, @@ -36,245 +38,471 @@ pub struct Utxo { pub struct BtcTransaction { pub to: Address, - pub amount: i64, + pub amount: u64, pub unspents: Vec, - pub fee: i64, + pub fee: u64, } impl BtcTransaction { - pub fn sign_transaction( + pub fn sign_Transaction( &self, network: Network, path: &str, - change_idx: i32, - extra_data: &Vec, + change_idx: Option, + extra_data: Option<&str>, + seg_wit: &str, ) -> Result { //path check check_path_validity(path)?; - let mut path_str = path.to_string(); - if !path.ends_with("/") { - path_str = format!("{}{}", path_str, "/"); - } + //check uxto number if &self.unspents.len() > &MAX_UTXO_NUMBER { return Err(CoinError::ImkeyExceededMaxUtxoNumber.into()); } - //utxo address verify - let utxo_pub_key_vec = address_verify(&self.unspents, network, TransTypeFlg::BTC)?; - //calc utxo total amount if self.get_total_amount() < self.amount { return Err(CoinError::ImkeyInsufficientFunds.into()); } - //add send to output - let mut txouts: Vec = vec![]; - txouts.push(self.build_send_to_output()); - - //add change output - if self.get_change_amount() > DUST_THRESHOLD { - let path_temp = format!("{}{}{}", path_str, "1/", change_idx); - let address_str = BtcAddress::get_address(network, path_temp.as_str())?; - let address_obj = Address::from_str(address_str.as_str())?; - txouts.push(TxOut { - value: self.get_change_amount() as u64, - script_pubkey: address_obj.script_pubkey(), - }); - } + //utxo address verify + let utxo_pub_key_vec = address_verify(&self.unspents, network)?; - //add the op_return - if !extra_data.is_empty() { - if extra_data.len() > MAX_OPRETURN_SIZE { - return Err(CoinError::ImkeySdkIllegalArgument.into()); - } - txouts.push(self.build_op_return_output(&extra_data)) - } + let output = self.tx_output(change_idx, &path, network, seg_wit, extra_data)?; - //output data serialize let mut tx_to_sign = Transaction { version: 1i32, lock_time: PackedLockTime::ZERO, input: vec![], - output: txouts, + output, }; - let mut output_serialize_data = serialize(&tx_to_sign); - - output_serialize_data.remove(5); - output_serialize_data.remove(5); - //add sign type - let mut encoder_hash = Vec::new(); - let len = EcdsaSighashType::All - .to_u32() - .consensus_encode(&mut encoder_hash) - .unwrap(); - debug_assert_eq!(len, encoder_hash.len()); - output_serialize_data.extend(encoder_hash); - //set input number - output_serialize_data.remove(4); - output_serialize_data.insert(4, self.unspents.len() as u8); - - //add fee amount - output_serialize_data.extend(bigint_to_byte_vec(self.fee)); - - //add address version - let address_version = get_address_version(network, self.to.to_string().as_str())?; - output_serialize_data.push(address_version); - - //set 01 tag and length - output_serialize_data.insert(0, output_serialize_data.len() as u8); - output_serialize_data.insert(0, 0x01); + self.calc_tx_hash(&mut tx_to_sign)?; + + self.tx_preview(&tx_to_sign, network)?; + + let input_with_sigs: Vec = vec![]; + for (idx, utxo) in self.unspents.iter().enumerate() { + let script = Script::from_str(&utxo.script_pubkey)?; + if script.is_p2pkh() { + self.sign_p2pkh_input(idx, &utxo_pub_key_vec[idx], &mut tx_to_sign)?; + } else if script.is_p2sh() { + self.sign_p2sh_nested_p2wpkh_input(idx, &utxo_pub_key_vec[idx], &mut tx_to_sign)?; + } else if script.is_v0_p2wpkh() { + self.sign_p2wpkh_input(idx, &utxo_pub_key_vec[idx], &mut tx_to_sign)?; + } else if script.is_v1_p2tr() { + self.sign_p2tr_input( + idx, + &utxo_pub_key_vec[idx], + &mut tx_to_sign, + SchnorrSighashType::Default, + )?; + } else { + return Err(CoinError::InvalidUtxo.into()); + }; + } - //use local private key sign data - let key_manager_obj = KEY_MANAGER.lock(); - let mut output_pareper_data = - secp256k1_sign(&key_manager_obj.pri_key, &output_serialize_data)?; - output_pareper_data.insert(0, output_pareper_data.len() as u8); - output_pareper_data.insert(0, 0x00); - output_pareper_data.extend(output_serialize_data.iter()); + let tx_bytes = serialize(&tx_to_sign); - let btc_prepare_apdu_vec = BtcApdu::btc_prepare(0x41, 0x00, &output_pareper_data); - for temp_str in btc_prepare_apdu_vec { - ApduCheck::check_response(&send_apdu_timeout(temp_str, TIMEOUT_LONG)?)?; - } + Ok(TxSignResult { + signature: tx_bytes.to_hex(), + tx_hash: tx_to_sign.txid().to_hex(), + wtx_id: tx_to_sign.wtxid().to_hex(), + }) + } - let mut lock_script_ver: Vec