Updating Bitcoin and BDK crates (#10)

* Update `ark-lib` to latest `bitcoin` crate

* Updated Arkd and Noah to latest `bdk_wallet`

* Fixed linting issues
This commit is contained in:
42Pupusas
2024-07-22 02:58:25 -06:00
committed by GitHub
parent 92cb3b15bb
commit b97fb826e9
21 changed files with 275 additions and 223 deletions

View File

@@ -23,18 +23,19 @@ serde_json = "1"
ciborium = "0.2.1" ciborium = "0.2.1"
# bitcoin stack # bitcoin stack
bitcoin = { version = "0.30", features = [ "serde", "rand", "rand-std" ] } bitcoin = { version = "0.32", features = [ "serde", "rand", "rand-std" ] }
bip39 = { version = "2.0.0", features = [ "rand", "serde" ] } bip39 = { version = "2.0.0", features = [ "rand", "serde" ] }
miniscript = "10.0" miniscript = "10.0"
rand = { version = "0.8.5", features = [ "std", "std_rng" ] } rand = { version = "0.8.5", features = [ "std", "std_rng" ] }
# bdk = "1.0.0-alpha.3" # bdk = { git = "https://github.com/stevenroose/bdk.git", rev = "67602f5b33ea82775d94a28df9f3f66d2ca9aa19" }
# bdk_electrum = "0.5.0" # bdk_esplora = { git = "https://github.com/stevenroose/bdk.git", rev = "67602f5b33ea82775d94a28df9f3f66d2ca9aa19" }
# bdk_file_store = "0.3.0" # bdk_file_store = { git = "https://github.com/stevenroose/bdk.git", rev = "67602f5b33ea82775d94a28df9f3f66d2ca9aa19" }
# bdk_bitcoind_rpc = "0.2.0" # bdk_bitcoind_rpc = { git = "https://github.com/stevenroose/bdk.git", rev = "67602f5b33ea82775d94a28df9f3f66d2ca9aa19" }
bdk = { git = "https://github.com/stevenroose/bdk.git", rev = "67602f5b33ea82775d94a28df9f3f66d2ca9aa19" } # bdk = "0.29.0"
bdk_esplora = { git = "https://github.com/stevenroose/bdk.git", rev = "67602f5b33ea82775d94a28df9f3f66d2ca9aa19" } bdk_wallet = "=1.0.0-alpha.13"
bdk_file_store = { git = "https://github.com/stevenroose/bdk.git", rev = "67602f5b33ea82775d94a28df9f3f66d2ca9aa19" } bdk_esplora = "0.15.0"
bdk_bitcoind_rpc = { git = "https://github.com/stevenroose/bdk.git", rev = "67602f5b33ea82775d94a28df9f3f66d2ca9aa19" } bdk_file_store = "0.13.0"
bdk_bitcoind_rpc = "0.12.0"
sled = "0.34.7" sled = "0.34.7"

View File

@@ -6,11 +6,11 @@ use bitcoin::{
Address, Amount, Network, OutPoint, Script, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Address, Amount, Network, OutPoint, Script, ScriptBuf, Sequence, Transaction, TxIn, TxOut,
Witness, Witness,
}; };
use bitcoin::secp256k1::{KeyPair, PublicKey}; use bitcoin::secp256k1::{Keypair, PublicKey};
use bitcoin::sighash::{self, SighashCache, TapSighashType}; use bitcoin::sighash::{self, SighashCache, TapSighashType};
use crate::{fee, util}; use crate::{fee, util};
use crate::util::KeyPairExt; use crate::util::KeypairExt;
/// The size in vbytes of each connector tx. /// The size in vbytes of each connector tx.
@@ -51,7 +51,7 @@ impl ConnectorChain {
/// Create the scriptPubkey to create a connector chain using the given publick key. /// Create the scriptPubkey to create a connector chain using the given publick key.
pub fn output_script(pubkey: PublicKey) -> ScriptBuf { pub fn output_script(pubkey: PublicKey) -> ScriptBuf {
ScriptBuf::new_v1_p2tr(&util::SECP, pubkey.x_only_public_key().0, None) ScriptBuf::new_p2tr(&util::SECP, pubkey.x_only_public_key().0, None)
} }
/// Create the address to create a connector chain using the given publick key. /// Create the address to create a connector chain using the given publick key.
@@ -63,7 +63,7 @@ impl ConnectorChain {
pub fn output(len: usize, pubkey: PublicKey) -> TxOut { pub fn output(len: usize, pubkey: PublicKey) -> TxOut {
TxOut { TxOut {
script_pubkey: Self::output_script(pubkey), script_pubkey: Self::output_script(pubkey),
value: Self::required_budget(len).to_sat(), value: Self::required_budget(len),
} }
} }
@@ -85,7 +85,7 @@ impl ConnectorChain {
} }
/// Iterator over the signed transactions in this chain. /// Iterator over the signed transactions in this chain.
pub fn iter_signed_txs<'a>(&'a self, sign_key: &'a KeyPair) -> ConnectorTxIter<'a> { pub fn iter_signed_txs<'a>(&'a self, sign_key: &'a Keypair) -> ConnectorTxIter<'a> {
ConnectorTxIter { ConnectorTxIter {
len: self.len, len: self.len,
spk: &self.spk, spk: &self.spk,
@@ -118,7 +118,7 @@ impl ConnectorChain {
pub struct ConnectorTxIter<'a> { pub struct ConnectorTxIter<'a> {
len: usize, len: usize,
spk: &'a Script, spk: &'a Script,
sign_key: Option<KeyPair>, sign_key: Option<Keypair>,
prev: OutPoint, prev: OutPoint,
idx: usize, idx: usize,
@@ -133,7 +133,7 @@ impl<'a> iter::Iterator for ConnectorTxIter<'a> {
} }
let mut ret = Transaction { let mut ret = Transaction {
version: 2, version: bitcoin::transaction::Version::TWO,
lock_time: bitcoin::absolute::LockTime::ZERO, lock_time: bitcoin::absolute::LockTime::ZERO,
input: vec![TxIn { input: vec![TxIn {
previous_output: self.prev, previous_output: self.prev,
@@ -144,11 +144,11 @@ impl<'a> iter::Iterator for ConnectorTxIter<'a> {
output: vec![ output: vec![
TxOut { TxOut {
script_pubkey: self.spk.to_owned(), script_pubkey: self.spk.to_owned(),
value: ConnectorChain::required_budget(self.len - self.idx - 1).to_sat(), value: ConnectorChain::required_budget(self.len - self.idx - 1),
}, },
TxOut { TxOut {
script_pubkey: self.spk.to_owned(), script_pubkey: self.spk.to_owned(),
value: fee::DUST.to_sat(), value: fee::DUST,
}, },
], ],
}; };
@@ -156,7 +156,7 @@ impl<'a> iter::Iterator for ConnectorTxIter<'a> {
if let Some(keypair) = self.sign_key { if let Some(keypair) = self.sign_key {
let prevout = TxOut { let prevout = TxOut {
script_pubkey: self.spk.to_owned(), script_pubkey: self.spk.to_owned(),
value: ConnectorChain::required_budget(self.len - self.idx).to_sat(), value: ConnectorChain::required_budget(self.len - self.idx),
}; };
let mut shc = SighashCache::new(&ret); let mut shc = SighashCache::new(&ret);
let sighash = shc.taproot_key_spend_signature_hash( let sighash = shc.taproot_key_spend_signature_hash(
@@ -167,7 +167,7 @@ impl<'a> iter::Iterator for ConnectorTxIter<'a> {
} }
self.idx += 1; self.idx += 1;
self.prev = OutPoint::new(ret.txid(), 0); self.prev = OutPoint::new(ret.compute_txid(), 0);
Some(ret) Some(ret)
} }
@@ -191,7 +191,7 @@ impl<'a> iter::Iterator for ConnectorIter<'a> {
} }
if let Some(tx) = self.txs.next() { if let Some(tx) = self.txs.next() {
let txid = tx.txid(); let txid = tx.compute_txid();
self.maybe_last = Some(OutPoint::new(txid, 0)); self.maybe_last = Some(OutPoint::new(txid, 0));
Some(OutPoint::new(txid, 1)) Some(OutPoint::new(txid, 1))
} else { } else {
@@ -217,7 +217,7 @@ mod test {
#[test] #[test]
fn test_budget() { fn test_budget() {
let key = KeyPair::new(&util::SECP, &mut rand::thread_rng()); let key = Keypair::new(&util::SECP, &mut rand::thread_rng());
let utxo = OutPoint::new(Txid::all_zeros(), 0); let utxo = OutPoint::new(Txid::all_zeros(), 0);
let chain = ConnectorChain::new(1, utxo, key.public_key()); let chain = ConnectorChain::new(1, utxo, key.public_key());
@@ -238,27 +238,27 @@ mod test {
assert_eq!(chain.iter_signed_txs(&key).count(), 99); assert_eq!(chain.iter_signed_txs(&key).count(), 99);
for tx in chain.iter_signed_txs(&key) { for tx in chain.iter_signed_txs(&key) {
assert_eq!(tx.vsize() as u64, TX_SIZE); assert_eq!(tx.vsize() as u64, TX_SIZE);
assert_eq!(tx.input[0].witness.serialized_len(), INPUT_WEIGHT); assert_eq!(tx.input[0].witness.size(), INPUT_WEIGHT);
} }
let size = chain.iter_signed_txs(&key).map(|t| t.vsize() as u64).sum::<u64>(); let size = chain.iter_signed_txs(&key).map(|t| t.vsize() as u64).sum::<u64>();
assert_eq!(size, ConnectorChain::total_vsize(100)); assert_eq!(size, ConnectorChain::total_vsize(100));
chain.iter_unsigned_txs().for_each(|t| assert_eq!(t.output[1].value, fee::DUST.to_sat())); chain.iter_unsigned_txs().for_each(|t| assert_eq!(t.output[1].value, fee::DUST));
assert_eq!(fee::DUST.to_sat(), chain.iter_unsigned_txs().last().unwrap().output[0].value); assert_eq!(fee::DUST, chain.iter_unsigned_txs().last().unwrap().output[0].value);
let total_value = chain.iter_unsigned_txs().map(|t| t.output[1].value).sum::<u64>() let total_value = chain.iter_unsigned_txs().map(|t| t.output[1].value).sum::<Amount>()
+ chain.iter_unsigned_txs().last().unwrap().output[0].value + chain.iter_unsigned_txs().last().unwrap().output[0].value
+ size; + Amount::from_sat(size);
assert_eq!(ConnectorChain::required_budget(100).to_sat(), total_value); assert_eq!(ConnectorChain::required_budget(100), total_value);
// random checks // random checks
let mut txs = chain.iter_unsigned_txs(); let mut txs = chain.iter_unsigned_txs();
assert_eq!(txs.next().unwrap().output[0].value, ConnectorChain::required_budget(99).to_sat()); assert_eq!(txs.next().unwrap().output[0].value, ConnectorChain::required_budget(99));
assert_eq!(txs.next().unwrap().output[0].value, ConnectorChain::required_budget(98).to_sat()); assert_eq!(txs.next().unwrap().output[0].value, ConnectorChain::required_budget(98));
} }
#[test] #[test]
fn test_signatures() { fn test_signatures() {
let key = KeyPair::new(&util::SECP, &mut rand::thread_rng()); let key = Keypair::new(&util::SECP, &mut rand::thread_rng());
let utxo = OutPoint::new(Txid::all_zeros(), 0); let utxo = OutPoint::new(Txid::all_zeros(), 0);
let spk = ConnectorChain::output_script(key.public_key()); let spk = ConnectorChain::output_script(key.public_key());

View File

@@ -18,9 +18,9 @@ pub fn op_true_script() -> ScriptBuf {
pub fn dust_anchor() -> TxOut { pub fn dust_anchor() -> TxOut {
TxOut { TxOut {
script_pubkey: { script_pubkey: {
ScriptBuf::new_v0_p2wsh(&op_true_script().wscript_hash()) ScriptBuf::new_p2wsh(&op_true_script().wscript_hash())
}, },
value: DUST.to_sat(), value: DUST,
} }
} }

View File

@@ -1,6 +1,6 @@
use bitcoin::{OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Witness}; use bitcoin::{Amount, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Witness};
use bitcoin::sighash::{self, SighashCache, TapSighash, TapSighashType}; use bitcoin::sighash::{self, SighashCache, TapSighash, TapSighashType};
use crate::{fee, util, Vtxo}; use crate::{fee, util, Vtxo};
@@ -12,7 +12,7 @@ pub const SIGNED_FORFEIT_TX_VSIZE: u64 = 0;
pub fn create_forfeit_tx(vtxo: &Vtxo, connector: OutPoint) -> Transaction { pub fn create_forfeit_tx(vtxo: &Vtxo, connector: OutPoint) -> Transaction {
// NB we gain the dust from the connector and lose the dust from the fee anchor // NB we gain the dust from the connector and lose the dust from the fee anchor
let leftover = vtxo.amount().to_sat() - SIGNED_FORFEIT_TX_VSIZE; // @ 1 sat/vb let leftover = vtxo.amount() - Amount::from_sat(SIGNED_FORFEIT_TX_VSIZE); // @ 1 sat/vb
//TODO(stevenroose) improve this hack //TODO(stevenroose) improve this hack
let vtxo_fee_anchor_point = { let vtxo_fee_anchor_point = {
let mut point = vtxo.point(); let mut point = vtxo.point();
@@ -20,7 +20,7 @@ pub fn create_forfeit_tx(vtxo: &Vtxo, connector: OutPoint) -> Transaction {
point point
}; };
Transaction { Transaction {
version: 2, version: bitcoin::transaction::Version::TWO,
lock_time: bitcoin::absolute::LockTime::ZERO, lock_time: bitcoin::absolute::LockTime::ZERO,
input: vec![ input: vec![
TxIn { TxIn {
@@ -46,7 +46,7 @@ pub fn create_forfeit_tx(vtxo: &Vtxo, connector: OutPoint) -> Transaction {
output: vec![ output: vec![
TxOut { TxOut {
value: leftover, value: leftover,
script_pubkey: ScriptBuf::new_v1_p2tr(&util::SECP, vtxo.spec().combined_pubkey(), None), script_pubkey: ScriptBuf::new_p2tr(&util::SECP, vtxo.spec().combined_pubkey(), None),
}, },
fee::dust_anchor(), fee::dust_anchor(),
], ],
@@ -58,11 +58,11 @@ pub fn forfeit_sighash(vtxo: &Vtxo, connector: OutPoint) -> (TapSighash, Transac
let exit_spk = spec.exit_spk(); let exit_spk = spec.exit_spk();
let exit_prevout = TxOut { let exit_prevout = TxOut {
script_pubkey: exit_spk, script_pubkey: exit_spk,
value: vtxo.amount().to_sat(), value: vtxo.amount(),
}; };
let connector_prevout = TxOut { let connector_prevout = TxOut {
script_pubkey: ConnectorChain::output_script(spec.asp_pubkey), script_pubkey: ConnectorChain::output_script(spec.asp_pubkey),
value: fee::DUST.to_sat(), value: fee::DUST,
}; };
let tx = create_forfeit_tx(vtxo, connector); let tx = create_forfeit_tx(vtxo, connector);
let sighash = SighashCache::new(&tx).taproot_key_spend_signature_hash( let sighash = SighashCache::new(&tx).taproot_key_spend_signature_hash(

View File

@@ -73,14 +73,14 @@ impl OffboardRequest {
P2PKH_DUST_VB P2PKH_DUST_VB
} else if script.is_p2sh() { } else if script.is_p2sh() {
P2SH_DUST_VB P2SH_DUST_VB
} else if script.is_v0_p2wpkh() { } else if script.is_p2wpkh() {
P2WPKH_DUST_VB P2WPKH_DUST_VB
} else if script.is_v0_p2wsh() { } else if script.is_p2wsh() {
P2WSH_DUST_VB P2WSH_DUST_VB
} else if script.is_v1_p2tr() { } else if script.is_p2tr() {
P2TR_DUST_VB P2TR_DUST_VB
} else if script.is_op_return() { } else if script.is_op_return() {
bitcoin::consensus::encode::VarInt(script.len() as u64).len() as u64 bitcoin::consensus::encode::VarInt(script.len() as u64).size() as u64
+ script.len() as u64 + script.len() as u64
+ 8 + 8
// the input data (scriptSig and witness length fields included) // the input data (scriptSig and witness length fields included)
@@ -104,7 +104,7 @@ impl OffboardRequest {
pub fn to_txout(&self) -> TxOut { pub fn to_txout(&self) -> TxOut {
TxOut { TxOut {
script_pubkey: self.script_pubkey.clone(), script_pubkey: self.script_pubkey.clone(),
value: self.amount.to_sat(), value: self.amount,
} }
} }
@@ -216,7 +216,7 @@ pub fn exit_spk(
exit_delta: u16, exit_delta: u16,
) -> ScriptBuf { ) -> ScriptBuf {
let taproot = exit_taproot(user_pubkey, asp_pubkey, exit_delta); let taproot = exit_taproot(user_pubkey, asp_pubkey, exit_delta);
ScriptBuf::new_v1_p2tr_tweaked(taproot.output_key()) ScriptBuf::new_p2tr_tweaked(taproot.output_key())
} }
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
@@ -299,9 +299,9 @@ impl Vtxo {
/// This can be an on-chain utxo or an off-chain vtxo. /// This can be an on-chain utxo or an off-chain vtxo.
pub fn point(&self) -> OutPoint { pub fn point(&self) -> OutPoint {
match self { match self {
Vtxo::Onboard { .. } => OutPoint::new(self.vtxo_tx().txid(), 0), Vtxo::Onboard { .. } => OutPoint::new(self.vtxo_tx().compute_txid(), 0),
Vtxo::Round { exit_branch, .. } => { Vtxo::Round { exit_branch, .. } => {
OutPoint::new(exit_branch.last().unwrap().txid(), 0).into() OutPoint::new(exit_branch.last().unwrap().compute_txid(), 0).into()
}, },
Vtxo::Oor { final_point, .. } => *final_point, Vtxo::Oor { final_point, .. } => *final_point,
} }
@@ -320,7 +320,7 @@ impl Vtxo {
Vtxo::Onboard { base, .. } => base.spec.amount, Vtxo::Onboard { base, .. } => base.spec.amount,
Vtxo::Round { base, .. } => base.spec.amount, Vtxo::Round { base, .. } => base.spec.amount,
Vtxo::Oor { oor_tx, final_point, .. } => { Vtxo::Oor { oor_tx, final_point, .. } => {
Amount::from_sat(oor_tx.output[final_point.vout as usize].value) oor_tx.output[final_point.vout as usize].value
}, },
} }
} }
@@ -328,7 +328,7 @@ impl Vtxo {
pub fn txout(&self) -> TxOut { pub fn txout(&self) -> TxOut {
TxOut { TxOut {
script_pubkey: self.spec().exit_spk(), script_pubkey: self.spec().exit_spk(),
value: self.amount().to_sat(), value: self.amount(),
} }
} }
@@ -346,7 +346,7 @@ impl Vtxo {
pub fn fee_anchor(&self) -> OutPoint { pub fn fee_anchor(&self) -> OutPoint {
let tx = self.vtxo_tx(); let tx = self.vtxo_tx();
OutPoint::new(tx.txid(), tx.output.len() as u32 - 1) OutPoint::new(tx.compute_txid(), tx.output.len() as u32 - 1)
} }
/// Splits this vtxo in a set of non-OOR vtxos and the attached OOR txs. /// Splits this vtxo in a set of non-OOR vtxos and the attached OOR txs.

View File

@@ -4,7 +4,7 @@ pub use secp256k1_zkp::{
MusigAggNonce, MusigKeyAggCache, MusigPubNonce, MusigPartialSignature, MusigSecNonce, MusigAggNonce, MusigKeyAggCache, MusigPubNonce, MusigPartialSignature, MusigSecNonce,
MusigSession, MusigSessionId, MusigSession, MusigSessionId,
}; };
use bitcoin::secp256k1::{rand, schnorr, KeyPair, PublicKey, SecretKey, XOnlyPublicKey}; use bitcoin::secp256k1::{rand, schnorr, Keypair, PublicKey, SecretKey, XOnlyPublicKey};
use crate::util; use crate::util;
@@ -29,12 +29,12 @@ pub fn seckey_to(sk: SecretKey) -> zkp::SecretKey {
zkp::SecretKey::from_slice(&sk.secret_bytes()).unwrap() zkp::SecretKey::from_slice(&sk.secret_bytes()).unwrap()
} }
pub fn keypair_to(kp: &KeyPair) -> zkp::Keypair { pub fn keypair_to(kp: &Keypair) -> zkp::Keypair {
zkp::Keypair::from_seckey_slice(&SECP, &kp.secret_bytes()).unwrap() zkp::Keypair::from_seckey_slice(&SECP, &kp.secret_bytes()).unwrap()
} }
pub fn keypair_from(kp: &zkp::Keypair) -> KeyPair { pub fn keypair_from(kp: &zkp::Keypair) -> Keypair {
KeyPair::from_seckey_slice(&util::SECP, &kp.secret_bytes()).unwrap() Keypair::from_seckey_slice(&util::SECP, &kp.secret_bytes()).unwrap()
} }
pub fn sig_from(s: zkp::schnorr::Signature) -> schnorr::Signature { pub fn sig_from(s: zkp::schnorr::Signature) -> schnorr::Signature {
@@ -61,7 +61,7 @@ pub fn combine_keys(keys: impl IntoIterator<Item = PublicKey>) -> XOnlyPublicKey
xonly_from(key_agg(keys).agg_pk()) xonly_from(key_agg(keys).agg_pk())
} }
pub fn nonce_pair(key: &KeyPair) -> (MusigSecNonce, MusigPubNonce) { pub fn nonce_pair(key: &Keypair) -> (MusigSecNonce, MusigPubNonce) {
let kp = keypair_to(key); let kp = keypair_to(key);
zkp::new_musig_nonce_pair( zkp::new_musig_nonce_pair(
&SECP, &SECP,
@@ -81,7 +81,7 @@ pub fn nonce_agg(pub_nonces: impl IntoIterator<Item = MusigPubNonce>) -> MusigAg
pub fn partial_sign( pub fn partial_sign(
pubkeys: impl IntoIterator<Item = PublicKey>, pubkeys: impl IntoIterator<Item = PublicKey>,
agg_nonce: MusigAggNonce, agg_nonce: MusigAggNonce,
key: &KeyPair, key: &Keypair,
sec_nonce: MusigSecNonce, sec_nonce: MusigSecNonce,
sighash: [u8; 32], sighash: [u8; 32],
tweak: Option<[u8; 32]>, tweak: Option<[u8; 32]>,
@@ -111,7 +111,7 @@ pub fn partial_sign(
/// Perform a deterministic partial sign for the given message and the /// Perform a deterministic partial sign for the given message and the
/// given counterparty key and nonce. /// given counterparty key and nonce.
pub fn deterministic_partial_sign( pub fn deterministic_partial_sign(
my_key: &KeyPair, my_key: &Keypair,
their_pubkeys: impl IntoIterator<Item = PublicKey>, their_pubkeys: impl IntoIterator<Item = PublicKey>,
their_nonces: impl IntoIterator<Item = MusigPubNonce>, their_nonces: impl IntoIterator<Item = MusigPubNonce>,
msg: [u8; 32], msg: [u8; 32],

View File

@@ -1,7 +1,7 @@
use std::iter; use std::iter;
use bitcoin::{Transaction, TxIn, TxOut, ScriptBuf, OutPoint, Txid, Witness, Script}; use bitcoin::{Amount, OutPoint, Script, ScriptBuf, Transaction, TxIn, TxOut, Txid, Witness};
use bitcoin::blockdata::opcodes; use bitcoin::blockdata::opcodes;
use bitcoin::hashes::Hash; use bitcoin::hashes::Hash;
@@ -30,7 +30,7 @@ fn empty_input() -> TxIn {
fn ctv_output() -> TxOut { fn ctv_output() -> TxOut {
TxOut { TxOut {
value: 0, value: Amount::ZERO,
script_pubkey: Script::builder() script_pubkey: Script::builder()
.push_opcode(opcodes::all::OP_NOP4) .push_opcode(opcodes::all::OP_NOP4)
.push_slice(&BYTES32) .push_slice(&BYTES32)
@@ -44,7 +44,7 @@ fn ctv_input() -> TxIn {
fn taproot_output() -> TxOut { fn taproot_output() -> TxOut {
TxOut { TxOut {
value: 0, value: Amount::ZERO,
script_pubkey: Script::builder() script_pubkey: Script::builder()
.push_opcode(opcodes::all::OP_PUSHNUM_1) .push_opcode(opcodes::all::OP_PUSHNUM_1)
.push_slice(&BYTES32) .push_slice(&BYTES32)
@@ -67,7 +67,7 @@ fn taproot_input() -> TxIn {
fn anchor_output() -> TxOut { fn anchor_output() -> TxOut {
TxOut { TxOut {
value: 0, value: Amount::ZERO,
script_pubkey: Script::builder() script_pubkey: Script::builder()
.push_opcode(opcodes::OP_TRUE) .push_opcode(opcodes::OP_TRUE)
.into_script(), .into_script(),
@@ -80,7 +80,7 @@ fn anchor_input() -> TxIn {
fn ctv_node_tx(radix: usize) -> Transaction { fn ctv_node_tx(radix: usize) -> Transaction {
Transaction { Transaction {
version: 2, version: bitcoin::transaction::Version::TWO,
lock_time: bitcoin::locktime::absolute::LockTime::from_consensus(0), lock_time: bitcoin::locktime::absolute::LockTime::from_consensus(0),
input: vec![ctv_input()], input: vec![ctv_input()],
output: iter::repeat(ctv_output()).take(radix).chain(Some(anchor_output())).collect(), output: iter::repeat(ctv_output()).take(radix).chain(Some(anchor_output())).collect(),
@@ -89,7 +89,7 @@ fn ctv_node_tx(radix: usize) -> Transaction {
fn ctv_leaf_tx() -> Transaction { fn ctv_leaf_tx() -> Transaction {
Transaction { Transaction {
version: 2, version: bitcoin::transaction::Version::TWO,
lock_time: bitcoin::locktime::absolute::LockTime::from_consensus(0), lock_time: bitcoin::locktime::absolute::LockTime::from_consensus(0),
input: vec![ctv_input()], input: vec![ctv_input()],
output: vec![taproot_output(), anchor_output()], output: vec![taproot_output(), anchor_output()],
@@ -98,7 +98,7 @@ fn ctv_leaf_tx() -> Transaction {
fn clark_node_tx(radix: usize) -> Transaction { fn clark_node_tx(radix: usize) -> Transaction {
Transaction { Transaction {
version: 2, version: bitcoin::transaction::Version::TWO,
lock_time: bitcoin::locktime::absolute::LockTime::from_consensus(0), lock_time: bitcoin::locktime::absolute::LockTime::from_consensus(0),
input: vec![taproot_input()], input: vec![taproot_input()],
output: iter::repeat(taproot_output()).take(radix).chain(Some(anchor_output())).collect(), output: iter::repeat(taproot_output()).take(radix).chain(Some(anchor_output())).collect(),
@@ -107,7 +107,7 @@ fn clark_node_tx(radix: usize) -> Transaction {
fn clark_leaf_tx() -> Transaction { fn clark_leaf_tx() -> Transaction {
Transaction { Transaction {
version: 2, version: bitcoin::transaction::Version::TWO,
lock_time: bitcoin::locktime::absolute::LockTime::from_consensus(0), lock_time: bitcoin::locktime::absolute::LockTime::from_consensus(0),
input: vec![taproot_input()], input: vec![taproot_input()],
output: vec![taproot_output(), anchor_output()], output: vec![taproot_output(), anchor_output()],
@@ -137,7 +137,7 @@ fn calc_exit_cost(n: usize, radix: usize) {
// for exit cost the largest radix has to be taken anyway // for exit cost the largest radix has to be taken anyway
let exit_tx = Transaction { let exit_tx = Transaction {
version: 2, version: bitcoin::transaction::Version::TWO,
lock_time: bitcoin::locktime::absolute::LockTime::from_consensus(0), lock_time: bitcoin::locktime::absolute::LockTime::from_consensus(0),
input: iter::repeat(anchor_input()).take(levels).chain(Some(taproot_input())).collect(), input: iter::repeat(anchor_input()).take(levels).chain(Some(taproot_input())).collect(),
output: vec![taproot_output()], output: vec![taproot_output()],

View File

@@ -8,7 +8,7 @@
use bitcoin::{taproot, Amount, OutPoint, Sequence, ScriptBuf, Transaction, TxIn, TxOut, Witness}; use bitcoin::{taproot, Amount, OutPoint, Sequence, ScriptBuf, Transaction, TxIn, TxOut, Witness};
use bitcoin::blockdata::locktime::absolute::LockTime; use bitcoin::blockdata::locktime::absolute::LockTime;
use bitcoin::hashes::Hash; use bitcoin::hashes::Hash;
use bitcoin::secp256k1::{schnorr, KeyPair}; use bitcoin::secp256k1::{schnorr, Keypair};
use bitcoin::sighash::{self, SighashCache, TapSighash}; use bitcoin::sighash::{self, SighashCache, TapSighash};
use crate::{fee, musig, util, BaseVtxo, Vtxo, VtxoSpec}; use crate::{fee, musig, util, BaseVtxo, Vtxo, VtxoSpec};
@@ -37,7 +37,7 @@ pub fn onboard_taptweak(spec: &VtxoSpec) -> taproot::TapTweakHash {
} }
pub fn onboard_spk(spec: &VtxoSpec) -> ScriptBuf { pub fn onboard_spk(spec: &VtxoSpec) -> ScriptBuf {
ScriptBuf::new_v1_p2tr_tweaked(onboard_taproot(spec).output_key()) ScriptBuf::new_p2tr_tweaked(onboard_taproot(spec).output_key())
} }
/// The additional amount that needs to be sent into the onboard tx. /// The additional amount that needs to be sent into the onboard tx.
@@ -84,7 +84,7 @@ pub struct AspPart {
pub signature: musig::MusigPartialSignature, pub signature: musig::MusigPartialSignature,
} }
pub fn new_asp(user: &UserPart, key: &KeyPair) -> AspPart { pub fn new_asp(user: &UserPart, key: &Keypair) -> AspPart {
let (reveal_sighash, _reveal_tx) = reveal_tx_sighash(&user.spec, user.utxo); let (reveal_sighash, _reveal_tx) = reveal_tx_sighash(&user.spec, user.utxo);
let msg = reveal_sighash.to_byte_array(); let msg = reveal_sighash.to_byte_array();
let tweak = onboard_taptweak(&user.spec); let tweak = onboard_taptweak(&user.spec);
@@ -103,7 +103,7 @@ pub fn create_reveal_tx(
signature: Option<&schnorr::Signature>, signature: Option<&schnorr::Signature>,
) -> Transaction { ) -> Transaction {
Transaction { Transaction {
version: 2, version: bitcoin::transaction::Version::TWO,
lock_time: LockTime::ZERO, lock_time: LockTime::ZERO,
input: vec![TxIn { input: vec![TxIn {
previous_output: utxo, previous_output: utxo,
@@ -120,7 +120,7 @@ pub fn create_reveal_tx(
output: vec![ output: vec![
TxOut { TxOut {
script_pubkey: spec.exit_spk(), script_pubkey: spec.exit_spk(),
value: spec.amount.to_sat(), value: spec.amount,
}, },
fee::dust_anchor(), fee::dust_anchor(),
], ],
@@ -132,7 +132,7 @@ pub fn reveal_tx_sighash(spec: &VtxoSpec, utxo: OutPoint) -> (TapSighash, Transa
let prev = TxOut { let prev = TxOut {
script_pubkey: onboard_spk(&spec), script_pubkey: onboard_spk(&spec),
//TODO(stevenroose) consider storing both leaf and input values in vtxo struct //TODO(stevenroose) consider storing both leaf and input values in vtxo struct
value: spec.amount.to_sat() + onboard_surplus().to_sat(), value: spec.amount + onboard_surplus(),
}; };
let sighash = SighashCache::new(&reveal_tx).taproot_key_spend_signature_hash( let sighash = SighashCache::new(&reveal_tx).taproot_key_spend_signature_hash(
0, &sighash::Prevouts::All(&[&prev]), sighash::TapSighashType::Default, 0, &sighash::Prevouts::All(&[&prev]), sighash::TapSighashType::Default,
@@ -144,7 +144,7 @@ pub fn finish(
user: UserPart, user: UserPart,
asp: AspPart, asp: AspPart,
private: PrivateUserPart, private: PrivateUserPart,
key: &KeyPair, key: &Keypair,
) -> Vtxo { ) -> Vtxo {
let (reveal_sighash, _reveal_tx) = reveal_tx_sighash(&user.spec, user.utxo); let (reveal_sighash, _reveal_tx) = reveal_tx_sighash(&user.spec, user.utxo);
let agg_nonce = musig::nonce_agg([user.nonce, asp.nonce]); let agg_nonce = musig::nonce_agg([user.nonce, asp.nonce]);
@@ -193,7 +193,7 @@ mod test {
//! Passes through the entire flow so that all assertions //! Passes through the entire flow so that all assertions
//! inside the code are ran at least once. //! inside the code are ran at least once.
let key = KeyPair::new(&util::SECP, &mut rand::thread_rng()); let key = Keypair::new(&util::SECP, &mut rand::thread_rng());
let utxo = "0000000000000000000000000000000000000000000000000000000000000001:1".parse().unwrap(); let utxo = "0000000000000000000000000000000000000000000000000000000000000001:1".parse().unwrap();
let spec = VtxoSpec { let spec = VtxoSpec {
user_pubkey: key.public_key(), user_pubkey: key.public_key(),

View File

@@ -7,7 +7,7 @@ use bitcoin::{
Witness, Witness,
}; };
use bitcoin::hashes::Hash; use bitcoin::hashes::Hash;
use bitcoin::secp256k1::{schnorr, KeyPair, PublicKey}; use bitcoin::secp256k1::{schnorr, Keypair, PublicKey};
use bitcoin::sighash::{self, SighashCache, TapSighash, TapSighashType}; use bitcoin::sighash::{self, SighashCache, TapSighash, TapSighashType};
use crate::{fee, musig, util, Vtxo, VtxoRequest, VtxoSpec}; use crate::{fee, musig, util, Vtxo, VtxoRequest, VtxoSpec};
@@ -35,7 +35,7 @@ impl OorPayment {
pub fn unsigned_transaction(&self) -> Transaction { pub fn unsigned_transaction(&self) -> Transaction {
Transaction { Transaction {
version: 2, version: bitcoin::transaction::Version::TWO,
lock_time: bitcoin::absolute::LockTime::ZERO, lock_time: bitcoin::absolute::LockTime::ZERO,
input: self.inputs.iter().map(|input| { input: self.inputs.iter().map(|input| {
TxIn { TxIn {
@@ -48,7 +48,7 @@ impl OorPayment {
output: self.outputs.iter().map(|output| { output: self.outputs.iter().map(|output| {
let spk = crate::exit_spk(output.pubkey, self.asp_pubkey, self.exit_delta); let spk = crate::exit_spk(output.pubkey, self.asp_pubkey, self.exit_delta);
TxOut { TxOut {
value: output.amount.to_sat(), value: output.amount,
script_pubkey: spk, script_pubkey: spk,
} }
}).chain([fee::dust_anchor()]).collect(), }).chain([fee::dust_anchor()]).collect(),
@@ -56,7 +56,7 @@ impl OorPayment {
} }
pub fn txid(&self) -> Txid { pub fn txid(&self) -> Txid {
self.unsigned_transaction().txid() self.unsigned_transaction().compute_txid()
} }
pub fn sighashes(&self) -> Vec<TapSighash> { pub fn sighashes(&self) -> Vec<TapSighash> {
@@ -98,7 +98,7 @@ impl OorPayment {
pub fn sign_asp( pub fn sign_asp(
&self, &self,
keypair: &KeyPair, keypair: &Keypair,
user_nonces: &[musig::MusigPubNonce], user_nonces: &[musig::MusigPubNonce],
) -> (Vec<musig::MusigPubNonce>, Vec<musig::MusigPartialSignature>) { ) -> (Vec<musig::MusigPubNonce>, Vec<musig::MusigPartialSignature>) {
assert_eq!(self.inputs.len(), user_nonces.len()); assert_eq!(self.inputs.len(), user_nonces.len());
@@ -124,7 +124,7 @@ impl OorPayment {
pub fn sign_finalize_user( pub fn sign_finalize_user(
self, self,
keypair: &KeyPair, keypair: &Keypair,
our_sec_nonces: Vec<musig::MusigSecNonce>, our_sec_nonces: Vec<musig::MusigSecNonce>,
our_pub_nonces: &[musig::MusigPubNonce], our_pub_nonces: &[musig::MusigPubNonce],
asp_nonces: &[musig::MusigPubNonce], asp_nonces: &[musig::MusigPubNonce],
@@ -188,7 +188,7 @@ impl OorTransaction {
for (input, sig) in tx.input.iter_mut().zip(self.signatures.iter()) { for (input, sig) in tx.input.iter_mut().zip(self.signatures.iter()) {
assert!(input.witness.is_empty()); assert!(input.witness.is_empty());
input.witness.push(&sig[..]); input.witness.push(&sig[..]);
debug_assert_eq!(crate::TAPROOT_KEYSPEND_WEIGHT, input.witness.serialized_len()); debug_assert_eq!(crate::TAPROOT_KEYSPEND_WEIGHT, input.witness.size());
} }
//TODO(stevenroose) there seems to be a bug in the tx.weight method, //TODO(stevenroose) there seems to be a bug in the tx.weight method,
// this +2 might be fixed later // this +2 might be fixed later
@@ -203,7 +203,7 @@ impl OorTransaction {
let expiry_height = self.payment.inputs.iter().map(|i| i.spec().expiry_height).min().unwrap(); let expiry_height = self.payment.inputs.iter().map(|i| i.spec().expiry_height).min().unwrap();
let oor_tx = self.signed_transaction(); let oor_tx = self.signed_transaction();
let oor_txid = oor_tx.txid(); let oor_txid = oor_tx.compute_txid();
self.payment.outputs.iter().enumerate().map(|(idx, output)| { self.payment.outputs.iter().enumerate().map(|(idx, output)| {
Vtxo::Oor { Vtxo::Oor {
inputs: inputs.clone(), inputs: inputs.clone(),

View File

@@ -131,12 +131,12 @@ impl VtxoTreeSpec {
} }
pub fn cosign_spk(&self) -> ScriptBuf { pub fn cosign_spk(&self) -> ScriptBuf {
ScriptBuf::new_v1_p2tr_tweaked(self.cosign_taproot().output_key()) ScriptBuf::new_p2tr_tweaked(self.cosign_taproot().output_key())
} }
fn node_tx(&self, children: &[&Transaction]) -> Transaction { fn node_tx(&self, children: &[&Transaction]) -> Transaction {
Transaction { Transaction {
version: 2, version: bitcoin::transaction::Version::TWO,
lock_time: bitcoin::absolute::LockTime::ZERO, lock_time: bitcoin::absolute::LockTime::ZERO,
input: vec![TxIn { input: vec![TxIn {
previous_output: OutPoint::null(), previous_output: OutPoint::null(),
@@ -159,7 +159,7 @@ impl VtxoTreeSpec {
}; };
TxOut { TxOut {
script_pubkey: self.cosign_spk(), script_pubkey: self.cosign_spk(),
value: child.output.iter().map(|o| o.value).sum::<u64>() + fee_budget, value: child.output.iter().map(|o| o.value).sum::<Amount>() + Amount::from_sat(fee_budget),
} }
}).collect(), }).collect(),
} }
@@ -178,7 +178,7 @@ impl VtxoTreeSpec {
fn leaf_tx(&self, vtxo: &VtxoRequest) -> Transaction { fn leaf_tx(&self, vtxo: &VtxoRequest) -> Transaction {
let vtxo_spec = self.vtxo_spec(vtxo); let vtxo_spec = self.vtxo_spec(vtxo);
Transaction { Transaction {
version: 2, version: bitcoin::transaction::Version::TWO,
lock_time: bitcoin::absolute::LockTime::ZERO, lock_time: bitcoin::absolute::LockTime::ZERO,
input: vec![TxIn { input: vec![TxIn {
previous_output: OutPoint::null(), previous_output: OutPoint::null(),
@@ -189,7 +189,7 @@ impl VtxoTreeSpec {
output: vec![ output: vec![
TxOut { TxOut {
script_pubkey: vtxo_spec.exit_spk(), script_pubkey: vtxo_spec.exit_spk(),
value: vtxo.amount.to_sat(), value: vtxo.amount,
}, },
fee::dust_anchor(), fee::dust_anchor(),
], ],
@@ -205,7 +205,7 @@ impl VtxoTreeSpec {
// This is the root, set to the tree's on-chain utxo. // This is the root, set to the tree's on-chain utxo.
tree.element_at_mut(cursor).unwrap().input[0].previous_output = utxo; tree.element_at_mut(cursor).unwrap().input[0].previous_output = utxo;
while cursor >= tree.nb_leaves() { while cursor >= tree.nb_leaves() {
let txid = tree.element_at(cursor).unwrap().txid(); let txid = tree.element_at(cursor).unwrap().compute_txid();
let nb_children = tree.nb_children_of(cursor).unwrap(); let nb_children = tree.nb_children_of(cursor).unwrap();
for i in 0..nb_children { for i in 0..nb_children {
let prevout = OutPoint::new(txid, i as u32); let prevout = OutPoint::new(txid, i as u32);
@@ -228,7 +228,7 @@ impl VtxoTreeSpec {
// this is the root // this is the root
TxOut { TxOut {
script_pubkey: self.cosign_spk(), script_pubkey: self.cosign_spk(),
value: self.total_required_value().to_sat(), value: self.total_required_value(),
} }
}; };
let el = tree.element_at(idx).unwrap(); let el = tree.element_at(idx).unwrap();
@@ -313,20 +313,21 @@ mod test {
use std::str::FromStr; use std::str::FromStr;
use bitcoin::hashes::sha256; use bitcoin::hashes::{sha256, Hash};
use bitcoin::secp256k1::{self, rand, KeyPair}; use bitcoin::secp256k1::{self, rand, Keypair, Message};
use crate::musig; use crate::musig;
#[test] #[test]
fn test_node_tx_sizes() { fn test_node_tx_sizes() {
let secp = secp256k1::Secp256k1::new(); let secp = secp256k1::Secp256k1::new();
let key1 = KeyPair::new(&secp, &mut rand::thread_rng()); // asp let key1 = Keypair::new(&secp, &mut rand::thread_rng()); // asp
let key2 = KeyPair::new(&secp, &mut rand::thread_rng()); let key2 = Keypair::new(&secp, &mut rand::thread_rng());
let sha = sha256::Hash::from_str("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a").unwrap(); let sha = sha256::Hash::from_str("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a").unwrap();
let sig = secp.sign_schnorr(&sha.into(), &key1); let message = Message::from_digest_slice(&sha.to_byte_array()).unwrap();
let sig = secp.sign_schnorr(&message, &key1);
let dest = VtxoRequest { let dest = VtxoRequest {
pubkey: KeyPair::new(&secp, &mut rand::thread_rng()).public_key(), pubkey: Keypair::new(&secp, &mut rand::thread_rng()).public_key(),
amount: Amount::from_sat(100_000), amount: Amount::from_sat(100_000),
}; };
let point = "0000000000000000000000000000000000000000000000000000000000000001:1".parse().unwrap(); let point = "0000000000000000000000000000000000000000000000000000000000000001:1".parse().unwrap();
@@ -352,7 +353,7 @@ mod test {
let mut iter = exit.iter().enumerate().peekable(); let mut iter = exit.iter().enumerate().peekable();
while let Some((i, cur)) = iter.next() { while let Some((i, cur)) = iter.next() {
if let Some((_, next)) = iter.peek() { if let Some((_, next)) = iter.peek() {
assert_eq!(next.input[0].previous_output.txid, cur.txid(), "{}", i); assert_eq!(next.input[0].previous_output.txid, cur.compute_txid(), "{}", i);
} }
} }
@@ -362,7 +363,7 @@ mod test {
assert_eq!(leaf.vsize() as u64, LEAF_TX_VSIZE); assert_eq!(leaf.vsize() as u64, LEAF_TX_VSIZE);
for node in iter { for node in iter {
assert_eq!( assert_eq!(
node.input[0].witness.serialized_len(), node.input[0].witness.size(),
crate::TAPROOT_KEYSPEND_WEIGHT, crate::TAPROOT_KEYSPEND_WEIGHT,
); );
match node.output.len() { match node.output.len() {

View File

@@ -2,16 +2,16 @@
use std::borrow::Borrow; use std::borrow::Borrow;
use bitcoin::{opcodes, taproot, ScriptBuf}; use bitcoin::{opcodes, taproot, ScriptBuf};
use bitcoin::secp256k1::{self, KeyPair, XOnlyPublicKey}; use bitcoin::secp256k1::{self, Keypair, XOnlyPublicKey};
lazy_static::lazy_static! { lazy_static::lazy_static! {
/// Global secp context. /// Global secp context.
pub static ref SECP: secp256k1::Secp256k1<secp256k1::All> = secp256k1::Secp256k1::new(); pub static ref SECP: secp256k1::Secp256k1<secp256k1::All> = secp256k1::Secp256k1::new();
} }
pub trait KeyPairExt: Borrow<KeyPair> { pub trait KeypairExt: Borrow<Keypair> {
/// Adapt this key pair to be used in a key-spend-only taproot. /// Adapt this key pair to be used in a key-spend-only taproot.
fn for_keyspend(&self) -> KeyPair { fn for_keyspend(&self) -> Keypair {
let tweak = taproot::TapTweakHash::from_key_and_tweak( let tweak = taproot::TapTweakHash::from_key_and_tweak(
self.borrow().x_only_public_key().0, None, self.borrow().x_only_public_key().0, None,
).to_scalar(); ).to_scalar();
@@ -19,7 +19,7 @@ pub trait KeyPairExt: Borrow<KeyPair> {
} }
} }
impl KeyPairExt for KeyPair {} impl KeypairExt for Keypair {}
/// Create a tapscript that is a checksig and a relative timelock. /// Create a tapscript that is a checksig and a relative timelock.
pub fn delayed_sign(delay_blocks: u16, pubkey: XOnlyPublicKey) -> ScriptBuf { pub fn delayed_sign(delay_blocks: u16, pubkey: XOnlyPublicKey) -> ScriptBuf {

View File

@@ -22,7 +22,7 @@ serde_json.workspace = true
ciborium.workspace = true ciborium.workspace = true
bitcoin.workspace = true bitcoin.workspace = true
bip39.workspace = true bip39.workspace = true
bdk.workspace = true bdk_wallet.workspace = true
bdk_file_store.workspace = true bdk_file_store.workspace = true
bdk_bitcoind_rpc.workspace = true bdk_bitcoind_rpc.workspace = true
prost.workspace = true prost.workspace = true

View File

@@ -102,7 +102,7 @@ pub struct StoredRound {
impl StoredRound { impl StoredRound {
pub fn id(&self) -> Txid { pub fn id(&self) -> Txid {
self.tx.txid() self.tx.compute_txid()
} }
fn encode(&self) -> Vec<u8> { fn encode(&self) -> Vec<u8> {

View File

@@ -20,12 +20,12 @@ use std::str::FromStr;
use std::time::Duration; use std::time::Duration;
use anyhow::Context; use anyhow::Context;
use bdk_bitcoind_rpc::bitcoincore_rpc::RpcApi; use bdk_bitcoind_rpc::bitcoincore_rpc::{RawTx, RpcApi};
use bitcoin::{bip32, sighash, psbt, taproot, Amount, Address, OutPoint, Transaction, Witness}; use bitcoin::{bip32, sighash, psbt, taproot, Amount, Address, OutPoint, Transaction, Witness};
use bitcoin::secp256k1::{self, KeyPair}; use bitcoin::secp256k1::{self, Keypair};
use tokio::sync::{Mutex, RwLock}; use tokio::sync::{Mutex, RwLock};
use ark::util::KeyPairExt; use ark::util::KeypairExt;
use ark::musig; use ark::musig;
use crate::psbtext::{PsbtInputExt, RoundMeta}; use crate::psbtext::{PsbtInputExt, RoundMeta};
@@ -91,11 +91,11 @@ pub struct RoundHandle {
pub struct App { pub struct App {
config: Config, config: Config,
db: database::Db, db: database::Db,
master_xpriv: bip32::ExtendedPrivKey, master_xpriv: bip32::Xpriv,
master_key: KeyPair, master_key: Keypair,
wallet: Mutex<bdk::Wallet<bdk_file_store::Store<'static, bdk::wallet::ChangeSet>>>, wallet: Mutex<bdk_wallet::Wallet>,
wallet_store: Mutex<bdk_file_store::Store<bdk_wallet::wallet::ChangeSet>>,
bitcoind: bdk_bitcoind_rpc::bitcoincore_rpc::Client, bitcoind: bdk_bitcoind_rpc::bitcoincore_rpc::Client,
rounds: Option<RoundHandle>, rounds: Option<RoundHandle>,
} }
@@ -146,23 +146,26 @@ impl App {
.context("db error")? .context("db error")?
.context("db doesn't contain seed")?; .context("db doesn't contain seed")?;
let (master_key, xpriv) = { let (master_key, xpriv) = {
let seed_xpriv = bip32::ExtendedPrivKey::new_master(config.network, &seed).unwrap(); let seed_xpriv = bip32::Xpriv::new_master(config.network, &seed).unwrap();
let path = bip32::DerivationPath::from_str("m/0").unwrap(); let path = bip32::DerivationPath::from_str("m/0").unwrap();
let xpriv = seed_xpriv.derive_priv(&SECP, &path).unwrap(); let xpriv = seed_xpriv.derive_priv(&SECP, &path).unwrap();
let keypair = KeyPair::from_secret_key(&SECP, &xpriv.private_key); let keypair = Keypair::from_secret_key(&SECP, &xpriv.private_key);
(keypair, xpriv) (keypair, xpriv)
}; };
let wallet = {
let db_path = datadir.join("wallet.db"); let db_path = datadir.join("wallet.db");
info!("Loading wallet db from {}", db_path.display()); info!("Loading wallet db from {}", db_path.display());
let db = bdk_file_store::Store::<bdk::wallet::ChangeSet>::open_or_create_new( let mut file_store = bdk_file_store::Store::<bdk_wallet::wallet::ChangeSet>::open_or_create_new(
DB_MAGIC.as_bytes(), db_path, DB_MAGIC.as_bytes(), db_path,
)?; )?;
let wallet = {
let change_set = file_store.aggregate_changesets()?;
let desc = format!("tr({})", xpriv); let edesc = format!("tr({}/84'/0'/0'/0/*)", xpriv);
debug!("Opening BDK wallet with descriptor {}", desc); let idesc = format!("tr({}/84'/0'/0'/1/*)", xpriv);
bdk::Wallet::new_or_load(&desc, None, db, config.network)
debug!("Opening BDK wallet with descriptor {}", edesc);
bdk_wallet::Wallet::new_or_load(&edesc, &idesc, change_set, config.network)
.context("failed to create or load bdk wallet")? .context("failed to create or load bdk wallet")?
}; };
@@ -173,12 +176,13 @@ impl App {
Ok(Arc::new(App { Ok(Arc::new(App {
config: config, config,
db: db, db,
master_xpriv: xpriv, master_xpriv: xpriv,
master_key: master_key, master_key,
wallet: Mutex::new(wallet), wallet: Mutex::new(wallet),
bitcoind: bitcoind, wallet_store: Mutex::new(file_store),
bitcoind,
rounds: None, rounds: None,
})) }))
} }
@@ -192,9 +196,9 @@ impl App {
mut_self.rounds = Some(RoundHandle { mut_self.rounds = Some(RoundHandle {
round_busy: RwLock::new(()), round_busy: RwLock::new(()),
round_event_tx: round_event_tx, round_event_tx,
round_input_tx: round_input_tx, round_input_tx,
round_trigger_tx: round_trigger_tx, round_trigger_tx,
}); });
let app = self.clone(); let app = self.clone();
@@ -237,14 +241,15 @@ impl App {
pub async fn onchain_address(&self) -> anyhow::Result<Address> { pub async fn onchain_address(&self) -> anyhow::Result<Address> {
let mut wallet = self.wallet.lock().await; let mut wallet = self.wallet.lock().await;
let ret = wallet.try_get_address(bdk::wallet::AddressIndex::New)?.address; let ret = wallet.next_unused_address(bdk_wallet::KeychainKind::External).address;
// should always return the same address // should always return the same address
debug_assert_eq!(ret, wallet.try_get_address(bdk::wallet::AddressIndex::New)?.address); debug_assert_eq!(ret, wallet.next_unused_address(bdk_wallet::KeychainKind::External).address);
Ok(ret) Ok(ret)
} }
pub async fn sync_onchain_wallet(& self) -> anyhow::Result<Amount> { pub async fn sync_onchain_wallet(& self) -> anyhow::Result<Amount> {
let mut wallet = self.wallet.lock().await; let mut wallet = self.wallet.lock().await;
let mut file_store = self.wallet_store.lock().await;
let prev_tip = wallet.latest_checkpoint(); let prev_tip = wallet.latest_checkpoint();
// let keychain_spks = self.wallet.spks_of_all_keychains(); // let keychain_spks = self.wallet.spks_of_all_keychains();
@@ -255,29 +260,33 @@ impl App {
if em.block_height() % 10_000 == 0 { if em.block_height() % 10_000 == 0 {
debug!("Synced until block {}, committing...", em.block_height()); debug!("Synced until block {}, committing...", em.block_height());
wallet.commit()?; if let Some(change_set) = wallet.take_staged() {
file_store.append_changeset(&change_set)?;
}
} }
} }
// mempool // mempool
let mempool = emitter.mempool()?; let mempool = emitter.mempool()?;
wallet.apply_unconfirmed_txs(mempool.iter().map(|(tx, time)| (tx, *time))); wallet.apply_unconfirmed_txs(mempool.iter().map(|(tx, time)| (tx, *time)));
wallet.commit()?; if let Some(change_set) = wallet.take_staged() {
file_store.append_changeset(&change_set)?;
}
// rebroadcast unconfirmed txs // rebroadcast unconfirmed txs
// NB during some round failures we commit a tx but fail to broadcast it, // NB during some round failures we commit a tx but fail to broadcast it,
// so this ensures we still broadcast them afterwards // so this ensures we still broadcast them afterwards
for tx in wallet.transactions() { for tx in wallet.transactions() {
if !tx.chain_position.is_confirmed() { if !tx.chain_position.is_confirmed() {
let bc = self.bitcoind.send_raw_transaction(tx.tx_node.tx); let bc = self.bitcoind.send_raw_transaction(tx.tx_node.tx.raw_hex());
if let Err(e) = bc { if let Err(e) = bc {
warn!("Error broadcasting pending tx: {}", e); warn!("Error broadcasting pending tx: {}", e);
} }
} }
} }
let balance = wallet.get_balance(); let balance = wallet.balance();
Ok(Amount::from_sat(balance.total())) Ok(balance.total())
} }
pub async fn drain( pub async fn drain(
@@ -288,20 +297,23 @@ impl App {
let addr = address.require_network(self.config.network)?; let addr = address.require_network(self.config.network)?;
let mut file_store = self.wallet_store.lock().await;
let mut wallet = self.wallet.lock().await; let mut wallet = self.wallet.lock().await;
let mut b = wallet.build_tx(); let mut b = wallet.build_tx();
b.drain_to(addr.script_pubkey()); b.drain_to(addr.script_pubkey());
b.drain_wallet(); b.drain_wallet();
let mut psbt = b.finish().context("error building tx")?; let mut psbt = b.finish().context("error building tx")?;
let finalized = wallet.sign(&mut psbt, bdk::SignOptions::default())?; let finalized = wallet.sign(&mut psbt, bdk_wallet::SignOptions::default())?;
assert!(finalized); assert!(finalized);
let tx = psbt.extract_tx(); let tx = psbt.extract_tx()?;
wallet.commit()?; if let Some(change_set) = wallet.take_staged() {
file_store.append_changeset(&change_set)?;
};
drop(wallet); drop(wallet);
if let Err(e) = self.bitcoind.send_raw_transaction(&tx) { if let Err(e) = self.bitcoind.send_raw_transaction(tx.raw_hex()) {
error!("Error broadcasting tx: {}", e); error!("Error broadcasting tx: {}", e);
error!("Try yourself: {}", bitcoin::consensus::encode::serialize_hex(&tx)); error!("Try yourself: {}", tx.raw_hex());
} }
Ok(tx) Ok(tx)
@@ -404,7 +416,7 @@ impl App {
let wit = Witness::from_slice( let wit = Witness::from_slice(
&[&sig[..], script.as_bytes(), &control.serialize()], &[&sig[..], script.as_bytes(), &control.serialize()],
); );
debug_assert_eq!(wit.serialized_len(), ark::tree::signed::NODE_SPEND_WEIGHT); debug_assert_eq!(wit.size(), ark::tree::signed::NODE_SPEND_WEIGHT);
input.final_script_witness = Some(wit); input.final_script_witness = Some(wit);
}, },
RoundMeta::Connector => { RoundMeta::Connector => {
@@ -443,6 +455,6 @@ pub(crate) struct SpendableUtxo {
impl SpendableUtxo { impl SpendableUtxo {
pub fn amount(&self) -> Amount { pub fn amount(&self) -> Amount {
Amount::from_sat(self.psbt.witness_utxo.as_ref().unwrap().value) self.psbt.witness_utxo.as_ref().unwrap().value
} }
} }

View File

@@ -137,7 +137,7 @@ async fn inner_main() -> anyhow::Result<()> {
}, },
Command::Drain { address } => { Command::Drain { address } => {
let app = App::open(&cli.datadir.context("need datadir")?).context("server init")?; let app = App::open(&cli.datadir.context("need datadir")?).context("server init")?;
println!("{}", app.drain(address).await?.txid()); println!("{}", app.drain(address).await?.compute_txid());
}, },
Command::GetMnemonic => { Command::GetMnemonic => {
let app = App::open(&cli.datadir.context("need datadir")?).context("server init")?; let app = App::open(&cli.datadir.context("need datadir")?).context("server init")?;

View File

@@ -9,7 +9,7 @@ use bdk_bitcoind_rpc::bitcoincore_rpc::RpcApi;
use bitcoin::{Amount, FeeRate, OutPoint, Sequence, Transaction}; use bitcoin::{Amount, FeeRate, OutPoint, Sequence, Transaction};
use bitcoin::hashes::Hash; use bitcoin::hashes::Hash;
use bitcoin::locktime::absolute::LockTime; use bitcoin::locktime::absolute::LockTime;
use bitcoin::secp256k1::{rand, KeyPair, PublicKey}; use bitcoin::secp256k1::{rand, Keypair, PublicKey};
use bitcoin::sighash::TapSighash; use bitcoin::sighash::TapSighash;
use ark::{musig, OffboardRequest, VtxoRequest, Vtxo, VtxoId}; use ark::{musig, OffboardRequest, VtxoRequest, Vtxo, VtxoId};
@@ -205,7 +205,7 @@ pub async fn run_round_scheduler(
// NB allowed_inputs should NOT be cleared here. // NB allowed_inputs should NOT be cleared here.
// Generate a one-time use signing key. // Generate a one-time use signing key.
let cosign_key = KeyPair::new(&SECP, &mut rand::thread_rng()); let cosign_key = Keypair::new(&SECP, &mut rand::thread_rng());
cosigners.insert(cosign_key.public_key()); cosigners.insert(cosign_key.public_key());
// Start receiving payments. // Start receiving payments.
@@ -322,24 +322,24 @@ pub async fn run_round_scheduler(
let mut wallet = app.wallet.lock().await; let mut wallet = app.wallet.lock().await;
let mut round_tx_psbt = { let mut round_tx_psbt = {
let mut b = wallet.build_tx(); let mut b = wallet.build_tx();
b.ordering(bdk::wallet::tx_builder::TxOrdering::Untouched); b.ordering(bdk_wallet::wallet::tx_builder::TxOrdering::Untouched);
b.nlocktime(LockTime::from_height(tip).expect("actual height")); b.nlocktime(LockTime::from_height(tip).expect("actual height"));
for utxo in &spendable_utxos { for utxo in &spendable_utxos {
b.add_foreign_utxo_with_sequence( b.add_foreign_utxo_with_sequence(
utxo.point, utxo.psbt.clone(), utxo.weight, Sequence::ZERO, utxo.point, utxo.psbt.clone(), utxo.weight, Sequence::ZERO,
).expect("bdk rejected foreign utxo"); ).expect("bdk rejected foreign utxo");
} }
b.add_recipient(vtxos_spec.cosign_spk(), vtxos_spec.total_required_value().to_sat()); b.add_recipient(vtxos_spec.cosign_spk(), vtxos_spec.total_required_value());
b.add_recipient(connector_output.script_pubkey, connector_output.value); b.add_recipient(connector_output.script_pubkey, connector_output.value);
for offb in &all_offboards { for offb in &all_offboards {
b.add_recipient(offb.script_pubkey.clone(), offb.amount.to_sat()); b.add_recipient(offb.script_pubkey.clone(), offb.amount);
} }
b.fee_rate(round_tx_feerate.to_bdk()); b.fee_rate(round_tx_feerate.to_bdk());
b.finish().expect("bdk failed to create round tx") b.finish().expect("bdk failed to create round tx")
}; };
let round_tx = round_tx_psbt.clone().extract_tx(); let round_tx = round_tx_psbt.clone().extract_tx()?;
let vtxos_utxo = OutPoint::new(round_tx.txid(), 0); let vtxos_utxo = OutPoint::new(round_tx.compute_txid(), 0);
let conns_utxo = OutPoint::new(round_tx.txid(), 1); let conns_utxo = OutPoint::new(round_tx.compute_txid(), 1);
// Generate vtxo nonces and combine with user's nonces. // Generate vtxo nonces and combine with user's nonces.
let (sec_vtxo_nonces, pub_vtxo_nonces) = { let (sec_vtxo_nonces, pub_vtxo_nonces) = {
@@ -570,18 +570,21 @@ pub async fn run_round_scheduler(
// Sign the on-chain tx. // Sign the on-chain tx.
app.sign_round_utxo_inputs(&mut round_tx_psbt).context("signing round inputs")?; app.sign_round_utxo_inputs(&mut round_tx_psbt).context("signing round inputs")?;
let opts = bdk::SignOptions { let opts = bdk_wallet::SignOptions {
trust_witness_utxo: true, trust_witness_utxo: true,
..Default::default() ..Default::default()
}; };
let finalized = wallet.sign(&mut round_tx_psbt, opts)?; let finalized = wallet.sign(&mut round_tx_psbt, opts)?;
assert!(finalized); assert!(finalized);
let round_tx = round_tx_psbt.extract_tx(); let round_tx = round_tx_psbt.extract_tx()?;
wallet.commit()?; let mut file_store = app.wallet_store.lock().await;
if let Some(change_set) = wallet.take_staged() {
file_store.append_changeset(&change_set)?;
}
drop(wallet); // we no longer need the lock drop(wallet); // we no longer need the lock
// Broadcast over bitcoind. // Broadcast over bitcoind.
debug!("Broadcasting round tx {}", round_tx.txid()); debug!("Broadcasting round tx {}", round_tx.compute_txid());
let bc = app.bitcoind.send_raw_transaction(&round_tx); let bc = app.bitcoind.send_raw_transaction(&round_tx);
if let Err(e) = bc { if let Err(e) = bc {
warn!("Couldn't broadcast round tx: {}", e); warn!("Couldn't broadcast round tx: {}", e);
@@ -596,7 +599,7 @@ pub async fn run_round_scheduler(
}); });
// Store forfeit txs and round info in database. // Store forfeit txs and round info in database.
let round_id = round_tx.txid(); let round_id = round_tx.compute_txid();
for (id, vtxo) in all_inputs { for (id, vtxo) in all_inputs {
let forfeit_sigs = forfeit_sigs.remove(&id).unwrap(); let forfeit_sigs = forfeit_sigs.remove(&id).unwrap();
let point = vtxo.point(); let point = vtxo.point();
@@ -615,7 +618,7 @@ pub async fn run_round_scheduler(
app.db.remove_round(round)?; app.db.remove_round(round)?;
} }
info!("Finished round {} with tx {}", round_id, round_tx.txid()); info!("Finished round {} with tx {}", round_id, round_tx.compute_txid());
break 'attempt; break 'attempt;
} }
} }

View File

@@ -5,8 +5,8 @@ use std::borrow::Borrow;
use bitcoin::FeeRate; use bitcoin::FeeRate;
pub trait FeeRateExt: Borrow<FeeRate> + Copy { pub trait FeeRateExt: Borrow<FeeRate> + Copy {
fn to_bdk(self) -> bdk::FeeRate { fn to_bdk(self) -> bitcoin::FeeRate {
bdk::FeeRate::from_sat_per_kwu(self.borrow().to_sat_per_kwu() as f32) bitcoin::FeeRate::from_sat_per_kwu(self.borrow().to_sat_per_kwu())
} }
} }

View File

@@ -29,7 +29,7 @@ ciborium.workspace = true
bitcoin.workspace = true bitcoin.workspace = true
bip39.workspace = true bip39.workspace = true
miniscript.workspace = true miniscript.workspace = true
bdk.workspace = true bdk_wallet.workspace = true
bdk_file_store.workspace = true bdk_file_store.workspace = true
bdk_bitcoind_rpc.workspace = true bdk_bitcoind_rpc.workspace = true
bdk_esplora.workspace = true bdk_esplora.workspace = true

View File

@@ -116,10 +116,10 @@ impl Wallet {
// Broadcast exit txs. // Broadcast exit txs.
for tx in &exit.broadcast { for tx in &exit.broadcast {
trace!("Broadcasting tx {}: {}", tx.txid(), bitcoin::consensus::encode::serialize_hex(tx)); trace!("Broadcasting tx {}: {}", tx.compute_txid(), bitcoin::consensus::encode::serialize_hex(tx));
if let Err(e) = self.onchain.broadcast_tx(tx).await { if let Err(e) = self.onchain.broadcast_tx(tx).await {
error!("Error broadcasting exit tx {}: {}", tx.txid(), e); error!("Error broadcasting exit tx {}: {}", tx.compute_txid(), e);
error!("Tx {}: {}", tx.txid(), bitcoin::consensus::encode::serialize_hex(tx)); error!("Tx {}: {}", tx.compute_txid(), bitcoin::consensus::encode::serialize_hex(tx));
} }
} }
@@ -145,7 +145,7 @@ impl Wallet {
// Then we'll send a tx that will pay the fee for all the txs we made. // Then we'll send a tx that will pay the fee for all the txs we made.
let tx = self.onchain.spend_fee_anchors(&exit.fee_anchors, exit.total_size).await?; let tx = self.onchain.spend_fee_anchors(&exit.fee_anchors, exit.total_size).await?;
info!("Sent anchor spend tx: {}", tx.txid()); info!("Sent anchor spend tx: {}", tx.compute_txid());
// After we succesfully stored the claim inputs, we can drop the vtxos. // After we succesfully stored the claim inputs, we can drop the vtxos.
for id in exit.started { for id in exit.started {
@@ -232,7 +232,7 @@ impl Wallet {
let wit = Witness::from_slice( let wit = Witness::from_slice(
&[&sig[..], exit_script.as_bytes(), &cb.serialize()], &[&sig[..], exit_script.as_bytes(), &cb.serialize()],
); );
debug_assert_eq!(wit.serialized_len(), claim.satisfaction_weight()); debug_assert_eq!(wit.size(), claim.satisfaction_weight());
input.final_script_witness = Some(wit); input.final_script_witness = Some(wit);
} }

View File

@@ -17,7 +17,7 @@ use std::str::FromStr;
use anyhow::{bail, Context}; use anyhow::{bail, Context};
use bitcoin::{bip32, secp256k1, Address, Amount, FeeRate, Network, OutPoint, Transaction, Txid}; use bitcoin::{bip32, secp256k1, Address, Amount, FeeRate, Network, OutPoint, Transaction, Txid};
use bitcoin::hashes::Hash; use bitcoin::hashes::Hash;
use bitcoin::secp256k1::{rand, KeyPair, PublicKey}; use bitcoin::secp256k1::{rand, Keypair, PublicKey};
use tokio_stream::StreamExt; use tokio_stream::StreamExt;
use ark::{musig, BaseVtxo, OffboardRequest, VtxoRequest, Vtxo, VtxoId, VtxoSpec}; use ark::{musig, BaseVtxo, OffboardRequest, VtxoRequest, Vtxo, VtxoId, VtxoSpec};
@@ -98,7 +98,7 @@ pub struct Wallet {
config: Config, config: Config,
db: database::Db, db: database::Db,
onchain: onchain::Wallet, onchain: onchain::Wallet,
vtxo_seed: bip32::ExtendedPrivKey, vtxo_seed: bip32::Xpriv,
// ASP stuff // ASP stuff
asp: rpc::ArkServiceClient<tonic::transport::Channel>, asp: rpc::ArkServiceClient<tonic::transport::Channel>,
ark_info: ArkInfo, ark_info: ArkInfo,
@@ -184,7 +184,7 @@ impl Wallet {
}; };
onchain::ChainSource::Bitcoind { onchain::ChainSource::Bitcoind {
url: url.clone(), url: url.clone(),
auth: auth, auth,
} }
} else { } else {
bail!("Need to either provide esplora or bitcoind info"); bail!("Need to either provide esplora or bitcoind info");
@@ -195,7 +195,7 @@ impl Wallet {
let db = database::Db::open(&datadir.join("db")).context("failed to open db")?; let db = database::Db::open(&datadir.join("db")).context("failed to open db")?;
let vtxo_seed = { let vtxo_seed = {
let master = bip32::ExtendedPrivKey::new_master(config.network, &seed).unwrap(); let master = bip32::Xpriv::new_master(config.network, &seed).unwrap();
master.derive_priv(&SECP, &[350.into()]).unwrap() master.derive_priv(&SECP, &[350.into()]).unwrap()
}; };
@@ -277,7 +277,7 @@ impl Wallet {
asp_pubkey: self.ark_info.asp_pubkey, asp_pubkey: self.ark_info.asp_pubkey,
expiry_height: current_height + self.ark_info.vtxo_expiry_delta as u32, expiry_height: current_height + self.ark_info.vtxo_expiry_delta as u32,
exit_delta: self.ark_info.vtxo_exit_delta, exit_delta: self.ark_info.vtxo_exit_delta,
amount: amount, amount,
}; };
let onboard_amount = amount + ark::onboard::onboard_surplus(); let onboard_amount = amount + ark::onboard::onboard_surplus();
let addr = Address::from_script(&ark::onboard::onboard_spk(&spec), self.config.network).unwrap(); let addr = Address::from_script(&ark::onboard::onboard_spk(&spec), self.config.network).unwrap();
@@ -285,7 +285,7 @@ impl Wallet {
// We create the onboard tx template, but don't sign it yet. // We create the onboard tx template, but don't sign it yet.
self.onchain.sync().await.context("sync error")?; self.onchain.sync().await.context("sync error")?;
let onboard_tx = self.onchain.prepare_tx(addr, onboard_amount)?; let onboard_tx = self.onchain.prepare_tx(addr, onboard_amount)?;
let utxo = OutPoint::new(onboard_tx.unsigned_tx.txid(), 0); let utxo = OutPoint::new(onboard_tx.unsigned_tx.compute_txid(), 0);
// We ask the ASP to cosign our onboard vtxo reveal tx. // We ask the ASP to cosign our onboard vtxo reveal tx.
let (user_part, priv_user_part) = ark::onboard::new_user(spec, utxo); let (user_part, priv_user_part) = ark::onboard::new_user(spec, utxo);
@@ -332,8 +332,8 @@ impl Wallet {
}, },
utxo: vtxos.utxo, utxo: vtxos.utxo,
}, },
leaf_idx: leaf_idx, leaf_idx,
exit_branch: exit_branch, exit_branch,
}; };
if self.db.has_forfeited_vtxo(vtxo.id())? { if self.db.has_forfeited_vtxo(vtxo.id())? {
@@ -571,7 +571,6 @@ impl Wallet {
} }
pub async fn send_ark_onchain_payment(&mut self, addr: Address, amount: Amount) -> anyhow::Result<()> { pub async fn send_ark_onchain_payment(&mut self, addr: Address, amount: Amount) -> anyhow::Result<()> {
ensure!(addr.network == self.config.network, "invalid addr network");
//TODO(stevenroose) impl key derivation //TODO(stevenroose) impl key derivation
let vtxo_key = self.vtxo_seed.to_keypair(&SECP); let vtxo_key = self.vtxo_seed.to_keypair(&SECP);
@@ -592,7 +591,7 @@ impl Wallet {
self.participate_round(move |_id, offb_fr| { self.participate_round(move |_id, offb_fr| {
let offb = OffboardRequest { let offb = OffboardRequest {
script_pubkey: addr.script_pubkey(), script_pubkey: addr.script_pubkey(),
amount: amount, amount,
}; };
let out_value = amount + offb.fee(offb_fr).expect("script from address"); let out_value = amount + offb.fee(offb_fr).expect("script from address");
let change = { let change = {
@@ -659,7 +658,7 @@ impl Wallet {
'round: loop { 'round: loop {
let cosign_key = KeyPair::new(&SECP, &mut rand::thread_rng()); let cosign_key = Keypair::new(&SECP, &mut rand::thread_rng());
debug!("Participating in round {} with cosign pubkey {}", debug!("Participating in round {} with cosign pubkey {}",
round_id, cosign_key.public_key(), round_id, cosign_key.public_key(),
); );
@@ -736,8 +735,8 @@ impl Wallet {
} }
}; };
let vtxos_utxo = OutPoint::new(round_tx.txid(), 0); let vtxos_utxo = OutPoint::new(round_tx.compute_txid(), 0);
let conns_utxo = OutPoint::new(round_tx.txid(), 1); let conns_utxo = OutPoint::new(round_tx.compute_txid(), 1);
// Check that the proposal contains our inputs. // Check that the proposal contains our inputs.
let mut my_vtxos = vtxo_reqs.clone(); let mut my_vtxos = vtxo_reqs.clone();
@@ -911,7 +910,7 @@ impl Wallet {
} }
// We also broadcast the tx, just to have it go around faster. // We also broadcast the tx, just to have it go around faster.
info!("Round finished, broadcasting round tx {}", round_tx.txid()); info!("Round finished, broadcasting round tx {}", round_tx.compute_txid());
if let Err(e) = self.onchain.broadcast_tx(&round_tx).await { if let Err(e) = self.onchain.broadcast_tx(&round_tx).await {
warn!("Couldn't broadcast round tx: {}", e); warn!("Couldn't broadcast round tx: {}", e);
} }

View File

@@ -2,16 +2,18 @@
mod chain; mod chain;
pub use self::chain::ChainSource; pub use self::chain::ChainSource;
use std::collections::BTreeSet;
use std::io::Write;
use std::path::Path; use std::path::Path;
use anyhow::Context; use anyhow::Context;
use bdk::SignOptions; use bdk_wallet::{KeychainKind, SignOptions};
use bdk_file_store::Store; use bdk_file_store::Store;
use bdk_esplora::EsploraAsyncExt; use bdk_esplora::EsploraAsyncExt;
use bitcoin::{ use bitcoin::{
bip32, psbt, Address, Amount, Network, OutPoint, Sequence, Transaction, TxOut, Txid, bip32, psbt, Address, Amount, Network, OutPoint, Script, Sequence, Transaction, TxOut, Txid
}; };
use bitcoin::psbt::PartiallySignedTransaction as Psbt; //TODO(stevenroose) when v0.31 use bitcoin::psbt::Psbt; //TODO(stevenroose) when v0.31
use crate::exit; use crate::exit;
use crate::psbtext::PsbtInputExt; use crate::psbtext::PsbtInputExt;
@@ -20,7 +22,8 @@ use self::chain::ChainSourceClient;
const DB_MAGIC: &str = "onchain_bdk"; const DB_MAGIC: &str = "onchain_bdk";
pub struct Wallet { pub struct Wallet {
wallet: bdk::Wallet<Store<'static, bdk::wallet::ChangeSet>>, wallet: bdk_wallet::Wallet,
file_store: Store<bdk_wallet::wallet::ChangeSet>,
chain_source: ChainSourceClient, chain_source: ChainSourceClient,
} }
@@ -32,18 +35,19 @@ impl Wallet {
chain_source: ChainSource, chain_source: ChainSource,
) -> anyhow::Result<Wallet> { ) -> anyhow::Result<Wallet> {
let db_path = dir.join("bdkwallet.db"); let db_path = dir.join("bdkwallet.db");
let db = Store::<bdk::wallet::ChangeSet>::open_or_create_new(DB_MAGIC.as_bytes(), db_path)?; let mut db = Store::<bdk_wallet::wallet::ChangeSet>::open_or_create_new(DB_MAGIC.as_bytes(), db_path)?;
let change_set = db.aggregate_changesets()?;
//TODO(stevenroose) taproot? //TODO(stevenroose) taproot?
let xpriv = bip32::ExtendedPrivKey::new_master(network, &seed).expect("valid seed"); let xpriv = bip32::Xpriv::new_master(network, &seed).expect("valid seed");
let edesc = format!("tr({}/84'/0'/0'/0/*)", xpriv); let edesc = format!("tr({}/84'/0'/0'/0/*)", xpriv);
let idesc = format!("tr({}/84'/0'/0'/1/*)", xpriv); let idesc = format!("tr({}/84'/0'/0'/1/*)", xpriv);
let wallet = bdk::Wallet::new_or_load(&edesc, Some(&idesc), db, network) let wallet = bdk_wallet::Wallet::new_or_load(&edesc, &idesc, change_set, network)
.context("failed to create or load bdk wallet")?; .context("failed to create or load bdk wallet")?;
let chain_source = ChainSourceClient::new(chain_source)?; let chain_source = ChainSourceClient::new(chain_source)?;
Ok(Wallet { wallet, chain_source }) Ok(Wallet { wallet, chain_source, file_store: db })
} }
pub async fn tip(&self) -> anyhow::Result<u32> { pub async fn tip(&self) -> anyhow::Result<u32> {
@@ -58,6 +62,18 @@ impl Wallet {
self.chain_source.txout_confirmations(outpoint).await self.chain_source.txout_confirmations(outpoint).await
} }
fn generate_inspect(kind: KeychainKind) -> impl FnMut(u32, &Script) + Send + Sync + 'static {
let mut once = Some(());
let mut stdout = std::io::stdout();
move |spk_i, _| {
match once.take() {
Some(_) => print!("\nScanning keychain [{:?}]", kind),
None => print!(" {:<3}", spk_i),
};
stdout.flush().expect("must flush");
}
}
pub async fn sync(&mut self) -> anyhow::Result<Amount> { pub async fn sync(&mut self) -> anyhow::Result<Amount> {
debug!("Starting wallet sync..."); debug!("Starting wallet sync...");
@@ -71,40 +87,59 @@ impl Wallet {
self.wallet.apply_block_connected_to( self.wallet.apply_block_connected_to(
&em.block, em.block_height(), em.connected_to(), &em.block, em.block_height(), em.connected_to(),
)?; )?;
self.wallet.commit()?; if let Some(change_set) = self.wallet.take_staged() {
self.file_store.append_changeset(&change_set)?;
}
} }
let mempool = emitter.mempool()?; let mempool = emitter.mempool()?;
self.wallet.apply_unconfirmed_txs(mempool.iter().map(|(tx, time)| (tx, *time))); self.wallet.apply_unconfirmed_txs(mempool.iter().map(|(tx, time)| (tx, *time)));
self.wallet.commit()?; if let Some(change_set) = self.wallet.take_staged() {
self.file_store.append_changeset(&change_set)?;
}
}, },
ChainSourceClient::Esplora(ref client) => { ChainSourceClient::Esplora(ref client) => {
let request = self.wallet.start_full_scan().inspect_spks_for_all_keychains({
let mut once = BTreeSet::<KeychainKind>::new();
move |keychain, spk_i, _| {
match once.insert(keychain) {
true => print!("\nScanning keychain [{:?}]", keychain),
false => print!(" {:<3}", spk_i),
}
std::io::stdout().flush().expect("must flush")
}
}).inspect_spks_for_keychain(
KeychainKind::External,
Self::generate_inspect(KeychainKind::External),
)
.inspect_spks_for_keychain(
KeychainKind::Internal,
Self::generate_inspect(KeychainKind::Internal),
);
const STOP_GAP: usize = 50; const STOP_GAP: usize = 50;
const PARALLEL_REQUESTS: usize = 5;
let mut update = client
.full_scan(request, STOP_GAP, PARALLEL_REQUESTS)
.await?;
let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
let _ = update.graph_update.update_last_seen_unconfirmed(now);
let prev_tip = self.wallet.latest_checkpoint();
let keychain_spks = self.wallet.spks_of_all_keychains().into_iter().collect();
let (update_graph, last_active_indices) =
client.full_scan(keychain_spks, STOP_GAP, 4).await?;
let missing_heights = update_graph.missing_heights(self.wallet.local_chain());
let chain_update = client.update_local_chain(prev_tip, missing_heights).await?;
let update = bdk::wallet::Update {
last_active_indices,
graph: update_graph,
chain: Some(chain_update),
};
self.wallet.apply_update(update)?; self.wallet.apply_update(update)?;
self.wallet.commit()?; if let Some(change_set) = self.wallet.take_staged() {
self.file_store.append_changeset(&change_set)?;
}
}, },
} }
let balance = self.wallet.get_balance(); let balance = self.wallet.balance();
Ok(Amount::from_sat(balance.total())) Ok(balance.total())
} }
/// Fee rate to use for regular txs like onboards. /// Fee rate to use for regular txs like onboards.
fn regular_fee_rate_bdk(&self) -> bdk::FeeRate { fn regular_fee_rate_bdk(&self) -> bitcoin::FeeRate {
//TODO(stevenroose) get from somewhere //TODO(stevenroose) get from somewhere
bdk::FeeRate::from_sat_per_vb(10.0) bitcoin::FeeRate::from_sat_per_vb(10).unwrap()
} }
/// Fee rate to use for regular txs like onboards. /// Fee rate to use for regular txs like onboards.
@@ -113,16 +148,16 @@ impl Wallet {
} }
/// Fee rate to use for urgent txs like exits. /// Fee rate to use for urgent txs like exits.
fn urgent_fee_rate_bdk(&self) -> bdk::FeeRate { fn urgent_fee_rate_bdk(&self) -> bitcoin::FeeRate {
//TODO(stevenroose) get from somewhere //TODO(stevenroose) get from somewhere
bdk::FeeRate::from_sat_per_vb(100.0) bitcoin::FeeRate::from_sat_per_vb(100).unwrap()
} }
pub fn prepare_tx(&mut self, dest: Address, amount: Amount) -> anyhow::Result<Psbt> { pub fn prepare_tx(&mut self, dest: Address, amount: Amount) -> anyhow::Result<Psbt> {
let fee_rate = self.regular_fee_rate_bdk(); let fee_rate = self.regular_fee_rate_bdk();
let mut b = self.wallet.build_tx(); let mut b = self.wallet.build_tx();
b.ordering(bdk::wallet::tx_builder::TxOrdering::Untouched); b.ordering(bdk_wallet::wallet::tx_builder::TxOrdering::Untouched);
b.add_recipient(dest.script_pubkey(), amount.to_sat()); b.add_recipient(dest.script_pubkey(), amount);
b.fee_rate(fee_rate); b.fee_rate(fee_rate);
b.enable_rbf(); b.enable_rbf();
Ok(b.finish()?) Ok(b.finish()?)
@@ -135,8 +170,10 @@ impl Wallet {
}; };
let finalized = self.wallet.sign(&mut psbt, opts).context("failed to sign")?; let finalized = self.wallet.sign(&mut psbt, opts).context("failed to sign")?;
assert!(finalized); assert!(finalized);
self.wallet.commit().context("error committing wallet")?; if let Some(change_set) = self.wallet.take_staged() {
Ok(psbt.extract_tx()) self.file_store.append_changeset(&change_set)?;
}
Ok(psbt.extract_tx()?)
} }
pub async fn send_money(&mut self, dest: Address, amount: Amount) -> anyhow::Result<Txid> { pub async fn send_money(&mut self, dest: Address, amount: Amount) -> anyhow::Result<Txid> {
@@ -144,17 +181,16 @@ impl Wallet {
let psbt = self.prepare_tx(dest, amount)?; let psbt = self.prepare_tx(dest, amount)?;
let tx = self.finish_tx(psbt)?; let tx = self.finish_tx(psbt)?;
self.broadcast_tx(&tx).await?; self.broadcast_tx(&tx).await?;
Ok(tx.txid()) Ok(tx.compute_txid())
} }
pub fn new_address(&mut self) -> anyhow::Result<Address> { pub fn new_address(&mut self) -> anyhow::Result<Address> {
Ok(self.wallet.try_get_address(bdk::wallet::AddressIndex::New)?.address) Ok(self.wallet.next_unused_address(KeychainKind::External).address)
} }
fn add_anchors<A, B, C>(b: &mut bdk::TxBuilder<A, B, C>, anchors: &[OutPoint]) fn add_anchors<A>(b: &mut bdk_wallet::TxBuilder<A>, anchors: &[OutPoint])
where where
B: bdk::wallet::coin_selection::CoinSelectionAlgorithm, A: bdk_wallet::wallet::coin_selection::CoinSelectionAlgorithm,
C: bdk::wallet::tx_builder::TxBuilderContext,
{ {
for utxo in anchors { for utxo in anchors {
let psbt_in = psbt::Input { let psbt_in = psbt::Input {
@@ -181,15 +217,15 @@ impl Wallet {
// overshoot the fee, but we prefer that over undershooting it. // overshoot the fee, but we prefer that over undershooting it.
let urgent_fee_rate = self.urgent_fee_rate_bdk(); let urgent_fee_rate = self.urgent_fee_rate_bdk();
let package_fee = urgent_fee_rate.fee_vb(package_vsize); let package_fee = urgent_fee_rate.fee_vb(package_vsize.try_into()?).unwrap();
// Since BDK doesn't allow tx without recipients, we add a drain output. // Since BDK doesn't allow tx without recipients, we add a drain output.
let change_addr = self.wallet.try_get_internal_address(bdk::wallet::AddressIndex::New)?; let change_addr = self.wallet.next_unused_address(KeychainKind::Internal);
let template_size = { let template_size = {
let mut b = self.wallet.build_tx(); let mut b = self.wallet.build_tx();
Wallet::add_anchors(&mut b, anchors); Wallet::add_anchors(&mut b, anchors);
b.add_recipient(change_addr.address.script_pubkey(), package_fee + ark::P2TR_DUST_SAT); b.add_recipient(change_addr.address.script_pubkey(), package_fee + Amount::from_sat(ark::P2TR_DUST_SAT));
b.fee_rate(urgent_fee_rate); b.fee_rate(urgent_fee_rate);
let mut psbt = b.finish().expect("failed to craft anchor spend template"); let mut psbt = b.finish().expect("failed to craft anchor spend template");
let opts = SignOptions { let opts = SignOptions {
@@ -199,11 +235,11 @@ impl Wallet {
let finalized = self.wallet.sign(&mut psbt, opts) let finalized = self.wallet.sign(&mut psbt, opts)
.expect("failed to sign anchor spend template"); .expect("failed to sign anchor spend template");
assert!(finalized); assert!(finalized);
psbt.extract_tx().vsize() psbt.extract_tx()?.vsize()
}; };
let total_vsize = template_size + package_vsize; let total_vsize = template_size + package_vsize;
let total_fee = self.urgent_fee_rate_bdk().fee_vb(total_vsize); let total_fee = self.urgent_fee_rate_bdk().fee_vb(total_vsize as u64).unwrap();
// Then build actual tx. // Then build actual tx.
let mut b = self.wallet.build_tx(); let mut b = self.wallet.build_tx();
@@ -224,7 +260,7 @@ impl Wallet {
let urgent_fee_rate = self.urgent_fee_rate_bdk(); let urgent_fee_rate = self.urgent_fee_rate_bdk();
// Since BDK doesn't allow tx without recipients, we add a drain output. // Since BDK doesn't allow tx without recipients, we add a drain output.
let change_addr = self.wallet.try_get_internal_address(bdk::wallet::AddressIndex::New)?; let change_addr = self.wallet.next_unused_address(KeychainKind::Internal);
let mut b = self.wallet.build_tx(); let mut b = self.wallet.build_tx();
b.version(2); b.version(2);
@@ -233,7 +269,7 @@ impl Wallet {
psbt_in.set_claim_input(input); psbt_in.set_claim_input(input);
psbt_in.witness_utxo = Some(TxOut { psbt_in.witness_utxo = Some(TxOut {
script_pubkey: input.spec.exit_spk(), script_pubkey: input.spec.exit_spk(),
value: input.spec.amount.to_sat(), value: input.spec.amount,
}); });
b.add_foreign_utxo_with_sequence( b.add_foreign_utxo_with_sequence(
input.utxo, input.utxo,