1use std::str::FromStr;
2
3use anyhow::Context;
4use bitcoin::{Amount, SignedAmount};
5use bitcoin::hex::DisplayHex;
6use futures::StreamExt;
7use lightning_invoice::Bolt11Invoice;
8use log::{info, trace, warn};
9
10use ark::arkoor::ArkoorPackageBuilder;
11use ark::{ProtocolEncoding, Vtxo, VtxoPolicy, VtxoRequest, musig};
12use ark::challenges::{LightningReceiveChallenge};
13use ark::lightning::{PaymentHash, Preimage};
14use bitcoin_ext::{AmountExt, BlockDelta, BlockHeight};
15use server_rpc::protos;
16use server_rpc::protos::prepare_lightning_receive_claim_request::LightningReceiveAntiDos;
17
18use crate::subsystem::{BarkSubsystem, LightningMovement, LightningReceiveMovement};
19use crate::{Wallet, error};
20use crate::movement::{MovementDestination, MovementStatus};
21use crate::movement::update::MovementUpdate;
22use crate::persist::models::LightningReceive;
23
24const LIGHTNING_PREPARE_CLAIM_DELTA: BlockDelta = 2;
27
28impl Wallet {
29 pub async fn bolt11_invoice(&self, amount: Amount) -> anyhow::Result<Bolt11Invoice> {
31 let mut srv = self.require_server()?;
32 let ark_info = srv.ark_info().await?;
33 let config = self.config();
34
35 let requested_min_cltv_delta = ark_info.vtxo_exit_delta +
40 ark_info.htlc_expiry_delta +
41 config.vtxo_exit_margin +
42 config.htlc_recv_claim_delta +
43 LIGHTNING_PREPARE_CLAIM_DELTA;
44
45 if requested_min_cltv_delta > ark_info.max_user_invoice_cltv_delta {
46 bail!("HTLC CLTV delta ({}) is greater than Server's max HTLC recv CLTV delta: {}",
47 requested_min_cltv_delta,
48 ark_info.max_user_invoice_cltv_delta,
49 );
50 }
51
52 let preimage = Preimage::random();
53 let payment_hash = preimage.compute_payment_hash();
54 info!("Start bolt11 board with preimage / payment hash: {} / {}",
55 preimage.as_hex(), payment_hash.as_hex());
56
57 let req = protos::StartLightningReceiveRequest {
58 payment_hash: payment_hash.to_vec(),
59 amount_sat: amount.to_sat(),
60 min_cltv_delta: requested_min_cltv_delta as u32,
61 };
62
63 let resp = srv.client.start_lightning_receive(req).await?.into_inner();
64 info!("Ark Server is ready to receive LN payment to invoice: {}.", resp.bolt11);
65
66 let invoice = Bolt11Invoice::from_str(&resp.bolt11)
67 .context("invalid bolt11 invoice returned by Ark server")?;
68
69 self.db.store_lightning_receive(
70 payment_hash,
71 preimage,
72 &invoice,
73 requested_min_cltv_delta,
74 )?;
75
76 Ok(invoice)
77 }
78
79 pub fn lightning_receive_status(
81 &self,
82 payment: impl Into<PaymentHash>,
83 ) -> anyhow::Result<Option<LightningReceive>> {
84 Ok(self.db.fetch_lightning_receive_by_payment_hash(payment.into())?)
85 }
86
87 async fn claim_lightning_receive(
110 &self,
111 receive: &LightningReceive,
112 ) -> anyhow::Result<Vec<Vtxo>> {
113 let movement_id = receive.movement_id
114 .context("No movement created for lightning receive")?;
115 let mut srv = self.require_server()?;
116
117 let inputs = {
119 let htlc_vtxos = receive.htlc_vtxos.as_ref()
120 .context("no HTLC VTXOs set on record yet")?;
121 let mut ret = htlc_vtxos.iter().map(|v| &v.vtxo).collect::<Vec<_>>();
122 ret.sort_by_key(|v| v.id());
123 ret
124 };
125
126 let (keypairs, sec_nonces, pub_nonces) = inputs.iter().map(|v| {
127 let keypair = self.get_vtxo_key(v)?;
128 let (sec_nonce, pub_nonce) = musig::nonce_pair(&keypair);
129 Ok((keypair, sec_nonce, pub_nonce))
130 }).collect::<anyhow::Result<(Vec<_>, Vec<_>, Vec<_>)>>()?;
131
132 let (claim_keypair, _) = self.derive_store_next_keypair()?;
134 let receive_policy = VtxoPolicy::new_pubkey(claim_keypair.public_key());
135
136 let pay_req = VtxoRequest {
137 policy: receive_policy.clone(),
138 amount: inputs.iter().map(|v| v.amount()).sum(),
139 };
140 trace!("ln arkoor builder params: inputs: {:?}; user_nonces: {:?}; req: {:?}",
141 inputs.iter().map(|v| v.id()).collect::<Vec<_>>(), pub_nonces, pay_req,
142 );
143 let builder = ArkoorPackageBuilder::new(
144 inputs.iter().copied(), &pub_nonces, pay_req, None,
145 )?;
146
147 info!("Claiming arkoor against payment preimage");
148 self.db.set_preimage_revealed(receive.payment_hash)?;
149 let resp = srv.client.claim_lightning_receive(protos::ClaimLightningReceiveRequest {
150 payment_hash: receive.payment_hash.to_byte_array().to_vec(),
151 payment_preimage: receive.payment_preimage.to_vec(),
152 vtxo_policy: receive_policy.serialize(),
153 user_pub_nonces: pub_nonces.iter().map(|n| n.serialize().to_vec()).collect(),
154 }).await?.into_inner();
155 let cosign_resp: Vec<_> = resp.try_into().context("invalid cosign response")?;
156
157 ensure!(builder.verify_cosign_response(&cosign_resp),
158 "invalid arkoor cosignature received from server",
159 );
160
161 let (outputs, change) = builder.build_vtxos(&cosign_resp, &keypairs, sec_nonces)?;
162 if change.is_some() {
163 bail!("shouldn't have change VTXO, this is a bug");
164 }
165
166 let mut effective_balance = Amount::ZERO;
167 for vtxo in &outputs {
168 trace!("Validating Lightning receive claim VTXO {}: {}",
172 vtxo.id(), vtxo.serialize_hex(),
173 );
174 self.validate_vtxo(vtxo).await
175 .context("invalid arkoor from lightning receive")?;
176 effective_balance += vtxo.amount();
177 }
178
179 self.store_spendable_vtxos(&outputs)?;
180 self.mark_vtxos_as_spent(inputs)?;
181 info!("Got arkoors from lightning: {}",
182 outputs.iter().map(|v| v.id().to_string()).collect::<Vec<_>>().join(", ")
183 );
184
185 self.movements.update_movement(
186 movement_id,
187 MovementUpdate::new()
188 .effective_balance(effective_balance.to_signed()?)
189 .produced_vtxos(&outputs)
190 ).await?;
191 self.movements.finish_movement(
192 movement_id, MovementStatus::Finished,
193 ).await?;
194
195 self.db.finish_pending_lightning_receive(receive.payment_hash)?;
196
197 Ok(outputs)
198 }
199
200 async fn compute_lightning_receive_anti_dos(
201 &self,
202 payment_hash: PaymentHash,
203 token: Option<&str>,
204 ) -> anyhow::Result<LightningReceiveAntiDos> {
205 Ok(if let Some(token) = token {
206 LightningReceiveAntiDos::Token(token.to_string())
207 } else {
208 let challenge = LightningReceiveChallenge::new(payment_hash);
209 let vtxo = self.select_vtxos_to_cover(Amount::ONE_SAT, None)
211 .and_then(|vtxos| vtxos.into_iter().next().ok_or_else(|| anyhow!("have no spendable vtxo to prove ownership of")))?;
212 let vtxo_keypair = self.get_vtxo_key(&vtxo).expect("owned vtxo should be in database");
213 LightningReceiveAntiDos::InputVtxo(protos::InputVtxo {
214 vtxo_id: vtxo.id().to_bytes().to_vec(),
215 ownership_proof: {
216 let sig = challenge.sign_with(vtxo.id(), vtxo_keypair);
217 sig.serialize().to_vec()
218 }
219 })
220 })
221 }
222
223 async fn check_lightning_receive(
254 &self,
255 payment_hash: PaymentHash,
256 wait: bool,
257 token: Option<&str>,
258 ) -> anyhow::Result<Option<LightningReceive>> {
259 let mut srv = self.require_server()?;
260 let current_height = self.chain.tip().await?;
261
262 let mut receive = self.db.fetch_lightning_receive_by_payment_hash(payment_hash)?
263 .context("no pending lightning receive found for payment hash, might already be claimed")?;
264
265 if receive.htlc_vtxos.is_some() {
267 return Ok(Some(receive))
268 }
269
270 info!("Waiting for payment...");
271 let sub = srv.client.check_lightning_receive(protos::CheckLightningReceiveRequest {
272 hash: payment_hash.to_byte_array().to_vec(), wait,
273 }).await?.into_inner();
274
275 let status = protos::LightningReceiveStatus::try_from(sub.status)
276 .with_context(|| format!("unknown payment status: {}", sub.status))?;
277 match status {
278 protos::LightningReceiveStatus::Accepted |
280 protos::LightningReceiveStatus::HtlcsReady => {},
281
282 protos::LightningReceiveStatus::Created => {
283 warn!("sender didn't initiate payment yet");
284 return Ok(None);
285 },
286 protos::LightningReceiveStatus::Settled => bail!("payment already settled"),
287 protos::LightningReceiveStatus::Cancelled => {
288 warn!("payment was cancelled. removing pending lightning receive");
289 self.exit_or_cancel_lightning_receive(&receive).await?;
290 return Ok(None);
291 },
292 }
293
294 let lightning_receive_anti_dos = match self.compute_lightning_receive_anti_dos(
295 payment_hash, token,
296 ).await {
297 Ok(anti_dos) => Some(anti_dos),
298 Err(e) => {
299 warn!("Could not compute anti-dos: {e}. Trying without");
300 None
301 },
302 };
303
304 let htlc_recv_expiry = current_height + receive.htlc_recv_cltv_delta as BlockHeight;
305
306 let (next_keypair, _) = self.derive_store_next_keypair()?;
307 let req = protos::PrepareLightningReceiveClaimRequest {
308 payment_hash: receive.payment_hash.to_vec(),
309 user_pubkey: next_keypair.public_key().serialize().to_vec(),
310 htlc_recv_expiry,
311 lightning_receive_anti_dos,
312 };
313 let res = srv.client.prepare_lightning_receive_claim(req).await
314 .context("error preparing lightning receive claim")?.into_inner();
315 let vtxos = res.htlc_vtxos.into_iter()
316 .map(|b| Vtxo::deserialize(&b))
317 .collect::<Result<Vec<_>, _>>()
318 .context("invalid htlc vtxos from server")?;
319
320 for vtxo in &vtxos {
322 trace!("Received HTLC VTXO {} from server: {}", vtxo.id(), vtxo.serialize_hex());
323 self.validate_vtxo(vtxo).await
324 .context("received invalid HTLC VTXO from server")?;
325
326 if let VtxoPolicy::ServerHtlcRecv(p) = vtxo.policy() {
327 if p.payment_hash != receive.payment_hash {
328 bail!("invalid payment hash on HTLC VTXOs received from server: {}",
329 p.payment_hash,
330 );
331 }
332 if p.user_pubkey != next_keypair.public_key() {
333 bail!("invalid pubkey on HTLC VTXOs received from server: {}", p.user_pubkey);
334 }
335 if p.htlc_expiry < htlc_recv_expiry {
336 bail!("HTLC VTXO expiry height is less than requested: Requested {}, received {}", htlc_recv_expiry, p.htlc_expiry);
337 }
338 } else {
339 bail!("invalid HTLC VTXO policy: {:?}", vtxo.policy());
340 }
341 }
342
343 let invoice_amount = receive.invoice.amount_milli_satoshis().map(|a| Amount::from_msat_floor(a))
345 .expect("ln receive invoice should have amount");
346 let htlc_amount = vtxos.iter().map(|v| v.amount()).sum::<Amount>();
347 ensure!(vtxos.iter().map(|v| v.amount()).sum::<Amount>() >= invoice_amount,
348 "Server didn't return enough VTXOs to cover invoice amount"
349 );
350
351 let movement_id = if let Some(movement_id) = receive.movement_id {
352 movement_id
353 } else {
354 self.movements.new_movement(
355 self.subsystem_ids[&BarkSubsystem::LightningReceive],
356 LightningReceiveMovement::Receive.to_string(),
357 ).await?
358 };
359 self.store_locked_vtxos(&vtxos, Some(movement_id))?;
360
361 let vtxo_ids = vtxos.iter().map(|v| v.id()).collect::<Vec<_>>();
362 self.db.update_lightning_receive(payment_hash, &vtxo_ids, movement_id)?;
363
364 self.movements.update_movement(
365 movement_id,
366 MovementUpdate::new()
367 .intended_balance(invoice_amount.to_signed()?)
368 .effective_balance(htlc_amount.to_signed()?)
369 .metadata(LightningMovement::htlc_metadata(&vtxos)?)
370 .received_on(
371 [MovementDestination::new(receive.invoice.to_string(), htlc_amount)],
372 ),
373 ).await?;
374
375 let vtxos = vtxos
376 .into_iter()
377 .map(|v| self.db
378 .get_wallet_vtxo(v.id())
379 .and_then(|op| op.context("Failed to get wallet VTXO for lightning receive"))
380 ).collect::<Result<Vec<_>, _>>()?;
381
382 receive.htlc_vtxos = Some(vtxos);
383 receive.movement_id = Some(movement_id);
384
385 Ok(Some(receive))
386 }
387
388 async fn exit_or_cancel_lightning_receive(
389 &self,
390 lightning_receive: &LightningReceive,
391 ) -> anyhow::Result<()> {
392 let vtxos = lightning_receive.htlc_vtxos.as_ref()
393 .map(|v| v.iter().map(|v| &v.vtxo).collect::<Vec<_>>());
394
395 let update_opt = match (vtxos, lightning_receive.preimage_revealed_at) {
396 (Some(vtxos), Some(_)) => {
397 warn!("LN receive is being cancelled but preimage has been disclosed. Exiting");
398 self.exit.write().await.mark_vtxos_for_exit(&vtxos).await?;
399 if let Some(movement_id) = lightning_receive.movement_id {
400 Some((
401 movement_id,
402 MovementUpdate::new().exited_vtxos(vtxos),
403 MovementStatus::Failed,
404 ))
405 } else {
406 error!("movement id is missing but we disclosed preimage: {}", lightning_receive.payment_hash);
407 None
408 }
409 }
410 (Some(vtxos), None) => {
411 warn!("HTLC-recv VTXOs are about to expire, but preimage has not been disclosed yet. Cancelling");
412 self.mark_vtxos_as_spent(vtxos)?;
413 if let Some(movement_id) = lightning_receive.movement_id {
414 Some((
415 movement_id,
416 MovementUpdate::new()
417 .effective_balance(SignedAmount::ZERO),
418 MovementStatus::Cancelled,
419 ))
420 } else {
421 error!("movement id is missing but we got HTLC vtxos: {}", lightning_receive.payment_hash);
422 None
423 }
424 }
425 (None, Some(_)) => {
426 error!("No HTLC vtxos set on ln receive but preimage has been disclosed. Cancelling");
427 lightning_receive.movement_id.map(|id| (id,
428 MovementUpdate::new()
429 .effective_balance(SignedAmount::ZERO),
430 MovementStatus::Cancelled,
431 ))
432 }
433 (None, None) => None,
434 };
435
436 if let Some((movement_id, update, status)) = update_opt {
437 self.movements.update_movement(movement_id, update).await?;
438 self.movements.finish_movement(movement_id, status).await?;
439 }
440
441 self.db.finish_pending_lightning_receive(lightning_receive.payment_hash)?;
442
443 Ok(())
444 }
445
446 pub async fn try_claim_lightning_receive(
472 &self,
473 payment_hash: PaymentHash,
474 wait: bool,
475 token: Option<&str>,
476 ) -> anyhow::Result<()> {
477 let srv = self.require_server()?;
478 let ark_info = srv.ark_info().await?;
479
480 let receive = match self.check_lightning_receive(payment_hash, wait, token).await? {
481 Some(receive) => receive,
482 None => return Ok(()),
483 };
484
485 if receive.finished_at.is_some() {
486 return Ok(());
487 }
488
489 let vtxos = match receive.htlc_vtxos {
490 None => return Ok(()),
492 Some(ref vtxos) => vtxos,
493 };
494
495 if let Err(e) = self.claim_lightning_receive(&receive).await {
496 error!("Failed to claim pubkey vtxo from htlc vtxo: {}", e);
497 let tip = self.chain.tip().await?;
498
499 let first_vtxo = &vtxos.first().unwrap().vtxo;
500 debug_assert!(vtxos.iter().all(|v| {
501 v.vtxo.policy() == first_vtxo.policy() && v.vtxo.exit_delta() == first_vtxo.exit_delta()
502 }), "all htlc vtxos for the same payment hash should have the same policy and exit delta");
503
504 let vtxo_htlc_expiry = first_vtxo.policy().as_server_htlc_recv()
505 .expect("only server htlc recv vtxos can be pending lightning recv").htlc_expiry;
506
507 let safe_exit_margin = first_vtxo.exit_delta() +
508 ark_info.htlc_expiry_delta +
509 self.config.vtxo_exit_margin;
510
511 if tip > vtxo_htlc_expiry.saturating_sub(safe_exit_margin as BlockHeight) {
512 warn!("HTLC-recv VTXOs are about to expire, interupting lightning receive");
513 self.exit_or_cancel_lightning_receive(&receive).await?;
514 }
515
516 return Err(e)
517 }
518
519 Ok(())
520 }
521
522 pub async fn try_claim_all_lightning_receives(&self, wait: bool) -> anyhow::Result<()> {
537 tokio_stream::iter(self.pending_lightning_receives()?)
539 .for_each_concurrent(3, |rcv| async move {
540 if let Err(e) = self.try_claim_lightning_receive(rcv.invoice.into(), wait, None).await {
541 error!("Error claiming lightning receive: {:#}", e);
542 }
543 }).await;
544
545 Ok(())
546 }
547}