Skip to content

Commit

Permalink
feat: taproot sign script (#98)
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

---------

Co-authored-by: Sun Feng <[email protected]>
  • Loading branch information
XuNeal and tyrone98 authored Jul 11, 2024
1 parent c223227 commit 74b634b
Show file tree
Hide file tree
Showing 11 changed files with 784 additions and 108 deletions.
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion token-core/tcx-btc-kin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ tcx-common = { path = "../tcx-common" }
hex = "=0.4.3"
base64 = "=0.13.1"

bitcoin = "=0.29.2"
bitcoin = {version = "=0.29.2", features = ["serde", "std", "secp-recovery"] }
secp256k1 = {version ="=0.24.3", features = ["rand", "recovery", "rand-std"] }
tiny-bip39 = "=1.0.0"
bitcoin_hashes = "=0.11.0"
Expand Down
10 changes: 10 additions & 0 deletions token-core/tcx-btc-kin/src/bch_sighash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,4 +181,14 @@ impl TxSignatureHasher for BitcoinCashSighash {
) -> Result<TapSighashHash> {
Err(Error::UnsupportedTaproot.into())
}

fn taproot_script_spend_signature_hash(
&mut self,
input_index: usize,
prevouts: &Prevouts<TxOut>,
tap_leaf_hash: TapLeafHash,
sighash_type: SchnorrSighashType,
) -> Result<TapSighashHash> {
Err(Error::UnsupportedTaproot.into())
}
}
56 changes: 55 additions & 1 deletion token-core/tcx-btc-kin/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub mod network;
pub mod signer;
pub mod transaction;

mod message;
mod psbt;

use core::result;
Expand Down Expand Up @@ -56,6 +57,9 @@ pub enum Error {
ConstructBchAddressFailed(String),
#[error("unsupported_taproot")]
UnsupportedTaproot,

#[error("missing_signature")]
MissingSignature,
}

pub mod bitcoin {
Expand All @@ -64,6 +68,10 @@ pub mod bitcoin {
pub type Address = crate::BtcKinAddress;
pub type TransactionInput = crate::transaction::BtcKinTxInput;
pub type TransactionOutput = crate::transaction::BtcKinTxOutput;

pub type MessageInput = crate::transaction::BtcMessageInput;

pub type MessageOutput = crate::transaction::BtcMessageOutput;
}

pub mod bitcoincash {
Expand All @@ -76,6 +84,10 @@ pub mod bitcoincash {
pub type TransactionInput = crate::transaction::BtcKinTxInput;

pub type TransactionOutput = crate::transaction::BtcKinTxOutput;

pub type MessageInput = crate::transaction::BtcMessageInput;

pub type MessageOutput = crate::transaction::BtcMessageOutput;
}

pub mod omni {
Expand All @@ -92,8 +104,10 @@ pub mod omni {

#[cfg(test)]
mod tests {
use tcx_constants::{TEST_MNEMONIC, TEST_PASSWORD};
use tcx_common::ToHex;
use tcx_constants::{CurveType, TEST_MNEMONIC, TEST_PASSWORD, TEST_WIF};
use tcx_keystore::{Keystore, Metadata};
use tcx_primitive::{PrivateKey, Secp256k1PrivateKey};

pub fn hd_keystore(mnemonic: &str) -> Keystore {
let mut keystore =
Expand All @@ -102,7 +116,47 @@ mod tests {
keystore
}

pub fn private_keystore(wif: &str) -> Keystore {
let sec_key = Secp256k1PrivateKey::from_wif(wif).unwrap();
let mut keystore = Keystore::from_private_key(
&sec_key.to_bytes().to_hex(),
TEST_PASSWORD,
CurveType::SECP256k1,
Metadata::default(),
None,
)
.unwrap();
keystore.unlock_by_password(TEST_PASSWORD).unwrap();
keystore
}

pub fn sample_hd_keystore() -> Keystore {
hd_keystore(TEST_MNEMONIC)
}

pub fn hex_keystore(hex: &str) -> Keystore {
let mut keystore = Keystore::from_private_key(
hex,
TEST_PASSWORD,
CurveType::SECP256k1,
Metadata::default(),
None,
)
.unwrap();
keystore.unlock_by_password(TEST_PASSWORD).unwrap();
keystore
}

pub fn wif_keystore(wif: &str) -> Keystore {
let hex = Secp256k1PrivateKey::from_wif(wif)
.unwrap()
.to_bytes()
.to_hex();

hex_keystore(&hex)
}

pub fn sample_wif_keystore() -> Keystore {
wif_keystore(TEST_WIF)
}
}
235 changes: 235 additions & 0 deletions token-core/tcx-btc-kin/src/message.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
use crate::psbt::PsbtSigner;
use crate::transaction::{BtcMessageInput, BtcMessageOutput};
use crate::{BtcKinAddress, Error, Result};
use bitcoin::psbt::PartiallySignedTransaction;
use bitcoin::{
OutPoint, PackedLockTime, Script, Sequence, Transaction, TxIn, TxOut, Txid, Witness,
};
use tcx_common::{sha256, utf8_or_hex_to_bytes, FromHex, ToHex};
use tcx_constants::{CoinInfo, CurveType};
use tcx_keystore::{Address, Keystore, MessageSigner, SignatureParameters};

const UTXO: &str = "0000000000000000000000000000000000000000000000000000000000000000";
const TAG: &str = "BIP0322-signed-message";
fn get_spend_tx_id(data: &[u8], script_pub_key: Script) -> Result<Txid> {
let tag_hash = sha256(&TAG.as_bytes().to_vec());
let mut to_sign = Vec::new();
to_sign.extend(tag_hash.clone());
to_sign.extend(tag_hash);
to_sign.extend(data);

let hash = sha256(&to_sign);
let mut script_sig = Vec::new();
script_sig.extend([0x00, 0x20]);
script_sig.extend(hash);

//Tx ins
let ins = vec![TxIn {
previous_output: OutPoint {
txid: UTXO.parse()?,
vout: 0xFFFFFFFF,
},
script_sig: Script::from(script_sig),
sequence: Sequence(0),
witness: Witness::new(),
}];

//Tx outs
let outs = vec![TxOut {
value: 0,
script_pubkey: script_pub_key,
}];

let tx = Transaction {
version: 0,
lock_time: PackedLockTime::ZERO,
input: ins,
output: outs,
};

Ok(tx.txid())
}

fn create_to_sign_empty(txid: Txid, script_pub_key: Script) -> Result<PartiallySignedTransaction> {
//Tx ins
let ins = vec![TxIn {
previous_output: OutPoint { txid, vout: 0 },
script_sig: Script::new(),
sequence: Sequence(0),
witness: Witness::new(),
}];

//Tx outs
let outs = vec![TxOut {
value: 0,
script_pubkey: Script::from(Vec::<u8>::from_hex("6a")?),
}];

let tx = Transaction {
version: 0,
lock_time: PackedLockTime::ZERO,
input: ins,
output: outs,
};

let mut psbt = PartiallySignedTransaction::from_unsigned_tx(tx)?;
psbt.inputs[0].witness_utxo = Some(TxOut {
value: 0,
script_pubkey: script_pub_key,
});

Ok(psbt)
}

fn witness_to_vec(witness: Vec<Vec<u8>>) -> Vec<u8> {
let mut ret: Vec<u8> = Vec::new();
ret.push(witness.len() as u8);
for item in witness {
ret.push(item.len() as u8);
ret.extend(item);
}
ret
}

impl MessageSigner<BtcMessageInput, BtcMessageOutput> for Keystore {
fn sign_message(
&mut self,
params: &SignatureParameters,
message_input: &BtcMessageInput,
) -> tcx_keystore::Result<BtcMessageOutput> {
let data = utf8_or_hex_to_bytes(&message_input.message)?;
let path = format!("{}/0/0", params.derivation_path);

let public_key = self.get_public_key(CurveType::SECP256k1, &path)?;
let coin_info = CoinInfo {
coin: params.chain_type.to_string(),
derivation_path: path.clone(),
curve: CurveType::SECP256k1,
network: params.network.to_string(),
seg_wit: params.seg_wit.to_string(),
};

let address = BtcKinAddress::from_public_key(&public_key, &coin_info)?;

let tx_id = get_spend_tx_id(&data, address.script_pubkey())?;
let mut psbt = create_to_sign_empty(tx_id, address.script_pubkey())?;
let mut psbt_signer = PsbtSigner::new(
&mut psbt,
self,
&params.chain_type,
&params.derivation_path,
true,
);

psbt_signer.sign()?;

if let Some(witness) = &psbt.inputs[0].final_script_witness {
Ok(BtcMessageOutput {
signature: witness_to_vec(witness.to_vec()).to_hex(),
})
} else {
Err(Error::MissingSignature.into())
}
}
}

#[cfg(test)]
mod tests {
use crate::tests::{sample_hd_keystore, wif_keystore};
use crate::BtcKinAddress;
use tcx_constants::{CoinInfo, CurveType};
use tcx_keystore::{Address, MessageSigner};

#[test]
fn test_to_spend_tx_id() {
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: "VERSION_0".to_string(),
};

let account = ks.derive_coin::<BtcKinAddress>(&coin_info).unwrap();
let address = BtcKinAddress::from_public_key(&account.public_key, &coin_info).unwrap();

assert_eq!(
super::get_spend_tx_id(message.as_bytes(), address.script_pubkey())
.unwrap()
.to_string(),
"24bca2df5140bcf6a6aeafd141ad40b0595aa6998ca0fc733488d7131ca7763f"
);
}

#[test]
fn test_bip322_segwit() {
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: "VERSION_0".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: "VERSION_0".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, "024830450221009f003820d1db93bf78be08dafdd05b7dde7c31a73c9be36b705a15329bd3d0e502203eb6f1a34466995e4b9c281bf4a093a1f55a21b2ef961438c9ae284efab27dda0121026b5b6a9d041bc5187e0b34f9e496436c7bff261c6c1b5f3c06b433c61394b868");
}

#[test]
fn test_bip322_taproot() {
let message = "Sign this message to log in to https://www.subber.xyz // 200323342";
let mut ks = wif_keystore("L4F5BYm82Bck6VEY64EbqQkoBXqkegq9X9yc6iLTV3cyJoqUasnY");
let coin_info = CoinInfo {
coin: "BITCOIN".to_string(),
derivation_path: "m/86'/0'/0'/0/0".to_string(),
curve: CurveType::SECP256k1,
network: "MAINNET".to_string(),
seg_wit: "VERSION_1".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: "VERSION_1".to_string(),
derivation_path: "m/86'/0'/0'".to_string(),
};

let output = ks
.sign_message(
&params,
&super::BtcMessageInput {
message: message.to_string(),
},
)
.unwrap();

// assert_eq!(output.signature, "0140717dbc46e9d816d7c9e26b5a5f6153c1fceb734489afaaee4ed80bc7c119a39af44de7f6d66c30e530c7c696a25d45bab052cc55012fc57ef6cb24313b31014b");
}
}
Loading

0 comments on commit 74b634b

Please sign in to comment.