Skip to content

Commit 6805acb

Browse files
committed
Support signing BOLT 12 invoices in NodeSigner
BOLT 12 messages need to be signed in the following scenarios: - constructing an InvoiceRequest after scanning an Offer, - constructing an Invoice after scanning a Refund, and - constructing an Invoice when handling an InvoiceRequest. Extend the NodeSigner trait to support signing BOLT 12 invoices such that it can be used in the latter contexts. The method could be used in an OffersMessageHandler.
1 parent 78c06cd commit 6805acb

File tree

7 files changed

+146
-1
lines changed

7 files changed

+146
-1
lines changed

fuzz/src/chanmon_consistency.rs

+15
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ use lightning::ln::channel::FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE;
4444
use lightning::ln::msgs::{self, CommitmentUpdate, ChannelMessageHandler, DecodeError, UpdateAddHTLC, Init};
4545
use lightning::ln::script::ShutdownScript;
4646
use lightning::ln::functional_test_utils::*;
47+
use lightning::offers::invoice::UnsignedBolt12Invoice;
48+
use lightning::offers::invoice_request::UnsignedInvoiceRequest;
4749
use lightning::util::enforcing_trait_impls::{EnforcingSigner, EnforcementState};
4850
use lightning::util::errors::APIError;
4951
use lightning::util::logger::Logger;
@@ -57,6 +59,7 @@ use crate::utils::test_persister::TestPersister;
5759
use bitcoin::secp256k1::{Message, PublicKey, SecretKey, Scalar, Secp256k1};
5860
use bitcoin::secp256k1::ecdh::SharedSecret;
5961
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature};
62+
use bitcoin::secp256k1::schnorr;
6063

6164
use std::mem;
6265
use std::cmp::{self, Ordering};
@@ -211,6 +214,18 @@ impl NodeSigner for KeyProvider {
211214
unreachable!()
212215
}
213216

217+
fn sign_bolt12_invoice_request(
218+
&self, _invoice_request: &UnsignedInvoiceRequest
219+
) -> Result<schnorr::Signature, ()> {
220+
unreachable!()
221+
}
222+
223+
fn sign_bolt12_invoice(
224+
&self, _invoice: &UnsignedBolt12Invoice,
225+
) -> Result<schnorr::Signature, ()> {
226+
unreachable!()
227+
}
228+
214229
fn sign_gossip_message(&self, msg: lightning::ln::msgs::UnsignedGossipMessage) -> Result<Signature, ()> {
215230
let msg_hash = Message::from_slice(&Sha256dHash::hash(&msg.encode()[..])[..]).map_err(|_| ())?;
216231
let secp_ctx = Secp256k1::signing_only();

fuzz/src/full_stack.rs

+15
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ use lightning::ln::peer_handler::{MessageHandler,PeerManager,SocketDescriptor,Ig
4040
use lightning::ln::msgs::{self, DecodeError};
4141
use lightning::ln::script::ShutdownScript;
4242
use lightning::ln::functional_test_utils::*;
43+
use lightning::offers::invoice::UnsignedBolt12Invoice;
44+
use lightning::offers::invoice_request::UnsignedInvoiceRequest;
4345
use lightning::routing::gossip::{P2PGossipSync, NetworkGraph};
4446
use lightning::routing::utxo::UtxoLookup;
4547
use lightning::routing::router::{InFlightHtlcs, PaymentParameters, Route, RouteParameters, Router};
@@ -55,6 +57,7 @@ use crate::utils::test_persister::TestPersister;
5557
use bitcoin::secp256k1::{Message, PublicKey, SecretKey, Scalar, Secp256k1};
5658
use bitcoin::secp256k1::ecdh::SharedSecret;
5759
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature};
60+
use bitcoin::secp256k1::schnorr;
5861

5962
use std::cell::RefCell;
6063
use hashbrown::{HashMap, hash_map};
@@ -316,6 +319,18 @@ impl NodeSigner for KeyProvider {
316319
unreachable!()
317320
}
318321

322+
fn sign_bolt12_invoice_request(
323+
&self, _invoice_request: &UnsignedInvoiceRequest
324+
) -> Result<schnorr::Signature, ()> {
325+
unreachable!()
326+
}
327+
328+
fn sign_bolt12_invoice(
329+
&self, _invoice: &UnsignedBolt12Invoice,
330+
) -> Result<schnorr::Signature, ()> {
331+
unreachable!()
332+
}
333+
319334
fn sign_gossip_message(&self, msg: lightning::ln::msgs::UnsignedGossipMessage) -> Result<Signature, ()> {
320335
let msg_hash = Message::from_slice(&Sha256dHash::hash(&msg.encode()[..])[..]).map_err(|_| ())?;
321336
let secp_ctx = Secp256k1::signing_only();

fuzz/src/onion_message.rs

+15
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ use bitcoin::blockdata::script::Script;
44
use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey};
55
use bitcoin::secp256k1::ecdh::SharedSecret;
66
use bitcoin::secp256k1::ecdsa::RecoverableSignature;
7+
use bitcoin::secp256k1::schnorr;
78

89
use lightning::sign::{Recipient, KeyMaterial, EntropySource, NodeSigner, SignerProvider};
910
use lightning::ln::msgs::{self, DecodeError, OnionMessageHandler};
1011
use lightning::ln::script::ShutdownScript;
12+
use lightning::offers::invoice::UnsignedBolt12Invoice;
13+
use lightning::offers::invoice_request::UnsignedInvoiceRequest;
1114
use lightning::util::enforcing_trait_impls::EnforcingSigner;
1215
use lightning::util::logger::Logger;
1316
use lightning::util::ser::{Readable, Writeable, Writer};
@@ -153,6 +156,18 @@ impl NodeSigner for KeyProvider {
153156
unreachable!()
154157
}
155158

159+
fn sign_bolt12_invoice_request(
160+
&self, _invoice_request: &UnsignedInvoiceRequest
161+
) -> Result<schnorr::Signature, ()> {
162+
unreachable!()
163+
}
164+
165+
fn sign_bolt12_invoice(
166+
&self, _invoice: &UnsignedBolt12Invoice,
167+
) -> Result<schnorr::Signature, ()> {
168+
unreachable!()
169+
}
170+
156171
fn sign_gossip_message(&self, _msg: lightning::ln::msgs::UnsignedGossipMessage) -> Result<bitcoin::secp256k1::ecdsa::Signature, ()> {
157172
unreachable!()
158173
}

lightning/src/offers/invoice.rs

+5
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,11 @@ impl UnsignedBolt12Invoice {
397397
Self { bytes, contents, tagged_hash }
398398
}
399399

400+
/// Returns the [`TaggedHash`] of the invoice to sign.
401+
pub fn tagged_hash(&self) -> &TaggedHash {
402+
&self.tagged_hash
403+
}
404+
400405
/// Signs the [`TaggedHash`] of the invoice using the given function.
401406
///
402407
/// Note: The hash computation may have included unknown, odd TLV records.

lightning/src/offers/invoice_request.rs

+5
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,11 @@ impl UnsignedInvoiceRequest {
372372
Self { bytes, contents, tagged_hash }
373373
}
374374

375+
/// Returns the [`TaggedHash`] of the invoice to sign.
376+
pub fn tagged_hash(&self) -> &TaggedHash {
377+
&self.tagged_hash
378+
}
379+
375380
/// Signs the [`TaggedHash`] of the invoice request using the given function.
376381
///
377382
/// Note: The hash computation may have included unknown, odd TLV records.

lightning/src/sign/mod.rs

+64-1
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@ use bitcoin::hashes::sha256::Hash as Sha256;
2626
use bitcoin::hashes::sha256d::Hash as Sha256dHash;
2727
use bitcoin::hash_types::WPubkeyHash;
2828

29-
use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey, Signing};
29+
use bitcoin::secp256k1::{KeyPair, PublicKey, Scalar, Secp256k1, SecretKey, Signing};
3030
use bitcoin::secp256k1::ecdh::SharedSecret;
3131
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature};
32+
use bitcoin::secp256k1::schnorr;
3233
use bitcoin::{PackedLockTime, secp256k1, Sequence, Witness};
3334

3435
use crate::util::transaction_utils;
@@ -41,6 +42,8 @@ use crate::ln::{chan_utils, PaymentPreimage};
4142
use crate::ln::chan_utils::{HTLCOutputInCommitment, make_funding_redeemscript, ChannelPublicKeys, HolderCommitmentTransaction, ChannelTransactionParameters, CommitmentTransaction, ClosingTransaction};
4243
use crate::ln::msgs::{UnsignedChannelAnnouncement, UnsignedGossipMessage};
4344
use crate::ln::script::ShutdownScript;
45+
use crate::offers::invoice::UnsignedBolt12Invoice;
46+
use crate::offers::invoice_request::UnsignedInvoiceRequest;
4447

4548
use crate::prelude::*;
4649
use core::convert::TryInto;
@@ -619,6 +622,36 @@ pub trait NodeSigner {
619622
/// Errors if the [`Recipient`] variant is not supported by the implementation.
620623
fn sign_invoice(&self, hrp_bytes: &[u8], invoice_data: &[u5], recipient: Recipient) -> Result<RecoverableSignature, ()>;
621624

625+
/// Signs the [`TaggedHash`] of a BOLT 12 invoice request.
626+
///
627+
/// May be called by a function passed to [`UnsignedInvoiceRequest::sign`] where
628+
/// `invoice_request` is the callee.
629+
///
630+
/// Implementors may check that the `invoice_request` is expected rather than blindly signing
631+
/// the tagged hash. An `Ok` result should sign `invoice_request.tagged_hash().as_digest()` with
632+
/// the node's signing key or an ephemeral key to preserve privacy, whichever is associated with
633+
/// [`UnsignedInvoiceRequest::payer_id`].
634+
///
635+
/// [`TaggedHash`]: crate::offers::merkle::TaggedHash
636+
fn sign_bolt12_invoice_request(
637+
&self, invoice_request: &UnsignedInvoiceRequest
638+
) -> Result<schnorr::Signature, ()>;
639+
640+
/// Signs the [`TaggedHash`] of a BOLT 12 invoice.
641+
///
642+
/// May be called by a function passed to [`UnsignedBolt12Invoice::sign`] where `invoice` is the
643+
/// callee.
644+
///
645+
/// Implementors may check that the `invoice` is expected rather than blindly signing the tagged
646+
/// hash. An `Ok` result should sign `invoice.tagged_hash().as_digest()` with the node's signing
647+
/// key or an ephemeral key to preserve privacy, whichever is associated with
648+
/// [`UnsignedBolt12Invoice::signing_pubkey`].
649+
///
650+
/// [`TaggedHash`]: crate::offers::merkle::TaggedHash
651+
fn sign_bolt12_invoice(
652+
&self, invoice: &UnsignedBolt12Invoice
653+
) -> Result<schnorr::Signature, ()>;
654+
622655
/// Sign a gossip message.
623656
///
624657
/// Note that if this fails, LDK may panic and the message will not be broadcast to the network
@@ -1449,6 +1482,24 @@ impl NodeSigner for KeysManager {
14491482
Ok(self.secp_ctx.sign_ecdsa_recoverable(&hash_to_message!(&Sha256::hash(&preimage)), secret))
14501483
}
14511484

1485+
fn sign_bolt12_invoice_request(
1486+
&self, invoice_request: &UnsignedInvoiceRequest
1487+
) -> Result<schnorr::Signature, ()> {
1488+
let message = invoice_request.tagged_hash().as_digest();
1489+
let keys = KeyPair::from_secret_key(&self.secp_ctx, &self.node_secret);
1490+
let aux_rand = self.get_secure_random_bytes();
1491+
Ok(self.secp_ctx.sign_schnorr_with_aux_rand(message, &keys, &aux_rand))
1492+
}
1493+
1494+
fn sign_bolt12_invoice(
1495+
&self, invoice: &UnsignedBolt12Invoice
1496+
) -> Result<schnorr::Signature, ()> {
1497+
let message = invoice.tagged_hash().as_digest();
1498+
let keys = KeyPair::from_secret_key(&self.secp_ctx, &self.node_secret);
1499+
let aux_rand = self.get_secure_random_bytes();
1500+
Ok(self.secp_ctx.sign_schnorr_with_aux_rand(message, &keys, &aux_rand))
1501+
}
1502+
14521503
fn sign_gossip_message(&self, msg: UnsignedGossipMessage) -> Result<Signature, ()> {
14531504
let msg_hash = hash_to_message!(&Sha256dHash::hash(&msg.encode()[..])[..]);
14541505
Ok(self.secp_ctx.sign_ecdsa(&msg_hash, &self.node_secret))
@@ -1557,6 +1608,18 @@ impl NodeSigner for PhantomKeysManager {
15571608
Ok(self.inner.secp_ctx.sign_ecdsa_recoverable(&hash_to_message!(&Sha256::hash(&preimage)), secret))
15581609
}
15591610

1611+
fn sign_bolt12_invoice_request(
1612+
&self, invoice_request: &UnsignedInvoiceRequest
1613+
) -> Result<schnorr::Signature, ()> {
1614+
self.inner.sign_bolt12_invoice_request(invoice_request)
1615+
}
1616+
1617+
fn sign_bolt12_invoice(
1618+
&self, invoice: &UnsignedBolt12Invoice
1619+
) -> Result<schnorr::Signature, ()> {
1620+
self.inner.sign_bolt12_invoice(invoice)
1621+
}
1622+
15601623
fn sign_gossip_message(&self, msg: UnsignedGossipMessage) -> Result<Signature, ()> {
15611624
self.inner.sign_gossip_message(msg)
15621625
}

lightning/src/util/test_utils.rs

+27
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ use crate::ln::features::{ChannelFeatures, InitFeatures, NodeFeatures};
2424
use crate::ln::{msgs, wire};
2525
use crate::ln::msgs::LightningError;
2626
use crate::ln::script::ShutdownScript;
27+
use crate::offers::invoice::UnsignedBolt12Invoice;
28+
use crate::offers::invoice_request::UnsignedInvoiceRequest;
2729
use crate::routing::gossip::{EffectiveCapacity, NetworkGraph, NodeId};
2830
use crate::routing::utxo::{UtxoLookup, UtxoLookupError, UtxoResult};
2931
use crate::routing::router::{find_route, InFlightHtlcs, Path, Route, RouteParameters, Router, ScorerAccountingForInFlightHtlcs};
@@ -47,6 +49,7 @@ use bitcoin::util::sighash::SighashCache;
4749
use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey};
4850
use bitcoin::secp256k1::ecdh::SharedSecret;
4951
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature};
52+
use bitcoin::secp256k1::schnorr;
5053

5154
#[cfg(any(test, feature = "_test_utils"))]
5255
use regex;
@@ -800,6 +803,18 @@ impl NodeSigner for TestNodeSigner {
800803
unreachable!()
801804
}
802805

806+
fn sign_bolt12_invoice_request(
807+
&self, _invoice_request: &UnsignedInvoiceRequest
808+
) -> Result<schnorr::Signature, ()> {
809+
unreachable!()
810+
}
811+
812+
fn sign_bolt12_invoice(
813+
&self, _invoice: &UnsignedBolt12Invoice,
814+
) -> Result<schnorr::Signature, ()> {
815+
unreachable!()
816+
}
817+
803818
fn sign_gossip_message(&self, _msg: msgs::UnsignedGossipMessage) -> Result<Signature, ()> {
804819
unreachable!()
805820
}
@@ -840,6 +855,18 @@ impl NodeSigner for TestKeysInterface {
840855
self.backing.sign_invoice(hrp_bytes, invoice_data, recipient)
841856
}
842857

858+
fn sign_bolt12_invoice_request(
859+
&self, invoice_request: &UnsignedInvoiceRequest
860+
) -> Result<schnorr::Signature, ()> {
861+
self.backing.sign_bolt12_invoice_request(invoice_request)
862+
}
863+
864+
fn sign_bolt12_invoice(
865+
&self, invoice: &UnsignedBolt12Invoice,
866+
) -> Result<schnorr::Signature, ()> {
867+
self.backing.sign_bolt12_invoice(invoice)
868+
}
869+
843870
fn sign_gossip_message(&self, msg: msgs::UnsignedGossipMessage) -> Result<Signature, ()> {
844871
self.backing.sign_gossip_message(msg)
845872
}

0 commit comments

Comments
 (0)