Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/add sign psbts and bip322 #109

Merged
merged 13 commits into from
Jul 24, 2024
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
Loading