mirror of
https://github.com/RoboSats/taptrade-core.git
synced 2025-07-20 09:43:30 +00:00
finish escrow output assembly
This commit is contained in:
@ -1,14 +1,13 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use axum::routing::trace;
|
use axum::routing::trace;
|
||||||
use bdk::{
|
use bdk::{
|
||||||
bitcoin::psbt::PartiallySignedTransaction,
|
bitcoin::{psbt::PartiallySignedTransaction, PublicKey},
|
||||||
descriptor::Descriptor,
|
descriptor::{policy, Descriptor},
|
||||||
miniscript::{descriptor::TapTree, policy::Concrete, Tap},
|
miniscript::{descriptor::TapTree, policy::Concrete, Miniscript, Tap, ToPublicKey},
|
||||||
SignOptions,
|
SignOptions,
|
||||||
};
|
};
|
||||||
use bitcoin::PublicKey;
|
|
||||||
use musig2::{secp256k1::PublicKey as MuSig2PubKey, KeyAggContext};
|
use musig2::{secp256k1::PublicKey as MuSig2PubKey, KeyAggContext};
|
||||||
use sha2::digest::typenum::bit;
|
use sha2::digest::typenum::{bit, Xor};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct EscrowPsbtConstructionData {
|
pub struct EscrowPsbtConstructionData {
|
||||||
@ -39,7 +38,7 @@ impl EscrowPsbtConstructionData {
|
|||||||
pub fn aggregate_musig_pubkeys(
|
pub fn aggregate_musig_pubkeys(
|
||||||
maker_musig_pubkey: &str,
|
maker_musig_pubkey: &str,
|
||||||
taker_musig_pubkey: &str,
|
taker_musig_pubkey: &str,
|
||||||
) -> Result<bdk::bitcoin::PublicKey> {
|
) -> Result<XOnlyPublicKey> {
|
||||||
debug!(
|
debug!(
|
||||||
"Aggregating musig pubkeys: {} and {}",
|
"Aggregating musig pubkeys: {} and {}",
|
||||||
maker_musig_pubkey, taker_musig_pubkey
|
maker_musig_pubkey, taker_musig_pubkey
|
||||||
@ -52,77 +51,74 @@ pub fn aggregate_musig_pubkeys(
|
|||||||
let key_agg_ctx = KeyAggContext::new(pubkeys).context("Error aggregating musig pubkeys")?;
|
let key_agg_ctx = KeyAggContext::new(pubkeys).context("Error aggregating musig pubkeys")?;
|
||||||
let agg_pk: MuSig2PubKey = key_agg_ctx.aggregated_pubkey();
|
let agg_pk: MuSig2PubKey = key_agg_ctx.aggregated_pubkey();
|
||||||
let bitcoin_pk = bdk::bitcoin::PublicKey::from_slice(&agg_pk.serialize())
|
let bitcoin_pk = bdk::bitcoin::PublicKey::from_slice(&agg_pk.serialize())
|
||||||
.context("Error converting musig pk to bitcoin pk")?;
|
.context("Error converting musig pk to bitcoin pk")?
|
||||||
|
.to_x_only_pubkey();
|
||||||
Ok(bitcoin_pk)
|
Ok(bitcoin_pk)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// this function builds the escrow output with all possible spending conditions
|
||||||
pub fn build_escrow_transaction_output_descriptor(
|
pub fn build_escrow_transaction_output_descriptor(
|
||||||
maker_escrow_data: &EscrowPsbtConstructionData,
|
maker_escrow_data: &EscrowPsbtConstructionData,
|
||||||
taker_escrow_data: &EscrowPsbtConstructionData,
|
taker_escrow_data: &EscrowPsbtConstructionData,
|
||||||
coordinator_pk: &XOnlyPublicKey,
|
coordinator_pk: &XOnlyPublicKey,
|
||||||
) -> Result<String> {
|
) -> Result<Descriptor<XOnlyPublicKey>> {
|
||||||
let maker_pk = maker_escrow_data.taproot_xonly_pubkey_hex.clone();
|
let maker_pk = maker_escrow_data.taproot_xonly_pubkey_hex.clone();
|
||||||
let taker_pk = taker_escrow_data.taproot_xonly_pubkey_hex.clone();
|
let taker_pk = taker_escrow_data.taproot_xonly_pubkey_hex.clone();
|
||||||
let coordinator_pk = hex::encode(coordinator_pk.serialize());
|
let coordinator_pk = hex::encode(coordinator_pk.serialize());
|
||||||
|
|
||||||
// let script_a = format!("and(and(after({}),{}),{})", "144", maker_pk, coordinator_pk);
|
let policy_a_string = format!("and(pk({}),pk({}))", maker_pk, coordinator_pk);
|
||||||
// let script_b = format!(
|
let policy_b_string = format!("and(pk({}),pk({}))", taker_pk, coordinator_pk);
|
||||||
// "and_v(v:{},and_v(v:{},{}))",
|
let policy_c_string = format!("and(pk({}),after(12228))", maker_pk);
|
||||||
// maker_pk, taker_pk, coordinator_pk
|
let policy_d_string = format!("and(and(pk({}),pk({})),after(2048))", maker_pk, taker_pk);
|
||||||
// );
|
|
||||||
let script_c: String = format!("and(pk({}),pk({}))", maker_pk, coordinator_pk);
|
|
||||||
let script_d = format!("and(pk({}),pk({}))", taker_pk, coordinator_pk);
|
|
||||||
let script_e = format!("and(pk({}),after(12228))", maker_pk);
|
|
||||||
let script_f = format!("and(and(pk({}),pk({})),after(2048))", maker_pk, taker_pk);
|
|
||||||
|
|
||||||
// let compiled_a = Concrete::<String>::from_str(&script_a)?.compile::<Tap>()?;
|
// parse the policy strings into policy objects
|
||||||
// let compiled_b = Concrete::<String>::from_str(&script_b)?.compile()?;
|
let policy_a = Concrete::<XOnlyPublicKey>::from_str(&policy_a_string)
|
||||||
let compiled_c = Concrete::<String>::from_str(&script_c)
|
.context("Failed to parse policy string a")?;
|
||||||
.context("Failed to parse script_c")?
|
let policy_b = Concrete::<XOnlyPublicKey>::from_str(&policy_b_string)
|
||||||
|
.context("Failed to parse policy string b")?;
|
||||||
|
let policy_c = Concrete::<XOnlyPublicKey>::from_str(&policy_c_string)
|
||||||
|
.context("Failed to parse policy string c")?;
|
||||||
|
let policy_d = Concrete::<XOnlyPublicKey>::from_str(&policy_d_string)
|
||||||
|
.context("Failed to parse policy string d")?;
|
||||||
|
|
||||||
|
// Compile the policies into Miniscript
|
||||||
|
let miniscript_a = policy_a
|
||||||
.compile::<Tap>()
|
.compile::<Tap>()
|
||||||
.context("Failed to compile script_c")?;
|
.context("Failed to compile miniscript a")?;
|
||||||
let compiled_d = Concrete::<String>::from_str(&script_d)
|
let miniscript_b = policy_b
|
||||||
.context("Failed to parse script_d")?
|
|
||||||
.compile::<Tap>()
|
.compile::<Tap>()
|
||||||
.context("Failed to compile script_d")?;
|
.context("Failed to compile miniscript b")?;
|
||||||
let compiled_e = Concrete::<String>::from_str(&script_e)
|
let miniscript_c = policy_c
|
||||||
.context("Failed to parse script_e")?
|
|
||||||
.compile::<Tap>()
|
.compile::<Tap>()
|
||||||
.context("Failed to compile script_e")?;
|
.context("Failed to compile miniscript c")?;
|
||||||
let compiled_f = Concrete::<String>::from_str(&script_f)
|
let miniscript_d = policy_d
|
||||||
.context("Failed to parse script_f")?
|
|
||||||
.compile::<Tap>()
|
.compile::<Tap>()
|
||||||
.context("Failed to compile script_f")?;
|
.context("Failed to compile miniscript d")?;
|
||||||
|
|
||||||
// Create TapTree leaves
|
// Create TapTree leaves
|
||||||
// let tap_leaf_a = TapTree::Leaf(Arc::new(compiled_a));
|
let tap_leaf_a = bdk::miniscript::descriptor::TapTree::Leaf(Arc::new(miniscript_a));
|
||||||
// let tap_leaf_b = TapTree::Leaf(Arc::new(compiled_b));
|
let tap_leaf_b = bdk::miniscript::descriptor::TapTree::Leaf(Arc::new(miniscript_b));
|
||||||
let tap_leaf_c = TapTree::Leaf(Arc::new(compiled_c));
|
let tap_leaf_c = bdk::miniscript::descriptor::TapTree::Leaf(Arc::new(miniscript_c));
|
||||||
let tap_leaf_d = TapTree::Leaf(Arc::new(compiled_d));
|
let tap_leaf_d = bdk::miniscript::descriptor::TapTree::Leaf(Arc::new(miniscript_d));
|
||||||
let tap_leaf_e = TapTree::Leaf(Arc::new(compiled_e));
|
|
||||||
let tap_leaf_f = TapTree::Leaf(Arc::new(compiled_f));
|
|
||||||
|
|
||||||
let tap_node_cd = TapTree::Tree(Arc::new(tap_leaf_c), Arc::new(tap_leaf_d));
|
let tap_node_ab = TapTree::Tree(Arc::new(tap_leaf_a), Arc::new(tap_leaf_b));
|
||||||
let tap_node_ef = TapTree::Tree(Arc::new(tap_leaf_e), Arc::new(tap_leaf_f));
|
let tap_node_cd = TapTree::Tree(Arc::new(tap_leaf_d), Arc::new(tap_leaf_c));
|
||||||
|
|
||||||
// Create the TapTree (example combining leaves, adjust as necessary), will be used for Script Path Spending (Alternative Spending Paths) in the descriptor
|
// Create the TapTree (example combining leaves, adjust as necessary), will be used for Script Path Spending (Alternative Spending Paths) in the descriptor
|
||||||
let final_tap_tree =
|
let tap_root = TapTree::Tree(Arc::new(tap_node_ab), Arc::new(tap_node_cd));
|
||||||
TapTree::<bdk::bitcoin::PublicKey>::Tree(Arc::new(tap_node_cd), Arc::new(tap_node_ef));
|
|
||||||
|
|
||||||
// An internal key, that defines the way to spend the transaction directly, using Key Path Spending
|
// An internal key, that defines the way to spend the transaction directly, using Key Path Spending
|
||||||
let internal_agg_musig_key: bdk::bitcoin::PublicKey = aggregate_musig_pubkeys(
|
let internal_agg_musig_key: XOnlyPublicKey = aggregate_musig_pubkeys(
|
||||||
&maker_escrow_data.musig_pubkey_compressed_hex,
|
&maker_escrow_data.musig_pubkey_compressed_hex,
|
||||||
&taker_escrow_data.musig_pubkey_compressed_hex,
|
&taker_escrow_data.musig_pubkey_compressed_hex,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Create the descriptor
|
// Create the descriptor
|
||||||
let descriptor =
|
let descriptor = Descriptor::<XOnlyPublicKey>::new_tr(internal_agg_musig_key, Some(tap_root))
|
||||||
Descriptor::<bdk::bitcoin::PublicKey>::new_tr(internal_agg_musig_key, Some(final_tap_tree))
|
.context("Error assembling escrow output descriptor")?;
|
||||||
.context("Error assembling escrow output descriptor")?;
|
|
||||||
descriptor.sanity_check()?;
|
descriptor.sanity_check()?;
|
||||||
// https://docs.rs/miniscript/latest/miniscript/
|
|
||||||
// debug!("Escrow descriptor: {}", descriptor.address(network));
|
// debug!("Escrow descriptor: {}", descriptor.address(network));
|
||||||
Ok(descriptor.to_string())
|
Ok(descriptor) // then spend to descriptor.address(Network::Regtest)
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub fn assemble_escrow_psbts(
|
// pub fn assemble_escrow_psbts(
|
||||||
@ -162,19 +158,20 @@ impl<D: bdk::database::BatchDatabase> CoordinatorWallet<D> {
|
|||||||
|
|
||||||
let (escrow_psbt, details) = {
|
let (escrow_psbt, details) = {
|
||||||
// maybe we can generate a address/taproot pk directly from the descriptor without a new wallet?
|
// maybe we can generate a address/taproot pk directly from the descriptor without a new wallet?
|
||||||
let temp_wallet = Wallet::new(
|
// let temp_wallet = Wallet::new(
|
||||||
&escrow_output_descriptor,
|
// &escrow_output_descriptor,
|
||||||
None,
|
// None,
|
||||||
bitcoin::Network::Regtest,
|
// bitcoin::Network::Regtest,
|
||||||
MemoryDatabase::new(),
|
// MemoryDatabase::new(),
|
||||||
)?;
|
// )?;
|
||||||
// let escrow_address = temp_wallet
|
let escrow_address =
|
||||||
// .get_address(bdk::wallet::AddressIndex::New)?
|
escrow_output_descriptor.address(bdk::bitcoin::Network::Regtest)?;
|
||||||
// .address;
|
|
||||||
|
debug!("Created escrow address: {escrow_address}");
|
||||||
|
|
||||||
// dummy escrow address for testing the psbt signing flow
|
// dummy escrow address for testing the psbt signing flow
|
||||||
let escrow_address =
|
// let escrow_address =
|
||||||
Address::from_str(self.get_new_address().await?.as_str())?.assume_checked();
|
// Address::from_str(self.get_new_address().await?.as_str())?.assume_checked();
|
||||||
|
|
||||||
// using absolute fee for now, in production we should come up with a way to determine the tx weight
|
// using absolute fee for now, in production we should come up with a way to determine the tx weight
|
||||||
// upfront and substract the fee from the change outputs (10k == ~30/sat vbyte)
|
// upfront and substract the fee from the change outputs (10k == ~30/sat vbyte)
|
||||||
@ -187,9 +184,9 @@ impl<D: bdk::database::BatchDatabase> CoordinatorWallet<D> {
|
|||||||
|
|
||||||
let amount_escrow = escrow_amount_maker_sat + escrow_amount_taker_sat;
|
let amount_escrow = escrow_amount_maker_sat + escrow_amount_taker_sat;
|
||||||
|
|
||||||
// let wallet = self.wallet.lock().await;
|
let wallet = self.wallet.lock().await;
|
||||||
let mut builder = temp_wallet.build_tx();
|
// let mut builder = temp_wallet.build_tx();
|
||||||
// let mut builder = wallet.build_tx();
|
let mut builder = wallet.build_tx();
|
||||||
builder
|
builder
|
||||||
.manually_selected_only()
|
.manually_selected_only()
|
||||||
.add_recipient(escrow_address.script_pubkey(), amount_escrow)
|
.add_recipient(escrow_address.script_pubkey(), amount_escrow)
|
||||||
@ -221,7 +218,7 @@ impl<D: bdk::database::BatchDatabase> CoordinatorWallet<D> {
|
|||||||
Ok(EscrowPsbt {
|
Ok(EscrowPsbt {
|
||||||
escrow_tx_txid,
|
escrow_tx_txid,
|
||||||
escrow_psbt_hex: escrow_psbt.to_string(),
|
escrow_psbt_hex: escrow_psbt.to_string(),
|
||||||
escrow_output_descriptor,
|
escrow_output_descriptor: escrow_output_descriptor.to_string(),
|
||||||
coordinator_xonly_escrow_pk: coordinator_escrow_pk.to_string(),
|
coordinator_xonly_escrow_pk: coordinator_escrow_pk.to_string(),
|
||||||
escrow_amount_maker_sat,
|
escrow_amount_maker_sat,
|
||||||
escrow_amount_taker_sat,
|
escrow_amount_taker_sat,
|
||||||
|
@ -4,7 +4,7 @@ pub mod wallet_utils;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod wallet_tests;
|
mod wallet_tests;
|
||||||
|
|
||||||
use self::escrow_psbt::*;
|
pub use self::escrow_psbt::*;
|
||||||
use super::*;
|
use super::*;
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use bdk::{
|
use bdk::{
|
||||||
|
@ -1,13 +1,21 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use super::escrow_psbt::*;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use bdk::bitcoin::secp256k1::XOnlyPublicKey;
|
||||||
|
use bdk::miniscript::ToPublicKey;
|
||||||
use bdk::{
|
use bdk::{
|
||||||
bitcoin::{psbt::Input, Network},
|
bitcoin::{psbt::Input, Network},
|
||||||
blockchain::RpcBlockchain,
|
blockchain::RpcBlockchain,
|
||||||
database::MemoryDatabase,
|
database::MemoryDatabase,
|
||||||
wallet::AddressIndex,
|
miniscript::{
|
||||||
|
descriptor::{self},
|
||||||
|
policy::Concrete,
|
||||||
|
Descriptor, Tap,
|
||||||
|
},
|
||||||
Wallet,
|
Wallet,
|
||||||
};
|
};
|
||||||
|
use sha2::digest::XofReader;
|
||||||
|
|
||||||
async fn new_test_wallet(wallet_xprv: &str) -> CoordinatorWallet<MemoryDatabase> {
|
async fn new_test_wallet(wallet_xprv: &str) -> CoordinatorWallet<MemoryDatabase> {
|
||||||
dotenv().ok();
|
dotenv().ok();
|
||||||
@ -278,3 +286,34 @@ fn test_aggregate_musig_pubkeys() {
|
|||||||
);
|
);
|
||||||
assert!(agg_pk_result.is_ok());
|
assert!(agg_pk_result.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_miniscript_compilation() {
|
||||||
|
let maker_pk = "4987f3de20a9b1fa6f76c6758934953a8d615e415f1a656f0f6563694b53107d";
|
||||||
|
let taker_pk = "f1f1db08126af105974cde6021096525ed390cf9b7cde5fedb17a0b16ed31151";
|
||||||
|
let coordinator_pk = "4b588489c13b2fbcfc2c3b8b6c885e9c366768f216899ba059d6c467af432ad4";
|
||||||
|
let internal_key = bdk::bitcoin::PublicKey::from_str(
|
||||||
|
"03f00949d6dd1ce99a03f88a1a4f59117d553b0da51728bb7fd5b98fbf541337fb",
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.to_x_only_pubkey();
|
||||||
|
|
||||||
|
let policy_a_string = format!("and(pk({}),pk({}))", maker_pk, taker_pk);
|
||||||
|
let policy_b_string = format!("and(pk({}),pk({}))", maker_pk, coordinator_pk);
|
||||||
|
|
||||||
|
let policy_a = Concrete::<XOnlyPublicKey>::from_str(&policy_a_string).unwrap();
|
||||||
|
let policy_b = Concrete::<XOnlyPublicKey>::from_str(&policy_b_string).unwrap();
|
||||||
|
|
||||||
|
let miniscript_a = policy_a.compile::<Tap>().unwrap();
|
||||||
|
let miniscript_b = policy_b.compile::<Tap>().unwrap();
|
||||||
|
|
||||||
|
let tap_leaf_a = bdk::miniscript::descriptor::TapTree::Leaf(Arc::new(miniscript_a));
|
||||||
|
let tap_leaf_b = bdk::miniscript::descriptor::TapTree::Leaf(Arc::new(miniscript_b));
|
||||||
|
|
||||||
|
let tap_tree_root =
|
||||||
|
bdk::miniscript::descriptor::TapTree::Tree(Arc::new(tap_leaf_a), Arc::new(tap_leaf_b));
|
||||||
|
|
||||||
|
let descriptor =
|
||||||
|
Descriptor::<XOnlyPublicKey>::new_tr(internal_key, Some(tap_tree_root)).unwrap();
|
||||||
|
dbg!(descriptor.address(bdk::bitcoin::Network::Regtest).unwrap());
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user