ark/
lightning.rs

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