Skip to content

Commit

Permalink
add bip322 message signature
Browse files Browse the repository at this point in the history
  • Loading branch information
tyrone98 committed Jun 23, 2024
1 parent ea6622d commit 43b1047
Show file tree
Hide file tree
Showing 6 changed files with 345 additions and 48 deletions.
36 changes: 35 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 Down Expand Up @@ -93,7 +101,7 @@ pub mod omni {
#[cfg(test)]
mod tests {
use tcx_common::ToHex;
use tcx_constants::{CurveType, TEST_MNEMONIC, TEST_PASSWORD};
use tcx_constants::{CurveType, TEST_MNEMONIC, TEST_PASSWORD, TEST_WIF};
use tcx_keystore::{Keystore, Metadata};
use tcx_primitive::{PrivateKey, Secp256k1PrivateKey};

Expand Down Expand Up @@ -121,4 +129,30 @@ mod tests {
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 43b1047

Please sign in to comment.