Skip to content

Commit d556752

Browse files
committed
Test funding taproot tx
1 parent 0c209c4 commit d556752

File tree

7 files changed

+241
-77
lines changed

7 files changed

+241
-77
lines changed

.github/workflows/test.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ jobs:
4444
stable,
4545
nightly
4646
]
47+
test: [
48+
transactions,
49+
taproot
50+
]
4751

4852
runs-on: ubuntu-latest
4953
container:
@@ -74,7 +78,7 @@ jobs:
7478
profile: minimal
7579

7680
- name: Run regtest Bitcoin transactions
77-
run: cargo test --verbose --test transactions --features rpc -- --test-threads=1
81+
run: cargo test --verbose --test ${{ matrix.test }} --features taproot,rpc -- --test-threads=1
7882
env:
7983
RPC_HOST: bitcoind
8084
RPC_PORT: 18443

src/bitcoin/taproot/lock.rs

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
use std::marker::PhantomData;
22

3+
use bitcoin::blockdata::opcodes::all::{OP_CHECKSIG, OP_CHECKSIGADD, OP_EQUAL};
4+
use bitcoin::blockdata::script::Builder;
35
use bitcoin::blockdata::transaction::{TxIn, TxOut};
46
use bitcoin::blockdata::witness::Witness;
57
use bitcoin::secp256k1::rand::thread_rng;
68
use bitcoin::secp256k1::Secp256k1;
79
use bitcoin::util::psbt::PartiallySignedTransaction;
10+
use bitcoin::Script;
811
use bitcoin::{Amount, KeyPair, XOnlyPublicKey};
912

1013
use crate::script;
@@ -21,23 +24,25 @@ pub struct Lock;
2124

2225
impl SubTransaction for Lock {
2326
fn finalize(psbt: &mut PartiallySignedTransaction) -> Result<(), FError> {
24-
let (pubkey, full_sig) = psbt.inputs[0]
25-
.partial_sigs
26-
.iter()
27-
.next()
28-
.ok_or(FError::MissingSignature)?;
29-
psbt.inputs[0].final_script_witness = Some(Witness::from_vec(vec![
30-
full_sig.to_vec(),
31-
pubkey.serialize().to_vec(),
32-
]));
27+
//let (pubkey, full_sig) = psbt.inputs[0]
28+
// .partial_sigs
29+
// .iter()
30+
// .next()
31+
// .ok_or(FError::MissingSignature)?;
32+
//psbt.inputs[0].final_script_witness = Some(Witness::from_vec(vec![
33+
// full_sig.to_vec(),
34+
// pubkey.serialize().to_vec(),
35+
//]));
36+
let sig = psbt.inputs[0].tap_key_sig.ok_or(FError::MissingSignature)?;
37+
psbt.inputs[0].final_script_witness = Some(Witness::from_vec(vec![sig.to_vec()]));
3338
Ok(())
3439
}
3540
}
3641

3742
impl Lockable<Bitcoin<Taproot>, MetadataOutput> for Tx<Lock> {
3843
fn initialize(
3944
prev: &impl Fundable<Bitcoin<Taproot>, MetadataOutput>,
40-
_lock: script::DataLock<Bitcoin<Taproot>>,
45+
lock: script::DataLock<Bitcoin<Taproot>>,
4146
target_amount: Amount,
4247
) -> Result<Self, FError> {
4348
let secp = Secp256k1::new();
@@ -46,9 +51,37 @@ impl Lockable<Bitcoin<Taproot>, MetadataOutput> for Tx<Lock> {
4651
let untweaked_public_key =
4752
XOnlyPublicKey::from_keypair(&KeyPair::new(&secp, &mut thread_rng()));
4853
let spend_info = TaprootBuilder::new()
49-
// FIXME add script path for by and cancel
54+
// Buy script
55+
.add_leaf(
56+
1,
57+
Builder::new()
58+
.push_slice(lock.success.alice.serialize().as_ref())
59+
.push_opcode(OP_CHECKSIG)
60+
.push_slice(lock.success.bob.serialize().as_ref())
61+
.push_opcode(OP_CHECKSIGADD)
62+
.push_int(2)
63+
.push_opcode(OP_EQUAL)
64+
.into_script(),
65+
)
66+
// FIXME
67+
.unwrap()
68+
// Cancel script
69+
.add_leaf(
70+
1,
71+
Builder::new()
72+
.push_slice(lock.failure.alice.serialize().as_ref())
73+
.push_opcode(OP_CHECKSIG)
74+
.push_slice(lock.failure.bob.serialize().as_ref())
75+
.push_opcode(OP_CHECKSIGADD)
76+
.push_int(1) // FIXME this is just for making different script (same keys for now between success and failure)
77+
.push_opcode(OP_EQUAL)
78+
.into_script(),
79+
)
80+
// FIXME
81+
.unwrap()
5082
.finalize(&secp, untweaked_public_key)
5183
.expect("Valid taproot FIXME");
84+
println!("{:#?}", spend_info);
5285
let tweaked_pubkey = spend_info.output_key();
5386
let output_metadata = prev.get_consumable_output()?;
5487

@@ -61,13 +94,13 @@ impl Lockable<Bitcoin<Taproot>, MetadataOutput> for Tx<Lock> {
6194
lock_time: 0,
6295
input: vec![TxIn {
6396
previous_output: output_metadata.out_point,
64-
script_sig: bitcoin::Script::default(),
97+
script_sig: Script::default(),
6598
sequence: CSVTimelock::disable(),
6699
witness: Witness::new(),
67100
}],
68101
output: vec![TxOut {
69102
value: target_amount.as_sat(),
70-
script_pubkey: bitcoin::Script::new_v1_p2tr_tweaked(tweaked_pubkey),
103+
script_pubkey: Script::new_v1_p2tr_tweaked(tweaked_pubkey),
71104
}],
72105
};
73106

src/bitcoin/taproot/mod.rs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,28 @@ use std::convert::{TryFrom, TryInto};
55
use std::fmt;
66
use std::str::FromStr;
77

8-
use crate::bitcoin::taproot::funding::Funding;
8+
use crate::bitcoin::taproot::{funding::Funding, lock::Lock};
99

10+
use crate::bitcoin::transaction::Tx;
1011
use crate::bitcoin::{Bitcoin, BitcoinTaproot, Btc, Strategy};
1112
use crate::consensus::{self, CanonicalBytes};
1213
use crate::crypto::{Keys, SharedKeyId, SharedSecretKeys, Signatures};
14+
use bitcoin::util::taproot::TapSighashHash;
1315
//use crate::role::Arbitrating;
1416

15-
use bitcoin::hashes::sha256d::Hash as Sha256dHash;
16-
use bitcoin::secp256k1::{constants::SECRET_KEY_SIZE, schnorr::Signature, KeyPair, XOnlyPublicKey};
17+
use bitcoin::secp256k1::{
18+
constants::SECRET_KEY_SIZE, schnorr::Signature, KeyPair, Message, Secp256k1, XOnlyPublicKey,
19+
};
1720

1821
pub mod funding;
1922
pub mod lock;
2023

2124
/// Funding the swap creating a Taproot (SegWit v1) output.
2225
pub type FundingTx = Funding;
2326

27+
/// Locking the funding UTXO in a lock and allow buy or cancel transaction.
28+
pub type LockTx = Tx<Lock>;
29+
2430
/// Inner type for the Taproot strategy with on-chain scripts.
2531
#[derive(Clone, Debug, Copy, Eq, PartialEq)]
2632
pub struct Taproot;
@@ -127,7 +133,7 @@ impl SharedSecretKeys for Bitcoin<Taproot> {
127133
}
128134

129135
impl Signatures for Bitcoin<Taproot> {
130-
type Message = Sha256dHash;
136+
type Message = TapSighashHash;
131137
type Signature = Signature;
132138
type EncryptedSignature = Signature;
133139
}
@@ -144,3 +150,13 @@ impl CanonicalBytes for Signature {
144150
Signature::from_slice(bytes).map_err(consensus::Error::new)
145151
}
146152
}
153+
154+
/// Create a Schnorr signature for the given Taproot sighash
155+
pub fn sign_hash(
156+
sighash: TapSighashHash,
157+
keypair: &bitcoin::secp256k1::KeyPair,
158+
) -> Result<Signature, bitcoin::secp256k1::Error> {
159+
let context = Secp256k1::new();
160+
let msg = Message::from_slice(&sighash[..])?;
161+
Ok(context.sign_schnorr(&msg, keypair))
162+
}

src/bitcoin/transaction.rs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ use bitcoin::blockdata::transaction::{EcdsaSigHashType, OutPoint, TxIn, TxOut};
88
use bitcoin::util::address;
99
use bitcoin::util::ecdsa::EcdsaSig;
1010
use bitcoin::util::psbt::{self, PartiallySignedTransaction};
11+
use bitcoin::util::sighash::SigHashCache;
12+
use bitcoin::util::taproot::TapSighashHash;
1113

1214
#[cfg(feature = "experimental")]
1315
use bitcoin::{
@@ -17,8 +19,8 @@ use bitcoin::{
1719
};
1820
#[cfg(all(feature = "experimental", feature = "taproot"))]
1921
use bitcoin::{
20-
secp256k1::schnorr, util::schnorr::SchnorrSig, util::sighash::SchnorrSigHashType,
21-
XOnlyPublicKey,
22+
secp256k1::schnorr, util::schnorr::SchnorrSig, util::sighash::Prevouts,
23+
util::sighash::SchnorrSigHashType, XOnlyPublicKey,
2224
};
2325

2426
use thiserror::Error;
@@ -249,8 +251,27 @@ impl<T> Witnessable<Bitcoin<Taproot>> for Tx<T>
249251
where
250252
T: SubTransaction,
251253
{
252-
fn generate_witness_message(&self, _path: ScriptPath) -> Result<Hash, FError> {
253-
todo!()
254+
// FIXME: this only accounts for key spend and not for script spend
255+
fn generate_witness_message(&self, _path: ScriptPath) -> Result<TapSighashHash, FError> {
256+
let mut sighash = SigHashCache::new(&self.psbt.unsigned_tx);
257+
258+
let witness_utxo = self.psbt.inputs[0]
259+
.witness_utxo
260+
.clone()
261+
.ok_or(FError::MissingWitness)?;
262+
let script_pubkey = self.psbt.inputs[0]
263+
.witness_script
264+
.clone()
265+
.ok_or(FError::MissingWitness)?;
266+
let value = witness_utxo.value;
267+
268+
let txouts = vec![TxOut {
269+
value,
270+
script_pubkey,
271+
}];
272+
sighash
273+
.taproot_key_spend_signature_hash(0, &Prevouts::All(&txouts), SchnorrSigHashType::All)
274+
.map_err(FError::new)
254275
}
255276

256277
// FIXME: this only accounts for key spend and not for script spend

tests/protocol.rs

Lines changed: 50 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -193,8 +193,9 @@ fn execute_offline_protocol() {
193193
.sign_arbitrating_lock(&mut bob_key_manager, &core)
194194
.unwrap();
195195

196-
let mut lock = LockTx::from_partial(core.lock.clone());
197-
lock.add_witness(funding_key, signed_lock.lock_sig).unwrap();
196+
let mut lock = <LockTx as Transaction<BitcoinSegwitV0, _>>::from_partial(core.lock.clone());
197+
Witnessable::<BitcoinSegwitV0>::add_witness(&mut lock, funding_key, signed_lock.lock_sig)
198+
.unwrap();
198199
let _ = Broadcastable::<BitcoinSegwitV0>::finalize_and_extract(&mut lock).unwrap();
199200

200201
// ...seen arbitrating lock...
@@ -231,10 +232,14 @@ fn execute_offline_protocol() {
231232
)
232233
.unwrap();
233234

234-
let mut buy = BuyTx::from_partial(adaptor_buy.buy.clone());
235-
buy.add_witness(bob_params.buy, fully_sign_buy.buy_adapted_sig)
236-
.unwrap();
237-
buy.add_witness(alice_params.buy, fully_sign_buy.buy_sig)
235+
let mut buy = <BuyTx as Transaction<BitcoinSegwitV0, _>>::from_partial(adaptor_buy.buy.clone());
236+
Witnessable::<BitcoinSegwitV0>::add_witness(
237+
&mut buy,
238+
bob_params.buy,
239+
fully_sign_buy.buy_adapted_sig,
240+
)
241+
.unwrap();
242+
Witnessable::<BitcoinSegwitV0>::add_witness(&mut buy, alice_params.buy, fully_sign_buy.buy_sig)
238243
.unwrap();
239244
let buy_tx = Broadcastable::<BitcoinSegwitV0>::finalize_and_extract(&mut buy).unwrap();
240245

@@ -268,13 +273,21 @@ fn execute_offline_protocol() {
268273
// IF CANCEL PATH:
269274
//
270275

271-
let mut cancel = CancelTx::from_partial(core.cancel.clone());
272-
cancel
273-
.add_witness(bob_params.cancel, bob_cosign_cancel.cancel_sig)
274-
.unwrap();
275-
cancel
276-
.add_witness(alice_params.cancel, alice_cosign_cancel.cancel_sig)
277-
.unwrap();
276+
let mut cancel =
277+
<CancelTx as Transaction<BitcoinSegwitV0, _>>::from_partial(core.cancel.clone());
278+
279+
Witnessable::<BitcoinSegwitV0>::add_witness(
280+
&mut cancel,
281+
bob_params.cancel,
282+
bob_cosign_cancel.cancel_sig,
283+
)
284+
.unwrap();
285+
Witnessable::<BitcoinSegwitV0>::add_witness(
286+
&mut cancel,
287+
alice_params.cancel,
288+
alice_cosign_cancel.cancel_sig,
289+
)
290+
.unwrap();
278291
let _ = Broadcastable::<BitcoinSegwitV0>::finalize_and_extract(&mut cancel).unwrap();
279292

280293
// ...seen arbitrating cancel...
@@ -287,13 +300,21 @@ fn execute_offline_protocol() {
287300
.fully_sign_refund(&mut bob_key_manager, core.clone(), &adaptor_refund)
288301
.unwrap();
289302

290-
let mut refund = RefundTx::from_partial(core.refund.clone());
291-
refund
292-
.add_witness(alice_params.refund, fully_signed_refund.refund_adapted_sig)
293-
.unwrap();
294-
refund
295-
.add_witness(bob_params.refund, fully_signed_refund.refund_sig)
296-
.unwrap();
303+
let mut refund =
304+
<RefundTx as Transaction<BitcoinSegwitV0, _>>::from_partial(core.refund.clone());
305+
306+
Witnessable::<BitcoinSegwitV0>::add_witness(
307+
&mut refund,
308+
alice_params.refund,
309+
fully_signed_refund.refund_adapted_sig,
310+
)
311+
.unwrap();
312+
Witnessable::<BitcoinSegwitV0>::add_witness(
313+
&mut refund,
314+
bob_params.refund,
315+
fully_signed_refund.refund_sig,
316+
)
317+
.unwrap();
297318
let refund_tx = Broadcastable::<BitcoinSegwitV0>::finalize_and_extract(&mut refund).unwrap();
298319

299320
// ...seen refund tx on-chain...
@@ -339,9 +360,14 @@ fn execute_offline_protocol() {
339360
)
340361
.unwrap();
341362

342-
let mut punish = PunishTx::from_partial(fully_signed_punish.punish);
343-
punish
344-
.add_witness(alice_params.punish, fully_signed_punish.punish_sig)
345-
.unwrap();
363+
let mut punish =
364+
<PunishTx as Transaction<BitcoinSegwitV0, _>>::from_partial(fully_signed_punish.punish);
365+
366+
Witnessable::<BitcoinSegwitV0>::add_witness(
367+
&mut punish,
368+
alice_params.punish,
369+
fully_signed_punish.punish_sig,
370+
)
371+
.unwrap();
346372
let _ = Broadcastable::<BitcoinSegwitV0>::finalize_and_extract(&mut refund).unwrap();
347373
}

tests/taproot.rs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
#![cfg(all(feature = "rpc", feature = "taproot"))]
22

3+
use farcaster_core::bitcoin::fee::SatPerVByte;
34
use farcaster_core::bitcoin::taproot::*;
5+
use farcaster_core::bitcoin::*;
46
use farcaster_core::blockchain::*;
7+
use farcaster_core::script::*;
58
use farcaster_core::transaction::*;
69

10+
use bitcoin::secp256k1::Secp256k1;
711
use bitcoin::Amount;
812
use bitcoincore_rpc::json::AddressType;
913
use bitcoincore_rpc::RpcApi;
@@ -42,7 +46,6 @@ fn taproot_funding_tx() {
4246
let target_swap_amount = bitcoin::Amount::from_btc(8.0).unwrap();
4347

4448
let address = funding.get_address().unwrap();
45-
println!("{:?}", address);
4649
let txid = rpc::CLIENT
4750
.send_to_address(
4851
&address,
@@ -60,4 +63,39 @@ fn taproot_funding_tx() {
6063
// Minimum of fee of 122 sat
6164
let target_amount = Amount::from_sat(target_swap_amount.as_sat() - 122);
6265
funding.update(funding_tx_seen).unwrap();
66+
67+
let datalock = DataLock {
68+
timelock: timelock::CSVTimelock::new(10),
69+
success: DoubleKeys::new(&xpubkey_a1, &xpubkey_b1),
70+
failure: DoubleKeys::new(&xpubkey_a1, &xpubkey_b1),
71+
};
72+
73+
let fee = FeeStrategy::Fixed(SatPerVByte::from_sat(1));
74+
let politic = FeePriority::Low;
75+
76+
let mut lock = LockTx::initialize(&funding, datalock.clone(), target_amount).unwrap();
77+
78+
//
79+
// Sign lock tx
80+
//
81+
let msg = Witnessable::<BitcoinTaproot>::generate_witness_message(&lock, ScriptPath::Success)
82+
.unwrap();
83+
// tweak key pair with tap_tweak funding
84+
let secp = Secp256k1::new();
85+
let tweak = bitcoin::util::taproot::TaprootSpendInfo::new_key_spend(&secp, xpubkey_a1, None)
86+
.tap_tweak();
87+
let mut tweaked_keypair = keypair_a1.clone();
88+
tweaked_keypair.tweak_add_assign(&secp, &tweak).unwrap();
89+
let sig = sign_hash(msg, &tweaked_keypair).unwrap();
90+
Witnessable::<BitcoinTaproot>::add_witness(&mut lock, xpubkey_a1, sig).unwrap();
91+
let lock_finalized = Broadcastable::<BitcoinTaproot>::finalize_and_extract(&mut lock).unwrap();
92+
93+
rpc! {
94+
// Wait 100 blocks to unlock the coinbase
95+
mine 100;
96+
97+
// Broadcast the lock and mine the transaction
98+
then broadcast lock_finalized;
99+
then mine 1;
100+
}
63101
}

0 commit comments

Comments
 (0)