diff --git a/token-core/tcx-btc-kin/src/lib.rs b/token-core/tcx-btc-kin/src/lib.rs index f3fe6d15..bac3d331 100644 --- a/token-core/tcx-btc-kin/src/lib.rs +++ b/token-core/tcx-btc-kin/src/lib.rs @@ -27,7 +27,7 @@ pub type Result = result::Result; pub use address::{BtcKinAddress, WIFDisplay}; pub use bch_address::BchAddress; pub use network::BtcKinNetwork; -pub use psbt::sign_psbt; +pub use psbt::{sign_psbt, sign_psbts}; pub use transaction::{BtcKinTxInput, BtcKinTxOutput, OmniTxInput, Utxo}; pub const BITCOIN: &str = "BITCOIN"; diff --git a/token-core/tcx-btc-kin/src/message.rs b/token-core/tcx-btc-kin/src/message.rs index 800241b4..70364652 100644 --- a/token-core/tcx-btc-kin/src/message.rs +++ b/token-core/tcx-btc-kin/src/message.rs @@ -128,7 +128,13 @@ impl MessageSigner for Keystore { signature: witness_to_vec(witness.to_vec()).to_hex(), }) } else { - Err(Error::MissingSignature.into()) + if let Some(script_sig) = &psbt.inputs[0].final_script_sig { + Ok(BtcMessageOutput { + signature: format!("02{}", script_sig.to_hex()), + }) + } else { + Err(Error::MissingSignature.into()) + } } } } @@ -164,7 +170,78 @@ mod tests { } #[test] - fn test_bip322_segwit() { + fn test_bip32_p2sh_p2wpkh() { + let message = "hello world"; + let mut ks = sample_hd_keystore(); + let coin_info = CoinInfo { + coin: "BITCOIN".to_string(), + derivation_path: "m/49'/0'/0'/0/0".to_string(), + curve: CurveType::SECP256k1, + network: "MAINNET".to_string(), + seg_wit: "P2WPKH".to_string(), + }; + + let account = ks.derive_coin::(&coin_info).unwrap(); + let address = BtcKinAddress::from_public_key(&account.public_key, &coin_info).unwrap(); + + let params = tcx_keystore::SignatureParameters { + curve: CurveType::SECP256k1, + chain_type: "BITCOIN".to_string(), + network: "MAINNET".to_string(), + seg_wit: "P2WPKH".to_string(), + derivation_path: "m/49'/0'/0'".to_string(), + }; + + let output = ks + .sign_message( + ¶ms, + &super::BtcMessageInput { + message: message.to_string(), + }, + ) + .unwrap(); + + assert_eq!(output.signature, "02473044022000ae3c9439681a4ba05e74d0805210f71c31f92130bcec28934d29beaf5f4f890220327cbf8a189eee4cb35a2599f6fd97b0774bec2e4191d74b3460f746732f8a03012103036695c5f3de2e2792b170f59679d4db88a8516728012eaa42a22ce6f8bf593b"); + } + + #[test] + fn test_bip32_p2pkh() { + let message = "hello world"; + let mut ks = sample_hd_keystore(); + let coin_info = CoinInfo { + coin: "BITCOIN".to_string(), + derivation_path: "m/44'/0'/0'/0/0".to_string(), + curve: CurveType::SECP256k1, + network: "MAINNET".to_string(), + seg_wit: "NONE".to_string(), + }; + + let account = ks.derive_coin::(&coin_info).unwrap(); + println!("{}", account.address); + let address = BtcKinAddress::from_public_key(&account.public_key, &coin_info).unwrap(); + + let params = tcx_keystore::SignatureParameters { + curve: CurveType::SECP256k1, + chain_type: "BITCOIN".to_string(), + network: "MAINNET".to_string(), + seg_wit: "NONE".to_string(), + derivation_path: "m/44'/0'/0'".to_string(), + }; + + let output = ks + .sign_message( + ¶ms, + &super::BtcMessageInput { + message: message.to_string(), + }, + ) + .unwrap(); + + assert_eq!(output.signature, "02483045022100dbbdfedfb1902ca12c6cba14d4892a98f77c434daaa4f97fd35e618374c908f602206527ff2b1ce550c16c836c2ce3508bfae543fa6c11759d2f4966cc0d3552c4430121026b5b6a9d041bc5187e0b34f9e496436c7bff261c6c1b5f3c06b433c61394b868"); + } + + #[test] + fn test_bip322_p2wpkh() { let message = "hello world"; let mut ks = sample_hd_keystore(); let coin_info = CoinInfo { @@ -199,7 +276,7 @@ mod tests { } #[test] - fn test_bip322_taproot() { + fn test_bip322_p2tr() { let message = "Sign this message to log in to https://www.subber.xyz // 200323342"; let mut ks = wif_keystore("L4F5BYm82Bck6VEY64EbqQkoBXqkegq9X9yc6iLTV3cyJoqUasnY"); let coin_info = CoinInfo { diff --git a/token-core/tcx-btc-kin/src/psbt.rs b/token-core/tcx-btc-kin/src/psbt.rs index 08ab127b..edd30452 100644 --- a/token-core/tcx-btc-kin/src/psbt.rs +++ b/token-core/tcx-btc-kin/src/psbt.rs @@ -1,6 +1,6 @@ use crate::bch_sighash::BitcoinCashSighash; use crate::sighash::TxSignatureHasher; -use crate::transaction::{PsbtInput, PsbtOutput}; +use crate::transaction::{PsbtInput, PsbtOutput, PsbtsInput, PsbtsOutput}; use crate::{Error, Result, BITCOINCASH}; use bitcoin::blockdata::script::Builder; use bitcoin::consensus::{Decodable, Encodable}; @@ -118,7 +118,7 @@ impl<'a> PsbtSigner<'a> { } fn finalize_p2wpkh(&mut self, index: usize) { - let mut input = &mut self.psbt.inputs[index]; + let input = &mut self.psbt.inputs[index]; if !input.partial_sigs.is_empty() { let sig = input.partial_sigs.first_key_value().unwrap(); @@ -132,7 +132,7 @@ impl<'a> PsbtSigner<'a> { } fn finalize_p2pkh(&mut self, index: usize) { - let mut input = &mut self.psbt.inputs[index]; + let input = &mut self.psbt.inputs[index]; if !input.partial_sigs.is_empty() { let sig = input.partial_sigs.first_key_value().unwrap(); @@ -166,7 +166,7 @@ impl<'a> PsbtSigner<'a> { } fn finalize_p2tr(&mut self, index: usize) { - let mut input = &mut self.psbt.inputs[index]; + let input = &mut self.psbt.inputs[index]; if input.tap_key_sig.is_some() { let mut witness = Witness::new(); @@ -185,7 +185,7 @@ impl<'a> PsbtSigner<'a> { } fn clear_finalized_input(&mut self, index: usize) { - let mut input = &mut self.psbt.inputs[index]; + let input = &mut self.psbt.inputs[index]; input.tap_key_sig = None; input.tap_scripts = BTreeMap::new(); input.tap_internal_key = None; @@ -376,7 +376,7 @@ pub fn sign_psbt( keystore: &mut Keystore, psbt_input: PsbtInput, ) -> Result { - let mut reader = Cursor::new(Vec::::from_hex(psbt_input.data)?); + let mut reader = Cursor::new(Vec::::from_hex(psbt_input.psbt)?); let mut psbt = Psbt::consensus_decode(&mut reader)?; let mut signer = PsbtSigner::new( @@ -392,7 +392,28 @@ pub fn sign_psbt( let mut writer = Cursor::new(&mut vec); psbt.consensus_encode(&mut writer)?; - return Ok(PsbtOutput { data: vec.to_hex() }); + return Ok(PsbtOutput { psbt: vec.to_hex() }); +} + +pub fn sign_psbts( + chain_type: &str, + derivation_path: &str, + keystore: &mut Keystore, + psbts_input: PsbtsInput, +) -> Result { + let mut outputs = Vec::::new(); + + for item in psbts_input.psbts { + let psbt_input = PsbtInput { + psbt: item, + auto_finalize: psbts_input.auto_finalize, + }; + + let output = sign_psbt(chain_type, derivation_path, keystore, psbt_input)?; + outputs.push(output.psbt); + } + + Ok(PsbtsOutput { psbts: outputs }) } #[cfg(test)] @@ -429,12 +450,12 @@ mod tests { let account = hd.derive_coin::(&coin_info).unwrap(); let psbt_input = PsbtInput { - data: "70736274ff0100db0200000001fa4c8d58b9b6c56ed0b03f78115246c99eb70f99b837d7b4162911d1016cda340200000000fdffffff0350c30000000000002251202114eda66db694d87ff15ddd5d3c4e77306b6e6dd5720cbd90cd96e81016c2b30000000000000000496a47626274340066f873ad53d80688c7739d0d268acd956366275004fdceab9e9fc30034a4229ec20acf33c17e5a6c92cced9f1d530cccab7aa3e53400456202f02fac95e9c481fa00d47b1700000000002251208f4ca6a7384f50a1fe00cba593d5a834b480c65692a76ae6202e1ce46cb1c233d80f03000001012be3bf1d00000000002251208f4ca6a7384f50a1fe00cba593d5a834b480c65692a76ae6202e1ce46cb1c23301172066f873ad53d80688c7739d0d268acd956366275004fdceab9e9fc30034a4229e00000000".to_string(), + psbt: "70736274ff0100db0200000001fa4c8d58b9b6c56ed0b03f78115246c99eb70f99b837d7b4162911d1016cda340200000000fdffffff0350c30000000000002251202114eda66db694d87ff15ddd5d3c4e77306b6e6dd5720cbd90cd96e81016c2b30000000000000000496a47626274340066f873ad53d80688c7739d0d268acd956366275004fdceab9e9fc30034a4229ec20acf33c17e5a6c92cced9f1d530cccab7aa3e53400456202f02fac95e9c481fa00d47b1700000000002251208f4ca6a7384f50a1fe00cba593d5a834b480c65692a76ae6202e1ce46cb1c233d80f03000001012be3bf1d00000000002251208f4ca6a7384f50a1fe00cba593d5a834b480c65692a76ae6202e1ce46cb1c23301172066f873ad53d80688c7739d0d268acd956366275004fdceab9e9fc30034a4229e00000000".to_string(), auto_finalize: true, }; let psbt_output = super::sign_psbt("BITCOIN", "m/86'/1'/0'", &mut hd, psbt_input).unwrap(); - let mut reader = Cursor::new(Vec::::from_hex(psbt_output.data).unwrap()); + let mut reader = Cursor::new(Vec::::from_hex(psbt_output.psbt).unwrap()); let psbt = Psbt::consensus_decode(&mut reader).unwrap(); let tx = psbt.extract_tx(); let sig = schnorr::SchnorrSig::from_slice(&tx.input[0].witness.to_vec()[0]).unwrap(); @@ -468,12 +489,12 @@ mod tests { let account = hd.derive_coin::(&coin_info).unwrap(); let psbt_input = PsbtInput { - data: "70736274ff01005e02000000012bd2f6479f3eeaffe95c03b5fdd76a873d346459114dec99c59192a0cb6409e90000000000ffffffff01409c000000000000225120677cc88dc36a75707b370e27efff3e454d446ad55004dac1685c1725ee1a89ea000000000001012b50c3000000000000225120a9a3350206de400f09a73379ec1bcfa161fc11ac095e5f3d7354126f0ec8e87f6215c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0d2956573f010fa1a3c135279c5eb465ec2250205dcdfe2122637677f639b1021356c963cd9c458508d6afb09f3fa2f9b48faec88e75698339a4bbb11d3fc9b0efd570120aff94eb65a2fe773a57c5bd54e62d8436a5467573565214028422b41bd43e29bad200aee0509b16db71c999238a4827db945526859b13c95487ab46725357c9a9f25ac20113c3a32a9d320b72190a04a020a0db3976ef36972673258e9a38a364f3dc3b0ba2017921cf156ccb4e73d428f996ed11b245313e37e27c978ac4d2cc21eca4672e4ba203bb93dfc8b61887d771f3630e9a63e97cbafcfcc78556a474df83a31a0ef899cba2040afaf47c4ffa56de86410d8e47baa2bb6f04b604f4ea24323737ddc3fe092dfba2079a71ffd71c503ef2e2f91bccfc8fcda7946f4653cef0d9f3dde20795ef3b9f0ba20d21faf78c6751a0d38e6bd8028b907ff07e9a869a43fc837d6b3f8dff6119a36ba20f5199efae3f28bb82476163a7e458c7ad445d9bffb0682d10d3bdb2cb41f8e8eba20fa9d882d45f4060bdb8042183828cd87544f1ea997380e586cab77d5fd698737ba569cc001172050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac00000".to_string(), + psbt: "70736274ff01005e02000000012bd2f6479f3eeaffe95c03b5fdd76a873d346459114dec99c59192a0cb6409e90000000000ffffffff01409c000000000000225120677cc88dc36a75707b370e27efff3e454d446ad55004dac1685c1725ee1a89ea000000000001012b50c3000000000000225120a9a3350206de400f09a73379ec1bcfa161fc11ac095e5f3d7354126f0ec8e87f6215c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0d2956573f010fa1a3c135279c5eb465ec2250205dcdfe2122637677f639b1021356c963cd9c458508d6afb09f3fa2f9b48faec88e75698339a4bbb11d3fc9b0efd570120aff94eb65a2fe773a57c5bd54e62d8436a5467573565214028422b41bd43e29bad200aee0509b16db71c999238a4827db945526859b13c95487ab46725357c9a9f25ac20113c3a32a9d320b72190a04a020a0db3976ef36972673258e9a38a364f3dc3b0ba2017921cf156ccb4e73d428f996ed11b245313e37e27c978ac4d2cc21eca4672e4ba203bb93dfc8b61887d771f3630e9a63e97cbafcfcc78556a474df83a31a0ef899cba2040afaf47c4ffa56de86410d8e47baa2bb6f04b604f4ea24323737ddc3fe092dfba2079a71ffd71c503ef2e2f91bccfc8fcda7946f4653cef0d9f3dde20795ef3b9f0ba20d21faf78c6751a0d38e6bd8028b907ff07e9a869a43fc837d6b3f8dff6119a36ba20f5199efae3f28bb82476163a7e458c7ad445d9bffb0682d10d3bdb2cb41f8e8eba20fa9d882d45f4060bdb8042183828cd87544f1ea997380e586cab77d5fd698737ba569cc001172050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac00000".to_string(), auto_finalize: true, }; let psbt_output = super::sign_psbt("BITCOIN", "m/86'/1'/0'", &mut hd, psbt_input).unwrap(); - let mut reader = Cursor::new(Vec::::from_hex(psbt_output.data).unwrap()); + let mut reader = Cursor::new(Vec::::from_hex(psbt_output.psbt).unwrap()); let psbt = Psbt::consensus_decode(&mut reader).unwrap(); let tx = psbt.extract_tx(); let witness = tx.input[0].witness.to_vec(); diff --git a/token-core/tcx-btc-kin/src/transaction.rs b/token-core/tcx-btc-kin/src/transaction.rs index 3efaa5d7..ce64842e 100644 --- a/token-core/tcx-btc-kin/src/transaction.rs +++ b/token-core/tcx-btc-kin/src/transaction.rs @@ -57,7 +57,7 @@ pub struct OmniTxInput { #[derive(Clone, PartialEq, ::prost::Message)] pub struct PsbtInput { #[prost(string, tag = "1")] - pub data: ::prost::alloc::string::String, + pub psbt: ::prost::alloc::string::String, #[prost(bool, tag = "2")] pub auto_finalize: bool, } @@ -65,7 +65,21 @@ pub struct PsbtInput { #[derive(Clone, PartialEq, ::prost::Message)] pub struct PsbtOutput { #[prost(string, tag = "1")] - pub data: ::prost::alloc::string::String, + pub psbt: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PsbtsInput { + #[prost(string, repeated, tag = "1")] + pub psbts: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(bool, tag = "2")] + pub auto_finalize: bool, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PsbtsOutput { + #[prost(string, repeated, tag = "1")] + pub psbts: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/token-core/tcx-proto/src/btc_kin.proto b/token-core/tcx-proto/src/btc_kin.proto index 9238fb3f..cd0c9f12 100644 --- a/token-core/tcx-proto/src/btc_kin.proto +++ b/token-core/tcx-proto/src/btc_kin.proto @@ -39,12 +39,21 @@ message OmniTxInput { } message PsbtInput { - string data = 1; + string psbt = 1; bool autoFinalize = 2; } message PsbtOutput { - string data = 1; + string psbt = 1; +} + +message PsbtsInput { + repeated string psbts = 1; + bool autoFinalize = 2; +} + +message PsbtsOutput { + repeated string psbts = 1; } message BtcMessageInput { diff --git a/token-core/tcx/src/handler.rs b/token-core/tcx/src/handler.rs index fe0a28ae..d1c45ad7 100644 --- a/token-core/tcx/src/handler.rs +++ b/token-core/tcx/src/handler.rs @@ -53,7 +53,7 @@ use crate::filemanager::{delete_keystore_file, KEYSTORE_MAP}; use crate::IS_DEBUG; use base58::FromBase58; -use tcx_btc_kin::transaction::PsbtInput; +use tcx_btc_kin::transaction::{PsbtInput, PsbtsInput}; use tcx_keystore::tcx_ensure; use tcx_constants::coin_info::coin_info_from_param; @@ -966,6 +966,35 @@ pub(crate) fn sign_psbt(data: &[u8]) -> Result> { )?) } +pub(crate) fn sign_psbts(data: &[u8]) -> Result> { + let param: SignParam = SignParam::decode(data).expect("sign_psbts param"); + + let mut map = KEYSTORE_MAP.write(); + let keystore: &mut Keystore = match map.get_mut(¶m.id) { + Some(keystore) => Ok(keystore), + _ => Err(anyhow!("{}", "wallet_not_found")), + }?; + + let mut guard = KeystoreGuard::unlock(keystore, param.key.clone().unwrap().into())?; + let psbt_inputs = PsbtsInput::decode( + param + .input + .as_ref() + .expect("psbts") + .value + .clone() + .as_slice(), + ) + .expect("psbts decode"); + + encode_message(tcx_btc_kin::sign_psbts( + ¶m.chain_type, + ¶m.path, + guard.keystore_mut(), + psbt_inputs, + )?) +} + pub fn get_derived_key(data: &[u8]) -> Result> { let param: WalletKeyParam = WalletKeyParam::decode(data).expect("get_derived_key param"); let mut map: parking_lot::lock_api::RwLockWriteGuard< diff --git a/token-core/tcx/src/lib.rs b/token-core/tcx/src/lib.rs index 40e985c0..3fcb1900 100644 --- a/token-core/tcx/src/lib.rs +++ b/token-core/tcx/src/lib.rs @@ -27,8 +27,8 @@ use crate::handler::{ encode_message, encrypt_data_to_ipfs, eth_batch_personal_sign, exists_json, exists_mnemonic, exists_private_key, export_json, export_mnemonic, export_private_key, get_derived_key, get_extended_public_keys, get_public_keys, import_json, import_mnemonic, import_private_key, - mnemonic_to_public, sign_authentication_message, sign_hashes, sign_message, sign_psbt, sign_tx, - unlock_then_crash, verify_password, + mnemonic_to_public, sign_authentication_message, sign_hashes, sign_message, sign_psbt, + sign_psbts, sign_tx, unlock_then_crash, verify_password, }; use crate::migration::{migrate_keystore, scan_legacy_keystores}; @@ -131,6 +131,7 @@ pub unsafe extern "C" fn call_tcx_api(hex_str: *const c_char) -> *const c_char { landingpad(|| mark_identity_wallets(&action.param.unwrap().value)) } "sign_psbt" => landingpad(|| sign_psbt(&action.param.unwrap().value)), + "sign_psbts" => landingpad(|| sign_psbts(&action.param.unwrap().value)), _ => landingpad(|| Err(anyhow!("unsupported_method"))), }; match reply {