Skip to content

Commit

Permalink
chore: move away from miniscript
Browse files Browse the repository at this point in the history
Signed-off-by: Jose Storopoli <[email protected]>
  • Loading branch information
storopoli committed Dec 5, 2024
1 parent 3c0c999 commit b2a319c
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 33 deletions.
3 changes: 2 additions & 1 deletion Cargo.lock

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

7 changes: 5 additions & 2 deletions crates/key-derivation/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
165 changes: 135 additions & 30 deletions crates/key-derivation/src/operator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
//!
Expand All @@ -30,9 +30,7 @@
use bitcoin::{
bip32::{ChildNumber, DerivationPath, Xpriv, Xpub},
secp256k1::SECP256K1,
XOnlyPublicKey,
};
use miniscript::{descriptor::Tr, Descriptor};

use crate::error::KeyError;

Expand Down Expand Up @@ -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)
Expand All @@ -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<XOnlyPublicKey> {
// use the `Tr` type to create a Taproot descriptor
todo!()
}

/// Operator's wallet descriptor.
pub fn wallet_descriptor(&self) -> Descriptor<XOnlyPublicKey> {
// use the `Tr` type to create a Taproot descriptor
todo!()
}

/// Operator's message descriptor.
pub fn message_descriptor(&self) -> Descriptor<XOnlyPublicKey> {
// 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<Xpriv> = 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::<Address<_>>().unwrap().assume_checked();
let dest_address = SENT_ADDRESS.parse::<Address<_>>().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();
}
}

0 comments on commit b2a319c

Please sign in to comment.