ark/
lightning.rs

1use bitcoin::constants::ChainHash;
2use bitcoin::key::Keypair;
3use bitcoin::{secp256k1, Network};
4pub use lightning::offers::invoice::Bolt12Invoice;
5pub use lightning_invoice::Bolt11Invoice;
6pub use lightning::offers::offer::{Amount as OfferAmount, Offer};
7
8use std::fmt;
9use std::borrow::Borrow;
10use std::io::Write as _;
11use std::str::FromStr;
12
13use bitcoin::Amount;
14use bitcoin::bech32::{encode_to_fmt, EncodeError, Hrp, NoChecksum, primitives::decode::CheckedHrpstring};
15use bitcoin::hashes::{sha256, Hash};
16use bitcoin::secp256k1::{rand, schnorr, Message, PublicKey};
17use bitcoin::taproot::TaprootSpendInfo;
18use lightning::offers::parse::Bolt12ParseError;
19use lightning::util::ser::Writeable;
20
21use bitcoin_ext::{AmountExt, BlockDelta, BlockHeight, P2TR_DUST};
22
23use crate::{musig, scripts, Vtxo, VtxoId, SECP};
24
25const BECH32_BOLT12_INVOICE_HRP: &str = "lni";
26
27/// The minimum fee we consider for an HTLC transaction.
28pub const HTLC_MIN_FEE: Amount = P2TR_DUST;
29
30
31/// A 32-byte secret preimage used for HTLC-based payments.
32#[derive(Clone, Copy, PartialEq, Eq, Hash)]
33pub struct Preimage([u8; 32]);
34impl_byte_newtype!(Preimage, 32);
35
36impl Preimage {
37	/// Generate a new random preimage.
38	pub fn random() -> Preimage {
39		Preimage(rand::random())
40	}
41
42	/// Hashes the preimage into the payment hash
43	pub fn compute_payment_hash(&self) -> PaymentHash {
44		sha256::Hash::hash(self.as_ref()).into()
45	}
46}
47
48/// The hash of a [Preimage], used to identify HTLC-based payments.
49#[derive(Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
50pub struct PaymentHash([u8; 32]);
51impl_byte_newtype!(PaymentHash, 32);
52
53impl From<sha256::Hash> for PaymentHash {
54	fn from(hash: sha256::Hash) -> Self {
55		PaymentHash(hash.to_byte_array())
56	}
57}
58
59impl From<Preimage> for PaymentHash {
60	fn from(preimage: Preimage) -> Self {
61		preimage.compute_payment_hash()
62	}
63}
64
65impl From<lightning::types::payment::PaymentHash> for PaymentHash {
66	fn from(hash: lightning::types::payment::PaymentHash) -> Self {
67		PaymentHash(hash.0)
68	}
69}
70
71impl<'a> From<&'a Bolt11Invoice> for PaymentHash {
72	fn from(i: &'a Bolt11Invoice) -> Self {
73		(*i.payment_hash()).into()
74	}
75}
76
77impl From<Bolt11Invoice> for PaymentHash {
78	fn from(i: Bolt11Invoice) -> Self {
79		(&i).into()
80	}
81}
82
83impl PaymentHash {
84	/// Converts this PaymentHash into a `bitcoin::hashes::sha256::Hash`.
85	pub fn to_sha256_hash(&self) -> bitcoin::hashes::sha256::Hash {
86		bitcoin::hashes::sha256::Hash::from_slice(&self.0)
87			.expect("PaymentHash must be 32 bytes, which is always valid for sha256::Hash")
88	}
89}
90
91#[derive(Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
92pub struct LightningReceiveChallenge(PaymentHash);
93
94impl LightningReceiveChallenge {
95	const CHALLENGE_MESSAGE_PREFIX: &'static [u8; 32] = b"Lightning receive VTXO challenge";
96
97	pub fn new(value: PaymentHash) -> Self {
98		Self(value)
99	}
100
101	/// Combines [VtxoId] and the inner [PaymentHash] to prove ownership of
102	/// a VTXO while commiting to the Lightning receive associated with the unique
103	/// payment hash.
104	fn as_signable_message(&self, vtxo_id: VtxoId) -> Message {
105		let mut engine = sha256::Hash::engine();
106		engine.write_all(Self::CHALLENGE_MESSAGE_PREFIX).unwrap();
107		engine.write_all(&self.0.to_byte_array()).unwrap();
108		engine.write_all(&vtxo_id.to_bytes()).unwrap();
109
110		let hash = sha256::Hash::from_engine(engine).to_byte_array();
111		Message::from_digest(hash)
112	}
113
114	pub fn sign_with(
115		&self,
116		vtxo_id: VtxoId,
117		vtxo_keypair: Keypair,
118	) -> schnorr::Signature {
119		SECP.sign_schnorr(
120			&LightningReceiveChallenge::as_signable_message(self, vtxo_id),
121			&vtxo_keypair,
122		)
123	}
124
125	pub fn verify_input_vtxo_sig(
126		&self,
127		vtxo: &Vtxo,
128		sig: &schnorr::Signature,
129	) -> Result<(), secp256k1::Error> {
130		SECP.verify_schnorr(
131			sig,
132			&LightningReceiveChallenge::as_signable_message(self, vtxo.id()),
133			&vtxo.user_pubkey().x_only_public_key().0,
134		)
135	}
136}
137
138/// Construct taproot spending information for a VTXO that enables outgoing
139/// Lightning payments. This relates to the [crate::VtxoPolicy::ServerHtlcSend]
140/// policy.
141///
142/// This will build a taproot with 3 clauses:
143/// 1. The keyspend path allows Alice and Server to collaborate to spend
144/// the HTLC. The Server can use this path to revoke the HTLC if payment
145/// failed
146///
147/// 2. One leaf of the tree allows Server to spend the HTLC after the
148/// expiry, if it knows the preimage. Server can use this path if Alice
149/// tries to spend using 3rd path.
150///
151/// 3. The other leaf allows Alice to spend the HTLC after its expiry
152/// and with a delay. Alice must use this path if the server fails to
153/// provide the preimage and refuse to revoke the HTLC. It will either
154/// force the Server to reveal the preimage (by spending using 2nd path)
155/// or give Alice her money back.
156pub fn server_htlc_send_taproot(
157	payment_hash: PaymentHash,
158	server_pubkey: PublicKey,
159	user_pubkey: PublicKey,
160	exit_delta: BlockDelta,
161	htlc_expiry: BlockHeight,
162) -> TaprootSpendInfo {
163	let server_branch = scripts::hash_delay_sign(
164		payment_hash.to_sha256_hash(), exit_delta, server_pubkey.x_only_public_key().0,
165	);
166	let user_branch = scripts::delay_timelock_sign(
167		2 * exit_delta, htlc_expiry, user_pubkey.x_only_public_key().0,
168	);
169
170	let combined_pk = musig::combine_keys([user_pubkey, server_pubkey]);
171	bitcoin::taproot::TaprootBuilder::new()
172		.add_leaf(1, server_branch).unwrap()
173		.add_leaf(1, user_branch).unwrap()
174		.finalize(&SECP, combined_pk).unwrap()
175}
176
177/// Construct taproot spending information for a VTXO that enables incoming
178/// Lightning payments. This relates to the [crate::VtxoPolicy::ServerHtlcRecv]
179/// policy.
180///
181/// This will build a taproot with 3 clauses:
182/// 1. The keyspend path allows Alice and Server to collaborate to spend
183/// the HTLC. This is the expected path to be used. Server should only
184/// accept to collaborate if Alice reveals the preimage.
185///
186/// 2. One leaf of the tree allows Server to spend the HTLC after the
187/// expiry, with an exit delta delay. Server can use this path if Alice
188/// tries to spend the HTLC using the 3rd path after the HTLC expiry
189///
190/// 3. The other leaf of the tree allows Alice to spend the HTLC if she
191/// knows the preimage, but with a greater exit delta delay than Server.
192/// Alice must use this path if she revealed the preimage but Server
193/// refused to collaborate using the 1rst path.
194pub fn server_htlc_receive_taproot(
195	payment_hash: PaymentHash,
196	server_pubkey: PublicKey,
197	user_pubkey: PublicKey,
198	exit_delta: BlockDelta,
199	htlc_expiry_delta: BlockDelta,
200	htlc_expiry: BlockHeight,
201) -> TaprootSpendInfo {
202	let server_branch =
203		scripts::delay_timelock_sign(exit_delta, htlc_expiry, server_pubkey.x_only_public_key().0);
204	let user_branch = scripts::hash_delay_sign(
205		payment_hash.to_sha256_hash(),
206		exit_delta + htlc_expiry_delta,
207		user_pubkey.x_only_public_key().0,
208	);
209
210	let combined_pk = musig::combine_keys([user_pubkey, server_pubkey]);
211	bitcoin::taproot::TaprootBuilder::new()
212		.add_leaf(1, server_branch).unwrap()
213		.add_leaf(1, user_branch).unwrap()
214		.finalize(&SECP, combined_pk).unwrap()
215}
216
217
218#[derive(Debug, Clone)]
219pub enum PaymentStatus {
220	Pending,
221	Complete,
222	Failed,
223}
224
225impl fmt::Display for PaymentStatus {
226	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227		fmt::Debug::fmt(self, f)
228	}
229}
230
231/// Enum to represent either a lightning [Bolt11Invoice] or a [Bolt12Invoice].
232#[derive(Debug, Clone, PartialEq, Eq, Hash)]
233pub enum Invoice {
234	Bolt11(Bolt11Invoice),
235	Bolt12(Bolt12Invoice),
236}
237
238#[derive(Debug, thiserror::Error)]
239#[error("cannot parse invoice")]
240pub struct InvoiceParseError;
241
242impl FromStr for Invoice {
243	type Err = InvoiceParseError;
244
245	fn from_str(s: &str) -> Result<Self, Self::Err> {
246		if let Ok(bolt11) = Bolt11Invoice::from_str(s) {
247			Ok(Invoice::Bolt11(bolt11))
248		} else if let Ok(bolt12) = Bolt12Invoice::from_str(s) {
249			Ok(Invoice::Bolt12(bolt12))
250		} else {
251			Err(InvoiceParseError)
252		}
253	}
254}
255
256impl From<Bolt11Invoice> for Invoice {
257	fn from(invoice: Bolt11Invoice) -> Self {
258		Invoice::Bolt11(invoice)
259	}
260}
261
262impl From<Bolt12Invoice> for Invoice {
263	fn from(invoice: Bolt12Invoice) -> Self {
264		Invoice::Bolt12(invoice)
265	}
266}
267
268impl<'a> TryFrom<&'a str> for Invoice {
269	type Error = <Invoice as FromStr>::Err;
270	fn try_from(invoice: &'a str) -> Result<Self, Self::Error> {
271	    FromStr::from_str(invoice)
272	}
273}
274
275impl TryFrom<String> for Invoice {
276	type Error = <Invoice as FromStr>::Err;
277	fn try_from(invoice: String) -> Result<Self, Self::Error> {
278	    FromStr::from_str(&invoice)
279	}
280}
281
282impl serde::Serialize for Invoice {
283	fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
284		s.collect_str(self)
285	}
286}
287
288impl<'de> serde::Deserialize<'de> for Invoice {
289	fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
290		struct Visitor;
291		impl<'de> serde::de::Visitor<'de> for Visitor {
292			type Value = Invoice;
293			fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
294				write!(f, "a lightning invoice")
295			}
296			fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
297				Invoice::from_str(v).map_err(serde::de::Error::custom)
298			}
299		}
300		d.deserialize_str(Visitor)
301	}
302}
303
304
305#[derive(Debug, thiserror::Error)]
306#[error("invoice amount mismatch: invoice={invoice}, user={user}")]
307pub enum CheckAmountError {
308	#[error("invalid user amount: invoice={invoice}, user={user}")]
309	InvalidUserAmount { invoice: Amount, user: Amount },
310	#[error("offer currency is not supported: {amount:?}")]
311	UnsupportedCurrency { amount: OfferAmount },
312	#[error("user amount required")]
313	UserAmountRequired,
314}
315
316#[derive(Debug, thiserror::Error)]
317#[error("invalid invoice signature: {0}")]
318pub struct CheckSignatureError(pub String);
319
320impl Invoice {
321	pub fn into_bolt11(self) -> Option<Bolt11Invoice> {
322		match self {
323			Invoice::Bolt11(invoice) => Some(invoice),
324			Invoice::Bolt12(_) => None
325		}
326	}
327
328	pub fn payment_hash(&self) -> PaymentHash {
329		match self {
330			Invoice::Bolt11(invoice) => PaymentHash::from(*invoice.payment_hash().as_byte_array()),
331			Invoice::Bolt12(invoice) => PaymentHash::from(invoice.payment_hash()),
332		}
333	}
334
335	pub fn network(&self) -> Network {
336		match self {
337			Invoice::Bolt11(invoice) => invoice.network(),
338			Invoice::Bolt12(invoice) => match invoice.chain() {
339				ChainHash::BITCOIN => Network::Bitcoin,
340				ChainHash::TESTNET3 => Network::Testnet,
341				ChainHash::TESTNET4 => Network::Testnet4,
342				ChainHash::SIGNET => Network::Signet,
343				ChainHash::REGTEST => Network::Regtest,
344				_ => panic!("unsupported network"),
345			},
346		}
347	}
348
349	/// See [get_invoice_final_amount] for more details.
350	pub fn get_final_amount(&self, user_amount: Option<Amount>) -> Result<Amount, CheckAmountError> {
351		match self {
352			Invoice::Bolt11(invoice) => invoice.get_final_amount(user_amount),
353			Invoice::Bolt12(invoice) => invoice.get_final_amount(user_amount),
354		}
355	}
356
357	pub fn amount_msat(&self) -> Option<u64> {
358		match self {
359			Invoice::Bolt11(invoice) => invoice.amount_milli_satoshis(),
360			Invoice::Bolt12(invoice) => Some(invoice.amount_msats()),
361		}
362	}
363
364	pub fn check_signature(&self) -> Result<(), CheckSignatureError> {
365		match self {
366			Invoice::Bolt11(invoice) => invoice
367				.check_signature()
368				.map_err(|e| CheckSignatureError(e.to_string())),
369			Invoice::Bolt12(invoice) => invoice.check_signature(),
370		}
371	}
372}
373
374impl fmt::Display for Invoice {
375	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
376		match self {
377			Invoice::Bolt11(invoice) => write!(f, "{}", invoice.to_string()),
378			Invoice::Bolt12(invoice) => encode_to_fmt::<NoChecksum, _>(
379				f,
380				Hrp::parse("lni").unwrap(),
381				&invoice.bytes(),
382			)
383			.map_err(|e| match e {
384				EncodeError::Fmt(e) => e,
385				_ => fmt::Error {},
386			}),
387		}
388	}
389}
390
391/// Get the amount to be paid. It checks both user and invoice
392/// equality if both are provided, else it tries to return one
393/// of them, or returns an error if neither are provided.
394fn get_invoice_final_amount(invoice_amount: Option<Amount>, user_amount: Option<Amount>) -> Result<Amount, CheckAmountError> {
395	match (invoice_amount, user_amount) {
396		(Some(invoice_amount), Some(user_amount)) => {
397			// NB: If provided, the user amount must be at least the invoice amount
398			// and we allow up to 2x the invoice amount, as specified in BOLT 4
399			if user_amount >= invoice_amount && user_amount <= invoice_amount * 2 {
400				return Ok(user_amount);
401			}
402
403			return Err(CheckAmountError::InvalidUserAmount {
404				invoice: invoice_amount,
405				user: user_amount,
406			});
407		}
408		(Some(invoice_amount), None) => {
409			return Ok(invoice_amount);
410		}
411		(None, Some(user_amount)) => {
412			return Ok(user_amount);
413		}
414		(None, None) => {
415			return Err(CheckAmountError::UserAmountRequired);
416		}
417	}
418}
419pub trait Bolt11InvoiceExt: Borrow<Bolt11Invoice> {
420	/// See [get_invoice_final_amount] for more details.
421	fn get_final_amount(&self, user_amount: Option<Amount>) -> Result<Amount, CheckAmountError> {
422		let invoice_amount = self.borrow().amount_milli_satoshis()
423			.map(Amount::from_msat_ceil);
424
425		get_invoice_final_amount(invoice_amount, user_amount)
426	}
427}
428
429impl Bolt11InvoiceExt for Bolt11Invoice {}
430
431pub trait Bolt12InvoiceExt: Borrow<Bolt12Invoice> {
432	fn payment_hash(&self) -> PaymentHash { PaymentHash::from(self.borrow().payment_hash()) }
433
434	/// See [get_invoice_final_amount] for more details.
435	fn get_final_amount(&self, user_amount: Option<Amount>) -> Result<Amount, CheckAmountError> {
436		let invoice_amount = Amount::from_msat_ceil(self.borrow().amount_msats());
437		get_invoice_final_amount(Some(invoice_amount), user_amount)
438	}
439
440	fn check_signature(&self) -> Result<(), CheckSignatureError> {
441		let message = Message::from_digest(self.borrow().signable_hash());
442		let signature = self.borrow().signature();
443
444		if let Some(pubkey) = self.borrow().issuer_signing_pubkey() {
445			Ok(SECP.verify_schnorr(&signature, &message, &pubkey.into())
446				.map_err(|_| CheckSignatureError("invalid signature".to_string()))?)
447		} else {
448			Err(CheckSignatureError("no pubkey on offer, cannot verify signature".to_string()))
449		}
450	}
451
452	fn bytes(&self) -> Vec<u8> {
453		let mut bytes = Vec::new();
454		self.borrow().write(&mut bytes).expect("Writing into a Vec is infallible");
455		bytes
456	}
457
458	fn from_bytes(bytes: &[u8]) -> Result<Bolt12Invoice, Bolt12ParseError> {
459		Bolt12Invoice::try_from(bytes.to_vec())
460	}
461
462	fn validate_issuance(&self, offer: Offer) -> Result<(), CheckSignatureError> {
463		if self.borrow().issuer_signing_pubkey() != offer.issuer_signing_pubkey() {
464			Err(CheckSignatureError("public keys mismatch".to_string()))
465		} else {
466			Ok(())
467		}
468	}
469
470	fn from_str(s: &str) -> Result<Bolt12Invoice, Bolt12ParseError> {
471		let dec = CheckedHrpstring::new::<NoChecksum>(&s)?;
472		if dec.hrp().to_lowercase() != BECH32_BOLT12_INVOICE_HRP {
473			return Err(Bolt12ParseError::InvalidBech32Hrp);
474		}
475
476		let data = dec.byte_iter().collect::<Vec<_>>();
477		Bolt12Invoice::try_from(data)
478	}
479}
480
481impl Bolt12InvoiceExt for Bolt12Invoice {}