From da1edc423f9d79f176cba6cf9f07f1458ebd019a Mon Sep 17 00:00:00 2001 From: Sun Feng Date: Mon, 17 Jun 2024 14:46:02 +0800 Subject: [PATCH 1/5] Add PsbtSigner --- token-core/tcx-btc-kin/src/lib.rs | 2 + token-core/tcx-btc-kin/src/psbt.rs | 260 ++++++++++++++++++++++ token-core/tcx-btc-kin/src/transaction.rs | 12 + token-core/tcx-proto/src/btc_kin.proto | 8 + 4 files changed, 282 insertions(+) create mode 100644 token-core/tcx-btc-kin/src/psbt.rs diff --git a/token-core/tcx-btc-kin/src/lib.rs b/token-core/tcx-btc-kin/src/lib.rs index c283f3a6..7a242e9e 100644 --- a/token-core/tcx-btc-kin/src/lib.rs +++ b/token-core/tcx-btc-kin/src/lib.rs @@ -9,6 +9,8 @@ pub mod network; pub mod signer; pub mod transaction; +mod psbt; + use core::result; use thiserror::Error; diff --git a/token-core/tcx-btc-kin/src/psbt.rs b/token-core/tcx-btc-kin/src/psbt.rs new file mode 100644 index 00000000..0f873ba2 --- /dev/null +++ b/token-core/tcx-btc-kin/src/psbt.rs @@ -0,0 +1,260 @@ +use crate::bch_sighash::BitcoinCashSighash; +use crate::sighash::TxSignatureHasher; +use crate::transaction::{PsbtInput, PsbtOutput}; +use crate::{Error, Result, BITCOINCASH}; +use bitcoin::consensus::{Decodable, Encodable}; +use bitcoin::psbt::{Prevouts, Psbt}; +use bitcoin::schnorr::TapTweak; +use bitcoin::util::bip32::KeySource; +use bitcoin::util::sighash::SighashCache; +use bitcoin::{ + EcdsaSig, EcdsaSighashType, SchnorrSig, SchnorrSighashType, Script, TxOut, WPubkeyHash, Witness, +}; +use bitcoin_hashes::{hash160, Hash}; +use secp256k1::ecdsa::Signature; +use secp256k1::Message; +use std::collections::BTreeMap; +use std::io::Cursor; +use tcx_common::{FromHex, ToHex}; +use tcx_constants::CurveType; +use tcx_keystore::Keystore; +use tcx_primitive::{PrivateKey, Secp256k1PrivateKey, SECP256K1_ENGINE}; + +pub struct PsbtSigner<'a> { + psbt: &'a mut Psbt, + keystore: &'a mut Keystore, + + prevouts: Vec, + sighash_cache: Box, +} + +impl<'a> PsbtSigner<'a> { + pub fn new(psbt: &'a mut Psbt, keystore: &'a mut Keystore, chain_type: &str) -> Self { + let unsigned_tx = &psbt.unsigned_tx; + + let sighash_cache: Box = if chain_type == BITCOINCASH { + Box::new(BitcoinCashSighash::new(unsigned_tx.clone(), 0x40)) + } else { + Box::new(SighashCache::new(Box::new(unsigned_tx.clone()))) + }; + + PsbtSigner { + psbt, + keystore, + sighash_cache, + prevouts: Vec::new(), + } + } + + fn hash160(&self, input: &[u8]) -> hash160::Hash { + hash160::Hash::hash(input) + } + + fn sign_ecdsa(data: &[u8], key: &Secp256k1PrivateKey) -> Result { + let msg = Message::from_slice(data)?; + let sig = SECP256K1_ENGINE.sign_ecdsa(&msg, &key.0.inner); + Ok(sig) + } + + fn prevouts(&self) -> Result> { + let len = self.psbt.inputs.len(); + let mut utxos = Vec::with_capacity(len); + + for i in 0..len { + let input = &self.psbt.inputs[i]; + let utxo = if let Some(witness_utxo) = &input.witness_utxo { + witness_utxo + } else if let Some(non_witness_utxo) = &input.non_witness_utxo { + let vout = self.psbt.unsigned_tx.input[i].previous_output.vout; + &non_witness_utxo.output[vout as usize] + } else { + return Err(Error::InvalidUtxo.into()); + }; + utxos.push(utxo.clone()); + } + + Ok(utxos) + } + + fn get_private_key(&mut self, index: usize) -> Result { + let key_sources: Vec<&KeySource> = + self.psbt.inputs[index].bip32_derivation.values().collect(); + let path = if key_sources.len() > 0 { + key_sources[0].1.to_string() + } else { + "".to_string() + }; + + if self.keystore.derivable() { + Ok(self + .keystore + .get_private_key(CurveType::SECP256k1, &path)? + .as_secp256k1()? + .clone()) + } else { + Ok(self + .keystore + .get_private_key(CurveType::SECP256k1, &path)? + .as_secp256k1()? + .clone()) + } + } + + fn sign_p2pkh(&mut self, index: usize) -> Result<()> { + let key = self.get_private_key(index)?; + + let prevout = &self.prevouts[index]; + + let hash = self.sighash_cache.legacy_hash( + index, + &prevout.script_pubkey, + prevout.value, + EcdsaSighashType::All.to_u32(), + )?; + + let sig = Self::sign_ecdsa(&hash, &key)?; + self.psbt.inputs[index] + .partial_sigs + .insert(key.public_key().0, EcdsaSig::sighash_all(sig)); + + Ok(()) + } + + fn sign_p2sh_nested_p2wpkh(&mut self, index: usize) -> Result<()> { + let prevout = &self.prevouts[index].clone(); + let key = self.get_private_key(index)?; + let pub_key = key.public_key(); + + let script = Script::new_v0_p2wpkh(&WPubkeyHash::from_hash( + self.hash160(&pub_key.to_compressed()), + )); + + let hash = self.sighash_cache.segwit_hash( + index, + &script.p2wpkh_script_code().expect("must be v0_p2wpkh"), + prevout.value, + EcdsaSighashType::All, + )?; + let sig = Self::sign_ecdsa(&hash, &key)?; + + self.psbt.inputs[index] + .partial_sigs + .insert(pub_key.0, EcdsaSig::sighash_all(sig)); + + Ok(()) + } + + fn sign_p2wpkh(&mut self, index: usize) -> Result<()> { + let key = self.get_private_key(index)?; + let prevout = &self.prevouts[index]; + + let hash = self.sighash_cache.segwit_hash( + index, + &prevout + .script_pubkey + .p2wpkh_script_code() + .expect("must be v0_p2wpkh"), + prevout.value, + EcdsaSighashType::All, + )?; + let sig = Self::sign_ecdsa(&hash, &key)?; + self.psbt.inputs[index] + .partial_sigs + .insert(key.public_key().0, EcdsaSig::sighash_all(sig)); + + Ok(()) + } + + fn sign_p2tr(&mut self, index: usize) -> Result<()> { + let key = self.get_private_key(index)?; + + let key_pair = bitcoin::schnorr::UntweakedKeyPair::from_seckey_slice( + &SECP256K1_ENGINE, + &key.to_bytes(), + )? + .tap_tweak(&SECP256K1_ENGINE, None); + + let hash = self.sighash_cache.taproot_hash( + index, + &Prevouts::All(&self.prevouts.clone()), + None, + None, + SchnorrSighashType::Default, + )?; + + let msg = Message::from_slice(&hash[..])?; + let sig = SECP256K1_ENGINE.sign_schnorr(&msg, &key_pair.to_inner()); + + self.psbt.inputs[index].tap_key_sig = Some(SchnorrSig { + hash_ty: SchnorrSighashType::Default, + sig, + }); + + Ok(()) + } + + fn sign(&mut self) -> Result<()> { + self.prevouts = self.prevouts()?; + + for idx in 0..self.prevouts.len() { + let prevout = &self.prevouts[idx]; + + if prevout.script_pubkey.is_p2pkh() { + self.sign_p2pkh(idx)?; + } else if prevout.script_pubkey.is_p2sh() { + self.sign_p2sh_nested_p2wpkh(idx)?; + } else if prevout.script_pubkey.is_v0_p2wpkh() { + self.sign_p2wpkh(idx)?; + } else if prevout.script_pubkey.is_v1_p2tr() { + self.sign_p2tr(idx)?; + } + } + + Ok(()) + } +} + +pub fn sign_psbt(psbt_input: PsbtInput) -> Result { + let mut reader = Cursor::new(Vec::::from_hex(psbt_input.data)?); + let mut psbt = Psbt::consensus_decode(&mut reader)?; + + // FINALIZER + psbt.inputs.iter_mut().for_each(|input| { + let mut script_witness: Witness = Witness::new(); + + if input.tap_key_sig.is_some() { + script_witness.push(input.tap_key_sig.unwrap().to_vec()); + input.final_script_witness = Some(script_witness); + + // Clear all the data fields as per the spec. + input.partial_sigs = BTreeMap::new(); + input.sighash_type = None; + input.redeem_script = None; + input.witness_script = None; + input.bip32_derivation = BTreeMap::new(); + } + }); + + println!("psbt: {:?}", psbt.inputs); + + let mut vec = Vec::::new(); + let mut writer = Cursor::new(&mut vec); + psbt.consensus_encode(&mut writer)?; + + return Ok(PsbtOutput { data: vec.to_hex() }); +} + +#[cfg(test)] +mod tests { + use crate::transaction::PsbtInput; + + #[test] + fn test_sign_psbt() { + let psbt_input = PsbtInput { + data: "70736274ff0100db02000000017e4e5ccaa5a84f4e2761816d948db0530283d2ddab9e2b0bf14432247177b67c0000000000fdffffff0350c30000000000002251202f03f11af54df4be96db1c8d6ee9ab2a29558479ff93ad019d182deed8f8c33d0000000000000000496a4762627434001fa696928d908ffd29c2ab9ebf8ad48946bf9d57b64c2e4f588988c830bd2571f4940b238dcd00535fde9730345bab6ff4ea6d413cc3602c4033c10f251c7e81fa0057620000000000002251206649a3708d5510aeb8140ffb6ed5866db64b817ea62902628ad7d04730484aab080803000001012bf4260100000000002251206649a3708d5510aeb8140ffb6ed5866db64b817ea62902628ad7d04730484aab0117201fa696928d908ffd29c2ab9ebf8ad48946bf9d57b64c2e4f588988c830bd257100000000".to_string(), + }; + + let result = super::sign_psbt(psbt_input).unwrap(); + assert_eq!(result.data, "70736274ff0100db02000000017e4e5ccaa5a84f4e2761816d948db0530283d2ddab9e2b0bf14432247177b67c0000000000fdffffff0350c30000000000002251202f03f11af54df4be96db1c8d6ee9ab2a29558479ff93ad019d182deed8f8c33d0000000000000000496a4762627434001fa696928d908ffd29c2ab9ebf8ad48946bf9d57b64c2e4f588988c830bd2571f4940b238dcd00535fde9730345bab6ff4ea6d413cc3602c4033c10f251c7e81fa0057620000000000002251206649a3708d5510aeb8140ffb6ed5866db64b817ea62902628ad7d04730484aab080803000001012bf4260100000000002251206649a3708d5510aeb8140ffb6ed5866db64b817ea62902628ad7d04730484aab0117201fa696928d908ffd29c2ab9ebf8ad48946bf9d57b64c2e4f588988c830bd257100000000"); + } +} diff --git a/token-core/tcx-btc-kin/src/transaction.rs b/token-core/tcx-btc-kin/src/transaction.rs index 2b237ead..900d793c 100644 --- a/token-core/tcx-btc-kin/src/transaction.rs +++ b/token-core/tcx-btc-kin/src/transaction.rs @@ -53,3 +53,15 @@ pub struct OmniTxInput { #[prost(uint32, tag = "5")] pub property_id: u32, } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PsbtInput { + #[prost(string, tag = "1")] + pub data: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PsbtOutput { + #[prost(string, tag = "1")] + pub data: ::prost::alloc::string::String, +} diff --git a/token-core/tcx-proto/src/btc_kin.proto b/token-core/tcx-proto/src/btc_kin.proto index af688ce6..966b3257 100644 --- a/token-core/tcx-proto/src/btc_kin.proto +++ b/token-core/tcx-proto/src/btc_kin.proto @@ -37,3 +37,11 @@ message OmniTxInput { uint64 fee = 4; uint32 propertyId = 5; } + +message PsbtInput { + string data = 1; +} + +message PsbtOutput { + string data = 1; +} From ebc2aa58220ff0cee4816667ebda25d1dd626e49 Mon Sep 17 00:00:00 2001 From: Sun Feng Date: Tue, 18 Jun 2024 10:52:24 +0800 Subject: [PATCH 2/5] finish taproot sign in psbt --- token-core/tcx-btc-kin/src/lib.rs | 17 +++++++++++++++++ token-core/tcx-btc-kin/src/psbt.rs | 11 +++++++++-- token-core/tcx-btc-kin/src/signer.rs | 14 +++----------- token-core/tcx-btc-kin/src/transaction.rs | 2 ++ token-core/tcx-proto/src/btc_kin.proto | 1 + 5 files changed, 32 insertions(+), 13 deletions(-) diff --git a/token-core/tcx-btc-kin/src/lib.rs b/token-core/tcx-btc-kin/src/lib.rs index 7a242e9e..e3287fc0 100644 --- a/token-core/tcx-btc-kin/src/lib.rs +++ b/token-core/tcx-btc-kin/src/lib.rs @@ -89,3 +89,20 @@ pub mod omni { pub type TransactionOutput = crate::transaction::BtcKinTxOutput; } + +#[cfg(test)] +mod tests { + use tcx_constants::{TEST_MNEMONIC, TEST_PASSWORD}; + use tcx_keystore::{Keystore, Metadata}; + + pub fn hd_keystore(mnemonic: &str) -> Keystore { + let mut keystore = + Keystore::from_mnemonic(mnemonic, TEST_PASSWORD, Metadata::default()).unwrap(); + keystore.unlock_by_password(TEST_PASSWORD).unwrap(); + keystore + } + + pub fn sample_hd_keystore() -> Keystore { + hd_keystore(TEST_MNEMONIC) + } +} diff --git a/token-core/tcx-btc-kin/src/psbt.rs b/token-core/tcx-btc-kin/src/psbt.rs index 0f873ba2..ea9d08d2 100644 --- a/token-core/tcx-btc-kin/src/psbt.rs +++ b/token-core/tcx-btc-kin/src/psbt.rs @@ -214,10 +214,13 @@ impl<'a> PsbtSigner<'a> { } } -pub fn sign_psbt(psbt_input: PsbtInput) -> Result { +pub fn sign_psbt(keystore: &mut Keystore, psbt_input: PsbtInput) -> Result { let mut reader = Cursor::new(Vec::::from_hex(psbt_input.data)?); let mut psbt = Psbt::consensus_decode(&mut reader)?; + let mut signer = PsbtSigner::new(&mut psbt, keystore, &psbt_input.chain_type); + signer.sign()?; + // FINALIZER psbt.inputs.iter_mut().for_each(|input| { let mut script_witness: Witness = Witness::new(); @@ -246,15 +249,19 @@ pub fn sign_psbt(psbt_input: PsbtInput) -> Result { #[cfg(test)] mod tests { + use crate::tests::sample_hd_keystore; use crate::transaction::PsbtInput; #[test] fn test_sign_psbt() { + let mut hd = sample_hd_keystore(); + let psbt_input = PsbtInput { data: "70736274ff0100db02000000017e4e5ccaa5a84f4e2761816d948db0530283d2ddab9e2b0bf14432247177b67c0000000000fdffffff0350c30000000000002251202f03f11af54df4be96db1c8d6ee9ab2a29558479ff93ad019d182deed8f8c33d0000000000000000496a4762627434001fa696928d908ffd29c2ab9ebf8ad48946bf9d57b64c2e4f588988c830bd2571f4940b238dcd00535fde9730345bab6ff4ea6d413cc3602c4033c10f251c7e81fa0057620000000000002251206649a3708d5510aeb8140ffb6ed5866db64b817ea62902628ad7d04730484aab080803000001012bf4260100000000002251206649a3708d5510aeb8140ffb6ed5866db64b817ea62902628ad7d04730484aab0117201fa696928d908ffd29c2ab9ebf8ad48946bf9d57b64c2e4f588988c830bd257100000000".to_string(), + chain_type: "BITCOIN".to_string(), }; - let result = super::sign_psbt(psbt_input).unwrap(); + let result = super::sign_psbt(&mut hd, psbt_input).unwrap(); assert_eq!(result.data, "70736274ff0100db02000000017e4e5ccaa5a84f4e2761816d948db0530283d2ddab9e2b0bf14432247177b67c0000000000fdffffff0350c30000000000002251202f03f11af54df4be96db1c8d6ee9ab2a29558479ff93ad019d182deed8f8c33d0000000000000000496a4762627434001fa696928d908ffd29c2ab9ebf8ad48946bf9d57b64c2e4f588988c830bd2571f4940b238dcd00535fde9730345bab6ff4ea6d413cc3602c4033c10f251c7e81fa0057620000000000002251206649a3708d5510aeb8140ffb6ed5866db64b817ea62902628ad7d04730484aab080803000001012bf4260100000000002251206649a3708d5510aeb8140ffb6ed5866db64b817ea62902628ad7d04730484aab0117201fa696928d908ffd29c2ab9ebf8ad48946bf9d57b64c2e4f588988c830bd257100000000"); } } diff --git a/token-core/tcx-btc-kin/src/signer.rs b/token-core/tcx-btc-kin/src/signer.rs index 99f1e2bd..1248ebac 100644 --- a/token-core/tcx-btc-kin/src/signer.rs +++ b/token-core/tcx-btc-kin/src/signer.rs @@ -469,21 +469,10 @@ mod tests { hex_keystore(&hex) } - fn hd_keystore(mnemonic: &str) -> Keystore { - let mut keystore = - Keystore::from_mnemonic(mnemonic, TEST_PASSWORD, Metadata::default()).unwrap(); - keystore.unlock_by_password(TEST_PASSWORD).unwrap(); - keystore - } - fn sample_private_key_keystore() -> Keystore { wif_keystore(TEST_WIF) } - fn sample_hd_keystore() -> Keystore { - hd_keystore(TEST_MNEMONIC) - } - mod kin { use super::*; @@ -528,6 +517,7 @@ mod tests { mod omni { use super::*; + use crate::tests::sample_hd_keystore; use crate::OMNI; #[test] @@ -646,6 +636,7 @@ mod tests { mod btc { use super::*; + use crate::tests::sample_hd_keystore; use bitcoin::psbt::serialize::Deserialize; use secp256k1::schnorr::Signature; use secp256k1::XOnlyPublicKey; @@ -1154,6 +1145,7 @@ mod tests { mod ltc { use super::*; + use crate::tests::sample_hd_keystore; #[test] fn test_sign_with_hd_on_testnet() { diff --git a/token-core/tcx-btc-kin/src/transaction.rs b/token-core/tcx-btc-kin/src/transaction.rs index 900d793c..2a77c39d 100644 --- a/token-core/tcx-btc-kin/src/transaction.rs +++ b/token-core/tcx-btc-kin/src/transaction.rs @@ -58,6 +58,8 @@ pub struct OmniTxInput { pub struct PsbtInput { #[prost(string, tag = "1")] pub data: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub chain_type: ::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 966b3257..5f9dd7db 100644 --- a/token-core/tcx-proto/src/btc_kin.proto +++ b/token-core/tcx-proto/src/btc_kin.proto @@ -40,6 +40,7 @@ message OmniTxInput { message PsbtInput { string data = 1; + string chain_type = 2; } message PsbtOutput { From 92c3426572257c3d6aba273700bcbead80e37823 Mon Sep 17 00:00:00 2001 From: Sun Feng Date: Tue, 18 Jun 2024 15:44:06 +0800 Subject: [PATCH 3/5] add auto auto_finalize flag in psbt --- token-core/tcx-btc-kin/src/psbt.rs | 66 +++++++++++++++++------ token-core/tcx-btc-kin/src/transaction.rs | 2 + token-core/tcx-proto/src/btc_kin.proto | 1 + 3 files changed, 54 insertions(+), 15 deletions(-) diff --git a/token-core/tcx-btc-kin/src/psbt.rs b/token-core/tcx-btc-kin/src/psbt.rs index ea9d08d2..aac2e2cc 100644 --- a/token-core/tcx-btc-kin/src/psbt.rs +++ b/token-core/tcx-btc-kin/src/psbt.rs @@ -28,6 +28,51 @@ pub struct PsbtSigner<'a> { sighash_cache: Box, } +pub trait PsbtInputExtra { + fn is_taproot(&self) -> bool; + + fn clear_finalized_input(&mut self); + + fn finalize(&mut self); +} + +impl PsbtInputExtra for bitcoin::psbt::Input { + fn is_taproot(&self) -> bool { + return self.tap_internal_key.is_some() + || !self.tap_key_origins.is_empty() + || self.tap_merkle_root.is_some() + || self.tap_key_sig.is_some() + || !self.tap_script_sigs.is_empty(); + } + + fn clear_finalized_input(&mut self) { + self.tap_key_sig = None; + self.tap_scripts = BTreeMap::new(); + self.tap_internal_key = None; + self.tap_merkle_root = None; + self.tap_script_sigs = BTreeMap::new(); + + self.partial_sigs = BTreeMap::new(); + self.sighash_type = None; + self.redeem_script = None; + self.witness_script = None; + self.bip32_derivation = BTreeMap::new(); + self.unknown = BTreeMap::new(); + } + + fn finalize(&mut self) { + if self.is_taproot() { + if self.tap_key_sig.is_some() { + let mut witness = Witness::new(); + witness.push(self.tap_key_sig.unwrap().to_vec()); + self.final_script_witness = Some(witness); + } + } + + self.clear_finalized_input(); + } +} + impl<'a> PsbtSigner<'a> { pub fn new(psbt: &'a mut Psbt, keystore: &'a mut Keystore, chain_type: &str) -> Self { let unsigned_tx = &psbt.unsigned_tx; @@ -222,21 +267,11 @@ pub fn sign_psbt(keystore: &mut Keystore, psbt_input: PsbtInput) -> Result Date: Wed, 19 Jun 2024 16:32:28 +0800 Subject: [PATCH 4/5] fix invalid schnorr signature --- token-core/tcx-btc-kin/src/psbt.rs | 80 ++++++++++++++--------- token-core/tcx-btc-kin/src/transaction.rs | 4 +- token-core/tcx-proto/src/btc_kin.proto | 2 +- 3 files changed, 53 insertions(+), 33 deletions(-) diff --git a/token-core/tcx-btc-kin/src/psbt.rs b/token-core/tcx-btc-kin/src/psbt.rs index aac2e2cc..cdef4df9 100644 --- a/token-core/tcx-btc-kin/src/psbt.rs +++ b/token-core/tcx-btc-kin/src/psbt.rs @@ -23,6 +23,7 @@ use tcx_primitive::{PrivateKey, Secp256k1PrivateKey, SECP256K1_ENGINE}; pub struct PsbtSigner<'a> { psbt: &'a mut Psbt, keystore: &'a mut Keystore, + derivation_path: String, prevouts: Vec, sighash_cache: Box, @@ -74,18 +75,25 @@ impl PsbtInputExtra for bitcoin::psbt::Input { } impl<'a> PsbtSigner<'a> { - pub fn new(psbt: &'a mut Psbt, keystore: &'a mut Keystore, chain_type: &str) -> Self { - let unsigned_tx = &psbt.unsigned_tx; + pub fn new( + psbt: &'a mut Psbt, + keystore: &'a mut Keystore, + chain_type: &str, + derivation_path: &str, + ) -> Self { + let unsigned_tx = psbt.unsigned_tx.clone(); let sighash_cache: Box = if chain_type == BITCOINCASH { - Box::new(BitcoinCashSighash::new(unsigned_tx.clone(), 0x40)) + Box::new(BitcoinCashSighash::new(unsigned_tx, 0x40)) } else { - Box::new(SighashCache::new(Box::new(unsigned_tx.clone()))) + Box::new(SighashCache::new(Box::new(unsigned_tx))) }; PsbtSigner { psbt, keystore, + derivation_path: derivation_path.to_string(), + sighash_cache, prevouts: Vec::new(), } @@ -121,32 +129,22 @@ impl<'a> PsbtSigner<'a> { Ok(utxos) } - fn get_private_key(&mut self, index: usize) -> Result { - let key_sources: Vec<&KeySource> = - self.psbt.inputs[index].bip32_derivation.values().collect(); - let path = if key_sources.len() > 0 { - key_sources[0].1.to_string() + fn get_private_key(&mut self) -> Result { + let path = if !self.derivation_path.is_empty() { + self.derivation_path.clone() + "/0/0" } else { "".to_string() }; - if self.keystore.derivable() { - Ok(self - .keystore - .get_private_key(CurveType::SECP256k1, &path)? - .as_secp256k1()? - .clone()) - } else { - Ok(self - .keystore - .get_private_key(CurveType::SECP256k1, &path)? - .as_secp256k1()? - .clone()) - } + Ok(self + .keystore + .get_private_key(CurveType::SECP256k1, &path)? + .as_secp256k1()? + .clone()) } fn sign_p2pkh(&mut self, index: usize) -> Result<()> { - let key = self.get_private_key(index)?; + let key = self.get_private_key()?; let prevout = &self.prevouts[index]; @@ -167,7 +165,7 @@ impl<'a> PsbtSigner<'a> { fn sign_p2sh_nested_p2wpkh(&mut self, index: usize) -> Result<()> { let prevout = &self.prevouts[index].clone(); - let key = self.get_private_key(index)?; + let key = self.get_private_key()?; let pub_key = key.public_key(); let script = Script::new_v0_p2wpkh(&WPubkeyHash::from_hash( @@ -190,7 +188,7 @@ impl<'a> PsbtSigner<'a> { } fn sign_p2wpkh(&mut self, index: usize) -> Result<()> { - let key = self.get_private_key(index)?; + let key = self.get_private_key()?; let prevout = &self.prevouts[index]; let hash = self.sighash_cache.segwit_hash( @@ -203,6 +201,7 @@ impl<'a> PsbtSigner<'a> { EcdsaSighashType::All, )?; let sig = Self::sign_ecdsa(&hash, &key)?; + self.psbt.inputs[index] .partial_sigs .insert(key.public_key().0, EcdsaSig::sighash_all(sig)); @@ -211,7 +210,7 @@ impl<'a> PsbtSigner<'a> { } fn sign_p2tr(&mut self, index: usize) -> Result<()> { - let key = self.get_private_key(index)?; + let key = self.get_private_key()?; let key_pair = bitcoin::schnorr::UntweakedKeyPair::from_seckey_slice( &SECP256K1_ENGINE, @@ -221,7 +220,7 @@ impl<'a> PsbtSigner<'a> { let hash = self.sighash_cache.taproot_hash( index, - &Prevouts::All(&self.prevouts.clone()), + &Prevouts::All(self.prevouts.as_slice()), None, None, SchnorrSighashType::Default, @@ -263,7 +262,12 @@ pub fn sign_psbt(keystore: &mut Keystore, psbt_input: PsbtInput) -> Result::from_hex(psbt_input.data)?); let mut psbt = Psbt::consensus_decode(&mut reader)?; - let mut signer = PsbtSigner::new(&mut psbt, keystore, &psbt_input.chain_type); + let mut signer = PsbtSigner::new( + &mut psbt, + keystore, + &psbt_input.chain_type, + &psbt_input.derivation_path, + ); signer.sign()?; // FINALIZER @@ -284,16 +288,30 @@ pub fn sign_psbt(keystore: &mut Keystore, psbt_input: PsbtInput) -> Result(&coin_info).unwrap(); + println!("{:?}", account); let psbt_input = PsbtInput { - data: "70736274ff0100db02000000017e4e5ccaa5a84f4e2761816d948db0530283d2ddab9e2b0bf14432247177b67c0000000000fdffffff0350c30000000000002251202f03f11af54df4be96db1c8d6ee9ab2a29558479ff93ad019d182deed8f8c33d0000000000000000496a4762627434001fa696928d908ffd29c2ab9ebf8ad48946bf9d57b64c2e4f588988c830bd2571f4940b238dcd00535fde9730345bab6ff4ea6d413cc3602c4033c10f251c7e81fa0057620000000000002251206649a3708d5510aeb8140ffb6ed5866db64b817ea62902628ad7d04730484aab080803000001012bf4260100000000002251206649a3708d5510aeb8140ffb6ed5866db64b817ea62902628ad7d04730484aab0117201fa696928d908ffd29c2ab9ebf8ad48946bf9d57b64c2e4f588988c830bd257100000000".to_string(), + data: "70736274ff0100db02000000012af69a72ee07e65a20a3a0aa89aff4f1d5601bfcca0a4f8752daf1eda2bff4390200000000fdffffff0350c30000000000002251206b4b71543b6d3e1eadb123d4e1362674312c813fd20ba5a25bfe11a29d750a2d0000000000000000496a476262743400aff94eb65a2fe773a57c5bd54e62d8436a5467573565214028422b41bd43e29b143a1961c97682d59b5f48d5b469ac74ac45273245947865e3c4ad575e4fa646fa0037e0b90000000000225120ea20ffb077323528b8345c7fa517a2ebee1b52649ee2559a6d5ea87160b50b2e080803000001012b08aaba0000000000225120ea20ffb077323528b8345c7fa517a2ebee1b52649ee2559a6d5ea87160b50b2e011720aff94eb65a2fe773a57c5bd54e62d8436a5467573565214028422b41bd43e29b00000000".to_string(), chain_type: "BITCOIN".to_string(), + derivation_path: "m/86'/1'/0'".to_string(), auto_finalize: true }; diff --git a/token-core/tcx-btc-kin/src/transaction.rs b/token-core/tcx-btc-kin/src/transaction.rs index df180c45..1bb1b775 100644 --- a/token-core/tcx-btc-kin/src/transaction.rs +++ b/token-core/tcx-btc-kin/src/transaction.rs @@ -60,7 +60,9 @@ pub struct PsbtInput { pub data: ::prost::alloc::string::String, #[prost(string, tag = "2")] pub chain_type: ::prost::alloc::string::String, - #[prost(bool, tag = "3")] + #[prost(string, tag = "3")] + pub derivation_path: ::prost::alloc::string::String, + #[prost(bool, tag = "4")] pub auto_finalize: bool, } #[allow(clippy::derive_partial_eq_without_eq)] diff --git a/token-core/tcx-proto/src/btc_kin.proto b/token-core/tcx-proto/src/btc_kin.proto index 7cfeebbd..e81e0222 100644 --- a/token-core/tcx-proto/src/btc_kin.proto +++ b/token-core/tcx-proto/src/btc_kin.proto @@ -40,7 +40,7 @@ message OmniTxInput { message PsbtInput { string data = 1; - string chain_type = 2; + string derivation_path = 2; bool auto_finalize = 3; } From b40013d40fda4eb1598461d4e384f1c285134eef Mon Sep 17 00:00:00 2001 From: Sun Feng Date: Wed, 19 Jun 2024 20:21:07 +0800 Subject: [PATCH 5/5] add sign_psbt into tcx --- token-core/tcx-btc-kin/src/lib.rs | 1 + token-core/tcx-btc-kin/src/psbt.rs | 24 ++++++++---------- token-core/tcx-btc-kin/src/transaction.rs | 6 +---- token-core/tcx-proto/src/btc_kin.proto | 1 - token-core/tcx/src/handler.rs | 30 +++++++++++++++++++++++ token-core/tcx/src/lib.rs | 3 ++- 6 files changed, 44 insertions(+), 21 deletions(-) diff --git a/token-core/tcx-btc-kin/src/lib.rs b/token-core/tcx-btc-kin/src/lib.rs index e3287fc0..3c1a0151 100644 --- a/token-core/tcx-btc-kin/src/lib.rs +++ b/token-core/tcx-btc-kin/src/lib.rs @@ -27,6 +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 transaction::{BtcKinTxInput, BtcKinTxOutput, OmniTxInput, Utxo}; pub const BITCOIN: &str = "BITCOIN"; diff --git a/token-core/tcx-btc-kin/src/psbt.rs b/token-core/tcx-btc-kin/src/psbt.rs index cdef4df9..84c32bc6 100644 --- a/token-core/tcx-btc-kin/src/psbt.rs +++ b/token-core/tcx-btc-kin/src/psbt.rs @@ -5,7 +5,6 @@ use crate::{Error, Result, BITCOINCASH}; use bitcoin::consensus::{Decodable, Encodable}; use bitcoin::psbt::{Prevouts, Psbt}; use bitcoin::schnorr::TapTweak; -use bitcoin::util::bip32::KeySource; use bitcoin::util::sighash::SighashCache; use bitcoin::{ EcdsaSig, EcdsaSighashType, SchnorrSig, SchnorrSighashType, Script, TxOut, WPubkeyHash, Witness, @@ -258,16 +257,16 @@ impl<'a> PsbtSigner<'a> { } } -pub fn sign_psbt(keystore: &mut Keystore, psbt_input: PsbtInput) -> Result { +pub fn sign_psbt( + chain_type: &str, + derivation_path: &str, + keystore: &mut Keystore, + psbt_input: PsbtInput, +) -> Result { let mut reader = Cursor::new(Vec::::from_hex(psbt_input.data)?); let mut psbt = Psbt::consensus_decode(&mut reader)?; - let mut signer = PsbtSigner::new( - &mut psbt, - keystore, - &psbt_input.chain_type, - &psbt_input.derivation_path, - ); + let mut signer = PsbtSigner::new(&mut psbt, keystore, chain_type, derivation_path); signer.sign()?; // FINALIZER @@ -295,8 +294,7 @@ mod tests { #[test] fn test_sign_psbt() { - let mut hd = - hd_keystore("bar razor crime recipe useful control duty age abuse slot enhance state"); + let mut hd = sample_hd_keystore(); let coin_info = CoinInfo { coin: "BITCOIN".to_string(), derivation_path: "m/86'/1'/0'/0/0".to_string(), @@ -309,13 +307,11 @@ mod tests { println!("{:?}", account); let psbt_input = PsbtInput { - data: "70736274ff0100db02000000012af69a72ee07e65a20a3a0aa89aff4f1d5601bfcca0a4f8752daf1eda2bff4390200000000fdffffff0350c30000000000002251206b4b71543b6d3e1eadb123d4e1362674312c813fd20ba5a25bfe11a29d750a2d0000000000000000496a476262743400aff94eb65a2fe773a57c5bd54e62d8436a5467573565214028422b41bd43e29b143a1961c97682d59b5f48d5b469ac74ac45273245947865e3c4ad575e4fa646fa0037e0b90000000000225120ea20ffb077323528b8345c7fa517a2ebee1b52649ee2559a6d5ea87160b50b2e080803000001012b08aaba0000000000225120ea20ffb077323528b8345c7fa517a2ebee1b52649ee2559a6d5ea87160b50b2e011720aff94eb65a2fe773a57c5bd54e62d8436a5467573565214028422b41bd43e29b00000000".to_string(), - chain_type: "BITCOIN".to_string(), - derivation_path: "m/86'/1'/0'".to_string(), + data: "70736274ff0100db0200000001fa4c8d58b9b6c56ed0b03f78115246c99eb70f99b837d7b4162911d1016cda340200000000fdffffff0350c30000000000002251202114eda66db694d87ff15ddd5d3c4e77306b6e6dd5720cbd90cd96e81016c2b30000000000000000496a47626274340066f873ad53d80688c7739d0d268acd956366275004fdceab9e9fc30034a4229ec20acf33c17e5a6c92cced9f1d530cccab7aa3e53400456202f02fac95e9c481fa00d47b1700000000002251208f4ca6a7384f50a1fe00cba593d5a834b480c65692a76ae6202e1ce46cb1c233d80f03000001012be3bf1d00000000002251208f4ca6a7384f50a1fe00cba593d5a834b480c65692a76ae6202e1ce46cb1c23301172066f873ad53d80688c7739d0d268acd956366275004fdceab9e9fc30034a4229e00000000".to_string(), auto_finalize: true }; - let result = super::sign_psbt(&mut hd, psbt_input).unwrap(); + let result = super::sign_psbt("BITCOIN", "m/86'/1'/0'", &mut hd, psbt_input).unwrap(); assert_eq!(result.data, "70736274ff0100db02000000017e4e5ccaa5a84f4e2761816d948db0530283d2ddab9e2b0bf14432247177b67c0000000000fdffffff0350c30000000000002251202f03f11af54df4be96db1c8d6ee9ab2a29558479ff93ad019d182deed8f8c33d0000000000000000496a4762627434001fa696928d908ffd29c2ab9ebf8ad48946bf9d57b64c2e4f588988c830bd2571f4940b238dcd00535fde9730345bab6ff4ea6d413cc3602c4033c10f251c7e81fa0057620000000000002251206649a3708d5510aeb8140ffb6ed5866db64b817ea62902628ad7d04730484aab080803000001012bf4260100000000002251206649a3708d5510aeb8140ffb6ed5866db64b817ea62902628ad7d04730484aab0117201fa696928d908ffd29c2ab9ebf8ad48946bf9d57b64c2e4f588988c830bd257100000000"); } } diff --git a/token-core/tcx-btc-kin/src/transaction.rs b/token-core/tcx-btc-kin/src/transaction.rs index 1bb1b775..7837aa5a 100644 --- a/token-core/tcx-btc-kin/src/transaction.rs +++ b/token-core/tcx-btc-kin/src/transaction.rs @@ -58,11 +58,7 @@ pub struct OmniTxInput { pub struct PsbtInput { #[prost(string, tag = "1")] pub data: ::prost::alloc::string::String, - #[prost(string, tag = "2")] - pub chain_type: ::prost::alloc::string::String, - #[prost(string, tag = "3")] - pub derivation_path: ::prost::alloc::string::String, - #[prost(bool, tag = "4")] + #[prost(bool, tag = "3")] pub auto_finalize: bool, } #[allow(clippy::derive_partial_eq_without_eq)] diff --git a/token-core/tcx-proto/src/btc_kin.proto b/token-core/tcx-proto/src/btc_kin.proto index e81e0222..9479affa 100644 --- a/token-core/tcx-proto/src/btc_kin.proto +++ b/token-core/tcx-proto/src/btc_kin.proto @@ -40,7 +40,6 @@ message OmniTxInput { message PsbtInput { string data = 1; - string derivation_path = 2; bool auto_finalize = 3; } diff --git a/token-core/tcx/src/handler.rs b/token-core/tcx/src/handler.rs index 69714d52..683322f2 100644 --- a/token-core/tcx/src/handler.rs +++ b/token-core/tcx/src/handler.rs @@ -53,6 +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_keystore::tcx_ensure; use tcx_constants::coin_info::coin_info_from_param; @@ -924,6 +925,35 @@ pub(crate) fn sign_message(data: &[u8]) -> Result> { sign_message_internal(¶m, guard.keystore_mut()) } +pub(crate) fn sign_psbt(data: &[u8]) -> Result> { + let param: SignParam = SignParam::decode(data).expect("sign_psbt 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_input = PsbtInput::decode( + param + .input + .as_ref() + .expect("psbt_input") + .value + .clone() + .as_slice(), + ) + .expect("psbt_input decode"); + + encode_message(tcx_btc_kin::sign_psbt( + ¶m.chain_type, + ¶m.path, + guard.keystore_mut(), + psbt_input, + )?) +} + 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 86aaf890..40e985c0 100644 --- a/token-core/tcx/src/lib.rs +++ b/token-core/tcx/src/lib.rs @@ -27,7 +27,7 @@ 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_tx, + mnemonic_to_public, sign_authentication_message, sign_hashes, sign_message, sign_psbt, sign_tx, unlock_then_crash, verify_password, }; use crate::migration::{migrate_keystore, scan_legacy_keystores}; @@ -130,6 +130,7 @@ pub unsafe extern "C" fn call_tcx_api(hex_str: *const c_char) -> *const c_char { "mark_identity_wallets" => { landingpad(|| mark_identity_wallets(&action.param.unwrap().value)) } + "sign_psbt" => landingpad(|| sign_psbt(&action.param.unwrap().value)), _ => landingpad(|| Err(anyhow!("unsupported_method"))), }; match reply {