Skip to content

Commit

Permalink
Feat/add sign psbts and bip322 (#109)
Browse files Browse the repository at this point in the history
* Add PsbtSigner

* finish taproot sign in psbt

* feat: add taproot sign script

fix: rebase issue

fix: append script and control block to witness

* fix: sign tap script no need tweak privatekey

Fix after rebase

* fix: merge missing code

* chore: remove println hash

* add bip322 message signature

* add multi address type in bitcoin psbt

* add tests for bitcoin bip322 sign

* add legacy address sign bip322

* fix conflict in VERSION

---------

Co-authored-by: XuNeal <[email protected]>
  • Loading branch information
tyrone98 and XuNeal authored Jul 24, 2024
1 parent 73e61bd commit 9681831
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 22 deletions.
2 changes: 1 addition & 1 deletion token-core/tcx-btc-kin/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pub type Result<T> = result::Result<T, anyhow::Error>;
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";
Expand Down
83 changes: 80 additions & 3 deletions token-core/tcx-btc-kin/src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,13 @@ impl MessageSigner<BtcMessageInput, BtcMessageOutput> 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())
}
}
}
}
Expand Down Expand Up @@ -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::<BtcKinAddress>(&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(
&params,
&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::<BtcKinAddress>(&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(
&params,
&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 {
Expand Down Expand Up @@ -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 {
Expand Down
43 changes: 32 additions & 11 deletions token-core/tcx-btc-kin/src/psbt.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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();
Expand All @@ -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();
Expand Down Expand Up @@ -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();
Expand All @@ -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;
Expand Down Expand Up @@ -376,7 +376,7 @@ pub fn sign_psbt(
keystore: &mut Keystore,
psbt_input: PsbtInput,
) -> Result<PsbtOutput> {
let mut reader = Cursor::new(Vec::<u8>::from_hex(psbt_input.data)?);
let mut reader = Cursor::new(Vec::<u8>::from_hex(psbt_input.psbt)?);
let mut psbt = Psbt::consensus_decode(&mut reader)?;

let mut signer = PsbtSigner::new(
Expand All @@ -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<PsbtsOutput> {
let mut outputs = Vec::<String>::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)]
Expand Down Expand Up @@ -429,12 +450,12 @@ mod tests {
let account = hd.derive_coin::<BtcKinAddress>(&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::<u8>::from_hex(psbt_output.data).unwrap());
let mut reader = Cursor::new(Vec::<u8>::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();
Expand Down Expand Up @@ -468,12 +489,12 @@ mod tests {
let account = hd.derive_coin::<BtcKinAddress>(&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::<u8>::from_hex(psbt_output.data).unwrap());
let mut reader = Cursor::new(Vec::<u8>::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();
Expand Down
18 changes: 16 additions & 2 deletions token-core/tcx-btc-kin/src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,29 @@ 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,
}
#[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,
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)]
Expand Down
14 changes: 12 additions & 2 deletions token-core/tcx-proto/src/btc_kin.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -54,3 +63,4 @@ message BtcMessageInput {
message BtcMessageOutput {
string signature = 1;
}

31 changes: 30 additions & 1 deletion token-core/tcx/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -960,6 +960,35 @@ pub(crate) fn sign_psbt(data: &[u8]) -> Result<Vec<u8>> {
)?)
}

pub(crate) fn sign_psbts(data: &[u8]) -> Result<Vec<u8>> {
let param: SignParam = SignParam::decode(data).expect("sign_psbts param");

let mut map = KEYSTORE_MAP.write();
let keystore: &mut Keystore = match map.get_mut(&param.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(
&param.chain_type,
&param.path,
guard.keystore_mut(),
psbt_inputs,
)?)
}

pub fn get_derived_key(data: &[u8]) -> Result<Vec<u8>> {
let param: WalletKeyParam = WalletKeyParam::decode(data).expect("get_derived_key param");
let mut map: parking_lot::lock_api::RwLockWriteGuard<
Expand Down
5 changes: 3 additions & 2 deletions token-core/tcx/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit 9681831

Please sign in to comment.