Skip to content

Commit

Permalink
Add invoice request BOLT 12 functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
orbitalturtle committed Oct 29, 2023
1 parent f6c3091 commit 5e5f440
Showing 1 changed file with 182 additions and 0 deletions.
182 changes: 182 additions & 0 deletions src/lndk_offers.rs
Original file line number Diff line number Diff line change
@@ -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, Bolt12ParseError> {
offer_str.parse::<Offer>()
}

#[allow(dead_code)]
pub(crate) fn request_invoice(
mut signer: impl MessageSigner,
offer: Offer,
metadata: Vec<u8>,
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<Vec<u8>, Status>;
fn sign_message(&mut self, key_loc: KeyLocator, msg: Message) -> Result<Vec<u8>, 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<Vec<u8>, 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<Vec<u8>, 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<Vec<u8>, Status>;
fn sign_message(&mut self, key_loc: KeyLocator, msg: Message) -> Result<Vec<u8>, 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);
}
}

0 comments on commit 5e5f440

Please sign in to comment.