-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add invoice request BOLT 12 functionality
- Loading branch information
1 parent
f6c3091
commit 5e5f440
Showing
1 changed file
with
182 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |