Skip to content

Commit 0731a73

Browse files
committed
WIP starting funding taproot tx impl
1 parent 29c41e4 commit 0731a73

File tree

5 files changed

+204
-9
lines changed

5 files changed

+204
-9
lines changed

contrib/run-rpc-tests.sh

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,20 @@
22

33
cd ..
44

5-
ID=$(docker run --rm -d -p 18443:18443 coblox/bitcoin-core\
6-
-regtest\
7-
-server\
8-
-fallbackfee=0.00001\
9-
-rpcbind=0.0.0.0\
10-
-rpcallowip=0.0.0.0/0\
11-
-rpcuser=test\
12-
-rpcpassword=cEl2o3tHHgzYeuu3CiiZ2FjdgSiw9wNeMFzoNbFmx9k=)
5+
ID=$(docker run --rm -d -p 18443:18443\
6+
--env NETWORK="regtest -rpcuser=test -rpcpassword=cEl2o3tHHgzYeuu3CiiZ2FjdgSiw9wNeMFzoNbFmx9k="\
7+
--env FALLBACKFEE="0.00001"\
8+
--env RPC_PORT="18443"\
9+
ghcr.io/farcaster-project/containers/bitcoin-core:22.0)
1310

1411
export CI=false RPC_USER=test RPC_PASS=cEl2o3tHHgzYeuu3CiiZ2FjdgSiw9wNeMFzoNbFmx9k=
15-
cargo test --test transactions --features rpc -- --test-threads=1
12+
13+
case "$1" in
14+
"--taproot")
15+
cargo test --test taproot --features taproot,rpc -- --test-threads=1;;
16+
*)
17+
cargo test --test transactions --features rpc -- --test-threads=1;;
18+
esac
1619

1720
docker kill $ID
1821

src/bitcoin/taproot/funding.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
use bitcoin::blockdata::transaction::{OutPoint, Transaction};
2+
use bitcoin::secp256k1::Secp256k1;
3+
use bitcoin::{Address, XOnlyPublicKey};
4+
5+
use crate::blockchain::Network;
6+
use crate::transaction::{Error, Fundable, Linkable};
7+
8+
use crate::bitcoin::taproot::Taproot;
9+
use crate::bitcoin::transaction::MetadataOutput;
10+
use crate::bitcoin::Bitcoin;
11+
use bitcoin::util::taproot::TaprootSpendInfo;
12+
13+
#[derive(Debug, Clone)]
14+
pub struct Funding {
15+
untweaked_public_key: Option<XOnlyPublicKey>,
16+
network: Option<bitcoin::Network>,
17+
seen_tx: Option<Transaction>,
18+
}
19+
20+
impl Linkable<MetadataOutput> for Funding {
21+
fn get_consumable_output(&self) -> Result<MetadataOutput, Error> {
22+
let secp = Secp256k1::new();
23+
24+
let address = Address::p2tr(
25+
&secp,
26+
self.untweaked_public_key.ok_or(Error::MissingPublicKey)?,
27+
None,
28+
self.network.ok_or(Error::MissingNetwork)?,
29+
);
30+
let script_pubkey = address.script_pubkey();
31+
32+
match &self.seen_tx {
33+
Some(t) => t
34+
.output
35+
.iter()
36+
.enumerate()
37+
.find(|(_, tx_out)| tx_out.script_pubkey == script_pubkey)
38+
.map(|(ix, tx_out)| MetadataOutput {
39+
out_point: OutPoint::new(t.txid(), ix as u32),
40+
tx_out: tx_out.clone(),
41+
script_pubkey: Some(script_pubkey),
42+
})
43+
.ok_or(Error::MissingUTXO),
44+
// The transaction has not been see yet, cannot infer the UTXO
45+
None => Err(Error::MissingOnchainTransaction),
46+
}
47+
}
48+
}
49+
50+
impl Fundable<Bitcoin<Taproot>, MetadataOutput> for Funding {
51+
fn initialize(pubkey: XOnlyPublicKey, network: Network) -> Result<Self, Error> {
52+
let network = match network {
53+
Network::Mainnet => bitcoin::Network::Bitcoin,
54+
Network::Testnet => bitcoin::Network::Testnet,
55+
Network::Local => bitcoin::Network::Regtest,
56+
};
57+
Ok(Funding {
58+
untweaked_public_key: Some(pubkey),
59+
network: Some(network),
60+
seen_tx: None,
61+
})
62+
}
63+
64+
fn get_address(&self) -> Result<Address, Error> {
65+
let secp = Secp256k1::new();
66+
let taproot_info = TaprootSpendInfo::new_key_spend(
67+
&secp,
68+
self.untweaked_public_key.ok_or(Error::MissingPublicKey)?,
69+
None,
70+
);
71+
72+
Ok(Address::p2tr(
73+
&secp,
74+
taproot_info.internal_key(),
75+
taproot_info.merkle_root(),
76+
self.network.ok_or(Error::MissingNetwork)?,
77+
))
78+
}
79+
80+
fn update(&mut self, tx: Transaction) -> Result<(), Error> {
81+
self.seen_tx = Some(tx);
82+
Ok(())
83+
}
84+
85+
fn raw(tx: Transaction) -> Result<Self, Error> {
86+
Ok(Self {
87+
untweaked_public_key: None,
88+
network: None,
89+
seen_tx: Some(tx),
90+
})
91+
}
92+
93+
fn was_seen(&self) -> bool {
94+
self.seen_tx.is_some()
95+
}
96+
}

src/bitcoin/taproot/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ use std::convert::{TryFrom, TryInto};
55
use std::fmt;
66
use std::str::FromStr;
77

8+
use crate::bitcoin::taproot::funding::Funding;
9+
810
use crate::bitcoin::{Bitcoin, BitcoinTaproot, Btc, Strategy};
911
use crate::consensus::{self, CanonicalBytes};
1012
use crate::crypto::{Keys, SharedKeyId, SharedSecretKeys, Signatures};
@@ -13,6 +15,11 @@ use crate::crypto::{Keys, SharedKeyId, SharedSecretKeys, Signatures};
1315
use bitcoin::hashes::sha256d::Hash as Sha256dHash;
1416
use bitcoin::secp256k1::{constants::SECRET_KEY_SIZE, schnorr::Signature, KeyPair, XOnlyPublicKey};
1517

18+
pub mod funding;
19+
20+
/// Funding the swap creating a Taproot (SegWit v1) output.
21+
pub type FundingTx = Funding;
22+
1623
/// Inner type for the Taproot strategy with on-chain scripts.
1724
#[derive(Clone, Debug, Copy, Eq, PartialEq)]
1825
pub struct Taproot;

tests/rpc/mod.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,32 @@ macro_rules! new_address {
7171
pair.0,
7272
)
7373
}};
74+
(taproot) => {{
75+
use bitcoin::network::constants::Network;
76+
use bitcoin::secp256k1::rand::thread_rng;
77+
use bitcoin::secp256k1::{KeyPair, Secp256k1};
78+
use bitcoin::util::address::Address;
79+
use bitcoin::util::taproot::TaprootSpendInfo;
80+
81+
// Generate random key pair
82+
let s = Secp256k1::new();
83+
let keypair = KeyPair::new(&s, &mut thread_rng());
84+
let xpubkey = keypair.public_key().into();
85+
86+
let taproot_info = TaprootSpendInfo::new_key_spend(&s, xpubkey, None);
87+
88+
// Generate pay-to-taproot address
89+
(
90+
Address::p2tr(
91+
&s,
92+
taproot_info.internal_key(),
93+
taproot_info.merkle_root(),
94+
Network::Regtest,
95+
),
96+
xpubkey,
97+
keypair,
98+
)
99+
}};
74100
}
75101

76102
macro_rules! send {

tests/taproot.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#![cfg(all(feature = "rpc", feature = "taproot"))]
2+
3+
use farcaster_core::bitcoin::taproot::*;
4+
use farcaster_core::blockchain::*;
5+
use farcaster_core::transaction::*;
6+
7+
use bitcoin::Amount;
8+
use bitcoincore_rpc::json::AddressType;
9+
use bitcoincore_rpc::RpcApi;
10+
11+
#[macro_use]
12+
mod rpc;
13+
14+
#[test]
15+
fn taproot_funding_tx() {
16+
let (_, xpubkey_a1, keypair_a1) = new_address!(taproot);
17+
//let (_, pubkey_a2, secret_a2) = new_address!();
18+
19+
let (_, xpubkey_b1, keypair_b1) = new_address!(taproot);
20+
//let (_, pubkey_b2, secret_b2) = new_address!();
21+
22+
let mut funding = FundingTx::initialize(xpubkey_a1, Network::Local).unwrap();
23+
24+
//
25+
// Create a wallet, mined funds, send to funding address with multiple UTXOs
26+
//
27+
if let Err(_) = rpc::CLIENT.create_wallet("test_wallet", Some(false), None, None, None) {
28+
let wallets = rpc::CLIENT.list_wallets().unwrap();
29+
if wallets.len() == 0 {
30+
rpc::CLIENT.load_wallet("test_wallet").unwrap();
31+
}
32+
if wallets.len() > 1 {
33+
panic!("More than one wallet loaded!");
34+
}
35+
}
36+
37+
let wallet_address = rpc::CLIENT
38+
.get_new_address(None, Some(AddressType::Bech32))
39+
.unwrap();
40+
rpc::CLIENT.generate_to_address(4, &wallet_address).unwrap();
41+
mine!(100);
42+
let target_swap_amount = bitcoin::Amount::from_btc(8.0).unwrap();
43+
44+
let address = funding.get_address().unwrap();
45+
println!("{:?}", address);
46+
let txid = rpc::CLIENT
47+
.send_to_address(
48+
&address,
49+
target_swap_amount,
50+
None,
51+
None,
52+
None,
53+
None,
54+
None,
55+
None,
56+
)
57+
.unwrap();
58+
59+
let funding_tx_seen: bitcoin::Transaction = rpc::CLIENT.get_by_id(&txid).unwrap();
60+
// Minimum of fee of 122 sat
61+
let target_amount = Amount::from_sat(target_swap_amount.as_sat() - 122);
62+
funding.update(funding_tx_seen).unwrap();
63+
}

0 commit comments

Comments
 (0)