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"
# 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" ] }
miniscript = "10.0"
rand = { version = "0.8.5", features = [ "std", "std_rng" ] }
# bdk = "1.0.0-alpha.3"
# bdk_electrum = "0.5.0"
# bdk_file_store = "0.3.0"
# bdk_bitcoind_rpc = "0.2.0"
bdk = { git = "https://github.com/stevenroose/bdk.git", rev = "67602f5b33ea82775d94a28df9f3f66d2ca9aa19" }
bdk_esplora = { git = "https://github.com/stevenroose/bdk.git", rev = "67602f5b33ea82775d94a28df9f3f66d2ca9aa19" }
bdk_file_store = { git = "https://github.com/stevenroose/bdk.git", rev = "67602f5b33ea82775d94a28df9f3f66d2ca9aa19" }
bdk_bitcoind_rpc = { git = "https://github.com/stevenroose/bdk.git", rev = "67602f5b33ea82775d94a28df9f3f66d2ca9aa19" }
# bdk = { git = "https://github.com/stevenroose/bdk.git", rev = "67602f5b33ea82775d94a28df9f3f66d2ca9aa19" }
# bdk_esplora = { git = "https://github.com/stevenroose/bdk.git", rev = "67602f5b33ea82775d94a28df9f3f66d2ca9aa19" }
# bdk_file_store = { git = "https://github.com/stevenroose/bdk.git", rev = "67602f5b33ea82775d94a28df9f3f66d2ca9aa19" }
# bdk_bitcoind_rpc = { git = "https://github.com/stevenroose/bdk.git", rev = "67602f5b33ea82775d94a28df9f3f66d2ca9aa19" }
# bdk = "0.29.0"
bdk_wallet = "=1.0.0-alpha.13"
bdk_esplora = "0.15.0"
bdk_file_store = "0.13.0"
bdk_bitcoind_rpc = "0.12.0"
sled = "0.34.7"

View File

@@ -6,11 +6,11 @@ use bitcoin::{
Address, Amount, Network, OutPoint, Script, ScriptBuf, Sequence, Transaction, TxIn, TxOut,
Witness,
};
use bitcoin::secp256k1::{KeyPair, PublicKey};
use bitcoin::secp256k1::{Keypair, PublicKey};
use bitcoin::sighash::{self, SighashCache, TapSighashType};
use crate::{fee, util};
use crate::util::KeyPairExt;
use crate::util::KeypairExt;
/// 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.
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.
@@ -63,7 +63,7 @@ impl ConnectorChain {
pub fn output(len: usize, pubkey: PublicKey) -> TxOut {
TxOut {
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.
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 {
len: self.len,
spk: &self.spk,
@@ -118,7 +118,7 @@ impl ConnectorChain {
pub struct ConnectorTxIter<'a> {
len: usize,
spk: &'a Script,
sign_key: Option<KeyPair>,
sign_key: Option<Keypair>,
prev: OutPoint,
idx: usize,
@@ -133,7 +133,7 @@ impl<'a> iter::Iterator for ConnectorTxIter<'a> {
}
let mut ret = Transaction {
version: 2,
version: bitcoin::transaction::Version::TWO,
lock_time: bitcoin::absolute::LockTime::ZERO,
input: vec![TxIn {
previous_output: self.prev,
@@ -144,11 +144,11 @@ impl<'a> iter::Iterator for ConnectorTxIter<'a> {
output: vec![
TxOut {
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 {
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 {
let prevout = TxOut {
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 sighash = shc.taproot_key_spend_signature_hash(
@@ -167,7 +167,7 @@ impl<'a> iter::Iterator for ConnectorTxIter<'a> {
}
self.idx += 1;
self.prev = OutPoint::new(ret.txid(), 0);
self.prev = OutPoint::new(ret.compute_txid(), 0);
Some(ret)
}
@@ -191,7 +191,7 @@ impl<'a> iter::Iterator for ConnectorIter<'a> {
}
if let Some(tx) = self.txs.next() {
let txid = tx.txid();
let txid = tx.compute_txid();
self.maybe_last = Some(OutPoint::new(txid, 0));
Some(OutPoint::new(txid, 1))
} else {
@@ -217,7 +217,7 @@ mod test {
#[test]
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 chain = ConnectorChain::new(1, utxo, key.public_key());
@@ -238,27 +238,27 @@ mod test {
assert_eq!(chain.iter_signed_txs(&key).count(), 99);
for tx in chain.iter_signed_txs(&key) {
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>();
assert_eq!(size, ConnectorChain::total_vsize(100));
chain.iter_unsigned_txs().for_each(|t| assert_eq!(t.output[1].value, fee::DUST.to_sat()));
assert_eq!(fee::DUST.to_sat(), chain.iter_unsigned_txs().last().unwrap().output[0].value);
chain.iter_unsigned_txs().for_each(|t| assert_eq!(t.output[1].value, fee::DUST));
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
+ size;
assert_eq!(ConnectorChain::required_budget(100).to_sat(), total_value);
+ Amount::from_sat(size);
assert_eq!(ConnectorChain::required_budget(100), total_value);
// random checks
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(98).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));
}
#[test]
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 spk = ConnectorChain::output_script(key.public_key());

View File

@@ -18,9 +18,9 @@ pub fn op_true_script() -> ScriptBuf {
pub fn dust_anchor() -> TxOut {
TxOut {
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 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 {
// 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
let vtxo_fee_anchor_point = {
let mut point = vtxo.point();
@@ -20,7 +20,7 @@ pub fn create_forfeit_tx(vtxo: &Vtxo, connector: OutPoint) -> Transaction {
point
};
Transaction {
version: 2,
version: bitcoin::transaction::Version::TWO,
lock_time: bitcoin::absolute::LockTime::ZERO,
input: vec![
TxIn {
@@ -46,7 +46,7 @@ pub fn create_forfeit_tx(vtxo: &Vtxo, connector: OutPoint) -> Transaction {
output: vec![
TxOut {
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(),
],
@@ -58,11 +58,11 @@ pub fn forfeit_sighash(vtxo: &Vtxo, connector: OutPoint) -> (TapSighash, Transac
let exit_spk = spec.exit_spk();
let exit_prevout = TxOut {
script_pubkey: exit_spk,
value: vtxo.amount().to_sat(),
value: vtxo.amount(),
};
let connector_prevout = TxOut {
script_pubkey: ConnectorChain::output_script(spec.asp_pubkey),
value: fee::DUST.to_sat(),
value: fee::DUST,
};
let tx = create_forfeit_tx(vtxo, connector);
let sighash = SighashCache::new(&tx).taproot_key_spend_signature_hash(

View File

@@ -73,14 +73,14 @@ impl OffboardRequest {
P2PKH_DUST_VB
} else if script.is_p2sh() {
P2SH_DUST_VB
} else if script.is_v0_p2wpkh() {
} else if script.is_p2wpkh() {
P2WPKH_DUST_VB
} else if script.is_v0_p2wsh() {
} else if script.is_p2wsh() {
P2WSH_DUST_VB
} else if script.is_v1_p2tr() {
} else if script.is_p2tr() {
P2TR_DUST_VB
} 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
+ 8
// the input data (scriptSig and witness length fields included)
@@ -104,7 +104,7 @@ impl OffboardRequest {
pub fn to_txout(&self) -> TxOut {
TxOut {
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,
) -> ScriptBuf {
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)]
@@ -299,9 +299,9 @@ impl Vtxo {
/// This can be an on-chain utxo or an off-chain vtxo.
pub fn point(&self) -> OutPoint {
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, .. } => {
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,
}
@@ -320,7 +320,7 @@ impl Vtxo {
Vtxo::Onboard { base, .. } => base.spec.amount,
Vtxo::Round { base, .. } => base.spec.amount,
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 {
TxOut {
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 {
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.

View File

@@ -4,7 +4,7 @@ pub use secp256k1_zkp::{
MusigAggNonce, MusigKeyAggCache, MusigPubNonce, MusigPartialSignature, MusigSecNonce,
MusigSession, MusigSessionId,
};
use bitcoin::secp256k1::{rand, schnorr, KeyPair, PublicKey, SecretKey, XOnlyPublicKey};
use bitcoin::secp256k1::{rand, schnorr, Keypair, PublicKey, SecretKey, XOnlyPublicKey};
use crate::util;
@@ -29,12 +29,12 @@ pub fn seckey_to(sk: SecretKey) -> zkp::SecretKey {
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()
}
pub fn keypair_from(kp: &zkp::Keypair) -> KeyPair {
KeyPair::from_seckey_slice(&util::SECP, &kp.secret_bytes()).unwrap()
pub fn keypair_from(kp: &zkp::Keypair) -> Keypair {
Keypair::from_seckey_slice(&util::SECP, &kp.secret_bytes()).unwrap()
}
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())
}
pub fn nonce_pair(key: &KeyPair) -> (MusigSecNonce, MusigPubNonce) {
pub fn nonce_pair(key: &Keypair) -> (MusigSecNonce, MusigPubNonce) {
let kp = keypair_to(key);
zkp::new_musig_nonce_pair(
&SECP,
@@ -81,7 +81,7 @@ pub fn nonce_agg(pub_nonces: impl IntoIterator<Item = MusigPubNonce>) -> MusigAg
pub fn partial_sign(
pubkeys: impl IntoIterator<Item = PublicKey>,
agg_nonce: MusigAggNonce,
key: &KeyPair,
key: &Keypair,
sec_nonce: MusigSecNonce,
sighash: [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
/// given counterparty key and nonce.
pub fn deterministic_partial_sign(
my_key: &KeyPair,
my_key: &Keypair,
their_pubkeys: impl IntoIterator<Item = PublicKey>,
their_nonces: impl IntoIterator<Item = MusigPubNonce>,
msg: [u8; 32],

View File

@@ -1,7 +1,7 @@
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::hashes::Hash;
@@ -30,7 +30,7 @@ fn empty_input() -> TxIn {
fn ctv_output() -> TxOut {
TxOut {
value: 0,
value: Amount::ZERO,
script_pubkey: Script::builder()
.push_opcode(opcodes::all::OP_NOP4)
.push_slice(&BYTES32)
@@ -44,7 +44,7 @@ fn ctv_input() -> TxIn {
fn taproot_output() -> TxOut {
TxOut {
value: 0,
value: Amount::ZERO,
script_pubkey: Script::builder()
.push_opcode(opcodes::all::OP_PUSHNUM_1)
.push_slice(&BYTES32)
@@ -67,7 +67,7 @@ fn taproot_input() -> TxIn {
fn anchor_output() -> TxOut {
TxOut {
value: 0,
value: Amount::ZERO,
script_pubkey: Script::builder()
.push_opcode(opcodes::OP_TRUE)
.into_script(),
@@ -80,7 +80,7 @@ fn anchor_input() -> TxIn {
fn ctv_node_tx(radix: usize) -> Transaction {
Transaction {
version: 2,
version: bitcoin::transaction::Version::TWO,
lock_time: bitcoin::locktime::absolute::LockTime::from_consensus(0),
input: vec![ctv_input()],
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 {
Transaction {
version: 2,
version: bitcoin::transaction::Version::TWO,
lock_time: bitcoin::locktime::absolute::LockTime::from_consensus(0),
input: vec![ctv_input()],
output: vec![taproot_output(), anchor_output()],
@@ -98,7 +98,7 @@ fn ctv_leaf_tx() -> Transaction {
fn clark_node_tx(radix: usize) -> Transaction {
Transaction {
version: 2,
version: bitcoin::transaction::Version::TWO,
lock_time: bitcoin::locktime::absolute::LockTime::from_consensus(0),
input: vec![taproot_input()],
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 {
Transaction {
version: 2,
version: bitcoin::transaction::Version::TWO,
lock_time: bitcoin::locktime::absolute::LockTime::from_consensus(0),
input: vec![taproot_input()],
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
let exit_tx = Transaction {
version: 2,
version: bitcoin::transaction::Version::TWO,
lock_time: bitcoin::locktime::absolute::LockTime::from_consensus(0),
input: iter::repeat(anchor_input()).take(levels).chain(Some(taproot_input())).collect(),
output: vec![taproot_output()],

View File

@@ -8,7 +8,7 @@
use bitcoin::{taproot, Amount, OutPoint, Sequence, ScriptBuf, Transaction, TxIn, TxOut, Witness};
use bitcoin::blockdata::locktime::absolute::LockTime;
use bitcoin::hashes::Hash;
use bitcoin::secp256k1::{schnorr, KeyPair};
use bitcoin::secp256k1::{schnorr, Keypair};
use bitcoin::sighash::{self, SighashCache, TapSighash};
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 {
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.
@@ -84,7 +84,7 @@ pub struct AspPart {
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 msg = reveal_sighash.to_byte_array();
let tweak = onboard_taptweak(&user.spec);
@@ -103,7 +103,7 @@ pub fn create_reveal_tx(
signature: Option<&schnorr::Signature>,
) -> Transaction {
Transaction {
version: 2,
version: bitcoin::transaction::Version::TWO,
lock_time: LockTime::ZERO,
input: vec![TxIn {
previous_output: utxo,
@@ -120,7 +120,7 @@ pub fn create_reveal_tx(
output: vec![
TxOut {
script_pubkey: spec.exit_spk(),
value: spec.amount.to_sat(),
value: spec.amount,
},
fee::dust_anchor(),
],
@@ -132,7 +132,7 @@ pub fn reveal_tx_sighash(spec: &VtxoSpec, utxo: OutPoint) -> (TapSighash, Transa
let prev = TxOut {
script_pubkey: onboard_spk(&spec),
//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(
0, &sighash::Prevouts::All(&[&prev]), sighash::TapSighashType::Default,
@@ -144,7 +144,7 @@ pub fn finish(
user: UserPart,
asp: AspPart,
private: PrivateUserPart,
key: &KeyPair,
key: &Keypair,
) -> Vtxo {
let (reveal_sighash, _reveal_tx) = reveal_tx_sighash(&user.spec, user.utxo);
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
//! 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 spec = VtxoSpec {
user_pubkey: key.public_key(),

View File

@@ -7,7 +7,7 @@ use bitcoin::{
Witness,
};
use bitcoin::hashes::Hash;
use bitcoin::secp256k1::{schnorr, KeyPair, PublicKey};
use bitcoin::secp256k1::{schnorr, Keypair, PublicKey};
use bitcoin::sighash::{self, SighashCache, TapSighash, TapSighashType};
use crate::{fee, musig, util, Vtxo, VtxoRequest, VtxoSpec};
@@ -35,7 +35,7 @@ impl OorPayment {
pub fn unsigned_transaction(&self) -> Transaction {
Transaction {
version: 2,
version: bitcoin::transaction::Version::TWO,
lock_time: bitcoin::absolute::LockTime::ZERO,
input: self.inputs.iter().map(|input| {
TxIn {
@@ -48,7 +48,7 @@ impl OorPayment {
output: self.outputs.iter().map(|output| {
let spk = crate::exit_spk(output.pubkey, self.asp_pubkey, self.exit_delta);
TxOut {
value: output.amount.to_sat(),
value: output.amount,
script_pubkey: spk,
}
}).chain([fee::dust_anchor()]).collect(),
@@ -56,7 +56,7 @@ impl OorPayment {
}
pub fn txid(&self) -> Txid {
self.unsigned_transaction().txid()
self.unsigned_transaction().compute_txid()
}
pub fn sighashes(&self) -> Vec<TapSighash> {
@@ -98,7 +98,7 @@ impl OorPayment {
pub fn sign_asp(
&self,
keypair: &KeyPair,
keypair: &Keypair,
user_nonces: &[musig::MusigPubNonce],
) -> (Vec<musig::MusigPubNonce>, Vec<musig::MusigPartialSignature>) {
assert_eq!(self.inputs.len(), user_nonces.len());
@@ -124,7 +124,7 @@ impl OorPayment {
pub fn sign_finalize_user(
self,
keypair: &KeyPair,
keypair: &Keypair,
our_sec_nonces: Vec<musig::MusigSecNonce>,
our_pub_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()) {
assert!(input.witness.is_empty());
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,
// 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 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)| {
Vtxo::Oor {
inputs: inputs.clone(),

View File

@@ -131,12 +131,12 @@ impl VtxoTreeSpec {
}
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 {
Transaction {
version: 2,
version: bitcoin::transaction::Version::TWO,
lock_time: bitcoin::absolute::LockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint::null(),
@@ -159,7 +159,7 @@ impl VtxoTreeSpec {
};
TxOut {
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(),
}
@@ -178,7 +178,7 @@ impl VtxoTreeSpec {
fn leaf_tx(&self, vtxo: &VtxoRequest) -> Transaction {
let vtxo_spec = self.vtxo_spec(vtxo);
Transaction {
version: 2,
version: bitcoin::transaction::Version::TWO,
lock_time: bitcoin::absolute::LockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint::null(),
@@ -189,7 +189,7 @@ impl VtxoTreeSpec {
output: vec![
TxOut {
script_pubkey: vtxo_spec.exit_spk(),
value: vtxo.amount.to_sat(),
value: vtxo.amount,
},
fee::dust_anchor(),
],
@@ -205,7 +205,7 @@ impl VtxoTreeSpec {
// This is the root, set to the tree's on-chain utxo.
tree.element_at_mut(cursor).unwrap().input[0].previous_output = utxo;
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();
for i in 0..nb_children {
let prevout = OutPoint::new(txid, i as u32);
@@ -228,7 +228,7 @@ impl VtxoTreeSpec {
// this is the root
TxOut {
script_pubkey: self.cosign_spk(),
value: self.total_required_value().to_sat(),
value: self.total_required_value(),
}
};
let el = tree.element_at(idx).unwrap();
@@ -313,20 +313,21 @@ mod test {
use std::str::FromStr;
use bitcoin::hashes::sha256;
use bitcoin::secp256k1::{self, rand, KeyPair};
use bitcoin::hashes::{sha256, Hash};
use bitcoin::secp256k1::{self, rand, Keypair, Message};
use crate::musig;
#[test]
fn test_node_tx_sizes() {
let secp = secp256k1::Secp256k1::new();
let key1 = KeyPair::new(&secp, &mut rand::thread_rng()); // asp
let key2 = KeyPair::new(&secp, &mut rand::thread_rng());
let key1 = Keypair::new(&secp, &mut rand::thread_rng()); // asp
let key2 = Keypair::new(&secp, &mut rand::thread_rng());
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 {
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),
};
let point = "0000000000000000000000000000000000000000000000000000000000000001:1".parse().unwrap();
@@ -352,7 +353,7 @@ mod test {
let mut iter = exit.iter().enumerate().peekable();
while let Some((i, cur)) = iter.next() {
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);
for node in iter {
assert_eq!(
node.input[0].witness.serialized_len(),
node.input[0].witness.size(),
crate::TAPROOT_KEYSPEND_WEIGHT,
);
match node.output.len() {

View File

@@ -2,16 +2,16 @@
use std::borrow::Borrow;
use bitcoin::{opcodes, taproot, ScriptBuf};
use bitcoin::secp256k1::{self, KeyPair, XOnlyPublicKey};
use bitcoin::secp256k1::{self, Keypair, XOnlyPublicKey};
lazy_static::lazy_static! {
/// Global secp context.
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.
fn for_keyspend(&self) -> KeyPair {
fn for_keyspend(&self) -> Keypair {
let tweak = taproot::TapTweakHash::from_key_and_tweak(
self.borrow().x_only_public_key().0, None,
).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.
pub fn delayed_sign(delay_blocks: u16, pubkey: XOnlyPublicKey) -> ScriptBuf {

View File

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

View File

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

View File

@@ -20,12 +20,12 @@ use std::str::FromStr;
use std::time::Duration;
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::secp256k1::{self, KeyPair};
use bitcoin::secp256k1::{self, Keypair};
use tokio::sync::{Mutex, RwLock};
use ark::util::KeyPairExt;
use ark::util::KeypairExt;
use ark::musig;
use crate::psbtext::{PsbtInputExt, RoundMeta};
@@ -91,11 +91,11 @@ pub struct RoundHandle {
pub struct App {
config: Config,
db: database::Db,
master_xpriv: bip32::ExtendedPrivKey,
master_key: KeyPair,
wallet: Mutex<bdk::Wallet<bdk_file_store::Store<'static, bdk::wallet::ChangeSet>>>,
master_xpriv: bip32::Xpriv,
master_key: Keypair,
wallet: Mutex<bdk_wallet::Wallet>,
wallet_store: Mutex<bdk_file_store::Store<bdk_wallet::wallet::ChangeSet>>,
bitcoind: bdk_bitcoind_rpc::bitcoincore_rpc::Client,
rounds: Option<RoundHandle>,
}
@@ -146,23 +146,26 @@ impl App {
.context("db error")?
.context("db doesn't contain seed")?;
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 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)
};
let wallet = {
let db_path = datadir.join("wallet.db");
info!("Loading wallet db from {}", db_path.display());
let db = bdk_file_store::Store::<bdk::wallet::ChangeSet>::open_or_create_new(
DB_MAGIC.as_bytes(), db_path,
let db_path = datadir.join("wallet.db");
info!("Loading wallet db from {}", db_path.display());
let mut file_store = bdk_file_store::Store::<bdk_wallet::wallet::ChangeSet>::open_or_create_new(
DB_MAGIC.as_bytes(), db_path,
)?;
let wallet = {
let change_set = file_store.aggregate_changesets()?;
let desc = format!("tr({})", xpriv);
debug!("Opening BDK wallet with descriptor {}", desc);
bdk::Wallet::new_or_load(&desc, None, db, config.network)
let edesc = format!("tr({}/84'/0'/0'/0/*)", xpriv);
let idesc = format!("tr({}/84'/0'/0'/1/*)", xpriv);
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")?
};
@@ -173,12 +176,13 @@ impl App {
Ok(Arc::new(App {
config: config,
db: db,
config,
db,
master_xpriv: xpriv,
master_key: master_key,
master_key,
wallet: Mutex::new(wallet),
bitcoind: bitcoind,
wallet_store: Mutex::new(file_store),
bitcoind,
rounds: None,
}))
}
@@ -192,9 +196,9 @@ impl App {
mut_self.rounds = Some(RoundHandle {
round_busy: RwLock::new(()),
round_event_tx: round_event_tx,
round_input_tx: round_input_tx,
round_trigger_tx: round_trigger_tx,
round_event_tx,
round_input_tx,
round_trigger_tx,
});
let app = self.clone();
@@ -237,14 +241,15 @@ impl App {
pub async fn onchain_address(&self) -> anyhow::Result<Address> {
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
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)
}
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 file_store = self.wallet_store.lock().await;
let prev_tip = wallet.latest_checkpoint();
// let keychain_spks = self.wallet.spks_of_all_keychains();
@@ -255,29 +260,33 @@ impl App {
if em.block_height() % 10_000 == 0 {
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
let mempool = emitter.mempool()?;
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
// NB during some round failures we commit a tx but fail to broadcast it,
// so this ensures we still broadcast them afterwards
for tx in wallet.transactions() {
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 {
warn!("Error broadcasting pending tx: {}", e);
}
}
}
let balance = wallet.get_balance();
Ok(Amount::from_sat(balance.total()))
let balance = wallet.balance();
Ok(balance.total())
}
pub async fn drain(
@@ -288,20 +297,23 @@ impl App {
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 b = wallet.build_tx();
b.drain_to(addr.script_pubkey());
b.drain_wallet();
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);
let tx = psbt.extract_tx();
wallet.commit()?;
let tx = psbt.extract_tx()?;
if let Some(change_set) = wallet.take_staged() {
file_store.append_changeset(&change_set)?;
};
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!("Try yourself: {}", bitcoin::consensus::encode::serialize_hex(&tx));
error!("Try yourself: {}", tx.raw_hex());
}
Ok(tx)
@@ -404,7 +416,7 @@ impl App {
let wit = Witness::from_slice(
&[&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);
},
RoundMeta::Connector => {
@@ -443,6 +455,6 @@ pub(crate) struct SpendableUtxo {
impl SpendableUtxo {
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 } => {
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 => {
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::hashes::Hash;
use bitcoin::locktime::absolute::LockTime;
use bitcoin::secp256k1::{rand, KeyPair, PublicKey};
use bitcoin::secp256k1::{rand, Keypair, PublicKey};
use bitcoin::sighash::TapSighash;
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.
// 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());
// Start receiving payments.
@@ -322,24 +322,24 @@ pub async fn run_round_scheduler(
let mut wallet = app.wallet.lock().await;
let mut round_tx_psbt = {
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"));
for utxo in &spendable_utxos {
b.add_foreign_utxo_with_sequence(
utxo.point, utxo.psbt.clone(), utxo.weight, Sequence::ZERO,
).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);
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.finish().expect("bdk failed to create round tx")
};
let round_tx = round_tx_psbt.clone().extract_tx();
let vtxos_utxo = OutPoint::new(round_tx.txid(), 0);
let conns_utxo = OutPoint::new(round_tx.txid(), 1);
let round_tx = round_tx_psbt.clone().extract_tx()?;
let vtxos_utxo = OutPoint::new(round_tx.compute_txid(), 0);
let conns_utxo = OutPoint::new(round_tx.compute_txid(), 1);
// Generate vtxo nonces and combine with user's nonces.
let (sec_vtxo_nonces, pub_vtxo_nonces) = {
@@ -570,18 +570,21 @@ pub async fn run_round_scheduler(
// Sign the on-chain tx.
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,
..Default::default()
};
let finalized = wallet.sign(&mut round_tx_psbt, opts)?;
assert!(finalized);
let round_tx = round_tx_psbt.extract_tx();
wallet.commit()?;
let round_tx = round_tx_psbt.extract_tx()?;
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
// 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);
if let Err(e) = bc {
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.
let round_id = round_tx.txid();
let round_id = round_tx.compute_txid();
for (id, vtxo) in all_inputs {
let forfeit_sigs = forfeit_sigs.remove(&id).unwrap();
let point = vtxo.point();
@@ -615,7 +618,7 @@ pub async fn run_round_scheduler(
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;
}
}

View File

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

View File

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

View File

@@ -116,10 +116,10 @@ impl Wallet {
// Broadcast exit txs.
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 {
error!("Error broadcasting exit tx {}: {}", tx.txid(), e);
error!("Tx {}: {}", tx.txid(), bitcoin::consensus::encode::serialize_hex(tx));
error!("Error broadcasting exit tx {}: {}", tx.compute_txid(), e);
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.
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.
for id in exit.started {
@@ -232,7 +232,7 @@ impl Wallet {
let wit = Witness::from_slice(
&[&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);
}

View File

@@ -17,7 +17,7 @@ use std::str::FromStr;
use anyhow::{bail, Context};
use bitcoin::{bip32, secp256k1, Address, Amount, FeeRate, Network, OutPoint, Transaction, Txid};
use bitcoin::hashes::Hash;
use bitcoin::secp256k1::{rand, KeyPair, PublicKey};
use bitcoin::secp256k1::{rand, Keypair, PublicKey};
use tokio_stream::StreamExt;
use ark::{musig, BaseVtxo, OffboardRequest, VtxoRequest, Vtxo, VtxoId, VtxoSpec};
@@ -98,7 +98,7 @@ pub struct Wallet {
config: Config,
db: database::Db,
onchain: onchain::Wallet,
vtxo_seed: bip32::ExtendedPrivKey,
vtxo_seed: bip32::Xpriv,
// ASP stuff
asp: rpc::ArkServiceClient<tonic::transport::Channel>,
ark_info: ArkInfo,
@@ -184,7 +184,7 @@ impl Wallet {
};
onchain::ChainSource::Bitcoind {
url: url.clone(),
auth: auth,
auth,
}
} else {
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 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()
};
@@ -277,7 +277,7 @@ impl Wallet {
asp_pubkey: self.ark_info.asp_pubkey,
expiry_height: current_height + self.ark_info.vtxo_expiry_delta as u32,
exit_delta: self.ark_info.vtxo_exit_delta,
amount: amount,
amount,
};
let onboard_amount = amount + ark::onboard::onboard_surplus();
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.
self.onchain.sync().await.context("sync error")?;
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.
let (user_part, priv_user_part) = ark::onboard::new_user(spec, utxo);
@@ -332,8 +332,8 @@ impl Wallet {
},
utxo: vtxos.utxo,
},
leaf_idx: leaf_idx,
exit_branch: exit_branch,
leaf_idx,
exit_branch,
};
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<()> {
ensure!(addr.network == self.config.network, "invalid addr network");
//TODO(stevenroose) impl key derivation
let vtxo_key = self.vtxo_seed.to_keypair(&SECP);
@@ -592,7 +591,7 @@ impl Wallet {
self.participate_round(move |_id, offb_fr| {
let offb = OffboardRequest {
script_pubkey: addr.script_pubkey(),
amount: amount,
amount,
};
let out_value = amount + offb.fee(offb_fr).expect("script from address");
let change = {
@@ -659,7 +658,7 @@ impl Wallet {
'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 {}",
round_id, cosign_key.public_key(),
);
@@ -736,8 +735,8 @@ impl Wallet {
}
};
let vtxos_utxo = OutPoint::new(round_tx.txid(), 0);
let conns_utxo = OutPoint::new(round_tx.txid(), 1);
let vtxos_utxo = OutPoint::new(round_tx.compute_txid(), 0);
let conns_utxo = OutPoint::new(round_tx.compute_txid(), 1);
// Check that the proposal contains our inputs.
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.
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 {
warn!("Couldn't broadcast round tx: {}", e);
}

View File

@@ -2,16 +2,18 @@
mod chain;
pub use self::chain::ChainSource;
use std::collections::BTreeSet;
use std::io::Write;
use std::path::Path;
use anyhow::Context;
use bdk::SignOptions;
use bdk_wallet::{KeychainKind, SignOptions};
use bdk_file_store::Store;
use bdk_esplora::EsploraAsyncExt;
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::psbtext::PsbtInputExt;
@@ -20,7 +22,8 @@ use self::chain::ChainSourceClient;
const DB_MAGIC: &str = "onchain_bdk";
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,
}
@@ -32,18 +35,19 @@ impl Wallet {
chain_source: ChainSource,
) -> anyhow::Result<Wallet> {
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?
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 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")?;
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> {
@@ -58,6 +62,18 @@ impl Wallet {
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> {
debug!("Starting wallet sync...");
@@ -71,40 +87,59 @@ impl Wallet {
self.wallet.apply_block_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()?;
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) => {
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 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.commit()?;
if let Some(change_set) = self.wallet.take_staged() {
self.file_store.append_changeset(&change_set)?;
}
},
}
let balance = self.wallet.get_balance();
Ok(Amount::from_sat(balance.total()))
let balance = self.wallet.balance();
Ok(balance.total())
}
/// 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
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.
@@ -113,16 +148,16 @@ impl Wallet {
}
/// 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
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> {
let fee_rate = self.regular_fee_rate_bdk();
let mut b = self.wallet.build_tx();
b.ordering(bdk::wallet::tx_builder::TxOrdering::Untouched);
b.add_recipient(dest.script_pubkey(), amount.to_sat());
b.ordering(bdk_wallet::wallet::tx_builder::TxOrdering::Untouched);
b.add_recipient(dest.script_pubkey(), amount);
b.fee_rate(fee_rate);
b.enable_rbf();
Ok(b.finish()?)
@@ -135,8 +170,10 @@ impl Wallet {
};
let finalized = self.wallet.sign(&mut psbt, opts).context("failed to sign")?;
assert!(finalized);
self.wallet.commit().context("error committing wallet")?;
Ok(psbt.extract_tx())
if let Some(change_set) = self.wallet.take_staged() {
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> {
@@ -144,17 +181,16 @@ impl Wallet {
let psbt = self.prepare_tx(dest, amount)?;
let tx = self.finish_tx(psbt)?;
self.broadcast_tx(&tx).await?;
Ok(tx.txid())
Ok(tx.compute_txid())
}
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
B: bdk::wallet::coin_selection::CoinSelectionAlgorithm,
C: bdk::wallet::tx_builder::TxBuilderContext,
A: bdk_wallet::wallet::coin_selection::CoinSelectionAlgorithm,
{
for utxo in anchors {
let psbt_in = psbt::Input {
@@ -181,15 +217,15 @@ impl Wallet {
// overshoot the fee, but we prefer that over undershooting it.
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.
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 mut b = self.wallet.build_tx();
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);
let mut psbt = b.finish().expect("failed to craft anchor spend template");
let opts = SignOptions {
@@ -199,11 +235,11 @@ impl Wallet {
let finalized = self.wallet.sign(&mut psbt, opts)
.expect("failed to sign anchor spend template");
assert!(finalized);
psbt.extract_tx().vsize()
psbt.extract_tx()?.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.
let mut b = self.wallet.build_tx();
@@ -224,7 +260,7 @@ impl Wallet {
let urgent_fee_rate = self.urgent_fee_rate_bdk();
// 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();
b.version(2);
@@ -233,7 +269,7 @@ impl Wallet {
psbt_in.set_claim_input(input);
psbt_in.witness_utxo = Some(TxOut {
script_pubkey: input.spec.exit_spk(),
value: input.spec.amount.to_sat(),
value: input.spec.amount,
});
b.add_foreign_utxo_with_sequence(
input.utxo,