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.
This commit is contained in:
Jeffrey Czyz 2023-02-27 12:10:32 -06:00
parent 63d0d5583d
commit 39012e3595
No known key found for this signature in database
GPG key ID: 3A4E08275D5E96D2
7 changed files with 146 additions and 1 deletions

View file

@ -44,6 +44,8 @@ use lightning::ln::channel::FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE;
use lightning::ln::msgs::{self, CommitmentUpdate, ChannelMessageHandler, DecodeError, UpdateAddHTLC, Init};
use lightning::ln::script::ShutdownScript;
use lightning::ln::functional_test_utils::*;
use lightning::offers::invoice::UnsignedBolt12Invoice;
use lightning::offers::invoice_request::UnsignedInvoiceRequest;
use lightning::util::enforcing_trait_impls::{EnforcingSigner, EnforcementState};
use lightning::util::errors::APIError;
use lightning::util::logger::Logger;
@ -57,6 +59,7 @@ use crate::utils::test_persister::TestPersister;
use bitcoin::secp256k1::{Message, PublicKey, SecretKey, Scalar, Secp256k1};
use bitcoin::secp256k1::ecdh::SharedSecret;
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature};
use bitcoin::secp256k1::schnorr;
use std::mem;
use std::cmp::{self, Ordering};
@ -211,6 +214,18 @@ impl NodeSigner for KeyProvider {
unreachable!()
}
fn sign_bolt12_invoice_request(
&self, _invoice_request: &UnsignedInvoiceRequest
) -> Result<schnorr::Signature, ()> {
unreachable!()
}
fn sign_bolt12_invoice(
&self, _invoice: &UnsignedBolt12Invoice,
) -> Result<schnorr::Signature, ()> {
unreachable!()
}
fn sign_gossip_message(&self, msg: lightning::ln::msgs::UnsignedGossipMessage) -> Result<Signature, ()> {
let msg_hash = Message::from_slice(&Sha256dHash::hash(&msg.encode()[..])[..]).map_err(|_| ())?;
let secp_ctx = Secp256k1::signing_only();

View file

@ -40,6 +40,8 @@ use lightning::ln::peer_handler::{MessageHandler,PeerManager,SocketDescriptor,Ig
use lightning::ln::msgs::{self, DecodeError};
use lightning::ln::script::ShutdownScript;
use lightning::ln::functional_test_utils::*;
use lightning::offers::invoice::UnsignedBolt12Invoice;
use lightning::offers::invoice_request::UnsignedInvoiceRequest;
use lightning::routing::gossip::{P2PGossipSync, NetworkGraph};
use lightning::routing::utxo::UtxoLookup;
use lightning::routing::router::{InFlightHtlcs, PaymentParameters, Route, RouteParameters, Router};
@ -55,6 +57,7 @@ use crate::utils::test_persister::TestPersister;
use bitcoin::secp256k1::{Message, PublicKey, SecretKey, Scalar, Secp256k1};
use bitcoin::secp256k1::ecdh::SharedSecret;
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature};
use bitcoin::secp256k1::schnorr;
use std::cell::RefCell;
use hashbrown::{HashMap, hash_map};
@ -316,6 +319,18 @@ impl NodeSigner for KeyProvider {
unreachable!()
}
fn sign_bolt12_invoice_request(
&self, _invoice_request: &UnsignedInvoiceRequest
) -> Result<schnorr::Signature, ()> {
unreachable!()
}
fn sign_bolt12_invoice(
&self, _invoice: &UnsignedBolt12Invoice,
) -> Result<schnorr::Signature, ()> {
unreachable!()
}
fn sign_gossip_message(&self, msg: lightning::ln::msgs::UnsignedGossipMessage) -> Result<Signature, ()> {
let msg_hash = Message::from_slice(&Sha256dHash::hash(&msg.encode()[..])[..]).map_err(|_| ())?;
let secp_ctx = Secp256k1::signing_only();

View file

@ -4,10 +4,13 @@ use bitcoin::blockdata::script::Script;
use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey};
use bitcoin::secp256k1::ecdh::SharedSecret;
use bitcoin::secp256k1::ecdsa::RecoverableSignature;
use bitcoin::secp256k1::schnorr;
use lightning::sign::{Recipient, KeyMaterial, EntropySource, NodeSigner, SignerProvider};
use lightning::ln::msgs::{self, DecodeError, OnionMessageHandler};
use lightning::ln::script::ShutdownScript;
use lightning::offers::invoice::UnsignedBolt12Invoice;
use lightning::offers::invoice_request::UnsignedInvoiceRequest;
use lightning::util::enforcing_trait_impls::EnforcingSigner;
use lightning::util::logger::Logger;
use lightning::util::ser::{Readable, Writeable, Writer};
@ -153,6 +156,18 @@ impl NodeSigner for KeyProvider {
unreachable!()
}
fn sign_bolt12_invoice_request(
&self, _invoice_request: &UnsignedInvoiceRequest
) -> Result<schnorr::Signature, ()> {
unreachable!()
}
fn sign_bolt12_invoice(
&self, _invoice: &UnsignedBolt12Invoice,
) -> Result<schnorr::Signature, ()> {
unreachable!()
}
fn sign_gossip_message(&self, _msg: lightning::ln::msgs::UnsignedGossipMessage) -> Result<bitcoin::secp256k1::ecdsa::Signature, ()> {
unreachable!()
}

View file

@ -397,6 +397,11 @@ impl UnsignedBolt12Invoice {
Self { bytes, contents, tagged_hash }
}
/// Returns the [`TaggedHash`] of the invoice to sign.
pub fn tagged_hash(&self) -> &TaggedHash {
&self.tagged_hash
}
/// Signs the [`TaggedHash`] of the invoice using the given function.
///
/// Note: The hash computation may have included unknown, odd TLV records.

View file

@ -372,6 +372,11 @@ impl UnsignedInvoiceRequest {
Self { bytes, contents, tagged_hash }
}
/// Returns the [`TaggedHash`] of the invoice to sign.
pub fn tagged_hash(&self) -> &TaggedHash {
&self.tagged_hash
}
/// Signs the [`TaggedHash`] of the invoice request using the given function.
///
/// Note: The hash computation may have included unknown, odd TLV records.

View file

@ -26,9 +26,10 @@ use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::hashes::sha256d::Hash as Sha256dHash;
use bitcoin::hash_types::WPubkeyHash;
use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey, Signing};
use bitcoin::secp256k1::{KeyPair, PublicKey, Scalar, Secp256k1, SecretKey, Signing};
use bitcoin::secp256k1::ecdh::SharedSecret;
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature};
use bitcoin::secp256k1::schnorr;
use bitcoin::{PackedLockTime, secp256k1, Sequence, Witness};
use crate::util::transaction_utils;
@ -41,6 +42,8 @@ use crate::ln::{chan_utils, PaymentPreimage};
use crate::ln::chan_utils::{HTLCOutputInCommitment, make_funding_redeemscript, ChannelPublicKeys, HolderCommitmentTransaction, ChannelTransactionParameters, CommitmentTransaction, ClosingTransaction};
use crate::ln::msgs::{UnsignedChannelAnnouncement, UnsignedGossipMessage};
use crate::ln::script::ShutdownScript;
use crate::offers::invoice::UnsignedBolt12Invoice;
use crate::offers::invoice_request::UnsignedInvoiceRequest;
use crate::prelude::*;
use core::convert::TryInto;
@ -619,6 +622,36 @@ pub trait NodeSigner {
/// Errors if the [`Recipient`] variant is not supported by the implementation.
fn sign_invoice(&self, hrp_bytes: &[u8], invoice_data: &[u5], recipient: Recipient) -> Result<RecoverableSignature, ()>;
/// Signs the [`TaggedHash`] of a BOLT 12 invoice request.
///
/// May be called by a function passed to [`UnsignedInvoiceRequest::sign`] where
/// `invoice_request` is the callee.
///
/// Implementors may check that the `invoice_request` is expected rather than blindly signing
/// the tagged hash. An `Ok` result should sign `invoice_request.tagged_hash().as_digest()` with
/// the node's signing key or an ephemeral key to preserve privacy, whichever is associated with
/// [`UnsignedInvoiceRequest::payer_id`].
///
/// [`TaggedHash`]: crate::offers::merkle::TaggedHash
fn sign_bolt12_invoice_request(
&self, invoice_request: &UnsignedInvoiceRequest
) -> Result<schnorr::Signature, ()>;
/// Signs the [`TaggedHash`] of a BOLT 12 invoice.
///
/// May be called by a function passed to [`UnsignedBolt12Invoice::sign`] where `invoice` is the
/// callee.
///
/// Implementors may check that the `invoice` is expected rather than blindly signing the tagged
/// hash. An `Ok` result should sign `invoice.tagged_hash().as_digest()` with the node's signing
/// key or an ephemeral key to preserve privacy, whichever is associated with
/// [`UnsignedBolt12Invoice::signing_pubkey`].
///
/// [`TaggedHash`]: crate::offers::merkle::TaggedHash
fn sign_bolt12_invoice(
&self, invoice: &UnsignedBolt12Invoice
) -> Result<schnorr::Signature, ()>;
/// Sign a gossip message.
///
/// 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 {
Ok(self.secp_ctx.sign_ecdsa_recoverable(&hash_to_message!(&Sha256::hash(&preimage)), secret))
}
fn sign_bolt12_invoice_request(
&self, invoice_request: &UnsignedInvoiceRequest
) -> Result<schnorr::Signature, ()> {
let message = invoice_request.tagged_hash().as_digest();
let keys = KeyPair::from_secret_key(&self.secp_ctx, &self.node_secret);
let aux_rand = self.get_secure_random_bytes();
Ok(self.secp_ctx.sign_schnorr_with_aux_rand(message, &keys, &aux_rand))
}
fn sign_bolt12_invoice(
&self, invoice: &UnsignedBolt12Invoice
) -> Result<schnorr::Signature, ()> {
let message = invoice.tagged_hash().as_digest();
let keys = KeyPair::from_secret_key(&self.secp_ctx, &self.node_secret);
let aux_rand = self.get_secure_random_bytes();
Ok(self.secp_ctx.sign_schnorr_with_aux_rand(message, &keys, &aux_rand))
}
fn sign_gossip_message(&self, msg: UnsignedGossipMessage) -> Result<Signature, ()> {
let msg_hash = hash_to_message!(&Sha256dHash::hash(&msg.encode()[..])[..]);
Ok(self.secp_ctx.sign_ecdsa(&msg_hash, &self.node_secret))
@ -1557,6 +1608,18 @@ impl NodeSigner for PhantomKeysManager {
Ok(self.inner.secp_ctx.sign_ecdsa_recoverable(&hash_to_message!(&Sha256::hash(&preimage)), secret))
}
fn sign_bolt12_invoice_request(
&self, invoice_request: &UnsignedInvoiceRequest
) -> Result<schnorr::Signature, ()> {
self.inner.sign_bolt12_invoice_request(invoice_request)
}
fn sign_bolt12_invoice(
&self, invoice: &UnsignedBolt12Invoice
) -> Result<schnorr::Signature, ()> {
self.inner.sign_bolt12_invoice(invoice)
}
fn sign_gossip_message(&self, msg: UnsignedGossipMessage) -> Result<Signature, ()> {
self.inner.sign_gossip_message(msg)
}

View file

@ -24,6 +24,8 @@ use crate::ln::features::{ChannelFeatures, InitFeatures, NodeFeatures};
use crate::ln::{msgs, wire};
use crate::ln::msgs::LightningError;
use crate::ln::script::ShutdownScript;
use crate::offers::invoice::UnsignedBolt12Invoice;
use crate::offers::invoice_request::UnsignedInvoiceRequest;
use crate::routing::gossip::{EffectiveCapacity, NetworkGraph, NodeId};
use crate::routing::utxo::{UtxoLookup, UtxoLookupError, UtxoResult};
use crate::routing::router::{find_route, InFlightHtlcs, Path, Route, RouteParameters, Router, ScorerAccountingForInFlightHtlcs};
@ -47,6 +49,7 @@ use bitcoin::util::sighash::SighashCache;
use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey};
use bitcoin::secp256k1::ecdh::SharedSecret;
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature};
use bitcoin::secp256k1::schnorr;
#[cfg(any(test, feature = "_test_utils"))]
use regex;
@ -800,6 +803,18 @@ impl NodeSigner for TestNodeSigner {
unreachable!()
}
fn sign_bolt12_invoice_request(
&self, _invoice_request: &UnsignedInvoiceRequest
) -> Result<schnorr::Signature, ()> {
unreachable!()
}
fn sign_bolt12_invoice(
&self, _invoice: &UnsignedBolt12Invoice,
) -> Result<schnorr::Signature, ()> {
unreachable!()
}
fn sign_gossip_message(&self, _msg: msgs::UnsignedGossipMessage) -> Result<Signature, ()> {
unreachable!()
}
@ -840,6 +855,18 @@ impl NodeSigner for TestKeysInterface {
self.backing.sign_invoice(hrp_bytes, invoice_data, recipient)
}
fn sign_bolt12_invoice_request(
&self, invoice_request: &UnsignedInvoiceRequest
) -> Result<schnorr::Signature, ()> {
self.backing.sign_bolt12_invoice_request(invoice_request)
}
fn sign_bolt12_invoice(
&self, invoice: &UnsignedBolt12Invoice,
) -> Result<schnorr::Signature, ()> {
self.backing.sign_bolt12_invoice(invoice)
}
fn sign_gossip_message(&self, msg: msgs::UnsignedGossipMessage) -> Result<Signature, ()> {
self.backing.sign_gossip_message(msg)
}