Skip to content

Commit

Permalink
Support signing BOLT 12 invoices in NodeSigner
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jkczyz committed Aug 21, 2023
1 parent 78c06cd commit 6805acb
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 1 deletion.
15 changes: 15 additions & 0 deletions fuzz/src/chanmon_consistency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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};
Expand Down Expand Up @@ -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();
Expand Down
15 changes: 15 additions & 0 deletions fuzz/src/full_stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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};
Expand Down Expand Up @@ -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();
Expand Down
15 changes: 15 additions & 0 deletions fuzz/src/onion_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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!()
}
Expand Down
5 changes: 5 additions & 0 deletions lightning/src/offers/invoice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 5 additions & 0 deletions lightning/src/offers/invoice_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
65 changes: 64 additions & 1 deletion lightning/src/sign/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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)
}
Expand Down
27 changes: 27 additions & 0 deletions lightning/src/util/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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;
Expand Down Expand Up @@ -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!()
}
Expand Down Expand Up @@ -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)
}
Expand Down

0 comments on commit 6805acb

Please sign in to comment.