mirror of
https://github.com/aljazceru/clArk.git
synced 2025-12-17 13:14:20 +01:00
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:
19
Cargo.toml
19
Cargo.toml
@@ -23,18 +23,19 @@ serde_json = "1"
|
|||||||
ciborium = "0.2.1"
|
ciborium = "0.2.1"
|
||||||
|
|
||||||
# bitcoin stack
|
# bitcoin stack
|
||||||
bitcoin = { version = "0.30", features = [ "serde", "rand", "rand-std" ] }
|
bitcoin = { version = "0.32", features = [ "serde", "rand", "rand-std" ] }
|
||||||
bip39 = { version = "2.0.0", features = [ "rand", "serde" ] }
|
bip39 = { version = "2.0.0", features = [ "rand", "serde" ] }
|
||||||
miniscript = "10.0"
|
miniscript = "10.0"
|
||||||
rand = { version = "0.8.5", features = [ "std", "std_rng" ] }
|
rand = { version = "0.8.5", features = [ "std", "std_rng" ] }
|
||||||
# bdk = "1.0.0-alpha.3"
|
# bdk = { git = "https://github.com/stevenroose/bdk.git", rev = "67602f5b33ea82775d94a28df9f3f66d2ca9aa19" }
|
||||||
# bdk_electrum = "0.5.0"
|
# bdk_esplora = { git = "https://github.com/stevenroose/bdk.git", rev = "67602f5b33ea82775d94a28df9f3f66d2ca9aa19" }
|
||||||
# bdk_file_store = "0.3.0"
|
# bdk_file_store = { git = "https://github.com/stevenroose/bdk.git", rev = "67602f5b33ea82775d94a28df9f3f66d2ca9aa19" }
|
||||||
# bdk_bitcoind_rpc = "0.2.0"
|
# bdk_bitcoind_rpc = { git = "https://github.com/stevenroose/bdk.git", rev = "67602f5b33ea82775d94a28df9f3f66d2ca9aa19" }
|
||||||
bdk = { git = "https://github.com/stevenroose/bdk.git", rev = "67602f5b33ea82775d94a28df9f3f66d2ca9aa19" }
|
# bdk = "0.29.0"
|
||||||
bdk_esplora = { git = "https://github.com/stevenroose/bdk.git", rev = "67602f5b33ea82775d94a28df9f3f66d2ca9aa19" }
|
bdk_wallet = "=1.0.0-alpha.13"
|
||||||
bdk_file_store = { git = "https://github.com/stevenroose/bdk.git", rev = "67602f5b33ea82775d94a28df9f3f66d2ca9aa19" }
|
bdk_esplora = "0.15.0"
|
||||||
bdk_bitcoind_rpc = { git = "https://github.com/stevenroose/bdk.git", rev = "67602f5b33ea82775d94a28df9f3f66d2ca9aa19" }
|
bdk_file_store = "0.13.0"
|
||||||
|
bdk_bitcoind_rpc = "0.12.0"
|
||||||
|
|
||||||
sled = "0.34.7"
|
sled = "0.34.7"
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ use bitcoin::{
|
|||||||
Address, Amount, Network, OutPoint, Script, ScriptBuf, Sequence, Transaction, TxIn, TxOut,
|
Address, Amount, Network, OutPoint, Script, ScriptBuf, Sequence, Transaction, TxIn, TxOut,
|
||||||
Witness,
|
Witness,
|
||||||
};
|
};
|
||||||
use bitcoin::secp256k1::{KeyPair, PublicKey};
|
use bitcoin::secp256k1::{Keypair, PublicKey};
|
||||||
use bitcoin::sighash::{self, SighashCache, TapSighashType};
|
use bitcoin::sighash::{self, SighashCache, TapSighashType};
|
||||||
|
|
||||||
use crate::{fee, util};
|
use crate::{fee, util};
|
||||||
use crate::util::KeyPairExt;
|
use crate::util::KeypairExt;
|
||||||
|
|
||||||
|
|
||||||
/// The size in vbytes of each connector tx.
|
/// The size in vbytes of each connector tx.
|
||||||
@@ -51,7 +51,7 @@ impl ConnectorChain {
|
|||||||
|
|
||||||
/// Create the scriptPubkey to create a connector chain using the given publick key.
|
/// Create the scriptPubkey to create a connector chain using the given publick key.
|
||||||
pub fn output_script(pubkey: PublicKey) -> ScriptBuf {
|
pub fn output_script(pubkey: PublicKey) -> ScriptBuf {
|
||||||
ScriptBuf::new_v1_p2tr(&util::SECP, pubkey.x_only_public_key().0, None)
|
ScriptBuf::new_p2tr(&util::SECP, pubkey.x_only_public_key().0, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create the address to create a connector chain using the given publick key.
|
/// Create the address to create a connector chain using the given publick key.
|
||||||
@@ -63,7 +63,7 @@ impl ConnectorChain {
|
|||||||
pub fn output(len: usize, pubkey: PublicKey) -> TxOut {
|
pub fn output(len: usize, pubkey: PublicKey) -> TxOut {
|
||||||
TxOut {
|
TxOut {
|
||||||
script_pubkey: Self::output_script(pubkey),
|
script_pubkey: Self::output_script(pubkey),
|
||||||
value: Self::required_budget(len).to_sat(),
|
value: Self::required_budget(len),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ impl ConnectorChain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Iterator over the signed transactions in this chain.
|
/// Iterator over the signed transactions in this chain.
|
||||||
pub fn iter_signed_txs<'a>(&'a self, sign_key: &'a KeyPair) -> ConnectorTxIter<'a> {
|
pub fn iter_signed_txs<'a>(&'a self, sign_key: &'a Keypair) -> ConnectorTxIter<'a> {
|
||||||
ConnectorTxIter {
|
ConnectorTxIter {
|
||||||
len: self.len,
|
len: self.len,
|
||||||
spk: &self.spk,
|
spk: &self.spk,
|
||||||
@@ -118,7 +118,7 @@ impl ConnectorChain {
|
|||||||
pub struct ConnectorTxIter<'a> {
|
pub struct ConnectorTxIter<'a> {
|
||||||
len: usize,
|
len: usize,
|
||||||
spk: &'a Script,
|
spk: &'a Script,
|
||||||
sign_key: Option<KeyPair>,
|
sign_key: Option<Keypair>,
|
||||||
|
|
||||||
prev: OutPoint,
|
prev: OutPoint,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
@@ -133,7 +133,7 @@ impl<'a> iter::Iterator for ConnectorTxIter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut ret = Transaction {
|
let mut ret = Transaction {
|
||||||
version: 2,
|
version: bitcoin::transaction::Version::TWO,
|
||||||
lock_time: bitcoin::absolute::LockTime::ZERO,
|
lock_time: bitcoin::absolute::LockTime::ZERO,
|
||||||
input: vec![TxIn {
|
input: vec![TxIn {
|
||||||
previous_output: self.prev,
|
previous_output: self.prev,
|
||||||
@@ -144,11 +144,11 @@ impl<'a> iter::Iterator for ConnectorTxIter<'a> {
|
|||||||
output: vec![
|
output: vec![
|
||||||
TxOut {
|
TxOut {
|
||||||
script_pubkey: self.spk.to_owned(),
|
script_pubkey: self.spk.to_owned(),
|
||||||
value: ConnectorChain::required_budget(self.len - self.idx - 1).to_sat(),
|
value: ConnectorChain::required_budget(self.len - self.idx - 1),
|
||||||
},
|
},
|
||||||
TxOut {
|
TxOut {
|
||||||
script_pubkey: self.spk.to_owned(),
|
script_pubkey: self.spk.to_owned(),
|
||||||
value: fee::DUST.to_sat(),
|
value: fee::DUST,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
@@ -156,7 +156,7 @@ impl<'a> iter::Iterator for ConnectorTxIter<'a> {
|
|||||||
if let Some(keypair) = self.sign_key {
|
if let Some(keypair) = self.sign_key {
|
||||||
let prevout = TxOut {
|
let prevout = TxOut {
|
||||||
script_pubkey: self.spk.to_owned(),
|
script_pubkey: self.spk.to_owned(),
|
||||||
value: ConnectorChain::required_budget(self.len - self.idx).to_sat(),
|
value: ConnectorChain::required_budget(self.len - self.idx),
|
||||||
};
|
};
|
||||||
let mut shc = SighashCache::new(&ret);
|
let mut shc = SighashCache::new(&ret);
|
||||||
let sighash = shc.taproot_key_spend_signature_hash(
|
let sighash = shc.taproot_key_spend_signature_hash(
|
||||||
@@ -167,7 +167,7 @@ impl<'a> iter::Iterator for ConnectorTxIter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.idx += 1;
|
self.idx += 1;
|
||||||
self.prev = OutPoint::new(ret.txid(), 0);
|
self.prev = OutPoint::new(ret.compute_txid(), 0);
|
||||||
Some(ret)
|
Some(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +191,7 @@ impl<'a> iter::Iterator for ConnectorIter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(tx) = self.txs.next() {
|
if let Some(tx) = self.txs.next() {
|
||||||
let txid = tx.txid();
|
let txid = tx.compute_txid();
|
||||||
self.maybe_last = Some(OutPoint::new(txid, 0));
|
self.maybe_last = Some(OutPoint::new(txid, 0));
|
||||||
Some(OutPoint::new(txid, 1))
|
Some(OutPoint::new(txid, 1))
|
||||||
} else {
|
} else {
|
||||||
@@ -217,7 +217,7 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_budget() {
|
fn test_budget() {
|
||||||
let key = KeyPair::new(&util::SECP, &mut rand::thread_rng());
|
let key = Keypair::new(&util::SECP, &mut rand::thread_rng());
|
||||||
let utxo = OutPoint::new(Txid::all_zeros(), 0);
|
let utxo = OutPoint::new(Txid::all_zeros(), 0);
|
||||||
|
|
||||||
let chain = ConnectorChain::new(1, utxo, key.public_key());
|
let chain = ConnectorChain::new(1, utxo, key.public_key());
|
||||||
@@ -238,27 +238,27 @@ mod test {
|
|||||||
assert_eq!(chain.iter_signed_txs(&key).count(), 99);
|
assert_eq!(chain.iter_signed_txs(&key).count(), 99);
|
||||||
for tx in chain.iter_signed_txs(&key) {
|
for tx in chain.iter_signed_txs(&key) {
|
||||||
assert_eq!(tx.vsize() as u64, TX_SIZE);
|
assert_eq!(tx.vsize() as u64, TX_SIZE);
|
||||||
assert_eq!(tx.input[0].witness.serialized_len(), INPUT_WEIGHT);
|
assert_eq!(tx.input[0].witness.size(), INPUT_WEIGHT);
|
||||||
}
|
}
|
||||||
let size = chain.iter_signed_txs(&key).map(|t| t.vsize() as u64).sum::<u64>();
|
let size = chain.iter_signed_txs(&key).map(|t| t.vsize() as u64).sum::<u64>();
|
||||||
assert_eq!(size, ConnectorChain::total_vsize(100));
|
assert_eq!(size, ConnectorChain::total_vsize(100));
|
||||||
chain.iter_unsigned_txs().for_each(|t| assert_eq!(t.output[1].value, fee::DUST.to_sat()));
|
chain.iter_unsigned_txs().for_each(|t| assert_eq!(t.output[1].value, fee::DUST));
|
||||||
assert_eq!(fee::DUST.to_sat(), chain.iter_unsigned_txs().last().unwrap().output[0].value);
|
assert_eq!(fee::DUST, chain.iter_unsigned_txs().last().unwrap().output[0].value);
|
||||||
|
|
||||||
let total_value = chain.iter_unsigned_txs().map(|t| t.output[1].value).sum::<u64>()
|
let total_value = chain.iter_unsigned_txs().map(|t| t.output[1].value).sum::<Amount>()
|
||||||
+ chain.iter_unsigned_txs().last().unwrap().output[0].value
|
+ chain.iter_unsigned_txs().last().unwrap().output[0].value
|
||||||
+ size;
|
+ Amount::from_sat(size);
|
||||||
assert_eq!(ConnectorChain::required_budget(100).to_sat(), total_value);
|
assert_eq!(ConnectorChain::required_budget(100), total_value);
|
||||||
|
|
||||||
// random checks
|
// random checks
|
||||||
let mut txs = chain.iter_unsigned_txs();
|
let mut txs = chain.iter_unsigned_txs();
|
||||||
assert_eq!(txs.next().unwrap().output[0].value, ConnectorChain::required_budget(99).to_sat());
|
assert_eq!(txs.next().unwrap().output[0].value, ConnectorChain::required_budget(99));
|
||||||
assert_eq!(txs.next().unwrap().output[0].value, ConnectorChain::required_budget(98).to_sat());
|
assert_eq!(txs.next().unwrap().output[0].value, ConnectorChain::required_budget(98));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_signatures() {
|
fn test_signatures() {
|
||||||
let key = KeyPair::new(&util::SECP, &mut rand::thread_rng());
|
let key = Keypair::new(&util::SECP, &mut rand::thread_rng());
|
||||||
let utxo = OutPoint::new(Txid::all_zeros(), 0);
|
let utxo = OutPoint::new(Txid::all_zeros(), 0);
|
||||||
let spk = ConnectorChain::output_script(key.public_key());
|
let spk = ConnectorChain::output_script(key.public_key());
|
||||||
|
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ pub fn op_true_script() -> ScriptBuf {
|
|||||||
pub fn dust_anchor() -> TxOut {
|
pub fn dust_anchor() -> TxOut {
|
||||||
TxOut {
|
TxOut {
|
||||||
script_pubkey: {
|
script_pubkey: {
|
||||||
ScriptBuf::new_v0_p2wsh(&op_true_script().wscript_hash())
|
ScriptBuf::new_p2wsh(&op_true_script().wscript_hash())
|
||||||
},
|
},
|
||||||
value: DUST.to_sat(),
|
value: DUST,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
|
|
||||||
use bitcoin::{OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Witness};
|
use bitcoin::{Amount, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Witness};
|
||||||
use bitcoin::sighash::{self, SighashCache, TapSighash, TapSighashType};
|
use bitcoin::sighash::{self, SighashCache, TapSighash, TapSighashType};
|
||||||
|
|
||||||
use crate::{fee, util, Vtxo};
|
use crate::{fee, util, Vtxo};
|
||||||
@@ -12,7 +12,7 @@ pub const SIGNED_FORFEIT_TX_VSIZE: u64 = 0;
|
|||||||
|
|
||||||
pub fn create_forfeit_tx(vtxo: &Vtxo, connector: OutPoint) -> Transaction {
|
pub fn create_forfeit_tx(vtxo: &Vtxo, connector: OutPoint) -> Transaction {
|
||||||
// NB we gain the dust from the connector and lose the dust from the fee anchor
|
// NB we gain the dust from the connector and lose the dust from the fee anchor
|
||||||
let leftover = vtxo.amount().to_sat() - SIGNED_FORFEIT_TX_VSIZE; // @ 1 sat/vb
|
let leftover = vtxo.amount() - Amount::from_sat(SIGNED_FORFEIT_TX_VSIZE); // @ 1 sat/vb
|
||||||
//TODO(stevenroose) improve this hack
|
//TODO(stevenroose) improve this hack
|
||||||
let vtxo_fee_anchor_point = {
|
let vtxo_fee_anchor_point = {
|
||||||
let mut point = vtxo.point();
|
let mut point = vtxo.point();
|
||||||
@@ -20,7 +20,7 @@ pub fn create_forfeit_tx(vtxo: &Vtxo, connector: OutPoint) -> Transaction {
|
|||||||
point
|
point
|
||||||
};
|
};
|
||||||
Transaction {
|
Transaction {
|
||||||
version: 2,
|
version: bitcoin::transaction::Version::TWO,
|
||||||
lock_time: bitcoin::absolute::LockTime::ZERO,
|
lock_time: bitcoin::absolute::LockTime::ZERO,
|
||||||
input: vec![
|
input: vec![
|
||||||
TxIn {
|
TxIn {
|
||||||
@@ -46,7 +46,7 @@ pub fn create_forfeit_tx(vtxo: &Vtxo, connector: OutPoint) -> Transaction {
|
|||||||
output: vec![
|
output: vec![
|
||||||
TxOut {
|
TxOut {
|
||||||
value: leftover,
|
value: leftover,
|
||||||
script_pubkey: ScriptBuf::new_v1_p2tr(&util::SECP, vtxo.spec().combined_pubkey(), None),
|
script_pubkey: ScriptBuf::new_p2tr(&util::SECP, vtxo.spec().combined_pubkey(), None),
|
||||||
},
|
},
|
||||||
fee::dust_anchor(),
|
fee::dust_anchor(),
|
||||||
],
|
],
|
||||||
@@ -58,11 +58,11 @@ pub fn forfeit_sighash(vtxo: &Vtxo, connector: OutPoint) -> (TapSighash, Transac
|
|||||||
let exit_spk = spec.exit_spk();
|
let exit_spk = spec.exit_spk();
|
||||||
let exit_prevout = TxOut {
|
let exit_prevout = TxOut {
|
||||||
script_pubkey: exit_spk,
|
script_pubkey: exit_spk,
|
||||||
value: vtxo.amount().to_sat(),
|
value: vtxo.amount(),
|
||||||
};
|
};
|
||||||
let connector_prevout = TxOut {
|
let connector_prevout = TxOut {
|
||||||
script_pubkey: ConnectorChain::output_script(spec.asp_pubkey),
|
script_pubkey: ConnectorChain::output_script(spec.asp_pubkey),
|
||||||
value: fee::DUST.to_sat(),
|
value: fee::DUST,
|
||||||
};
|
};
|
||||||
let tx = create_forfeit_tx(vtxo, connector);
|
let tx = create_forfeit_tx(vtxo, connector);
|
||||||
let sighash = SighashCache::new(&tx).taproot_key_spend_signature_hash(
|
let sighash = SighashCache::new(&tx).taproot_key_spend_signature_hash(
|
||||||
|
|||||||
@@ -73,14 +73,14 @@ impl OffboardRequest {
|
|||||||
P2PKH_DUST_VB
|
P2PKH_DUST_VB
|
||||||
} else if script.is_p2sh() {
|
} else if script.is_p2sh() {
|
||||||
P2SH_DUST_VB
|
P2SH_DUST_VB
|
||||||
} else if script.is_v0_p2wpkh() {
|
} else if script.is_p2wpkh() {
|
||||||
P2WPKH_DUST_VB
|
P2WPKH_DUST_VB
|
||||||
} else if script.is_v0_p2wsh() {
|
} else if script.is_p2wsh() {
|
||||||
P2WSH_DUST_VB
|
P2WSH_DUST_VB
|
||||||
} else if script.is_v1_p2tr() {
|
} else if script.is_p2tr() {
|
||||||
P2TR_DUST_VB
|
P2TR_DUST_VB
|
||||||
} else if script.is_op_return() {
|
} else if script.is_op_return() {
|
||||||
bitcoin::consensus::encode::VarInt(script.len() as u64).len() as u64
|
bitcoin::consensus::encode::VarInt(script.len() as u64).size() as u64
|
||||||
+ script.len() as u64
|
+ script.len() as u64
|
||||||
+ 8
|
+ 8
|
||||||
// the input data (scriptSig and witness length fields included)
|
// the input data (scriptSig and witness length fields included)
|
||||||
@@ -104,7 +104,7 @@ impl OffboardRequest {
|
|||||||
pub fn to_txout(&self) -> TxOut {
|
pub fn to_txout(&self) -> TxOut {
|
||||||
TxOut {
|
TxOut {
|
||||||
script_pubkey: self.script_pubkey.clone(),
|
script_pubkey: self.script_pubkey.clone(),
|
||||||
value: self.amount.to_sat(),
|
value: self.amount,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,7 +216,7 @@ pub fn exit_spk(
|
|||||||
exit_delta: u16,
|
exit_delta: u16,
|
||||||
) -> ScriptBuf {
|
) -> ScriptBuf {
|
||||||
let taproot = exit_taproot(user_pubkey, asp_pubkey, exit_delta);
|
let taproot = exit_taproot(user_pubkey, asp_pubkey, exit_delta);
|
||||||
ScriptBuf::new_v1_p2tr_tweaked(taproot.output_key())
|
ScriptBuf::new_p2tr_tweaked(taproot.output_key())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||||
@@ -299,9 +299,9 @@ impl Vtxo {
|
|||||||
/// This can be an on-chain utxo or an off-chain vtxo.
|
/// This can be an on-chain utxo or an off-chain vtxo.
|
||||||
pub fn point(&self) -> OutPoint {
|
pub fn point(&self) -> OutPoint {
|
||||||
match self {
|
match self {
|
||||||
Vtxo::Onboard { .. } => OutPoint::new(self.vtxo_tx().txid(), 0),
|
Vtxo::Onboard { .. } => OutPoint::new(self.vtxo_tx().compute_txid(), 0),
|
||||||
Vtxo::Round { exit_branch, .. } => {
|
Vtxo::Round { exit_branch, .. } => {
|
||||||
OutPoint::new(exit_branch.last().unwrap().txid(), 0).into()
|
OutPoint::new(exit_branch.last().unwrap().compute_txid(), 0).into()
|
||||||
},
|
},
|
||||||
Vtxo::Oor { final_point, .. } => *final_point,
|
Vtxo::Oor { final_point, .. } => *final_point,
|
||||||
}
|
}
|
||||||
@@ -320,7 +320,7 @@ impl Vtxo {
|
|||||||
Vtxo::Onboard { base, .. } => base.spec.amount,
|
Vtxo::Onboard { base, .. } => base.spec.amount,
|
||||||
Vtxo::Round { base, .. } => base.spec.amount,
|
Vtxo::Round { base, .. } => base.spec.amount,
|
||||||
Vtxo::Oor { oor_tx, final_point, .. } => {
|
Vtxo::Oor { oor_tx, final_point, .. } => {
|
||||||
Amount::from_sat(oor_tx.output[final_point.vout as usize].value)
|
oor_tx.output[final_point.vout as usize].value
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -328,7 +328,7 @@ impl Vtxo {
|
|||||||
pub fn txout(&self) -> TxOut {
|
pub fn txout(&self) -> TxOut {
|
||||||
TxOut {
|
TxOut {
|
||||||
script_pubkey: self.spec().exit_spk(),
|
script_pubkey: self.spec().exit_spk(),
|
||||||
value: self.amount().to_sat(),
|
value: self.amount(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,7 +346,7 @@ impl Vtxo {
|
|||||||
|
|
||||||
pub fn fee_anchor(&self) -> OutPoint {
|
pub fn fee_anchor(&self) -> OutPoint {
|
||||||
let tx = self.vtxo_tx();
|
let tx = self.vtxo_tx();
|
||||||
OutPoint::new(tx.txid(), tx.output.len() as u32 - 1)
|
OutPoint::new(tx.compute_txid(), tx.output.len() as u32 - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Splits this vtxo in a set of non-OOR vtxos and the attached OOR txs.
|
/// Splits this vtxo in a set of non-OOR vtxos and the attached OOR txs.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ pub use secp256k1_zkp::{
|
|||||||
MusigAggNonce, MusigKeyAggCache, MusigPubNonce, MusigPartialSignature, MusigSecNonce,
|
MusigAggNonce, MusigKeyAggCache, MusigPubNonce, MusigPartialSignature, MusigSecNonce,
|
||||||
MusigSession, MusigSessionId,
|
MusigSession, MusigSessionId,
|
||||||
};
|
};
|
||||||
use bitcoin::secp256k1::{rand, schnorr, KeyPair, PublicKey, SecretKey, XOnlyPublicKey};
|
use bitcoin::secp256k1::{rand, schnorr, Keypair, PublicKey, SecretKey, XOnlyPublicKey};
|
||||||
|
|
||||||
use crate::util;
|
use crate::util;
|
||||||
|
|
||||||
@@ -29,12 +29,12 @@ pub fn seckey_to(sk: SecretKey) -> zkp::SecretKey {
|
|||||||
zkp::SecretKey::from_slice(&sk.secret_bytes()).unwrap()
|
zkp::SecretKey::from_slice(&sk.secret_bytes()).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn keypair_to(kp: &KeyPair) -> zkp::Keypair {
|
pub fn keypair_to(kp: &Keypair) -> zkp::Keypair {
|
||||||
zkp::Keypair::from_seckey_slice(&SECP, &kp.secret_bytes()).unwrap()
|
zkp::Keypair::from_seckey_slice(&SECP, &kp.secret_bytes()).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn keypair_from(kp: &zkp::Keypair) -> KeyPair {
|
pub fn keypair_from(kp: &zkp::Keypair) -> Keypair {
|
||||||
KeyPair::from_seckey_slice(&util::SECP, &kp.secret_bytes()).unwrap()
|
Keypair::from_seckey_slice(&util::SECP, &kp.secret_bytes()).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sig_from(s: zkp::schnorr::Signature) -> schnorr::Signature {
|
pub fn sig_from(s: zkp::schnorr::Signature) -> schnorr::Signature {
|
||||||
@@ -61,7 +61,7 @@ pub fn combine_keys(keys: impl IntoIterator<Item = PublicKey>) -> XOnlyPublicKey
|
|||||||
xonly_from(key_agg(keys).agg_pk())
|
xonly_from(key_agg(keys).agg_pk())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn nonce_pair(key: &KeyPair) -> (MusigSecNonce, MusigPubNonce) {
|
pub fn nonce_pair(key: &Keypair) -> (MusigSecNonce, MusigPubNonce) {
|
||||||
let kp = keypair_to(key);
|
let kp = keypair_to(key);
|
||||||
zkp::new_musig_nonce_pair(
|
zkp::new_musig_nonce_pair(
|
||||||
&SECP,
|
&SECP,
|
||||||
@@ -81,7 +81,7 @@ pub fn nonce_agg(pub_nonces: impl IntoIterator<Item = MusigPubNonce>) -> MusigAg
|
|||||||
pub fn partial_sign(
|
pub fn partial_sign(
|
||||||
pubkeys: impl IntoIterator<Item = PublicKey>,
|
pubkeys: impl IntoIterator<Item = PublicKey>,
|
||||||
agg_nonce: MusigAggNonce,
|
agg_nonce: MusigAggNonce,
|
||||||
key: &KeyPair,
|
key: &Keypair,
|
||||||
sec_nonce: MusigSecNonce,
|
sec_nonce: MusigSecNonce,
|
||||||
sighash: [u8; 32],
|
sighash: [u8; 32],
|
||||||
tweak: Option<[u8; 32]>,
|
tweak: Option<[u8; 32]>,
|
||||||
@@ -111,7 +111,7 @@ pub fn partial_sign(
|
|||||||
/// Perform a deterministic partial sign for the given message and the
|
/// Perform a deterministic partial sign for the given message and the
|
||||||
/// given counterparty key and nonce.
|
/// given counterparty key and nonce.
|
||||||
pub fn deterministic_partial_sign(
|
pub fn deterministic_partial_sign(
|
||||||
my_key: &KeyPair,
|
my_key: &Keypair,
|
||||||
their_pubkeys: impl IntoIterator<Item = PublicKey>,
|
their_pubkeys: impl IntoIterator<Item = PublicKey>,
|
||||||
their_nonces: impl IntoIterator<Item = MusigPubNonce>,
|
their_nonces: impl IntoIterator<Item = MusigPubNonce>,
|
||||||
msg: [u8; 32],
|
msg: [u8; 32],
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
use std::iter;
|
use std::iter;
|
||||||
|
|
||||||
use bitcoin::{Transaction, TxIn, TxOut, ScriptBuf, OutPoint, Txid, Witness, Script};
|
use bitcoin::{Amount, OutPoint, Script, ScriptBuf, Transaction, TxIn, TxOut, Txid, Witness};
|
||||||
use bitcoin::blockdata::opcodes;
|
use bitcoin::blockdata::opcodes;
|
||||||
use bitcoin::hashes::Hash;
|
use bitcoin::hashes::Hash;
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ fn empty_input() -> TxIn {
|
|||||||
|
|
||||||
fn ctv_output() -> TxOut {
|
fn ctv_output() -> TxOut {
|
||||||
TxOut {
|
TxOut {
|
||||||
value: 0,
|
value: Amount::ZERO,
|
||||||
script_pubkey: Script::builder()
|
script_pubkey: Script::builder()
|
||||||
.push_opcode(opcodes::all::OP_NOP4)
|
.push_opcode(opcodes::all::OP_NOP4)
|
||||||
.push_slice(&BYTES32)
|
.push_slice(&BYTES32)
|
||||||
@@ -44,7 +44,7 @@ fn ctv_input() -> TxIn {
|
|||||||
|
|
||||||
fn taproot_output() -> TxOut {
|
fn taproot_output() -> TxOut {
|
||||||
TxOut {
|
TxOut {
|
||||||
value: 0,
|
value: Amount::ZERO,
|
||||||
script_pubkey: Script::builder()
|
script_pubkey: Script::builder()
|
||||||
.push_opcode(opcodes::all::OP_PUSHNUM_1)
|
.push_opcode(opcodes::all::OP_PUSHNUM_1)
|
||||||
.push_slice(&BYTES32)
|
.push_slice(&BYTES32)
|
||||||
@@ -67,7 +67,7 @@ fn taproot_input() -> TxIn {
|
|||||||
|
|
||||||
fn anchor_output() -> TxOut {
|
fn anchor_output() -> TxOut {
|
||||||
TxOut {
|
TxOut {
|
||||||
value: 0,
|
value: Amount::ZERO,
|
||||||
script_pubkey: Script::builder()
|
script_pubkey: Script::builder()
|
||||||
.push_opcode(opcodes::OP_TRUE)
|
.push_opcode(opcodes::OP_TRUE)
|
||||||
.into_script(),
|
.into_script(),
|
||||||
@@ -80,7 +80,7 @@ fn anchor_input() -> TxIn {
|
|||||||
|
|
||||||
fn ctv_node_tx(radix: usize) -> Transaction {
|
fn ctv_node_tx(radix: usize) -> Transaction {
|
||||||
Transaction {
|
Transaction {
|
||||||
version: 2,
|
version: bitcoin::transaction::Version::TWO,
|
||||||
lock_time: bitcoin::locktime::absolute::LockTime::from_consensus(0),
|
lock_time: bitcoin::locktime::absolute::LockTime::from_consensus(0),
|
||||||
input: vec![ctv_input()],
|
input: vec![ctv_input()],
|
||||||
output: iter::repeat(ctv_output()).take(radix).chain(Some(anchor_output())).collect(),
|
output: iter::repeat(ctv_output()).take(radix).chain(Some(anchor_output())).collect(),
|
||||||
@@ -89,7 +89,7 @@ fn ctv_node_tx(radix: usize) -> Transaction {
|
|||||||
|
|
||||||
fn ctv_leaf_tx() -> Transaction {
|
fn ctv_leaf_tx() -> Transaction {
|
||||||
Transaction {
|
Transaction {
|
||||||
version: 2,
|
version: bitcoin::transaction::Version::TWO,
|
||||||
lock_time: bitcoin::locktime::absolute::LockTime::from_consensus(0),
|
lock_time: bitcoin::locktime::absolute::LockTime::from_consensus(0),
|
||||||
input: vec![ctv_input()],
|
input: vec![ctv_input()],
|
||||||
output: vec![taproot_output(), anchor_output()],
|
output: vec![taproot_output(), anchor_output()],
|
||||||
@@ -98,7 +98,7 @@ fn ctv_leaf_tx() -> Transaction {
|
|||||||
|
|
||||||
fn clark_node_tx(radix: usize) -> Transaction {
|
fn clark_node_tx(radix: usize) -> Transaction {
|
||||||
Transaction {
|
Transaction {
|
||||||
version: 2,
|
version: bitcoin::transaction::Version::TWO,
|
||||||
lock_time: bitcoin::locktime::absolute::LockTime::from_consensus(0),
|
lock_time: bitcoin::locktime::absolute::LockTime::from_consensus(0),
|
||||||
input: vec![taproot_input()],
|
input: vec![taproot_input()],
|
||||||
output: iter::repeat(taproot_output()).take(radix).chain(Some(anchor_output())).collect(),
|
output: iter::repeat(taproot_output()).take(radix).chain(Some(anchor_output())).collect(),
|
||||||
@@ -107,7 +107,7 @@ fn clark_node_tx(radix: usize) -> Transaction {
|
|||||||
|
|
||||||
fn clark_leaf_tx() -> Transaction {
|
fn clark_leaf_tx() -> Transaction {
|
||||||
Transaction {
|
Transaction {
|
||||||
version: 2,
|
version: bitcoin::transaction::Version::TWO,
|
||||||
lock_time: bitcoin::locktime::absolute::LockTime::from_consensus(0),
|
lock_time: bitcoin::locktime::absolute::LockTime::from_consensus(0),
|
||||||
input: vec![taproot_input()],
|
input: vec![taproot_input()],
|
||||||
output: vec![taproot_output(), anchor_output()],
|
output: vec![taproot_output(), anchor_output()],
|
||||||
@@ -137,7 +137,7 @@ fn calc_exit_cost(n: usize, radix: usize) {
|
|||||||
// for exit cost the largest radix has to be taken anyway
|
// for exit cost the largest radix has to be taken anyway
|
||||||
|
|
||||||
let exit_tx = Transaction {
|
let exit_tx = Transaction {
|
||||||
version: 2,
|
version: bitcoin::transaction::Version::TWO,
|
||||||
lock_time: bitcoin::locktime::absolute::LockTime::from_consensus(0),
|
lock_time: bitcoin::locktime::absolute::LockTime::from_consensus(0),
|
||||||
input: iter::repeat(anchor_input()).take(levels).chain(Some(taproot_input())).collect(),
|
input: iter::repeat(anchor_input()).take(levels).chain(Some(taproot_input())).collect(),
|
||||||
output: vec![taproot_output()],
|
output: vec![taproot_output()],
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
use bitcoin::{taproot, Amount, OutPoint, Sequence, ScriptBuf, Transaction, TxIn, TxOut, Witness};
|
use bitcoin::{taproot, Amount, OutPoint, Sequence, ScriptBuf, Transaction, TxIn, TxOut, Witness};
|
||||||
use bitcoin::blockdata::locktime::absolute::LockTime;
|
use bitcoin::blockdata::locktime::absolute::LockTime;
|
||||||
use bitcoin::hashes::Hash;
|
use bitcoin::hashes::Hash;
|
||||||
use bitcoin::secp256k1::{schnorr, KeyPair};
|
use bitcoin::secp256k1::{schnorr, Keypair};
|
||||||
use bitcoin::sighash::{self, SighashCache, TapSighash};
|
use bitcoin::sighash::{self, SighashCache, TapSighash};
|
||||||
|
|
||||||
use crate::{fee, musig, util, BaseVtxo, Vtxo, VtxoSpec};
|
use crate::{fee, musig, util, BaseVtxo, Vtxo, VtxoSpec};
|
||||||
@@ -37,7 +37,7 @@ pub fn onboard_taptweak(spec: &VtxoSpec) -> taproot::TapTweakHash {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn onboard_spk(spec: &VtxoSpec) -> ScriptBuf {
|
pub fn onboard_spk(spec: &VtxoSpec) -> ScriptBuf {
|
||||||
ScriptBuf::new_v1_p2tr_tweaked(onboard_taproot(spec).output_key())
|
ScriptBuf::new_p2tr_tweaked(onboard_taproot(spec).output_key())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The additional amount that needs to be sent into the onboard tx.
|
/// The additional amount that needs to be sent into the onboard tx.
|
||||||
@@ -84,7 +84,7 @@ pub struct AspPart {
|
|||||||
pub signature: musig::MusigPartialSignature,
|
pub signature: musig::MusigPartialSignature,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_asp(user: &UserPart, key: &KeyPair) -> AspPart {
|
pub fn new_asp(user: &UserPart, key: &Keypair) -> AspPart {
|
||||||
let (reveal_sighash, _reveal_tx) = reveal_tx_sighash(&user.spec, user.utxo);
|
let (reveal_sighash, _reveal_tx) = reveal_tx_sighash(&user.spec, user.utxo);
|
||||||
let msg = reveal_sighash.to_byte_array();
|
let msg = reveal_sighash.to_byte_array();
|
||||||
let tweak = onboard_taptweak(&user.spec);
|
let tweak = onboard_taptweak(&user.spec);
|
||||||
@@ -103,7 +103,7 @@ pub fn create_reveal_tx(
|
|||||||
signature: Option<&schnorr::Signature>,
|
signature: Option<&schnorr::Signature>,
|
||||||
) -> Transaction {
|
) -> Transaction {
|
||||||
Transaction {
|
Transaction {
|
||||||
version: 2,
|
version: bitcoin::transaction::Version::TWO,
|
||||||
lock_time: LockTime::ZERO,
|
lock_time: LockTime::ZERO,
|
||||||
input: vec![TxIn {
|
input: vec![TxIn {
|
||||||
previous_output: utxo,
|
previous_output: utxo,
|
||||||
@@ -120,7 +120,7 @@ pub fn create_reveal_tx(
|
|||||||
output: vec![
|
output: vec![
|
||||||
TxOut {
|
TxOut {
|
||||||
script_pubkey: spec.exit_spk(),
|
script_pubkey: spec.exit_spk(),
|
||||||
value: spec.amount.to_sat(),
|
value: spec.amount,
|
||||||
},
|
},
|
||||||
fee::dust_anchor(),
|
fee::dust_anchor(),
|
||||||
],
|
],
|
||||||
@@ -132,7 +132,7 @@ pub fn reveal_tx_sighash(spec: &VtxoSpec, utxo: OutPoint) -> (TapSighash, Transa
|
|||||||
let prev = TxOut {
|
let prev = TxOut {
|
||||||
script_pubkey: onboard_spk(&spec),
|
script_pubkey: onboard_spk(&spec),
|
||||||
//TODO(stevenroose) consider storing both leaf and input values in vtxo struct
|
//TODO(stevenroose) consider storing both leaf and input values in vtxo struct
|
||||||
value: spec.amount.to_sat() + onboard_surplus().to_sat(),
|
value: spec.amount + onboard_surplus(),
|
||||||
};
|
};
|
||||||
let sighash = SighashCache::new(&reveal_tx).taproot_key_spend_signature_hash(
|
let sighash = SighashCache::new(&reveal_tx).taproot_key_spend_signature_hash(
|
||||||
0, &sighash::Prevouts::All(&[&prev]), sighash::TapSighashType::Default,
|
0, &sighash::Prevouts::All(&[&prev]), sighash::TapSighashType::Default,
|
||||||
@@ -144,7 +144,7 @@ pub fn finish(
|
|||||||
user: UserPart,
|
user: UserPart,
|
||||||
asp: AspPart,
|
asp: AspPart,
|
||||||
private: PrivateUserPart,
|
private: PrivateUserPart,
|
||||||
key: &KeyPair,
|
key: &Keypair,
|
||||||
) -> Vtxo {
|
) -> Vtxo {
|
||||||
let (reveal_sighash, _reveal_tx) = reveal_tx_sighash(&user.spec, user.utxo);
|
let (reveal_sighash, _reveal_tx) = reveal_tx_sighash(&user.spec, user.utxo);
|
||||||
let agg_nonce = musig::nonce_agg([user.nonce, asp.nonce]);
|
let agg_nonce = musig::nonce_agg([user.nonce, asp.nonce]);
|
||||||
@@ -193,7 +193,7 @@ mod test {
|
|||||||
//! Passes through the entire flow so that all assertions
|
//! Passes through the entire flow so that all assertions
|
||||||
//! inside the code are ran at least once.
|
//! inside the code are ran at least once.
|
||||||
|
|
||||||
let key = KeyPair::new(&util::SECP, &mut rand::thread_rng());
|
let key = Keypair::new(&util::SECP, &mut rand::thread_rng());
|
||||||
let utxo = "0000000000000000000000000000000000000000000000000000000000000001:1".parse().unwrap();
|
let utxo = "0000000000000000000000000000000000000000000000000000000000000001:1".parse().unwrap();
|
||||||
let spec = VtxoSpec {
|
let spec = VtxoSpec {
|
||||||
user_pubkey: key.public_key(),
|
user_pubkey: key.public_key(),
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use bitcoin::{
|
|||||||
Witness,
|
Witness,
|
||||||
};
|
};
|
||||||
use bitcoin::hashes::Hash;
|
use bitcoin::hashes::Hash;
|
||||||
use bitcoin::secp256k1::{schnorr, KeyPair, PublicKey};
|
use bitcoin::secp256k1::{schnorr, Keypair, PublicKey};
|
||||||
use bitcoin::sighash::{self, SighashCache, TapSighash, TapSighashType};
|
use bitcoin::sighash::{self, SighashCache, TapSighash, TapSighashType};
|
||||||
|
|
||||||
use crate::{fee, musig, util, Vtxo, VtxoRequest, VtxoSpec};
|
use crate::{fee, musig, util, Vtxo, VtxoRequest, VtxoSpec};
|
||||||
@@ -35,7 +35,7 @@ impl OorPayment {
|
|||||||
|
|
||||||
pub fn unsigned_transaction(&self) -> Transaction {
|
pub fn unsigned_transaction(&self) -> Transaction {
|
||||||
Transaction {
|
Transaction {
|
||||||
version: 2,
|
version: bitcoin::transaction::Version::TWO,
|
||||||
lock_time: bitcoin::absolute::LockTime::ZERO,
|
lock_time: bitcoin::absolute::LockTime::ZERO,
|
||||||
input: self.inputs.iter().map(|input| {
|
input: self.inputs.iter().map(|input| {
|
||||||
TxIn {
|
TxIn {
|
||||||
@@ -48,7 +48,7 @@ impl OorPayment {
|
|||||||
output: self.outputs.iter().map(|output| {
|
output: self.outputs.iter().map(|output| {
|
||||||
let spk = crate::exit_spk(output.pubkey, self.asp_pubkey, self.exit_delta);
|
let spk = crate::exit_spk(output.pubkey, self.asp_pubkey, self.exit_delta);
|
||||||
TxOut {
|
TxOut {
|
||||||
value: output.amount.to_sat(),
|
value: output.amount,
|
||||||
script_pubkey: spk,
|
script_pubkey: spk,
|
||||||
}
|
}
|
||||||
}).chain([fee::dust_anchor()]).collect(),
|
}).chain([fee::dust_anchor()]).collect(),
|
||||||
@@ -56,7 +56,7 @@ impl OorPayment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn txid(&self) -> Txid {
|
pub fn txid(&self) -> Txid {
|
||||||
self.unsigned_transaction().txid()
|
self.unsigned_transaction().compute_txid()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sighashes(&self) -> Vec<TapSighash> {
|
pub fn sighashes(&self) -> Vec<TapSighash> {
|
||||||
@@ -98,7 +98,7 @@ impl OorPayment {
|
|||||||
|
|
||||||
pub fn sign_asp(
|
pub fn sign_asp(
|
||||||
&self,
|
&self,
|
||||||
keypair: &KeyPair,
|
keypair: &Keypair,
|
||||||
user_nonces: &[musig::MusigPubNonce],
|
user_nonces: &[musig::MusigPubNonce],
|
||||||
) -> (Vec<musig::MusigPubNonce>, Vec<musig::MusigPartialSignature>) {
|
) -> (Vec<musig::MusigPubNonce>, Vec<musig::MusigPartialSignature>) {
|
||||||
assert_eq!(self.inputs.len(), user_nonces.len());
|
assert_eq!(self.inputs.len(), user_nonces.len());
|
||||||
@@ -124,7 +124,7 @@ impl OorPayment {
|
|||||||
|
|
||||||
pub fn sign_finalize_user(
|
pub fn sign_finalize_user(
|
||||||
self,
|
self,
|
||||||
keypair: &KeyPair,
|
keypair: &Keypair,
|
||||||
our_sec_nonces: Vec<musig::MusigSecNonce>,
|
our_sec_nonces: Vec<musig::MusigSecNonce>,
|
||||||
our_pub_nonces: &[musig::MusigPubNonce],
|
our_pub_nonces: &[musig::MusigPubNonce],
|
||||||
asp_nonces: &[musig::MusigPubNonce],
|
asp_nonces: &[musig::MusigPubNonce],
|
||||||
@@ -188,7 +188,7 @@ impl OorTransaction {
|
|||||||
for (input, sig) in tx.input.iter_mut().zip(self.signatures.iter()) {
|
for (input, sig) in tx.input.iter_mut().zip(self.signatures.iter()) {
|
||||||
assert!(input.witness.is_empty());
|
assert!(input.witness.is_empty());
|
||||||
input.witness.push(&sig[..]);
|
input.witness.push(&sig[..]);
|
||||||
debug_assert_eq!(crate::TAPROOT_KEYSPEND_WEIGHT, input.witness.serialized_len());
|
debug_assert_eq!(crate::TAPROOT_KEYSPEND_WEIGHT, input.witness.size());
|
||||||
}
|
}
|
||||||
//TODO(stevenroose) there seems to be a bug in the tx.weight method,
|
//TODO(stevenroose) there seems to be a bug in the tx.weight method,
|
||||||
// this +2 might be fixed later
|
// this +2 might be fixed later
|
||||||
@@ -203,7 +203,7 @@ impl OorTransaction {
|
|||||||
|
|
||||||
let expiry_height = self.payment.inputs.iter().map(|i| i.spec().expiry_height).min().unwrap();
|
let expiry_height = self.payment.inputs.iter().map(|i| i.spec().expiry_height).min().unwrap();
|
||||||
let oor_tx = self.signed_transaction();
|
let oor_tx = self.signed_transaction();
|
||||||
let oor_txid = oor_tx.txid();
|
let oor_txid = oor_tx.compute_txid();
|
||||||
self.payment.outputs.iter().enumerate().map(|(idx, output)| {
|
self.payment.outputs.iter().enumerate().map(|(idx, output)| {
|
||||||
Vtxo::Oor {
|
Vtxo::Oor {
|
||||||
inputs: inputs.clone(),
|
inputs: inputs.clone(),
|
||||||
|
|||||||
@@ -131,12 +131,12 @@ impl VtxoTreeSpec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn cosign_spk(&self) -> ScriptBuf {
|
pub fn cosign_spk(&self) -> ScriptBuf {
|
||||||
ScriptBuf::new_v1_p2tr_tweaked(self.cosign_taproot().output_key())
|
ScriptBuf::new_p2tr_tweaked(self.cosign_taproot().output_key())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_tx(&self, children: &[&Transaction]) -> Transaction {
|
fn node_tx(&self, children: &[&Transaction]) -> Transaction {
|
||||||
Transaction {
|
Transaction {
|
||||||
version: 2,
|
version: bitcoin::transaction::Version::TWO,
|
||||||
lock_time: bitcoin::absolute::LockTime::ZERO,
|
lock_time: bitcoin::absolute::LockTime::ZERO,
|
||||||
input: vec![TxIn {
|
input: vec![TxIn {
|
||||||
previous_output: OutPoint::null(),
|
previous_output: OutPoint::null(),
|
||||||
@@ -159,7 +159,7 @@ impl VtxoTreeSpec {
|
|||||||
};
|
};
|
||||||
TxOut {
|
TxOut {
|
||||||
script_pubkey: self.cosign_spk(),
|
script_pubkey: self.cosign_spk(),
|
||||||
value: child.output.iter().map(|o| o.value).sum::<u64>() + fee_budget,
|
value: child.output.iter().map(|o| o.value).sum::<Amount>() + Amount::from_sat(fee_budget),
|
||||||
}
|
}
|
||||||
}).collect(),
|
}).collect(),
|
||||||
}
|
}
|
||||||
@@ -178,7 +178,7 @@ impl VtxoTreeSpec {
|
|||||||
fn leaf_tx(&self, vtxo: &VtxoRequest) -> Transaction {
|
fn leaf_tx(&self, vtxo: &VtxoRequest) -> Transaction {
|
||||||
let vtxo_spec = self.vtxo_spec(vtxo);
|
let vtxo_spec = self.vtxo_spec(vtxo);
|
||||||
Transaction {
|
Transaction {
|
||||||
version: 2,
|
version: bitcoin::transaction::Version::TWO,
|
||||||
lock_time: bitcoin::absolute::LockTime::ZERO,
|
lock_time: bitcoin::absolute::LockTime::ZERO,
|
||||||
input: vec![TxIn {
|
input: vec![TxIn {
|
||||||
previous_output: OutPoint::null(),
|
previous_output: OutPoint::null(),
|
||||||
@@ -189,7 +189,7 @@ impl VtxoTreeSpec {
|
|||||||
output: vec![
|
output: vec![
|
||||||
TxOut {
|
TxOut {
|
||||||
script_pubkey: vtxo_spec.exit_spk(),
|
script_pubkey: vtxo_spec.exit_spk(),
|
||||||
value: vtxo.amount.to_sat(),
|
value: vtxo.amount,
|
||||||
},
|
},
|
||||||
fee::dust_anchor(),
|
fee::dust_anchor(),
|
||||||
],
|
],
|
||||||
@@ -205,7 +205,7 @@ impl VtxoTreeSpec {
|
|||||||
// This is the root, set to the tree's on-chain utxo.
|
// This is the root, set to the tree's on-chain utxo.
|
||||||
tree.element_at_mut(cursor).unwrap().input[0].previous_output = utxo;
|
tree.element_at_mut(cursor).unwrap().input[0].previous_output = utxo;
|
||||||
while cursor >= tree.nb_leaves() {
|
while cursor >= tree.nb_leaves() {
|
||||||
let txid = tree.element_at(cursor).unwrap().txid();
|
let txid = tree.element_at(cursor).unwrap().compute_txid();
|
||||||
let nb_children = tree.nb_children_of(cursor).unwrap();
|
let nb_children = tree.nb_children_of(cursor).unwrap();
|
||||||
for i in 0..nb_children {
|
for i in 0..nb_children {
|
||||||
let prevout = OutPoint::new(txid, i as u32);
|
let prevout = OutPoint::new(txid, i as u32);
|
||||||
@@ -228,7 +228,7 @@ impl VtxoTreeSpec {
|
|||||||
// this is the root
|
// this is the root
|
||||||
TxOut {
|
TxOut {
|
||||||
script_pubkey: self.cosign_spk(),
|
script_pubkey: self.cosign_spk(),
|
||||||
value: self.total_required_value().to_sat(),
|
value: self.total_required_value(),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let el = tree.element_at(idx).unwrap();
|
let el = tree.element_at(idx).unwrap();
|
||||||
@@ -313,20 +313,21 @@ mod test {
|
|||||||
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use bitcoin::hashes::sha256;
|
use bitcoin::hashes::{sha256, Hash};
|
||||||
use bitcoin::secp256k1::{self, rand, KeyPair};
|
use bitcoin::secp256k1::{self, rand, Keypair, Message};
|
||||||
|
|
||||||
use crate::musig;
|
use crate::musig;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_node_tx_sizes() {
|
fn test_node_tx_sizes() {
|
||||||
let secp = secp256k1::Secp256k1::new();
|
let secp = secp256k1::Secp256k1::new();
|
||||||
let key1 = KeyPair::new(&secp, &mut rand::thread_rng()); // asp
|
let key1 = Keypair::new(&secp, &mut rand::thread_rng()); // asp
|
||||||
let key2 = KeyPair::new(&secp, &mut rand::thread_rng());
|
let key2 = Keypair::new(&secp, &mut rand::thread_rng());
|
||||||
let sha = sha256::Hash::from_str("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a").unwrap();
|
let sha = sha256::Hash::from_str("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a").unwrap();
|
||||||
let sig = secp.sign_schnorr(&sha.into(), &key1);
|
let message = Message::from_digest_slice(&sha.to_byte_array()).unwrap();
|
||||||
|
let sig = secp.sign_schnorr(&message, &key1);
|
||||||
let dest = VtxoRequest {
|
let dest = VtxoRequest {
|
||||||
pubkey: KeyPair::new(&secp, &mut rand::thread_rng()).public_key(),
|
pubkey: Keypair::new(&secp, &mut rand::thread_rng()).public_key(),
|
||||||
amount: Amount::from_sat(100_000),
|
amount: Amount::from_sat(100_000),
|
||||||
};
|
};
|
||||||
let point = "0000000000000000000000000000000000000000000000000000000000000001:1".parse().unwrap();
|
let point = "0000000000000000000000000000000000000000000000000000000000000001:1".parse().unwrap();
|
||||||
@@ -352,7 +353,7 @@ mod test {
|
|||||||
let mut iter = exit.iter().enumerate().peekable();
|
let mut iter = exit.iter().enumerate().peekable();
|
||||||
while let Some((i, cur)) = iter.next() {
|
while let Some((i, cur)) = iter.next() {
|
||||||
if let Some((_, next)) = iter.peek() {
|
if let Some((_, next)) = iter.peek() {
|
||||||
assert_eq!(next.input[0].previous_output.txid, cur.txid(), "{}", i);
|
assert_eq!(next.input[0].previous_output.txid, cur.compute_txid(), "{}", i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,7 +363,7 @@ mod test {
|
|||||||
assert_eq!(leaf.vsize() as u64, LEAF_TX_VSIZE);
|
assert_eq!(leaf.vsize() as u64, LEAF_TX_VSIZE);
|
||||||
for node in iter {
|
for node in iter {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
node.input[0].witness.serialized_len(),
|
node.input[0].witness.size(),
|
||||||
crate::TAPROOT_KEYSPEND_WEIGHT,
|
crate::TAPROOT_KEYSPEND_WEIGHT,
|
||||||
);
|
);
|
||||||
match node.output.len() {
|
match node.output.len() {
|
||||||
|
|||||||
@@ -2,16 +2,16 @@
|
|||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
|
|
||||||
use bitcoin::{opcodes, taproot, ScriptBuf};
|
use bitcoin::{opcodes, taproot, ScriptBuf};
|
||||||
use bitcoin::secp256k1::{self, KeyPair, XOnlyPublicKey};
|
use bitcoin::secp256k1::{self, Keypair, XOnlyPublicKey};
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
/// Global secp context.
|
/// Global secp context.
|
||||||
pub static ref SECP: secp256k1::Secp256k1<secp256k1::All> = secp256k1::Secp256k1::new();
|
pub static ref SECP: secp256k1::Secp256k1<secp256k1::All> = secp256k1::Secp256k1::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait KeyPairExt: Borrow<KeyPair> {
|
pub trait KeypairExt: Borrow<Keypair> {
|
||||||
/// Adapt this key pair to be used in a key-spend-only taproot.
|
/// Adapt this key pair to be used in a key-spend-only taproot.
|
||||||
fn for_keyspend(&self) -> KeyPair {
|
fn for_keyspend(&self) -> Keypair {
|
||||||
let tweak = taproot::TapTweakHash::from_key_and_tweak(
|
let tweak = taproot::TapTweakHash::from_key_and_tweak(
|
||||||
self.borrow().x_only_public_key().0, None,
|
self.borrow().x_only_public_key().0, None,
|
||||||
).to_scalar();
|
).to_scalar();
|
||||||
@@ -19,7 +19,7 @@ pub trait KeyPairExt: Borrow<KeyPair> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeyPairExt for KeyPair {}
|
impl KeypairExt for Keypair {}
|
||||||
|
|
||||||
/// Create a tapscript that is a checksig and a relative timelock.
|
/// Create a tapscript that is a checksig and a relative timelock.
|
||||||
pub fn delayed_sign(delay_blocks: u16, pubkey: XOnlyPublicKey) -> ScriptBuf {
|
pub fn delayed_sign(delay_blocks: u16, pubkey: XOnlyPublicKey) -> ScriptBuf {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ serde_json.workspace = true
|
|||||||
ciborium.workspace = true
|
ciborium.workspace = true
|
||||||
bitcoin.workspace = true
|
bitcoin.workspace = true
|
||||||
bip39.workspace = true
|
bip39.workspace = true
|
||||||
bdk.workspace = true
|
bdk_wallet.workspace = true
|
||||||
bdk_file_store.workspace = true
|
bdk_file_store.workspace = true
|
||||||
bdk_bitcoind_rpc.workspace = true
|
bdk_bitcoind_rpc.workspace = true
|
||||||
prost.workspace = true
|
prost.workspace = true
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ pub struct StoredRound {
|
|||||||
|
|
||||||
impl StoredRound {
|
impl StoredRound {
|
||||||
pub fn id(&self) -> Txid {
|
pub fn id(&self) -> Txid {
|
||||||
self.tx.txid()
|
self.tx.compute_txid()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode(&self) -> Vec<u8> {
|
fn encode(&self) -> Vec<u8> {
|
||||||
|
|||||||
@@ -20,12 +20,12 @@ use std::str::FromStr;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use bdk_bitcoind_rpc::bitcoincore_rpc::RpcApi;
|
use bdk_bitcoind_rpc::bitcoincore_rpc::{RawTx, RpcApi};
|
||||||
use bitcoin::{bip32, sighash, psbt, taproot, Amount, Address, OutPoint, Transaction, Witness};
|
use bitcoin::{bip32, sighash, psbt, taproot, Amount, Address, OutPoint, Transaction, Witness};
|
||||||
use bitcoin::secp256k1::{self, KeyPair};
|
use bitcoin::secp256k1::{self, Keypair};
|
||||||
use tokio::sync::{Mutex, RwLock};
|
use tokio::sync::{Mutex, RwLock};
|
||||||
|
|
||||||
use ark::util::KeyPairExt;
|
use ark::util::KeypairExt;
|
||||||
use ark::musig;
|
use ark::musig;
|
||||||
|
|
||||||
use crate::psbtext::{PsbtInputExt, RoundMeta};
|
use crate::psbtext::{PsbtInputExt, RoundMeta};
|
||||||
@@ -91,11 +91,11 @@ pub struct RoundHandle {
|
|||||||
pub struct App {
|
pub struct App {
|
||||||
config: Config,
|
config: Config,
|
||||||
db: database::Db,
|
db: database::Db,
|
||||||
master_xpriv: bip32::ExtendedPrivKey,
|
master_xpriv: bip32::Xpriv,
|
||||||
master_key: KeyPair,
|
master_key: Keypair,
|
||||||
wallet: Mutex<bdk::Wallet<bdk_file_store::Store<'static, bdk::wallet::ChangeSet>>>,
|
wallet: Mutex<bdk_wallet::Wallet>,
|
||||||
|
wallet_store: Mutex<bdk_file_store::Store<bdk_wallet::wallet::ChangeSet>>,
|
||||||
bitcoind: bdk_bitcoind_rpc::bitcoincore_rpc::Client,
|
bitcoind: bdk_bitcoind_rpc::bitcoincore_rpc::Client,
|
||||||
|
|
||||||
rounds: Option<RoundHandle>,
|
rounds: Option<RoundHandle>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,23 +146,26 @@ impl App {
|
|||||||
.context("db error")?
|
.context("db error")?
|
||||||
.context("db doesn't contain seed")?;
|
.context("db doesn't contain seed")?;
|
||||||
let (master_key, xpriv) = {
|
let (master_key, xpriv) = {
|
||||||
let seed_xpriv = bip32::ExtendedPrivKey::new_master(config.network, &seed).unwrap();
|
let seed_xpriv = bip32::Xpriv::new_master(config.network, &seed).unwrap();
|
||||||
let path = bip32::DerivationPath::from_str("m/0").unwrap();
|
let path = bip32::DerivationPath::from_str("m/0").unwrap();
|
||||||
let xpriv = seed_xpriv.derive_priv(&SECP, &path).unwrap();
|
let xpriv = seed_xpriv.derive_priv(&SECP, &path).unwrap();
|
||||||
let keypair = KeyPair::from_secret_key(&SECP, &xpriv.private_key);
|
let keypair = Keypair::from_secret_key(&SECP, &xpriv.private_key);
|
||||||
(keypair, xpriv)
|
(keypair, xpriv)
|
||||||
};
|
};
|
||||||
|
|
||||||
let wallet = {
|
|
||||||
let db_path = datadir.join("wallet.db");
|
let db_path = datadir.join("wallet.db");
|
||||||
info!("Loading wallet db from {}", db_path.display());
|
info!("Loading wallet db from {}", db_path.display());
|
||||||
let db = bdk_file_store::Store::<bdk::wallet::ChangeSet>::open_or_create_new(
|
let mut file_store = bdk_file_store::Store::<bdk_wallet::wallet::ChangeSet>::open_or_create_new(
|
||||||
DB_MAGIC.as_bytes(), db_path,
|
DB_MAGIC.as_bytes(), db_path,
|
||||||
)?;
|
)?;
|
||||||
|
let wallet = {
|
||||||
|
let change_set = file_store.aggregate_changesets()?;
|
||||||
|
|
||||||
let desc = format!("tr({})", xpriv);
|
let edesc = format!("tr({}/84'/0'/0'/0/*)", xpriv);
|
||||||
debug!("Opening BDK wallet with descriptor {}", desc);
|
let idesc = format!("tr({}/84'/0'/0'/1/*)", xpriv);
|
||||||
bdk::Wallet::new_or_load(&desc, None, db, config.network)
|
|
||||||
|
debug!("Opening BDK wallet with descriptor {}", edesc);
|
||||||
|
bdk_wallet::Wallet::new_or_load(&edesc, &idesc, change_set, config.network)
|
||||||
.context("failed to create or load bdk wallet")?
|
.context("failed to create or load bdk wallet")?
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -173,12 +176,13 @@ impl App {
|
|||||||
|
|
||||||
|
|
||||||
Ok(Arc::new(App {
|
Ok(Arc::new(App {
|
||||||
config: config,
|
config,
|
||||||
db: db,
|
db,
|
||||||
master_xpriv: xpriv,
|
master_xpriv: xpriv,
|
||||||
master_key: master_key,
|
master_key,
|
||||||
wallet: Mutex::new(wallet),
|
wallet: Mutex::new(wallet),
|
||||||
bitcoind: bitcoind,
|
wallet_store: Mutex::new(file_store),
|
||||||
|
bitcoind,
|
||||||
rounds: None,
|
rounds: None,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@@ -192,9 +196,9 @@ impl App {
|
|||||||
|
|
||||||
mut_self.rounds = Some(RoundHandle {
|
mut_self.rounds = Some(RoundHandle {
|
||||||
round_busy: RwLock::new(()),
|
round_busy: RwLock::new(()),
|
||||||
round_event_tx: round_event_tx,
|
round_event_tx,
|
||||||
round_input_tx: round_input_tx,
|
round_input_tx,
|
||||||
round_trigger_tx: round_trigger_tx,
|
round_trigger_tx,
|
||||||
});
|
});
|
||||||
|
|
||||||
let app = self.clone();
|
let app = self.clone();
|
||||||
@@ -237,14 +241,15 @@ impl App {
|
|||||||
|
|
||||||
pub async fn onchain_address(&self) -> anyhow::Result<Address> {
|
pub async fn onchain_address(&self) -> anyhow::Result<Address> {
|
||||||
let mut wallet = self.wallet.lock().await;
|
let mut wallet = self.wallet.lock().await;
|
||||||
let ret = wallet.try_get_address(bdk::wallet::AddressIndex::New)?.address;
|
let ret = wallet.next_unused_address(bdk_wallet::KeychainKind::External).address;
|
||||||
// should always return the same address
|
// should always return the same address
|
||||||
debug_assert_eq!(ret, wallet.try_get_address(bdk::wallet::AddressIndex::New)?.address);
|
debug_assert_eq!(ret, wallet.next_unused_address(bdk_wallet::KeychainKind::External).address);
|
||||||
Ok(ret)
|
Ok(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn sync_onchain_wallet(&self) -> anyhow::Result<Amount> {
|
pub async fn sync_onchain_wallet(& self) -> anyhow::Result<Amount> {
|
||||||
let mut wallet = self.wallet.lock().await;
|
let mut wallet = self.wallet.lock().await;
|
||||||
|
let mut file_store = self.wallet_store.lock().await;
|
||||||
let prev_tip = wallet.latest_checkpoint();
|
let prev_tip = wallet.latest_checkpoint();
|
||||||
// let keychain_spks = self.wallet.spks_of_all_keychains();
|
// let keychain_spks = self.wallet.spks_of_all_keychains();
|
||||||
|
|
||||||
@@ -255,29 +260,33 @@ impl App {
|
|||||||
|
|
||||||
if em.block_height() % 10_000 == 0 {
|
if em.block_height() % 10_000 == 0 {
|
||||||
debug!("Synced until block {}, committing...", em.block_height());
|
debug!("Synced until block {}, committing...", em.block_height());
|
||||||
wallet.commit()?;
|
if let Some(change_set) = wallet.take_staged() {
|
||||||
|
file_store.append_changeset(&change_set)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// mempool
|
// mempool
|
||||||
let mempool = emitter.mempool()?;
|
let mempool = emitter.mempool()?;
|
||||||
wallet.apply_unconfirmed_txs(mempool.iter().map(|(tx, time)| (tx, *time)));
|
wallet.apply_unconfirmed_txs(mempool.iter().map(|(tx, time)| (tx, *time)));
|
||||||
wallet.commit()?;
|
if let Some(change_set) = wallet.take_staged() {
|
||||||
|
file_store.append_changeset(&change_set)?;
|
||||||
|
}
|
||||||
|
|
||||||
// rebroadcast unconfirmed txs
|
// rebroadcast unconfirmed txs
|
||||||
// NB during some round failures we commit a tx but fail to broadcast it,
|
// NB during some round failures we commit a tx but fail to broadcast it,
|
||||||
// so this ensures we still broadcast them afterwards
|
// so this ensures we still broadcast them afterwards
|
||||||
for tx in wallet.transactions() {
|
for tx in wallet.transactions() {
|
||||||
if !tx.chain_position.is_confirmed() {
|
if !tx.chain_position.is_confirmed() {
|
||||||
let bc = self.bitcoind.send_raw_transaction(tx.tx_node.tx);
|
let bc = self.bitcoind.send_raw_transaction(tx.tx_node.tx.raw_hex());
|
||||||
if let Err(e) = bc {
|
if let Err(e) = bc {
|
||||||
warn!("Error broadcasting pending tx: {}", e);
|
warn!("Error broadcasting pending tx: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let balance = wallet.get_balance();
|
let balance = wallet.balance();
|
||||||
Ok(Amount::from_sat(balance.total()))
|
Ok(balance.total())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn drain(
|
pub async fn drain(
|
||||||
@@ -288,20 +297,23 @@ impl App {
|
|||||||
|
|
||||||
let addr = address.require_network(self.config.network)?;
|
let addr = address.require_network(self.config.network)?;
|
||||||
|
|
||||||
|
let mut file_store = self.wallet_store.lock().await;
|
||||||
let mut wallet = self.wallet.lock().await;
|
let mut wallet = self.wallet.lock().await;
|
||||||
let mut b = wallet.build_tx();
|
let mut b = wallet.build_tx();
|
||||||
b.drain_to(addr.script_pubkey());
|
b.drain_to(addr.script_pubkey());
|
||||||
b.drain_wallet();
|
b.drain_wallet();
|
||||||
let mut psbt = b.finish().context("error building tx")?;
|
let mut psbt = b.finish().context("error building tx")?;
|
||||||
let finalized = wallet.sign(&mut psbt, bdk::SignOptions::default())?;
|
let finalized = wallet.sign(&mut psbt, bdk_wallet::SignOptions::default())?;
|
||||||
assert!(finalized);
|
assert!(finalized);
|
||||||
let tx = psbt.extract_tx();
|
let tx = psbt.extract_tx()?;
|
||||||
wallet.commit()?;
|
if let Some(change_set) = wallet.take_staged() {
|
||||||
|
file_store.append_changeset(&change_set)?;
|
||||||
|
};
|
||||||
drop(wallet);
|
drop(wallet);
|
||||||
|
|
||||||
if let Err(e) = self.bitcoind.send_raw_transaction(&tx) {
|
if let Err(e) = self.bitcoind.send_raw_transaction(tx.raw_hex()) {
|
||||||
error!("Error broadcasting tx: {}", e);
|
error!("Error broadcasting tx: {}", e);
|
||||||
error!("Try yourself: {}", bitcoin::consensus::encode::serialize_hex(&tx));
|
error!("Try yourself: {}", tx.raw_hex());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(tx)
|
Ok(tx)
|
||||||
@@ -404,7 +416,7 @@ impl App {
|
|||||||
let wit = Witness::from_slice(
|
let wit = Witness::from_slice(
|
||||||
&[&sig[..], script.as_bytes(), &control.serialize()],
|
&[&sig[..], script.as_bytes(), &control.serialize()],
|
||||||
);
|
);
|
||||||
debug_assert_eq!(wit.serialized_len(), ark::tree::signed::NODE_SPEND_WEIGHT);
|
debug_assert_eq!(wit.size(), ark::tree::signed::NODE_SPEND_WEIGHT);
|
||||||
input.final_script_witness = Some(wit);
|
input.final_script_witness = Some(wit);
|
||||||
},
|
},
|
||||||
RoundMeta::Connector => {
|
RoundMeta::Connector => {
|
||||||
@@ -443,6 +455,6 @@ pub(crate) struct SpendableUtxo {
|
|||||||
|
|
||||||
impl SpendableUtxo {
|
impl SpendableUtxo {
|
||||||
pub fn amount(&self) -> Amount {
|
pub fn amount(&self) -> Amount {
|
||||||
Amount::from_sat(self.psbt.witness_utxo.as_ref().unwrap().value)
|
self.psbt.witness_utxo.as_ref().unwrap().value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ async fn inner_main() -> anyhow::Result<()> {
|
|||||||
},
|
},
|
||||||
Command::Drain { address } => {
|
Command::Drain { address } => {
|
||||||
let app = App::open(&cli.datadir.context("need datadir")?).context("server init")?;
|
let app = App::open(&cli.datadir.context("need datadir")?).context("server init")?;
|
||||||
println!("{}", app.drain(address).await?.txid());
|
println!("{}", app.drain(address).await?.compute_txid());
|
||||||
},
|
},
|
||||||
Command::GetMnemonic => {
|
Command::GetMnemonic => {
|
||||||
let app = App::open(&cli.datadir.context("need datadir")?).context("server init")?;
|
let app = App::open(&cli.datadir.context("need datadir")?).context("server init")?;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use bdk_bitcoind_rpc::bitcoincore_rpc::RpcApi;
|
|||||||
use bitcoin::{Amount, FeeRate, OutPoint, Sequence, Transaction};
|
use bitcoin::{Amount, FeeRate, OutPoint, Sequence, Transaction};
|
||||||
use bitcoin::hashes::Hash;
|
use bitcoin::hashes::Hash;
|
||||||
use bitcoin::locktime::absolute::LockTime;
|
use bitcoin::locktime::absolute::LockTime;
|
||||||
use bitcoin::secp256k1::{rand, KeyPair, PublicKey};
|
use bitcoin::secp256k1::{rand, Keypair, PublicKey};
|
||||||
use bitcoin::sighash::TapSighash;
|
use bitcoin::sighash::TapSighash;
|
||||||
|
|
||||||
use ark::{musig, OffboardRequest, VtxoRequest, Vtxo, VtxoId};
|
use ark::{musig, OffboardRequest, VtxoRequest, Vtxo, VtxoId};
|
||||||
@@ -205,7 +205,7 @@ pub async fn run_round_scheduler(
|
|||||||
// NB allowed_inputs should NOT be cleared here.
|
// NB allowed_inputs should NOT be cleared here.
|
||||||
|
|
||||||
// Generate a one-time use signing key.
|
// Generate a one-time use signing key.
|
||||||
let cosign_key = KeyPair::new(&SECP, &mut rand::thread_rng());
|
let cosign_key = Keypair::new(&SECP, &mut rand::thread_rng());
|
||||||
cosigners.insert(cosign_key.public_key());
|
cosigners.insert(cosign_key.public_key());
|
||||||
|
|
||||||
// Start receiving payments.
|
// Start receiving payments.
|
||||||
@@ -322,24 +322,24 @@ pub async fn run_round_scheduler(
|
|||||||
let mut wallet = app.wallet.lock().await;
|
let mut wallet = app.wallet.lock().await;
|
||||||
let mut round_tx_psbt = {
|
let mut round_tx_psbt = {
|
||||||
let mut b = wallet.build_tx();
|
let mut b = wallet.build_tx();
|
||||||
b.ordering(bdk::wallet::tx_builder::TxOrdering::Untouched);
|
b.ordering(bdk_wallet::wallet::tx_builder::TxOrdering::Untouched);
|
||||||
b.nlocktime(LockTime::from_height(tip).expect("actual height"));
|
b.nlocktime(LockTime::from_height(tip).expect("actual height"));
|
||||||
for utxo in &spendable_utxos {
|
for utxo in &spendable_utxos {
|
||||||
b.add_foreign_utxo_with_sequence(
|
b.add_foreign_utxo_with_sequence(
|
||||||
utxo.point, utxo.psbt.clone(), utxo.weight, Sequence::ZERO,
|
utxo.point, utxo.psbt.clone(), utxo.weight, Sequence::ZERO,
|
||||||
).expect("bdk rejected foreign utxo");
|
).expect("bdk rejected foreign utxo");
|
||||||
}
|
}
|
||||||
b.add_recipient(vtxos_spec.cosign_spk(), vtxos_spec.total_required_value().to_sat());
|
b.add_recipient(vtxos_spec.cosign_spk(), vtxos_spec.total_required_value());
|
||||||
b.add_recipient(connector_output.script_pubkey, connector_output.value);
|
b.add_recipient(connector_output.script_pubkey, connector_output.value);
|
||||||
for offb in &all_offboards {
|
for offb in &all_offboards {
|
||||||
b.add_recipient(offb.script_pubkey.clone(), offb.amount.to_sat());
|
b.add_recipient(offb.script_pubkey.clone(), offb.amount);
|
||||||
}
|
}
|
||||||
b.fee_rate(round_tx_feerate.to_bdk());
|
b.fee_rate(round_tx_feerate.to_bdk());
|
||||||
b.finish().expect("bdk failed to create round tx")
|
b.finish().expect("bdk failed to create round tx")
|
||||||
};
|
};
|
||||||
let round_tx = round_tx_psbt.clone().extract_tx();
|
let round_tx = round_tx_psbt.clone().extract_tx()?;
|
||||||
let vtxos_utxo = OutPoint::new(round_tx.txid(), 0);
|
let vtxos_utxo = OutPoint::new(round_tx.compute_txid(), 0);
|
||||||
let conns_utxo = OutPoint::new(round_tx.txid(), 1);
|
let conns_utxo = OutPoint::new(round_tx.compute_txid(), 1);
|
||||||
|
|
||||||
// Generate vtxo nonces and combine with user's nonces.
|
// Generate vtxo nonces and combine with user's nonces.
|
||||||
let (sec_vtxo_nonces, pub_vtxo_nonces) = {
|
let (sec_vtxo_nonces, pub_vtxo_nonces) = {
|
||||||
@@ -570,18 +570,21 @@ pub async fn run_round_scheduler(
|
|||||||
|
|
||||||
// Sign the on-chain tx.
|
// Sign the on-chain tx.
|
||||||
app.sign_round_utxo_inputs(&mut round_tx_psbt).context("signing round inputs")?;
|
app.sign_round_utxo_inputs(&mut round_tx_psbt).context("signing round inputs")?;
|
||||||
let opts = bdk::SignOptions {
|
let opts = bdk_wallet::SignOptions {
|
||||||
trust_witness_utxo: true,
|
trust_witness_utxo: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let finalized = wallet.sign(&mut round_tx_psbt, opts)?;
|
let finalized = wallet.sign(&mut round_tx_psbt, opts)?;
|
||||||
assert!(finalized);
|
assert!(finalized);
|
||||||
let round_tx = round_tx_psbt.extract_tx();
|
let round_tx = round_tx_psbt.extract_tx()?;
|
||||||
wallet.commit()?;
|
let mut file_store = app.wallet_store.lock().await;
|
||||||
|
if let Some(change_set) = wallet.take_staged() {
|
||||||
|
file_store.append_changeset(&change_set)?;
|
||||||
|
}
|
||||||
drop(wallet); // we no longer need the lock
|
drop(wallet); // we no longer need the lock
|
||||||
|
|
||||||
// Broadcast over bitcoind.
|
// Broadcast over bitcoind.
|
||||||
debug!("Broadcasting round tx {}", round_tx.txid());
|
debug!("Broadcasting round tx {}", round_tx.compute_txid());
|
||||||
let bc = app.bitcoind.send_raw_transaction(&round_tx);
|
let bc = app.bitcoind.send_raw_transaction(&round_tx);
|
||||||
if let Err(e) = bc {
|
if let Err(e) = bc {
|
||||||
warn!("Couldn't broadcast round tx: {}", e);
|
warn!("Couldn't broadcast round tx: {}", e);
|
||||||
@@ -596,7 +599,7 @@ pub async fn run_round_scheduler(
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Store forfeit txs and round info in database.
|
// Store forfeit txs and round info in database.
|
||||||
let round_id = round_tx.txid();
|
let round_id = round_tx.compute_txid();
|
||||||
for (id, vtxo) in all_inputs {
|
for (id, vtxo) in all_inputs {
|
||||||
let forfeit_sigs = forfeit_sigs.remove(&id).unwrap();
|
let forfeit_sigs = forfeit_sigs.remove(&id).unwrap();
|
||||||
let point = vtxo.point();
|
let point = vtxo.point();
|
||||||
@@ -615,7 +618,7 @@ pub async fn run_round_scheduler(
|
|||||||
app.db.remove_round(round)?;
|
app.db.remove_round(round)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Finished round {} with tx {}", round_id, round_tx.txid());
|
info!("Finished round {} with tx {}", round_id, round_tx.compute_txid());
|
||||||
break 'attempt;
|
break 'attempt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ use std::borrow::Borrow;
|
|||||||
use bitcoin::FeeRate;
|
use bitcoin::FeeRate;
|
||||||
|
|
||||||
pub trait FeeRateExt: Borrow<FeeRate> + Copy {
|
pub trait FeeRateExt: Borrow<FeeRate> + Copy {
|
||||||
fn to_bdk(self) -> bdk::FeeRate {
|
fn to_bdk(self) -> bitcoin::FeeRate {
|
||||||
bdk::FeeRate::from_sat_per_kwu(self.borrow().to_sat_per_kwu() as f32)
|
bitcoin::FeeRate::from_sat_per_kwu(self.borrow().to_sat_per_kwu())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ ciborium.workspace = true
|
|||||||
bitcoin.workspace = true
|
bitcoin.workspace = true
|
||||||
bip39.workspace = true
|
bip39.workspace = true
|
||||||
miniscript.workspace = true
|
miniscript.workspace = true
|
||||||
bdk.workspace = true
|
bdk_wallet.workspace = true
|
||||||
bdk_file_store.workspace = true
|
bdk_file_store.workspace = true
|
||||||
bdk_bitcoind_rpc.workspace = true
|
bdk_bitcoind_rpc.workspace = true
|
||||||
bdk_esplora.workspace = true
|
bdk_esplora.workspace = true
|
||||||
|
|||||||
@@ -116,10 +116,10 @@ impl Wallet {
|
|||||||
|
|
||||||
// Broadcast exit txs.
|
// Broadcast exit txs.
|
||||||
for tx in &exit.broadcast {
|
for tx in &exit.broadcast {
|
||||||
trace!("Broadcasting tx {}: {}", tx.txid(), bitcoin::consensus::encode::serialize_hex(tx));
|
trace!("Broadcasting tx {}: {}", tx.compute_txid(), bitcoin::consensus::encode::serialize_hex(tx));
|
||||||
if let Err(e) = self.onchain.broadcast_tx(tx).await {
|
if let Err(e) = self.onchain.broadcast_tx(tx).await {
|
||||||
error!("Error broadcasting exit tx {}: {}", tx.txid(), e);
|
error!("Error broadcasting exit tx {}: {}", tx.compute_txid(), e);
|
||||||
error!("Tx {}: {}", tx.txid(), bitcoin::consensus::encode::serialize_hex(tx));
|
error!("Tx {}: {}", tx.compute_txid(), bitcoin::consensus::encode::serialize_hex(tx));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +145,7 @@ impl Wallet {
|
|||||||
|
|
||||||
// Then we'll send a tx that will pay the fee for all the txs we made.
|
// Then we'll send a tx that will pay the fee for all the txs we made.
|
||||||
let tx = self.onchain.spend_fee_anchors(&exit.fee_anchors, exit.total_size).await?;
|
let tx = self.onchain.spend_fee_anchors(&exit.fee_anchors, exit.total_size).await?;
|
||||||
info!("Sent anchor spend tx: {}", tx.txid());
|
info!("Sent anchor spend tx: {}", tx.compute_txid());
|
||||||
|
|
||||||
// After we succesfully stored the claim inputs, we can drop the vtxos.
|
// After we succesfully stored the claim inputs, we can drop the vtxos.
|
||||||
for id in exit.started {
|
for id in exit.started {
|
||||||
@@ -232,7 +232,7 @@ impl Wallet {
|
|||||||
let wit = Witness::from_slice(
|
let wit = Witness::from_slice(
|
||||||
&[&sig[..], exit_script.as_bytes(), &cb.serialize()],
|
&[&sig[..], exit_script.as_bytes(), &cb.serialize()],
|
||||||
);
|
);
|
||||||
debug_assert_eq!(wit.serialized_len(), claim.satisfaction_weight());
|
debug_assert_eq!(wit.size(), claim.satisfaction_weight());
|
||||||
input.final_script_witness = Some(wit);
|
input.final_script_witness = Some(wit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ use std::str::FromStr;
|
|||||||
use anyhow::{bail, Context};
|
use anyhow::{bail, Context};
|
||||||
use bitcoin::{bip32, secp256k1, Address, Amount, FeeRate, Network, OutPoint, Transaction, Txid};
|
use bitcoin::{bip32, secp256k1, Address, Amount, FeeRate, Network, OutPoint, Transaction, Txid};
|
||||||
use bitcoin::hashes::Hash;
|
use bitcoin::hashes::Hash;
|
||||||
use bitcoin::secp256k1::{rand, KeyPair, PublicKey};
|
use bitcoin::secp256k1::{rand, Keypair, PublicKey};
|
||||||
use tokio_stream::StreamExt;
|
use tokio_stream::StreamExt;
|
||||||
|
|
||||||
use ark::{musig, BaseVtxo, OffboardRequest, VtxoRequest, Vtxo, VtxoId, VtxoSpec};
|
use ark::{musig, BaseVtxo, OffboardRequest, VtxoRequest, Vtxo, VtxoId, VtxoSpec};
|
||||||
@@ -98,7 +98,7 @@ pub struct Wallet {
|
|||||||
config: Config,
|
config: Config,
|
||||||
db: database::Db,
|
db: database::Db,
|
||||||
onchain: onchain::Wallet,
|
onchain: onchain::Wallet,
|
||||||
vtxo_seed: bip32::ExtendedPrivKey,
|
vtxo_seed: bip32::Xpriv,
|
||||||
// ASP stuff
|
// ASP stuff
|
||||||
asp: rpc::ArkServiceClient<tonic::transport::Channel>,
|
asp: rpc::ArkServiceClient<tonic::transport::Channel>,
|
||||||
ark_info: ArkInfo,
|
ark_info: ArkInfo,
|
||||||
@@ -184,7 +184,7 @@ impl Wallet {
|
|||||||
};
|
};
|
||||||
onchain::ChainSource::Bitcoind {
|
onchain::ChainSource::Bitcoind {
|
||||||
url: url.clone(),
|
url: url.clone(),
|
||||||
auth: auth,
|
auth,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
bail!("Need to either provide esplora or bitcoind info");
|
bail!("Need to either provide esplora or bitcoind info");
|
||||||
@@ -195,7 +195,7 @@ impl Wallet {
|
|||||||
let db = database::Db::open(&datadir.join("db")).context("failed to open db")?;
|
let db = database::Db::open(&datadir.join("db")).context("failed to open db")?;
|
||||||
|
|
||||||
let vtxo_seed = {
|
let vtxo_seed = {
|
||||||
let master = bip32::ExtendedPrivKey::new_master(config.network, &seed).unwrap();
|
let master = bip32::Xpriv::new_master(config.network, &seed).unwrap();
|
||||||
master.derive_priv(&SECP, &[350.into()]).unwrap()
|
master.derive_priv(&SECP, &[350.into()]).unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -277,7 +277,7 @@ impl Wallet {
|
|||||||
asp_pubkey: self.ark_info.asp_pubkey,
|
asp_pubkey: self.ark_info.asp_pubkey,
|
||||||
expiry_height: current_height + self.ark_info.vtxo_expiry_delta as u32,
|
expiry_height: current_height + self.ark_info.vtxo_expiry_delta as u32,
|
||||||
exit_delta: self.ark_info.vtxo_exit_delta,
|
exit_delta: self.ark_info.vtxo_exit_delta,
|
||||||
amount: amount,
|
amount,
|
||||||
};
|
};
|
||||||
let onboard_amount = amount + ark::onboard::onboard_surplus();
|
let onboard_amount = amount + ark::onboard::onboard_surplus();
|
||||||
let addr = Address::from_script(&ark::onboard::onboard_spk(&spec), self.config.network).unwrap();
|
let addr = Address::from_script(&ark::onboard::onboard_spk(&spec), self.config.network).unwrap();
|
||||||
@@ -285,7 +285,7 @@ impl Wallet {
|
|||||||
// We create the onboard tx template, but don't sign it yet.
|
// We create the onboard tx template, but don't sign it yet.
|
||||||
self.onchain.sync().await.context("sync error")?;
|
self.onchain.sync().await.context("sync error")?;
|
||||||
let onboard_tx = self.onchain.prepare_tx(addr, onboard_amount)?;
|
let onboard_tx = self.onchain.prepare_tx(addr, onboard_amount)?;
|
||||||
let utxo = OutPoint::new(onboard_tx.unsigned_tx.txid(), 0);
|
let utxo = OutPoint::new(onboard_tx.unsigned_tx.compute_txid(), 0);
|
||||||
|
|
||||||
// We ask the ASP to cosign our onboard vtxo reveal tx.
|
// We ask the ASP to cosign our onboard vtxo reveal tx.
|
||||||
let (user_part, priv_user_part) = ark::onboard::new_user(spec, utxo);
|
let (user_part, priv_user_part) = ark::onboard::new_user(spec, utxo);
|
||||||
@@ -332,8 +332,8 @@ impl Wallet {
|
|||||||
},
|
},
|
||||||
utxo: vtxos.utxo,
|
utxo: vtxos.utxo,
|
||||||
},
|
},
|
||||||
leaf_idx: leaf_idx,
|
leaf_idx,
|
||||||
exit_branch: exit_branch,
|
exit_branch,
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.db.has_forfeited_vtxo(vtxo.id())? {
|
if self.db.has_forfeited_vtxo(vtxo.id())? {
|
||||||
@@ -571,7 +571,6 @@ impl Wallet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_ark_onchain_payment(&mut self, addr: Address, amount: Amount) -> anyhow::Result<()> {
|
pub async fn send_ark_onchain_payment(&mut self, addr: Address, amount: Amount) -> anyhow::Result<()> {
|
||||||
ensure!(addr.network == self.config.network, "invalid addr network");
|
|
||||||
|
|
||||||
//TODO(stevenroose) impl key derivation
|
//TODO(stevenroose) impl key derivation
|
||||||
let vtxo_key = self.vtxo_seed.to_keypair(&SECP);
|
let vtxo_key = self.vtxo_seed.to_keypair(&SECP);
|
||||||
@@ -592,7 +591,7 @@ impl Wallet {
|
|||||||
self.participate_round(move |_id, offb_fr| {
|
self.participate_round(move |_id, offb_fr| {
|
||||||
let offb = OffboardRequest {
|
let offb = OffboardRequest {
|
||||||
script_pubkey: addr.script_pubkey(),
|
script_pubkey: addr.script_pubkey(),
|
||||||
amount: amount,
|
amount,
|
||||||
};
|
};
|
||||||
let out_value = amount + offb.fee(offb_fr).expect("script from address");
|
let out_value = amount + offb.fee(offb_fr).expect("script from address");
|
||||||
let change = {
|
let change = {
|
||||||
@@ -659,7 +658,7 @@ impl Wallet {
|
|||||||
|
|
||||||
|
|
||||||
'round: loop {
|
'round: loop {
|
||||||
let cosign_key = KeyPair::new(&SECP, &mut rand::thread_rng());
|
let cosign_key = Keypair::new(&SECP, &mut rand::thread_rng());
|
||||||
debug!("Participating in round {} with cosign pubkey {}",
|
debug!("Participating in round {} with cosign pubkey {}",
|
||||||
round_id, cosign_key.public_key(),
|
round_id, cosign_key.public_key(),
|
||||||
);
|
);
|
||||||
@@ -736,8 +735,8 @@ impl Wallet {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let vtxos_utxo = OutPoint::new(round_tx.txid(), 0);
|
let vtxos_utxo = OutPoint::new(round_tx.compute_txid(), 0);
|
||||||
let conns_utxo = OutPoint::new(round_tx.txid(), 1);
|
let conns_utxo = OutPoint::new(round_tx.compute_txid(), 1);
|
||||||
|
|
||||||
// Check that the proposal contains our inputs.
|
// Check that the proposal contains our inputs.
|
||||||
let mut my_vtxos = vtxo_reqs.clone();
|
let mut my_vtxos = vtxo_reqs.clone();
|
||||||
@@ -911,7 +910,7 @@ impl Wallet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We also broadcast the tx, just to have it go around faster.
|
// We also broadcast the tx, just to have it go around faster.
|
||||||
info!("Round finished, broadcasting round tx {}", round_tx.txid());
|
info!("Round finished, broadcasting round tx {}", round_tx.compute_txid());
|
||||||
if let Err(e) = self.onchain.broadcast_tx(&round_tx).await {
|
if let Err(e) = self.onchain.broadcast_tx(&round_tx).await {
|
||||||
warn!("Couldn't broadcast round tx: {}", e);
|
warn!("Couldn't broadcast round tx: {}", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,16 +2,18 @@
|
|||||||
mod chain;
|
mod chain;
|
||||||
pub use self::chain::ChainSource;
|
pub use self::chain::ChainSource;
|
||||||
|
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
use std::io::Write;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use bdk::SignOptions;
|
use bdk_wallet::{KeychainKind, SignOptions};
|
||||||
use bdk_file_store::Store;
|
use bdk_file_store::Store;
|
||||||
use bdk_esplora::EsploraAsyncExt;
|
use bdk_esplora::EsploraAsyncExt;
|
||||||
use bitcoin::{
|
use bitcoin::{
|
||||||
bip32, psbt, Address, Amount, Network, OutPoint, Sequence, Transaction, TxOut, Txid,
|
bip32, psbt, Address, Amount, Network, OutPoint, Script, Sequence, Transaction, TxOut, Txid
|
||||||
};
|
};
|
||||||
use bitcoin::psbt::PartiallySignedTransaction as Psbt; //TODO(stevenroose) when v0.31
|
use bitcoin::psbt::Psbt; //TODO(stevenroose) when v0.31
|
||||||
|
|
||||||
use crate::exit;
|
use crate::exit;
|
||||||
use crate::psbtext::PsbtInputExt;
|
use crate::psbtext::PsbtInputExt;
|
||||||
@@ -20,7 +22,8 @@ use self::chain::ChainSourceClient;
|
|||||||
const DB_MAGIC: &str = "onchain_bdk";
|
const DB_MAGIC: &str = "onchain_bdk";
|
||||||
|
|
||||||
pub struct Wallet {
|
pub struct Wallet {
|
||||||
wallet: bdk::Wallet<Store<'static, bdk::wallet::ChangeSet>>,
|
wallet: bdk_wallet::Wallet,
|
||||||
|
file_store: Store<bdk_wallet::wallet::ChangeSet>,
|
||||||
chain_source: ChainSourceClient,
|
chain_source: ChainSourceClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,18 +35,19 @@ impl Wallet {
|
|||||||
chain_source: ChainSource,
|
chain_source: ChainSource,
|
||||||
) -> anyhow::Result<Wallet> {
|
) -> anyhow::Result<Wallet> {
|
||||||
let db_path = dir.join("bdkwallet.db");
|
let db_path = dir.join("bdkwallet.db");
|
||||||
let db = Store::<bdk::wallet::ChangeSet>::open_or_create_new(DB_MAGIC.as_bytes(), db_path)?;
|
let mut db = Store::<bdk_wallet::wallet::ChangeSet>::open_or_create_new(DB_MAGIC.as_bytes(), db_path)?;
|
||||||
|
let change_set = db.aggregate_changesets()?;
|
||||||
|
|
||||||
//TODO(stevenroose) taproot?
|
//TODO(stevenroose) taproot?
|
||||||
let xpriv = bip32::ExtendedPrivKey::new_master(network, &seed).expect("valid seed");
|
let xpriv = bip32::Xpriv::new_master(network, &seed).expect("valid seed");
|
||||||
let edesc = format!("tr({}/84'/0'/0'/0/*)", xpriv);
|
let edesc = format!("tr({}/84'/0'/0'/0/*)", xpriv);
|
||||||
let idesc = format!("tr({}/84'/0'/0'/1/*)", xpriv);
|
let idesc = format!("tr({}/84'/0'/0'/1/*)", xpriv);
|
||||||
|
|
||||||
let wallet = bdk::Wallet::new_or_load(&edesc, Some(&idesc), db, network)
|
let wallet = bdk_wallet::Wallet::new_or_load(&edesc, &idesc, change_set, network)
|
||||||
.context("failed to create or load bdk wallet")?;
|
.context("failed to create or load bdk wallet")?;
|
||||||
|
|
||||||
let chain_source = ChainSourceClient::new(chain_source)?;
|
let chain_source = ChainSourceClient::new(chain_source)?;
|
||||||
Ok(Wallet { wallet, chain_source })
|
Ok(Wallet { wallet, chain_source, file_store: db })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn tip(&self) -> anyhow::Result<u32> {
|
pub async fn tip(&self) -> anyhow::Result<u32> {
|
||||||
@@ -58,6 +62,18 @@ impl Wallet {
|
|||||||
self.chain_source.txout_confirmations(outpoint).await
|
self.chain_source.txout_confirmations(outpoint).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn generate_inspect(kind: KeychainKind) -> impl FnMut(u32, &Script) + Send + Sync + 'static {
|
||||||
|
let mut once = Some(());
|
||||||
|
let mut stdout = std::io::stdout();
|
||||||
|
move |spk_i, _| {
|
||||||
|
match once.take() {
|
||||||
|
Some(_) => print!("\nScanning keychain [{:?}]", kind),
|
||||||
|
None => print!(" {:<3}", spk_i),
|
||||||
|
};
|
||||||
|
stdout.flush().expect("must flush");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn sync(&mut self) -> anyhow::Result<Amount> {
|
pub async fn sync(&mut self) -> anyhow::Result<Amount> {
|
||||||
debug!("Starting wallet sync...");
|
debug!("Starting wallet sync...");
|
||||||
|
|
||||||
@@ -71,40 +87,59 @@ impl Wallet {
|
|||||||
self.wallet.apply_block_connected_to(
|
self.wallet.apply_block_connected_to(
|
||||||
&em.block, em.block_height(), em.connected_to(),
|
&em.block, em.block_height(), em.connected_to(),
|
||||||
)?;
|
)?;
|
||||||
self.wallet.commit()?;
|
if let Some(change_set) = self.wallet.take_staged() {
|
||||||
|
self.file_store.append_changeset(&change_set)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mempool = emitter.mempool()?;
|
let mempool = emitter.mempool()?;
|
||||||
self.wallet.apply_unconfirmed_txs(mempool.iter().map(|(tx, time)| (tx, *time)));
|
self.wallet.apply_unconfirmed_txs(mempool.iter().map(|(tx, time)| (tx, *time)));
|
||||||
self.wallet.commit()?;
|
if let Some(change_set) = self.wallet.take_staged() {
|
||||||
|
self.file_store.append_changeset(&change_set)?;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
ChainSourceClient::Esplora(ref client) => {
|
ChainSourceClient::Esplora(ref client) => {
|
||||||
|
let request = self.wallet.start_full_scan().inspect_spks_for_all_keychains({
|
||||||
|
let mut once = BTreeSet::<KeychainKind>::new();
|
||||||
|
move |keychain, spk_i, _| {
|
||||||
|
match once.insert(keychain) {
|
||||||
|
true => print!("\nScanning keychain [{:?}]", keychain),
|
||||||
|
false => print!(" {:<3}", spk_i),
|
||||||
|
}
|
||||||
|
std::io::stdout().flush().expect("must flush")
|
||||||
|
}
|
||||||
|
}).inspect_spks_for_keychain(
|
||||||
|
KeychainKind::External,
|
||||||
|
Self::generate_inspect(KeychainKind::External),
|
||||||
|
)
|
||||||
|
.inspect_spks_for_keychain(
|
||||||
|
KeychainKind::Internal,
|
||||||
|
Self::generate_inspect(KeychainKind::Internal),
|
||||||
|
);
|
||||||
const STOP_GAP: usize = 50;
|
const STOP_GAP: usize = 50;
|
||||||
|
const PARALLEL_REQUESTS: usize = 5;
|
||||||
|
|
||||||
|
let mut update = client
|
||||||
|
.full_scan(request, STOP_GAP, PARALLEL_REQUESTS)
|
||||||
|
.await?;
|
||||||
|
let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
|
||||||
|
let _ = update.graph_update.update_last_seen_unconfirmed(now);
|
||||||
|
|
||||||
let prev_tip = self.wallet.latest_checkpoint();
|
|
||||||
let keychain_spks = self.wallet.spks_of_all_keychains().into_iter().collect();
|
|
||||||
let (update_graph, last_active_indices) =
|
|
||||||
client.full_scan(keychain_spks, STOP_GAP, 4).await?;
|
|
||||||
let missing_heights = update_graph.missing_heights(self.wallet.local_chain());
|
|
||||||
let chain_update = client.update_local_chain(prev_tip, missing_heights).await?;
|
|
||||||
let update = bdk::wallet::Update {
|
|
||||||
last_active_indices,
|
|
||||||
graph: update_graph,
|
|
||||||
chain: Some(chain_update),
|
|
||||||
};
|
|
||||||
self.wallet.apply_update(update)?;
|
self.wallet.apply_update(update)?;
|
||||||
self.wallet.commit()?;
|
if let Some(change_set) = self.wallet.take_staged() {
|
||||||
|
self.file_store.append_changeset(&change_set)?;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
let balance = self.wallet.get_balance();
|
let balance = self.wallet.balance();
|
||||||
Ok(Amount::from_sat(balance.total()))
|
Ok(balance.total())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fee rate to use for regular txs like onboards.
|
/// Fee rate to use for regular txs like onboards.
|
||||||
fn regular_fee_rate_bdk(&self) -> bdk::FeeRate {
|
fn regular_fee_rate_bdk(&self) -> bitcoin::FeeRate {
|
||||||
//TODO(stevenroose) get from somewhere
|
//TODO(stevenroose) get from somewhere
|
||||||
bdk::FeeRate::from_sat_per_vb(10.0)
|
bitcoin::FeeRate::from_sat_per_vb(10).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fee rate to use for regular txs like onboards.
|
/// Fee rate to use for regular txs like onboards.
|
||||||
@@ -113,16 +148,16 @@ impl Wallet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Fee rate to use for urgent txs like exits.
|
/// Fee rate to use for urgent txs like exits.
|
||||||
fn urgent_fee_rate_bdk(&self) -> bdk::FeeRate {
|
fn urgent_fee_rate_bdk(&self) -> bitcoin::FeeRate {
|
||||||
//TODO(stevenroose) get from somewhere
|
//TODO(stevenroose) get from somewhere
|
||||||
bdk::FeeRate::from_sat_per_vb(100.0)
|
bitcoin::FeeRate::from_sat_per_vb(100).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn prepare_tx(&mut self, dest: Address, amount: Amount) -> anyhow::Result<Psbt> {
|
pub fn prepare_tx(&mut self, dest: Address, amount: Amount) -> anyhow::Result<Psbt> {
|
||||||
let fee_rate = self.regular_fee_rate_bdk();
|
let fee_rate = self.regular_fee_rate_bdk();
|
||||||
let mut b = self.wallet.build_tx();
|
let mut b = self.wallet.build_tx();
|
||||||
b.ordering(bdk::wallet::tx_builder::TxOrdering::Untouched);
|
b.ordering(bdk_wallet::wallet::tx_builder::TxOrdering::Untouched);
|
||||||
b.add_recipient(dest.script_pubkey(), amount.to_sat());
|
b.add_recipient(dest.script_pubkey(), amount);
|
||||||
b.fee_rate(fee_rate);
|
b.fee_rate(fee_rate);
|
||||||
b.enable_rbf();
|
b.enable_rbf();
|
||||||
Ok(b.finish()?)
|
Ok(b.finish()?)
|
||||||
@@ -135,8 +170,10 @@ impl Wallet {
|
|||||||
};
|
};
|
||||||
let finalized = self.wallet.sign(&mut psbt, opts).context("failed to sign")?;
|
let finalized = self.wallet.sign(&mut psbt, opts).context("failed to sign")?;
|
||||||
assert!(finalized);
|
assert!(finalized);
|
||||||
self.wallet.commit().context("error committing wallet")?;
|
if let Some(change_set) = self.wallet.take_staged() {
|
||||||
Ok(psbt.extract_tx())
|
self.file_store.append_changeset(&change_set)?;
|
||||||
|
}
|
||||||
|
Ok(psbt.extract_tx()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_money(&mut self, dest: Address, amount: Amount) -> anyhow::Result<Txid> {
|
pub async fn send_money(&mut self, dest: Address, amount: Amount) -> anyhow::Result<Txid> {
|
||||||
@@ -144,17 +181,16 @@ impl Wallet {
|
|||||||
let psbt = self.prepare_tx(dest, amount)?;
|
let psbt = self.prepare_tx(dest, amount)?;
|
||||||
let tx = self.finish_tx(psbt)?;
|
let tx = self.finish_tx(psbt)?;
|
||||||
self.broadcast_tx(&tx).await?;
|
self.broadcast_tx(&tx).await?;
|
||||||
Ok(tx.txid())
|
Ok(tx.compute_txid())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_address(&mut self) -> anyhow::Result<Address> {
|
pub fn new_address(&mut self) -> anyhow::Result<Address> {
|
||||||
Ok(self.wallet.try_get_address(bdk::wallet::AddressIndex::New)?.address)
|
Ok(self.wallet.next_unused_address(KeychainKind::External).address)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_anchors<A, B, C>(b: &mut bdk::TxBuilder<A, B, C>, anchors: &[OutPoint])
|
fn add_anchors<A>(b: &mut bdk_wallet::TxBuilder<A>, anchors: &[OutPoint])
|
||||||
where
|
where
|
||||||
B: bdk::wallet::coin_selection::CoinSelectionAlgorithm,
|
A: bdk_wallet::wallet::coin_selection::CoinSelectionAlgorithm,
|
||||||
C: bdk::wallet::tx_builder::TxBuilderContext,
|
|
||||||
{
|
{
|
||||||
for utxo in anchors {
|
for utxo in anchors {
|
||||||
let psbt_in = psbt::Input {
|
let psbt_in = psbt::Input {
|
||||||
@@ -181,15 +217,15 @@ impl Wallet {
|
|||||||
// overshoot the fee, but we prefer that over undershooting it.
|
// overshoot the fee, but we prefer that over undershooting it.
|
||||||
|
|
||||||
let urgent_fee_rate = self.urgent_fee_rate_bdk();
|
let urgent_fee_rate = self.urgent_fee_rate_bdk();
|
||||||
let package_fee = urgent_fee_rate.fee_vb(package_vsize);
|
let package_fee = urgent_fee_rate.fee_vb(package_vsize.try_into()?).unwrap();
|
||||||
|
|
||||||
// Since BDK doesn't allow tx without recipients, we add a drain output.
|
// Since BDK doesn't allow tx without recipients, we add a drain output.
|
||||||
let change_addr = self.wallet.try_get_internal_address(bdk::wallet::AddressIndex::New)?;
|
let change_addr = self.wallet.next_unused_address(KeychainKind::Internal);
|
||||||
|
|
||||||
let template_size = {
|
let template_size = {
|
||||||
let mut b = self.wallet.build_tx();
|
let mut b = self.wallet.build_tx();
|
||||||
Wallet::add_anchors(&mut b, anchors);
|
Wallet::add_anchors(&mut b, anchors);
|
||||||
b.add_recipient(change_addr.address.script_pubkey(), package_fee + ark::P2TR_DUST_SAT);
|
b.add_recipient(change_addr.address.script_pubkey(), package_fee + Amount::from_sat(ark::P2TR_DUST_SAT));
|
||||||
b.fee_rate(urgent_fee_rate);
|
b.fee_rate(urgent_fee_rate);
|
||||||
let mut psbt = b.finish().expect("failed to craft anchor spend template");
|
let mut psbt = b.finish().expect("failed to craft anchor spend template");
|
||||||
let opts = SignOptions {
|
let opts = SignOptions {
|
||||||
@@ -199,11 +235,11 @@ impl Wallet {
|
|||||||
let finalized = self.wallet.sign(&mut psbt, opts)
|
let finalized = self.wallet.sign(&mut psbt, opts)
|
||||||
.expect("failed to sign anchor spend template");
|
.expect("failed to sign anchor spend template");
|
||||||
assert!(finalized);
|
assert!(finalized);
|
||||||
psbt.extract_tx().vsize()
|
psbt.extract_tx()?.vsize()
|
||||||
};
|
};
|
||||||
|
|
||||||
let total_vsize = template_size + package_vsize;
|
let total_vsize = template_size + package_vsize;
|
||||||
let total_fee = self.urgent_fee_rate_bdk().fee_vb(total_vsize);
|
let total_fee = self.urgent_fee_rate_bdk().fee_vb(total_vsize as u64).unwrap();
|
||||||
|
|
||||||
// Then build actual tx.
|
// Then build actual tx.
|
||||||
let mut b = self.wallet.build_tx();
|
let mut b = self.wallet.build_tx();
|
||||||
@@ -224,7 +260,7 @@ impl Wallet {
|
|||||||
let urgent_fee_rate = self.urgent_fee_rate_bdk();
|
let urgent_fee_rate = self.urgent_fee_rate_bdk();
|
||||||
|
|
||||||
// Since BDK doesn't allow tx without recipients, we add a drain output.
|
// Since BDK doesn't allow tx without recipients, we add a drain output.
|
||||||
let change_addr = self.wallet.try_get_internal_address(bdk::wallet::AddressIndex::New)?;
|
let change_addr = self.wallet.next_unused_address(KeychainKind::Internal);
|
||||||
|
|
||||||
let mut b = self.wallet.build_tx();
|
let mut b = self.wallet.build_tx();
|
||||||
b.version(2);
|
b.version(2);
|
||||||
@@ -233,7 +269,7 @@ impl Wallet {
|
|||||||
psbt_in.set_claim_input(input);
|
psbt_in.set_claim_input(input);
|
||||||
psbt_in.witness_utxo = Some(TxOut {
|
psbt_in.witness_utxo = Some(TxOut {
|
||||||
script_pubkey: input.spec.exit_spk(),
|
script_pubkey: input.spec.exit_spk(),
|
||||||
value: input.spec.amount.to_sat(),
|
value: input.spec.amount,
|
||||||
});
|
});
|
||||||
b.add_foreign_utxo_with_sequence(
|
b.add_foreign_utxo_with_sequence(
|
||||||
input.utxo,
|
input.utxo,
|
||||||
|
|||||||
Reference in New Issue
Block a user