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::{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#[derive(Debug, thiserror::Error)]
305#[error("invalid invoice signature: {0}")]
306pub struct CheckSignatureError(pub String);
307
308impl Invoice {
309	pub fn into_bolt11(self) -> Option<Bolt11Invoice> {
310		match self {
311			Invoice::Bolt11(invoice) => Some(invoice),
312			Invoice::Bolt12(_) => None
313		}
314	}
315
316	pub fn payment_hash(&self) -> PaymentHash {
317		match self {
318			Invoice::Bolt11(invoice) => PaymentHash::from(*invoice.payment_hash().as_byte_array()),
319			Invoice::Bolt12(invoice) => PaymentHash::from(invoice.payment_hash()),
320		}
321	}
322
323	pub fn network(&self) -> Network {
324		match self {
325			Invoice::Bolt11(invoice) => invoice.network(),
326			Invoice::Bolt12(invoice) => match invoice.chain() {
327				ChainHash::BITCOIN => Network::Bitcoin,
328				ChainHash::TESTNET3 => Network::Testnet,
329				ChainHash::TESTNET4 => Network::Testnet4,
330				ChainHash::SIGNET => Network::Signet,
331				ChainHash::REGTEST => Network::Regtest,
332				_ => panic!("unsupported network"),
333			},
334		}
335	}
336
337	pub fn amount_msat(&self) -> Option<u64> {
338		match self {
339			Invoice::Bolt11(invoice) => invoice.amount_milli_satoshis(),
340			Invoice::Bolt12(invoice) => Some(invoice.amount_msats()),
341		}
342	}
343
344	pub fn check_signature(&self) -> Result<(), CheckSignatureError> {
345		match self {
346			Invoice::Bolt11(invoice) => invoice
347				.check_signature()
348				.map_err(|e| CheckSignatureError(e.to_string())),
349			Invoice::Bolt12(invoice) => invoice.check_signature(),
350		}
351	}
352}
353
354impl fmt::Display for Invoice {
355	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
356		match self {
357			Invoice::Bolt11(invoice) => write!(f, "{}", invoice.to_string()),
358			Invoice::Bolt12(invoice) => encode_to_fmt::<NoChecksum, _>(
359				f,
360				Hrp::parse("lni").unwrap(),
361				&invoice.bytes(),
362			)
363			.map_err(|e| match e {
364				EncodeError::Fmt(e) => e,
365				_ => fmt::Error {},
366			}),
367		}
368	}
369}
370
371pub trait Bolt12InvoiceExt: Borrow<Bolt12Invoice> {
372	fn payment_hash(&self) -> PaymentHash { PaymentHash::from(self.borrow().payment_hash()) }
373
374	fn check_signature(&self) -> Result<(), CheckSignatureError> {
375		let message = Message::from_digest(self.borrow().signable_hash());
376		let signature = self.borrow().signature();
377
378		if let Some(pubkey) = self.borrow().issuer_signing_pubkey() {
379			Ok(SECP.verify_schnorr(&signature, &message, &pubkey.into())
380				.map_err(|_| CheckSignatureError("invalid signature".to_string()))?)
381		} else {
382			Err(CheckSignatureError("no pubkey on offer, cannot verify signature".to_string()))
383		}
384	}
385
386	fn bytes(&self) -> Vec<u8> {
387		let mut bytes = Vec::new();
388		self.borrow().write(&mut bytes).expect("Writing into a Vec is infallible");
389		bytes
390	}
391
392	fn from_bytes(bytes: &[u8]) -> Result<Bolt12Invoice, Bolt12ParseError> {
393		Bolt12Invoice::try_from(bytes.to_vec())
394	}
395
396	fn validate_issuance(&self, offer: Offer) -> Result<(), CheckSignatureError> {
397		if self.borrow().issuer_signing_pubkey() != offer.issuer_signing_pubkey() {
398			Err(CheckSignatureError("public keys mismatch".to_string()))
399		} else {
400			Ok(())
401		}
402	}
403
404	fn from_str(s: &str) -> Result<Bolt12Invoice, Bolt12ParseError> {
405		let dec = CheckedHrpstring::new::<NoChecksum>(&s)?;
406		if dec.hrp().to_lowercase() != BECH32_BOLT12_INVOICE_HRP {
407			return Err(Bolt12ParseError::InvalidBech32Hrp);
408		}
409
410		let data = dec.byte_iter().collect::<Vec<_>>();
411		Bolt12Invoice::try_from(data)
412	}
413}
414
415impl Bolt12InvoiceExt for Bolt12Invoice {}