From b2a319cf13bab22e7428c03765b3246ef711bb23 Mon Sep 17 00:00:00 2001 From: Jose Storopoli Date: Thu, 5 Dec 2024 16:52:26 -0300 Subject: [PATCH] chore: move away from miniscript Signed-off-by: Jose Storopoli --- Cargo.lock | 3 +- crates/key-derivation/Cargo.toml | 7 +- crates/key-derivation/src/operator.rs | 165 +++++++++++++++++++++----- 3 files changed, 142 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ed7cf19ec..3ec3560bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13146,7 +13146,8 @@ name = "strata-key-derivation" version = "0.1.0" dependencies = [ "bitcoin", - "miniscript", + "corepc-node", + "secp256k1", "thiserror", ] diff --git a/crates/key-derivation/Cargo.toml b/crates/key-derivation/Cargo.toml index 840c248ce..6fc2e3c24 100644 --- a/crates/key-derivation/Cargo.toml +++ b/crates/key-derivation/Cargo.toml @@ -4,6 +4,9 @@ version = "0.1.0" edition = "2021" [dependencies] -bitcoin.workspace = true -miniscript.workspace = true +bitcoin = { workspace = true } +secp256k1 = { workspace = true, features = ["global-context"] } thiserror.workspace = true + +[dev-dependencies] +corepc-node = { version = "0.4.0", features = ["28_0"] } diff --git a/crates/key-derivation/src/operator.rs b/crates/key-derivation/src/operator.rs index 65333e878..61b8c059d 100644 --- a/crates/key-derivation/src/operator.rs +++ b/crates/key-derivation/src/operator.rs @@ -3,11 +3,11 @@ //! Bridge operators guarantee the security assumptions of the Strata BitVM-based //! bridge by enforcing that all peg-ins and peg-outs are valid. //! -//! Operators are responsible for their own keys and master [`Xpriv`](bitcoin::bip32::Xpriv) is not +//! Operators are responsible for their own keys and master [`Xpriv`] is not //! shared between operators. Hence, this crate has a BYOK (Bring Your Own Key) design. //! //! They use a set of keys to sign messages and bitcoin transactions. -//! The keys are derived from a master [`Xpriv`](bitcoin::bip32::Xpriv) +//! The keys are derived from a master [`Xpriv`] //! using a [BIP-32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) //! HD derivation path. //! @@ -30,9 +30,7 @@ use bitcoin::{ bip32::{ChildNumber, DerivationPath, Xpriv, Xpub}, secp256k1::SECP256K1, - XOnlyPublicKey, }; -use miniscript::{descriptor::Tr, Descriptor}; use crate::error::KeyError; @@ -87,6 +85,21 @@ impl OperatorKeys { }) } + /// Operator's master [`Xpriv`]. + pub fn master_xpriv(&self) -> Xpriv { + self.master + } + + /// Operator's wallet transaction signing [`Xpriv`]. + pub fn wallet_xpriv(&self) -> Xpriv { + self.wallet + } + + /// Operator's message signing [`Xpriv`]. + pub fn message_xpriv(&self) -> Xpriv { + self.signing + } + /// Operator's master [`Xpub`]. pub fn master_xpub(&self) -> Xpub { Xpub::from_priv(SECP256K1, &self.master) @@ -107,39 +120,131 @@ impl OperatorKeys { pub fn wallet_xpub(&self) -> Xpub { Xpub::from_priv(SECP256K1, &self.wallet) } - - /// Operator's master descriptor. - pub fn master_descriptor(&self) -> Descriptor { - // use the `Tr` type to create a Taproot descriptor - todo!() - } - - /// Operator's wallet descriptor. - pub fn wallet_descriptor(&self) -> Descriptor { - // use the `Tr` type to create a Taproot descriptor - todo!() - } - - /// Operator's message descriptor. - pub fn message_descriptor(&self) -> Descriptor { - // use the `Tr` type to create a Taproot descriptor - todo!() - } } #[cfg(test)] mod tests { + use std::{collections::BTreeMap, sync::LazyLock}; + + use bitcoin::{ + absolute, consensus, psbt::Input, transaction::Version, Address, Amount, BlockHash, + OutPoint, Psbt, Sequence, TapSighashType, Transaction, TxIn, TxOut, Witness, + }; + use super::*; - #[test] - fn test_operator_keys() { - // get nice xprivs from rust-bitcoin tests cases - todo!() - } + // What's better than bacon? bacon^24 of course + // Thix xpriv was generated by the bacon^24 mnemonic + // Don't use this in production! + const XPRIV_STR: &str = "tprv8ZgxMBicQKsPeh9dSitM82FU7Fz3ZgPkKmmovAr2aqwauAMVgjcEkZBb2etBtRPZ8XYVm7shxcKwVaDus7T5kauJXVsqAfzM4Tty13rRjAG"; + static XPRIV: LazyLock = LazyLock::new(|| XPRIV_STR.parse().unwrap()); + + // The first address derived from the xpriv above using a `tr()` descriptor. + const ADDRESS: &str = "bcrt1pzlfjwpazrmu4y0cud40370mykgwcv7efaphvthutwlu2ysqrzexqf8x2p7"; + + // The second address derived from the xpriv above using a `tr()` descriptor. + const SENT_ADDRESS: &str = "bcrt1ptnumm8l25dl65gnp2kn563yn9z53nunt4puqang2yqe7quus65pshn4a7s"; #[test] - fn test_operator_descriptors() { - // get nice xprivs from rust-bitcoin tests cases - todo!() + fn test_operator_keys() { + // Parse stuff + let address = ADDRESS.parse::>().unwrap().assume_checked(); + let dest_address = SENT_ADDRESS.parse::>().unwrap().assume_checked(); + + // Start a bitcoind node + let bitcoind = corepc_node::BitcoinD::from_downloaded().unwrap(); + + // Mine some blocks + let blocks = bitcoind + .client + .generate_to_address(101, &address) + .unwrap() + .0; + assert_eq!(blocks.len(), 101); + + // Mine more blocks + let _ = bitcoind + .client + .generate_to_address(1, &dest_address) + .unwrap() + .0; + + // Create the operator keys + let operator_keys = OperatorKeys::new(&XPRIV).unwrap(); + let wallet_key = operator_keys.wallet_xpriv(); + let wallet_pubkey = operator_keys.wallet_xpub(); + let wallet_fingerprint = wallet_pubkey.fingerprint(); + let derivation_path = DerivationPath::master(); + let (x_only_pubkey, _) = wallet_pubkey.public_key.x_only_public_key(); + + // Get the coinbase of the last mined block. + let block_hash: BlockHash = blocks.first().unwrap().parse().unwrap(); + let block = bitcoind.client.get_block(block_hash).unwrap(); + let coinbase_tx = block.txdata.last().unwrap(); + + // Create a transaction with a single input and output. + let txid = coinbase_tx.compute_txid(); + let outpoint = OutPoint::new(txid, 0); + let txin = TxIn { + previous_output: outpoint, + sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, + ..Default::default() + }; + let txout = TxOut { + value: Amount::from_btc(49.99).unwrap(), + script_pubkey: dest_address.script_pubkey(), + }; + let previous_txout = TxOut { + value: Amount::from_btc(50.0).unwrap(), + script_pubkey: address.script_pubkey(), + }; + + // Create the unsigned transaction + let transaction = Transaction { + version: Version::TWO, + lock_time: absolute::LockTime::ZERO, + input: vec![txin], + output: vec![txout], + }; + + // Create the PSBT + let mut psbt = Psbt::from_unsigned_tx(transaction).expect("could not create PSBT"); + let ty = TapSighashType::All.into(); + let origins = BTreeMap::from([( + x_only_pubkey, + (vec![], (wallet_fingerprint, derivation_path)), + )]); + + // Add the input to the PSBT + psbt.inputs = vec![Input { + witness_utxo: Some(previous_txout), + tap_key_origins: origins, + tap_internal_key: Some(x_only_pubkey), + sighash_type: Some(ty), + ..Default::default() + }]; + + // Sign the PSBT + psbt.sign(&wallet_key, SECP256K1) + .expect("could not sign PSBT"); + + // Finalize the PSBT + psbt.inputs[0].final_script_witness = Some(Witness::p2tr_key_spend( + &psbt.inputs[0].tap_key_sig.unwrap(), + )); + // Clear all the data fields as per the spec. + psbt.inputs[0].partial_sigs = BTreeMap::new(); + psbt.inputs[0].sighash_type = None; + psbt.inputs[0].redeem_script = None; + psbt.inputs[0].witness_script = None; + psbt.inputs[0].bip32_derivation = BTreeMap::new(); + + // Extract the transaction and serialize it + let signed_tx = psbt.extract_tx().expect("valid transaction"); + let serialized_signed_tx = consensus::encode::serialize_hex(&signed_tx); + println!("serialized_signed_tx: {}", serialized_signed_tx); + + // Broadcast the transaction + bitcoind.client.send_raw_transaction(&signed_tx).unwrap(); } }