From 0fb2409ce5e398e1b230cc7c21203a84678374ea Mon Sep 17 00:00:00 2001 From: Orbital Date: Thu, 12 Oct 2023 23:26:31 -0500 Subject: [PATCH] Add invoice request BOLT 12 functionality --- src/lndk_offers.rs | 182 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) diff --git a/src/lndk_offers.rs b/src/lndk_offers.rs index 117e116a..67ef7348 100644 --- a/src/lndk_offers.rs +++ b/src/lndk_offers.rs @@ -1,7 +1,189 @@ +use crate::blocking_client::BlockingClient; +use bitcoin::network::constants::Network; +use bitcoin::secp256k1::schnorr::Signature; +use bitcoin::secp256k1::{Error as Secp256k1Error, Message, PublicKey}; +use lightning::offers::invoice_request::InvoiceRequest; use lightning::offers::offer::Offer; use lightning::offers::parse::Bolt12ParseError; +use tonic_lnd::signrpc::{KeyLocator, SignMessageReq}; +use tonic_lnd::tonic::Status; // Decodes a bech32 string into an LDK offer. pub fn decode(offer_str: String) -> Result { offer_str.parse::() } + +#[allow(dead_code)] +pub(crate) fn request_invoice( + mut signer: impl MessageSigner, + offer: Offer, + metadata: Vec, + network: Network, + msats: u64, +) -> InvoiceRequest { + // Note that we need to use a blocking version of the LND client here because we need to use it in a + // blocking closure below to pass into LDK. + let key_loc = KeyLocator { + key_family: 6, + key_index: 1, + }; + + let pubkey_bytes = signer + .derive_key(key_loc.clone()) + .expect("failed to derive key"); + let pubkey = PublicKey::from_slice(&pubkey_bytes).expect("failed to deserialize public key"); + + let unsigned_invoice_req = match offer + .request_invoice(metadata, pubkey) + .unwrap() + .chain(network) + .unwrap() + .amount_msats(msats) + .unwrap() + .build() + { + Ok(req) => req, + Err(e) => panic!("ERROR requesting invoice request: {:?}", e), + }; + + // To create a valid invoice request, we also need to sign it. + let sign_closure = |msg: &Message| { + let signature_resp = signer + .sign_message(key_loc, msg.to_owned()) + .map_err(|_| Secp256k1Error::InvalidSignature)?; + + Signature::from_slice(&signature_resp) + }; + + match unsigned_invoice_req.sign(sign_closure) { + Ok(resp) => resp, + Err(e) => panic!("ERROR signing unsigned invoice request: {:?}", e), + } +} + +/// MessageSigner provides a layer of abstraction over LND's message signer. +pub(crate) trait MessageSigner { + fn derive_key(&mut self, key_loc: KeyLocator) -> Result, Status>; + fn sign_message(&mut self, key_loc: KeyLocator, msg: Message) -> Result, Status>; +} + +/// Bolt12Signer is responsible for signing the InvoiceRequest +pub(crate) struct Bolt12Signer { + client: BlockingClient, +} + +impl MessageSigner for Bolt12Signer { + fn derive_key(&mut self, key_loc: KeyLocator) -> Result, Status> { + match self.client.derive_key(key_loc) { + Ok(resp) => Ok(resp.into_inner().raw_key_bytes), + Err(e) => Err(e), + } + } + + fn sign_message(&mut self, key_loc: KeyLocator, msg: Message) -> Result, Status> { + let req = SignMessageReq { + msg: msg.as_ref().to_vec(), + key_loc: Some(key_loc), + schnorr_sig: true, + ..Default::default() + }; + + let resp = self.client.sign_message(req)?; + + let resp_inner = resp.into_inner(); + Ok(resp_inner.signature) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use mockall::mock; + use std::str::FromStr; + + fn get_offer() -> String { + "lno1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcgqgn3qzsyvfkx26qkyypvr5hfx60h9w9k934lt8s2n6zc0wwtgqlulw7dythr83dqx8tzumg".to_string() + } + + fn get_pubkey() -> String { + "0313ba7ccbd754c117962b9afab6c2870eb3ef43f364a9f6c43d0fabb4553776ba".to_string() + } + + fn get_signature() -> String { + "28b937976a29c15827433086440b36c2bec6ca5bd977557972dca8641cd59ffba50daafb8ee99a19c950976b46f47d9e7aa716652e5657dfc555b82eff467f18".to_string() + } + + mock! { + TestBolt12Signer{} + + impl MessageSigner for TestBolt12Signer { + fn derive_key(&mut self, key_loc: KeyLocator) -> Result, Status>; + fn sign_message(&mut self, key_loc: KeyLocator, msg: Message) -> Result, Status>; + } + } + + #[test] + fn test_request_invoice() { + let mut signer_mock = MockTestBolt12Signer::new(); + + signer_mock.expect_derive_key().returning(|_| { + Ok(PublicKey::from_str(&get_pubkey()) + .unwrap() + .serialize() + .to_vec()) + }); + + signer_mock.expect_sign_message().returning(|_, _| { + Ok(Signature::from_str(&get_signature()) + .unwrap() + .as_ref() + .to_vec()) + }); + + let offer = decode(get_offer()).unwrap(); + + let _ = request_invoice(signer_mock, offer, vec![], Network::Regtest, 10000); + } + + #[test] + #[should_panic] + fn test_request_invoice_derive_key_error() { + let mut signer_mock = MockTestBolt12Signer::new(); + + signer_mock + .expect_derive_key() + .returning(|_| Err(Status::unknown("error testing"))); + + signer_mock.expect_sign_message().returning(|_, _| { + Ok(Signature::from_str(&get_signature()) + .unwrap() + .as_ref() + .to_vec()) + }); + + let offer = decode(get_offer()).unwrap(); + + let _ = request_invoice(signer_mock, offer, vec![], Network::Regtest, 10000); + } + + #[test] + #[should_panic] + fn test_request_invoice_signer_error() { + let mut signer_mock = MockTestBolt12Signer::new(); + + signer_mock.expect_derive_key().returning(|_| { + Ok(PublicKey::from_str(&get_pubkey()) + .unwrap() + .serialize() + .to_vec()) + }); + + signer_mock + .expect_sign_message() + .returning(|_, _| Err(Status::unknown("error testing"))); + + let offer = decode(get_offer()).unwrap(); + + let _ = request_invoice(signer_mock, offer, vec![], Network::Regtest, 10000); + } +}