1pub mod direct;
3
4use crate::tx_util::create_spending_transaction;
5use bitcoin::absolute::LockTime;
6use bitcoin::secp256k1::{PublicKey, SecretKey};
7use bitcoin::{Address, Network, ScriptBuf, Transaction, Witness};
8use bitcoind_client::esplora_client::EsploraClient;
9use bitcoind_client::{explorer_from_url, BlockExplorerType, Explorer};
10use lightning::chain::transaction::OutPoint;
11use lightning::sign::DelayedPaymentOutputDescriptor;
12use lightning_signer::bitcoin::address::{NetworkChecked, NetworkUnchecked};
13use lightning_signer::bitcoin::consensus::encode::serialize_hex;
14use lightning_signer::bitcoin::{Sequence, TxOut, Txid};
15use lightning_signer::lightning::ln::channel_keys::RevocationKey;
16use lightning_signer::node::{Allowable, ToStringForNetwork};
17use lightning_signer::util::status::Status;
18use lightning_signer::{bitcoin, lightning};
19use log::*;
20use std::collections::BTreeMap;
21use url::Url;
22
23pub struct Iter<T: RecoverySign> {
25 signers: Vec<T>,
26}
27
28impl<T: RecoverySign> Iterator for Iter<T> {
29 type Item = T;
30
31 fn next(&mut self) -> Option<Self::Item> {
32 self.signers.pop()
33 }
34}
35
36#[derive(serde::Deserialize, Debug, Clone)]
37struct UtxoResponse {
38 txid: Txid,
39 vout: u32,
40 value: u64,
41}
42
43pub trait RecoveryKeys {
45 type Signer: RecoverySign;
46 fn iter(&self) -> Iter<Self::Signer>;
47 fn sign_onchain_tx(
48 &self,
49 tx: &Transaction,
50 segwit_flags: &[bool],
51 ipaths: &Vec<Vec<u32>>,
52 prev_outs: &Vec<TxOut>,
53 uniclosekeys: Vec<Option<(SecretKey, Vec<Vec<u8>>)>>,
54 opaths: &Vec<Vec<u32>>,
55 ) -> Result<Vec<Vec<Vec<u8>>>, Status>;
56 fn wallet_address_native(&self, index: u32) -> Result<Address, Status>;
57 fn wallet_address_taproot(&self, index: u32) -> Result<Address, Status>;
58}
59
60pub trait RecoverySign {
62 fn sign_holder_commitment_tx_for_recovery(
63 &self,
64 ) -> Result<
65 (Transaction, Vec<Transaction>, ScriptBuf, (SecretKey, Vec<Vec<u8>>), PublicKey),
66 Status,
67 >;
68 fn funding_outpoint(&self) -> OutPoint;
69 fn counterparty_selected_contest_delay(&self) -> u16;
70 fn get_per_commitment_point(&self) -> Result<PublicKey, Status>;
71}
72
73#[tokio::main(worker_threads = 2)]
74pub async fn recover_l1<R: RecoveryKeys>(
75 network: Network,
76 block_explorer_type: BlockExplorerType,
77 block_explorer_rpc: Option<Url>,
78 destination: &str,
79 keys: R,
80 max_index: u32,
81) {
82 match block_explorer_type {
83 BlockExplorerType::Esplora => {}
84 _ => {
85 panic!("only esplora supported for l1 recovery");
86 }
87 };
88
89 let url = block_explorer_rpc.expect("must have block explorer rpc");
90 let esplora = EsploraClient::new(url).await;
91
92 let mut utxos = Vec::new();
93 for index in 0..max_index {
94 let address = keys.wallet_address_native(index).expect("address");
95 let script_pubkey = address.payload.script_pubkey();
96 utxos.append(
97 &mut get_utxos(&esplora, address)
98 .await
99 .expect("get utxos")
100 .into_iter()
101 .map(|u| (index, u, script_pubkey.clone()))
102 .collect::<Vec<_>>(),
103 );
104
105 let taproot_address = keys.wallet_address_taproot(index).expect("address");
106 let taproot_script_pubkey = taproot_address.payload.script_pubkey();
107 utxos.append(
108 &mut get_utxos(&esplora, taproot_address)
109 .await
110 .expect("get utxos")
111 .into_iter()
112 .map(|u| (index, u, taproot_script_pubkey.clone()))
113 .collect::<Vec<_>>(),
114 );
115 }
116
117 if destination == "none" {
118 info!("no destination specified, only printing txs");
119 }
120
121 let destination_address: Address<NetworkUnchecked> =
122 destination.parse().expect("destination address must be valid");
123 assert!(
124 destination_address.is_valid_for_network(network),
125 "destination address must be valid for network"
126 );
127
128 let destination_address = destination_address.assume_checked();
129 let feerate_per_kw = get_feerate(&esplora).await.expect("get feerate");
130
131 for chunk in utxos.chunks(10) {
132 let tx = match make_l1_sweep(&keys, &destination_address, chunk, feerate_per_kw) {
133 Some(value) => value,
134 None => continue,
135 };
136
137 esplora.broadcast_transaction(&tx).await.expect("broadcast tx");
138 }
139}
140
141fn make_l1_sweep<R: RecoveryKeys>(
143 keys: &R,
144 destination_address: &Address<NetworkChecked>,
145 chunk: &[(u32, UtxoResponse, ScriptBuf)],
146 feerate_per_kw: u64,
147) -> Option<Transaction> {
148 let value = chunk.iter().map(|(_, u, _)| u.value).sum::<u64>();
149
150 let mut tx = Transaction {
151 version: 2,
152 lock_time: LockTime::ZERO,
153 input: chunk
154 .iter()
155 .map(|(_, u, _)| bitcoin::TxIn {
156 previous_output: bitcoin::OutPoint { txid: u.txid, vout: u.vout },
157 sequence: Sequence::ZERO,
158 witness: Witness::default(),
159 script_sig: ScriptBuf::new(),
160 })
161 .collect(),
162 output: vec![TxOut { value, script_pubkey: destination_address.payload.script_pubkey() }],
163 };
164 let total_fee = feerate_per_kw * tx.weight().to_wu() / 1000;
165 if total_fee > value - 1000 {
166 warn!("not enough value to pay fee {:?}", tx);
167 return None;
168 }
169 tx.output[0].value -= total_fee;
170 info!("sending tx {} - {}", tx.txid().to_string(), serialize_hex(&tx));
171
172 let ipaths = chunk.iter().map(|(i, _, _)| vec![*i]).collect::<Vec<_>>();
173 let prev_outs = chunk
174 .iter()
175 .map(|(_, u, script_pubkey)| TxOut { value: u.value, script_pubkey: script_pubkey.clone() })
176 .collect::<Vec<_>>();
177 let unicosekeys = chunk.iter().map(|_| None).collect::<Vec<_>>();
178
179 let witnesses = keys
181 .sign_onchain_tx(&tx, &vec![], &ipaths, &prev_outs, unicosekeys, &vec![vec![]])
182 .expect("sign tx");
183
184 for (i, witness) in witnesses.into_iter().enumerate() {
185 tx.input[i].witness = Witness::from_slice(&witness);
186 }
187 Some(tx)
188}
189
190async fn get_utxos(esplora: &EsploraClient, address: Address) -> Result<Vec<UtxoResponse>, ()> {
192 let utxos: Vec<UtxoResponse> =
193 esplora.get(&format!("address/{}/utxo", address)).await.map_err(|e| {
194 error!("{}", e);
195 })?;
196 Ok(utxos)
197}
198
199async fn get_feerate(esplora: &EsploraClient) -> Result<u64, ()> {
201 let fees: BTreeMap<String, f64> = esplora.get("fee-estimates").await.map_err(|e| {
202 error!("{}", e);
203 })?;
204 let feerate = (fees.get("24").expect("feerate") * 1000f64).ceil() as u64;
205 Ok(feerate)
206}
207
208#[tokio::main(worker_threads = 2)]
209pub async fn recover_close<R: RecoveryKeys>(
210 network: Network,
211 block_explorer_type: BlockExplorerType,
212 block_explorer_rpc: Option<Url>,
213 destination: &str,
214 keys: R,
215) {
216 let explorer_client = match block_explorer_rpc {
217 Some(url) => Some(explorer_from_url(network, block_explorer_type, url).await),
218 None => None,
219 };
220
221 let mut sweeps = Vec::new();
222
223 for signer in keys.iter() {
224 info!("# funding {:?}", signer.funding_outpoint());
225
226 let (tx, htlc_txs, revocable_script, uck, revocation_pubkey) =
227 signer.sign_holder_commitment_tx_for_recovery().expect("sign");
228 debug!("closing tx {:?}", &tx);
229 info!("closing txid {}", tx.txid());
230 if let Some(bitcoind_client) = &explorer_client {
231 let funding_confirms = bitcoind_client
232 .get_utxo_confirmations(&signer.funding_outpoint().into_bitcoin_outpoint())
233 .await
234 .expect("get_txout for funding");
235 if funding_confirms.is_some() {
236 info!(
237 "channel is open ({} confirms), broadcasting force-close {}",
238 funding_confirms.unwrap(),
239 tx.txid()
240 );
241 bitcoind_client.broadcast_transaction(&tx).await.expect("failed to broadcast");
242 } else {
243 let required_confirms = signer.counterparty_selected_contest_delay();
244 info!(
245 "channel is already closed, check outputs, waiting until {} confirms",
246 required_confirms
247 );
248 for (idx, out) in tx.output.iter().enumerate() {
249 let script = out.script_pubkey.clone();
250 if script == revocable_script {
251 info!("our revocable output {} @ {}", out.value, idx);
252 let out_point = OutPoint { txid: tx.txid(), index: idx as u16 };
253 let confirms = bitcoind_client
254 .get_utxo_confirmations(&out_point.into_bitcoin_outpoint())
255 .await
256 .expect("get_txout for our output");
257 if let Some(confirms) = confirms {
258 info!("revocable output is unspent ({} confirms)", confirms);
259 if confirms >= required_confirms as u64 {
260 info!("revocable output is mature, broadcasting sweep");
261 let to_self_delay = signer.counterparty_selected_contest_delay();
262 let descriptor = DelayedPaymentOutputDescriptor {
263 outpoint: out_point,
264 per_commitment_point: signer
265 .get_per_commitment_point()
266 .expect("commitment point"),
267 to_self_delay,
268 output: tx.output[idx].clone(),
269 revocation_pubkey: RevocationKey(revocation_pubkey),
270 channel_keys_id: [0; 32], channel_value_satoshis: 0,
272 channel_transaction_parameters: None,
273 };
274 sweeps.push((descriptor, uck.clone()));
275 } else {
276 warn!(
277 "revocable output is immature ({} < {})",
278 confirms, required_confirms
279 );
280 }
281 } else {
282 info!("revocable output is spent, skipping");
283 }
284 }
285 }
286 }
287 } else {
288 info!("tx: {}", serialize_hex(&tx));
289 for htlc_tx in htlc_txs {
290 info!("HTLC tx: {}", htlc_tx.txid());
291 }
292 }
293 }
294
295 if destination == "none" {
296 info!("no address specified, not sweeping");
297 return;
298 }
299
300 let wallet_path = vec![];
301 let destination_allowable = Allowable::from_str(destination, network).expect("address");
302 info!("sweeping to {}", destination_allowable.to_string(network));
303 let output_script = destination_allowable.to_script().expect("script");
304 for (descriptor, uck) in sweeps {
305 let feerate = 1000;
306 let sweep_tx = spend_delayed_outputs(
307 &keys,
308 &[descriptor],
309 uck,
310 output_script.clone(),
311 wallet_path.clone(),
312 feerate,
313 );
314 debug!("sweep tx {:?}", &sweep_tx);
315 info!("sweep txid {}", sweep_tx.txid());
316 if let Some(bitcoind_client) = &explorer_client {
317 bitcoind_client.broadcast_transaction(&sweep_tx).await.expect("failed to broadcast");
318 }
319 }
320}
321
322fn spend_delayed_outputs<R: RecoveryKeys>(
323 keys: &R,
324 descriptors: &[DelayedPaymentOutputDescriptor],
325 unilateral_close_key: (SecretKey, Vec<Vec<u8>>),
326 output_script: ScriptBuf,
327 opath: Vec<u32>,
328 feerate_sat_per_1000_weight: u32,
329) -> Transaction {
330 let mut tx =
331 create_spending_transaction(descriptors, output_script, feerate_sat_per_1000_weight)
332 .expect("create_spending_transaction");
333 let values_sat = descriptors.iter().map(|d| d.output.clone()).collect();
334 let ipaths = descriptors.iter().map(|_| vec![]).collect();
335 let uniclosekeys = descriptors.iter().map(|_| Some(unilateral_close_key.clone())).collect();
336 let input_txs = vec![]; let witnesses = keys
338 .sign_onchain_tx(&tx, &input_txs, &ipaths, &values_sat, uniclosekeys, &vec![opath])
339 .expect("sign");
340 assert_eq!(witnesses.len(), tx.input.len());
341 for (idx, w) in witnesses.into_iter().enumerate() {
342 tx.input[idx].witness = Witness::from_slice(&w);
343 }
344 tx
345}
346
347#[cfg(test)]
348mod tests {
349 use super::*;
350 use crate::recovery::direct::DirectRecoveryKeys;
351 use lightning_signer::node::SpendType;
352 use lightning_signer::util::test_utils::key::make_test_pubkey;
353 use lightning_signer::util::test_utils::{
354 init_node, make_test_previous_tx, TEST_NODE_CONFIG, TEST_SEED,
355 };
356 use std::collections::BTreeMap;
357
358 #[ignore]
359 #[tokio::test]
360 async fn esplora_utxo_test() {
361 fern::Dispatch::new().level(LevelFilter::Info).chain(std::io::stdout()).apply().unwrap();
362 let address: Address<NetworkUnchecked> =
363 "19XBuBAa78zccvfFrNWKB6PhnA1mMRASeT".parse().unwrap();
364 let address = address.assume_checked();
365 let esplora = EsploraClient::new("https://blockstream.info/api".parse().unwrap()).await;
366
367 let fees: BTreeMap<String, f64> =
368 esplora.get("fee-estimates").await.expect("fee_estimates");
369 info!("fees: {:?}", fees);
370
371 let utxos = get_utxos(&esplora, address.clone()).await.expect("get_utxos");
372 info!("address {} has {:?}", address, utxos);
373 }
374
375 #[test]
376 fn l1_sweep_test() {
377 let node = init_node(TEST_NODE_CONFIG, TEST_SEED[1]);
378 let pubkey = bitcoin::PublicKey::new(make_test_pubkey(2));
379 let address = Address::p2wpkh(&pubkey, Network::Testnet).unwrap();
380
381 node.add_allowlist(&[address.to_string()]).expect("add_allowlist");
382
383 let values = vec![(123, 12345u64, SpendType::P2wpkh)];
384 let (input_tx, input_txid) = make_test_previous_tx(&node, &values);
385 let utxo = UtxoResponse { txid: input_txid, vout: 0, value: 12345 };
386
387 let keys = DirectRecoveryKeys { node };
388 let tx = make_l1_sweep(
389 &keys,
390 &address,
391 &[(123, utxo, input_tx.output[0].script_pubkey.clone())],
392 1000,
393 )
394 .expect("make_l1_sweep");
395 tx.verify(|txo| {
396 if txo.txid == input_txid && txo.vout == 0 {
397 Some(input_tx.output[0].clone())
398 } else {
399 None
400 }
401 })
402 .expect("verify");
403
404 let utxo = UtxoResponse { txid: input_txid, vout: 0, value: 12346 };
406 let tx = make_l1_sweep(
407 &keys,
408 &address,
409 &[(123, utxo, input_tx.output[0].script_pubkey.clone())],
410 1000,
411 )
412 .expect("make_l1_sweep");
413 tx.verify(|txo| {
414 if txo.txid == input_txid && txo.vout == 0 {
415 Some(input_tx.output[0].clone())
416 } else {
417 None
418 }
419 })
420 .expect_err("verify");
421 }
422}