diff --git a/Cargo.toml b/Cargo.toml index 8985219..07fa8bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/ark-lib/src/connectors.rs b/ark-lib/src/connectors.rs index e4e2b62..6473da4 100644 --- a/ark-lib/src/connectors.rs +++ b/ark-lib/src/connectors.rs @@ -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, + sign_key: Option, 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::(); 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::() + let total_value = chain.iter_unsigned_txs().map(|t| t.output[1].value).sum::() + 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()); diff --git a/ark-lib/src/fee.rs b/ark-lib/src/fee.rs index 8aa7285..8b71ef7 100644 --- a/ark-lib/src/fee.rs +++ b/ark-lib/src/fee.rs @@ -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, } } diff --git a/ark-lib/src/forfeit.rs b/ark-lib/src/forfeit.rs index 43b85b6..f4535fe 100644 --- a/ark-lib/src/forfeit.rs +++ b/ark-lib/src/forfeit.rs @@ -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( diff --git a/ark-lib/src/lib.rs b/ark-lib/src/lib.rs index dcfecc2..ecf743d 100644 --- a/ark-lib/src/lib.rs +++ b/ark-lib/src/lib.rs @@ -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. diff --git a/ark-lib/src/musig.rs b/ark-lib/src/musig.rs index 404dc37..6c1079a 100644 --- a/ark-lib/src/musig.rs +++ b/ark-lib/src/musig.rs @@ -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) -> 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) -> MusigAg pub fn partial_sign( pubkeys: impl IntoIterator, 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, their_nonces: impl IntoIterator, msg: [u8; 32], diff --git a/ark-lib/src/napkin.rs b/ark-lib/src/napkin.rs index fd04aa8..4b9d72d 100644 --- a/ark-lib/src/napkin.rs +++ b/ark-lib/src/napkin.rs @@ -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()], diff --git a/ark-lib/src/onboard.rs b/ark-lib/src/onboard.rs index 0df7200..078de28 100644 --- a/ark-lib/src/onboard.rs +++ b/ark-lib/src/onboard.rs @@ -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(), diff --git a/ark-lib/src/oor.rs b/ark-lib/src/oor.rs index c27a9e0..09f8302 100644 --- a/ark-lib/src/oor.rs +++ b/ark-lib/src/oor.rs @@ -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 { @@ -98,7 +98,7 @@ impl OorPayment { pub fn sign_asp( &self, - keypair: &KeyPair, + keypair: &Keypair, user_nonces: &[musig::MusigPubNonce], ) -> (Vec, Vec) { 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, 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(), diff --git a/ark-lib/src/tree/signed.rs b/ark-lib/src/tree/signed.rs index 0c3b7aa..50905f3 100644 --- a/ark-lib/src/tree/signed.rs +++ b/ark-lib/src/tree/signed.rs @@ -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::() + fee_budget, + value: child.output.iter().map(|o| o.value).sum::() + 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() { diff --git a/ark-lib/src/util.rs b/ark-lib/src/util.rs index 4e8cd58..01e651d 100644 --- a/ark-lib/src/util.rs +++ b/ark-lib/src/util.rs @@ -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::Secp256k1::new(); } -pub trait KeyPairExt: Borrow { +pub trait KeypairExt: Borrow { /// 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 { } } -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 { diff --git a/arkd/Cargo.toml b/arkd/Cargo.toml index 68689c6..f8bcc0c 100644 --- a/arkd/Cargo.toml +++ b/arkd/Cargo.toml @@ -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 diff --git a/arkd/src/database/mod.rs b/arkd/src/database/mod.rs index e85ecb3..b76b337 100644 --- a/arkd/src/database/mod.rs +++ b/arkd/src/database/mod.rs @@ -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 { diff --git a/arkd/src/lib.rs b/arkd/src/lib.rs index 69c9901..050b310 100644 --- a/arkd/src/lib.rs +++ b/arkd/src/lib.rs @@ -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>>, + master_xpriv: bip32::Xpriv, + master_key: Keypair, + wallet: Mutex, + wallet_store: Mutex>, bitcoind: bdk_bitcoind_rpc::bitcoincore_rpc::Client, - rounds: Option, } @@ -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::::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::::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
{ 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 { + pub async fn sync_onchain_wallet(& self) -> anyhow::Result { 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 } } diff --git a/arkd/src/main.rs b/arkd/src/main.rs index 9db292a..5d496da 100644 --- a/arkd/src/main.rs +++ b/arkd/src/main.rs @@ -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")?; diff --git a/arkd/src/round/mod.rs b/arkd/src/round/mod.rs index b8f4f0a..aca9b01 100644 --- a/arkd/src/round/mod.rs +++ b/arkd/src/round/mod.rs @@ -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; } } diff --git a/arkd/src/util.rs b/arkd/src/util.rs index 59ef9f4..86c890d 100644 --- a/arkd/src/util.rs +++ b/arkd/src/util.rs @@ -5,8 +5,8 @@ use std::borrow::Borrow; use bitcoin::FeeRate; pub trait FeeRateExt: Borrow + 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()) } } diff --git a/noah/Cargo.toml b/noah/Cargo.toml index cefa517..b6e458a 100644 --- a/noah/Cargo.toml +++ b/noah/Cargo.toml @@ -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 diff --git a/noah/src/exit.rs b/noah/src/exit.rs index a2cc47f..8304a49 100644 --- a/noah/src/exit.rs +++ b/noah/src/exit.rs @@ -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); } diff --git a/noah/src/lib.rs b/noah/src/lib.rs index 79e3a4d..6cff0a3 100644 --- a/noah/src/lib.rs +++ b/noah/src/lib.rs @@ -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, 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); } diff --git a/noah/src/onchain/mod.rs b/noah/src/onchain/mod.rs index c37ec93..1d3c227 100644 --- a/noah/src/onchain/mod.rs +++ b/noah/src/onchain/mod.rs @@ -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>, + wallet: bdk_wallet::Wallet, + file_store: Store, chain_source: ChainSourceClient, } @@ -32,18 +35,19 @@ impl Wallet { chain_source: ChainSource, ) -> anyhow::Result { let db_path = dir.join("bdkwallet.db"); - let db = Store::::open_or_create_new(DB_MAGIC.as_bytes(), db_path)?; + let mut db = Store::::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 { @@ -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 { 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::::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 { 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 { @@ -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
{ - Ok(self.wallet.try_get_address(bdk::wallet::AddressIndex::New)?.address) + Ok(self.wallet.next_unused_address(KeychainKind::External).address) } - fn add_anchors(b: &mut bdk::TxBuilder, anchors: &[OutPoint]) + fn add_anchors(b: &mut bdk_wallet::TxBuilder, 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,