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;
14use lightning::offers::parse::Bolt12ParseError;
15use lightning::util::ser::Writeable;
16
17use bitcoin_ext::{AmountExt, P2TR_DUST};
18
19use crate::SECP;
20
21const BECH32_BOLT12_INVOICE_HRP: &str = "lni";
22
23/// The minimum fee we consider for an HTLC transaction.
24pub const HTLC_MIN_FEE: Amount = P2TR_DUST;
25
26pub const PREIMAGE_SIZE: usize = 32;
27pub const PAYMENT_HASH_SIZE: usize = 32;
28
29/// A 32-byte secret preimage used for HTLC-based payments.
30#[derive(Clone, Copy, PartialEq, Eq, Hash)]
31pub struct Preimage([u8; PREIMAGE_SIZE]);
32impl_byte_newtype!(Preimage, PREIMAGE_SIZE);
33
34impl Preimage {
35	/// Generate a new random preimage.
36	pub fn random() -> Preimage {
37		Preimage(rand::random())
38	}
39
40	/// Hashes the preimage into the payment hash
41	pub fn compute_payment_hash(&self) -> PaymentHash {
42		sha256::Hash::hash(self.as_ref()).into()
43	}
44}
45
46/// The hash of a [Preimage], used to identify HTLC-based payments.
47#[derive(Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
48pub struct PaymentHash([u8; PAYMENT_HASH_SIZE]);
49impl_byte_newtype!(PaymentHash, PAYMENT_HASH_SIZE);
50
51impl From<sha256::Hash> for PaymentHash {
52	fn from(hash: sha256::Hash) -> Self {
53		PaymentHash(hash.to_byte_array())
54	}
55}
56
57impl From<Preimage> for PaymentHash {
58	fn from(preimage: Preimage) -> Self {
59		preimage.compute_payment_hash()
60	}
61}
62
63impl From<lightning::types::payment::PaymentHash> for PaymentHash {
64	fn from(hash: lightning::types::payment::PaymentHash) -> Self {
65		PaymentHash(hash.0)
66	}
67}
68
69impl<'a> From<&'a Bolt11Invoice> for PaymentHash {
70	fn from(i: &'a Bolt11Invoice) -> Self {
71		(*i.payment_hash()).into()
72	}
73}
74
75impl From<Bolt11Invoice> for PaymentHash {
76	fn from(i: Bolt11Invoice) -> Self {
77		(&i).into()
78	}
79}
80
81impl PaymentHash {
82	/// Converts this PaymentHash into a `bitcoin::hashes::sha256::Hash`.
83	pub fn to_sha256_hash(&self) -> bitcoin::hashes::sha256::Hash {
84		bitcoin::hashes::sha256::Hash::from_slice(&self.0)
85			.expect("PaymentHash must be 32 bytes, which is always valid for sha256::Hash")
86	}
87}
88
89#[derive(Debug, Clone)]
90pub enum PaymentStatus {
91	Pending,
92	Success(Preimage),
93	Failed,
94}
95
96impl fmt::Display for PaymentStatus {
97	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98		fmt::Debug::fmt(self, f)
99	}
100}
101
102/// Enum to represent either a lightning [Bolt11Invoice] or a [Bolt12Invoice].
103#[derive(Debug, Clone, PartialEq, Eq, Hash)]
104pub enum Invoice {
105	Bolt11(Bolt11Invoice),
106	Bolt12(Bolt12Invoice),
107}
108
109#[derive(Debug, thiserror::Error)]
110#[error("cannot parse invoice")]
111pub struct InvoiceParseError;
112
113impl FromStr for Invoice {
114	type Err = InvoiceParseError;
115
116	fn from_str(s: &str) -> Result<Self, Self::Err> {
117		if let Ok(bolt11) = Bolt11Invoice::from_str(s) {
118			Ok(Invoice::Bolt11(bolt11))
119		} else if let Ok(bolt12) = Bolt12Invoice::from_str(s) {
120			Ok(Invoice::Bolt12(bolt12))
121		} else {
122			Err(InvoiceParseError)
123		}
124	}
125}
126
127impl From<Bolt11Invoice> for Invoice {
128	fn from(invoice: Bolt11Invoice) -> Self {
129		Invoice::Bolt11(invoice)
130	}
131}
132
133impl From<Bolt12Invoice> for Invoice {
134	fn from(invoice: Bolt12Invoice) -> Self {
135		Invoice::Bolt12(invoice)
136	}
137}
138
139impl<'a> TryFrom<&'a str> for Invoice {
140	type Error = <Invoice as FromStr>::Err;
141	fn try_from(invoice: &'a str) -> Result<Self, Self::Error> {
142	    FromStr::from_str(invoice)
143	}
144}
145
146impl TryFrom<String> for Invoice {
147	type Error = <Invoice as FromStr>::Err;
148	fn try_from(invoice: String) -> Result<Self, Self::Error> {
149	    FromStr::from_str(&invoice)
150	}
151}
152
153impl serde::Serialize for Invoice {
154	fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
155		s.collect_str(self)
156	}
157}
158
159impl<'de> serde::Deserialize<'de> for Invoice {
160	fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
161		struct Visitor;
162		impl<'de> serde::de::Visitor<'de> for Visitor {
163			type Value = Invoice;
164			fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165				write!(f, "a lightning invoice")
166			}
167			fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
168				Invoice::from_str(v).map_err(serde::de::Error::custom)
169			}
170		}
171		d.deserialize_str(Visitor)
172	}
173}
174
175
176#[derive(Debug, thiserror::Error)]
177#[error("invoice amount mismatch: invoice={invoice}, user={user}")]
178pub enum CheckAmountError {
179	#[error("invalid user amount: invoice={invoice}, user={user}")]
180	InvalidUserAmount { invoice: Amount, user: Amount },
181	#[error("offer currency is not supported: {amount:?}")]
182	UnsupportedCurrency { amount: OfferAmount },
183	#[error("user amount required")]
184	UserAmountRequired,
185}
186
187#[derive(Debug, thiserror::Error)]
188#[error("invalid invoice signature: {0}")]
189pub struct CheckSignatureError(pub String);
190
191impl Invoice {
192	pub fn into_bolt11(self) -> Option<Bolt11Invoice> {
193		match self {
194			Invoice::Bolt11(invoice) => Some(invoice),
195			Invoice::Bolt12(_) => None
196		}
197	}
198
199	pub fn payment_hash(&self) -> PaymentHash {
200		match self {
201			Invoice::Bolt11(invoice) => PaymentHash::from(*invoice.payment_hash().as_byte_array()),
202			Invoice::Bolt12(invoice) => PaymentHash::from(invoice.payment_hash()),
203		}
204	}
205
206	pub fn network(&self) -> Network {
207		match self {
208			Invoice::Bolt11(invoice) => invoice.network(),
209			Invoice::Bolt12(invoice) => match invoice.chain() {
210				ChainHash::BITCOIN => Network::Bitcoin,
211				ChainHash::TESTNET3 => Network::Testnet,
212				ChainHash::TESTNET4 => Network::Testnet4,
213				ChainHash::SIGNET => Network::Signet,
214				ChainHash::REGTEST => Network::Regtest,
215				_ => panic!("unsupported network"),
216			},
217		}
218	}
219
220	/// Get the amount to be paid. It checks both user and invoice
221	/// equality if both are provided, else it tries to return one
222	/// of them, or returns an error if neither are provided.
223	pub fn get_final_amount(&self, user_amount: Option<Amount>) -> Result<Amount, CheckAmountError> {
224		match self {
225			Invoice::Bolt11(invoice) => invoice.get_final_amount(user_amount),
226			Invoice::Bolt12(invoice) => invoice.get_final_amount(user_amount),
227		}
228	}
229
230	pub fn amount_msat(&self) -> Option<u64> {
231		match self {
232			Invoice::Bolt11(invoice) => invoice.amount_milli_satoshis(),
233			Invoice::Bolt12(invoice) => Some(invoice.amount_msats()),
234		}
235	}
236
237	pub fn check_signature(&self) -> Result<(), CheckSignatureError> {
238		match self {
239			Invoice::Bolt11(invoice) => invoice
240				.check_signature()
241				.map_err(|e| CheckSignatureError(e.to_string())),
242			Invoice::Bolt12(invoice) => invoice.check_signature(),
243		}
244	}
245}
246
247impl fmt::Display for Invoice {
248	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
249		match self {
250			Invoice::Bolt11(invoice) => write!(f, "{}", invoice.to_string()),
251			Invoice::Bolt12(invoice) => encode_to_fmt::<NoChecksum, _>(
252				f,
253				Hrp::parse("lni").unwrap(),
254				&invoice.bytes(),
255			)
256			.map_err(|e| match e {
257				EncodeError::Fmt(e) => e,
258				_ => fmt::Error {},
259			}),
260		}
261	}
262}
263
264/// Get the amount to be paid. It checks both user and invoice
265/// equality if both are provided, else it tries to return one
266/// of them, or returns an error if neither are provided.
267fn get_invoice_final_amount(invoice_amount: Option<Amount>, user_amount: Option<Amount>) -> Result<Amount, CheckAmountError> {
268	match (invoice_amount, user_amount) {
269		(Some(invoice_amount), Some(user_amount)) => {
270			// NB: If provided, the user amount must be at least the invoice amount
271			// and we allow up to 2x the invoice amount, as specified in BOLT 4
272			if user_amount >= invoice_amount && user_amount <= invoice_amount * 2 {
273				return Ok(user_amount);
274			}
275
276			return Err(CheckAmountError::InvalidUserAmount {
277				invoice: invoice_amount,
278				user: user_amount,
279			});
280		}
281		(Some(invoice_amount), None) => {
282			return Ok(invoice_amount);
283		}
284		(None, Some(user_amount)) => {
285			return Ok(user_amount);
286		}
287		(None, None) => {
288			return Err(CheckAmountError::UserAmountRequired);
289		}
290	}
291}
292
293/// Extension trait for the [Bolt11Invoice] type
294pub trait Bolt11InvoiceExt: Borrow<Bolt11Invoice> {
295	/// Get the amount to be paid. It checks both user and invoice
296	/// equality if both are provided, else it tries to return one
297	/// of them, or returns an error if neither are provided.
298	fn get_final_amount(&self, user_amount: Option<Amount>) -> Result<Amount, CheckAmountError> {
299		let invoice_amount = self.borrow().amount_milli_satoshis()
300			.map(Amount::from_msat_ceil);
301
302		get_invoice_final_amount(invoice_amount, user_amount)
303	}
304}
305
306impl Bolt11InvoiceExt for Bolt11Invoice {}
307
308/// Extension trait for the [Bolt12Invoice] type
309pub trait Bolt12InvoiceExt: Borrow<Bolt12Invoice> {
310	fn payment_hash(&self) -> PaymentHash { PaymentHash::from(self.borrow().payment_hash()) }
311
312	/// Get the amount to be paid. It checks both user and invoice
313	/// equality if both are provided, else it tries to return one
314	/// of them, or returns an error if neither are provided.
315	fn get_final_amount(&self, user_amount: Option<Amount>) -> Result<Amount, CheckAmountError> {
316		let invoice_amount = Amount::from_msat_ceil(self.borrow().amount_msats());
317		get_invoice_final_amount(Some(invoice_amount), user_amount)
318	}
319
320	fn check_signature(&self) -> Result<(), CheckSignatureError> {
321		let message = Message::from_digest(self.borrow().signable_hash());
322		let signature = self.borrow().signature();
323
324		if let Some(pubkey) = self.borrow().issuer_signing_pubkey() {
325			Ok(SECP.verify_schnorr(&signature, &message, &pubkey.into())
326				.map_err(|_| CheckSignatureError("invalid signature".to_string()))?)
327		} else {
328			Err(CheckSignatureError("no pubkey on offer, cannot verify signature".to_string()))
329		}
330	}
331
332	fn bytes(&self) -> Vec<u8> {
333		let mut bytes = Vec::new();
334		self.borrow().write(&mut bytes).expect("Writing into a Vec is infallible");
335		bytes
336	}
337
338	fn from_bytes(bytes: &[u8]) -> Result<Bolt12Invoice, Bolt12ParseError> {
339		Bolt12Invoice::try_from(bytes.to_vec())
340	}
341
342	fn validate_issuance(&self, offer: &Offer) -> Result<(), CheckSignatureError> {
343		if self.borrow().issuer_signing_pubkey() != offer.issuer_signing_pubkey() {
344			Err(CheckSignatureError("public keys mismatch".to_string()))
345		} else {
346			Ok(())
347		}
348	}
349
350	fn from_str(s: &str) -> Result<Bolt12Invoice, Bolt12ParseError> {
351		let dec = CheckedHrpstring::new::<NoChecksum>(&s)?;
352		if dec.hrp().to_lowercase() != BECH32_BOLT12_INVOICE_HRP {
353			return Err(Bolt12ParseError::InvalidBech32Hrp);
354		}
355
356		let data = dec.byte_iter().collect::<Vec<_>>();
357		Bolt12Invoice::try_from(data)
358	}
359}
360
361impl Bolt12InvoiceExt for Bolt12Invoice {}