From 39012e35957922eea239c6ed33a6aaf16e7dee9c Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Mon, 27 Feb 2023 12:10:32 -0600 Subject: [PATCH] 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. --- fuzz/src/chanmon_consistency.rs | 15 ++++++ fuzz/src/full_stack.rs | 15 ++++++ fuzz/src/onion_message.rs | 15 ++++++ lightning/src/offers/invoice.rs | 5 ++ lightning/src/offers/invoice_request.rs | 5 ++ lightning/src/sign/mod.rs | 65 ++++++++++++++++++++++++- lightning/src/util/test_utils.rs | 27 ++++++++++ 7 files changed, 146 insertions(+), 1 deletion(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index e923ef88..296b3a03 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -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 { + unreachable!() + } + + fn sign_bolt12_invoice( + &self, _invoice: &UnsignedBolt12Invoice, + ) -> Result { + unreachable!() + } + fn sign_gossip_message(&self, msg: lightning::ln::msgs::UnsignedGossipMessage) -> Result { let msg_hash = Message::from_slice(&Sha256dHash::hash(&msg.encode()[..])[..]).map_err(|_| ())?; let secp_ctx = Secp256k1::signing_only(); diff --git a/fuzz/src/full_stack.rs b/fuzz/src/full_stack.rs index 1fbd7dbe..cf8060ab 100644 --- a/fuzz/src/full_stack.rs +++ b/fuzz/src/full_stack.rs @@ -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 { + unreachable!() + } + + fn sign_bolt12_invoice( + &self, _invoice: &UnsignedBolt12Invoice, + ) -> Result { + unreachable!() + } + fn sign_gossip_message(&self, msg: lightning::ln::msgs::UnsignedGossipMessage) -> Result { let msg_hash = Message::from_slice(&Sha256dHash::hash(&msg.encode()[..])[..]).map_err(|_| ())?; let secp_ctx = Secp256k1::signing_only(); diff --git a/fuzz/src/onion_message.rs b/fuzz/src/onion_message.rs index d323ecb2..0ffc090e 100644 --- a/fuzz/src/onion_message.rs +++ b/fuzz/src/onion_message.rs @@ -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 { + unreachable!() + } + + fn sign_bolt12_invoice( + &self, _invoice: &UnsignedBolt12Invoice, + ) -> Result { + unreachable!() + } + fn sign_gossip_message(&self, _msg: lightning::ln::msgs::UnsignedGossipMessage) -> Result { unreachable!() } diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index 745b389f..75a844cd 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -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. diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index 41c86171..03af068d 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -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. diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index eb0b9cb6..65df5561 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -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; + /// 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; + + /// 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; + /// 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 { + 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 { + 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 { 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 { + self.inner.sign_bolt12_invoice_request(invoice_request) + } + + fn sign_bolt12_invoice( + &self, invoice: &UnsignedBolt12Invoice + ) -> Result { + self.inner.sign_bolt12_invoice(invoice) + } + fn sign_gossip_message(&self, msg: UnsignedGossipMessage) -> Result { self.inner.sign_gossip_message(msg) } diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index 187abe19..e7e29600 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -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 { + unreachable!() + } + + fn sign_bolt12_invoice( + &self, _invoice: &UnsignedBolt12Invoice, + ) -> Result { + unreachable!() + } + fn sign_gossip_message(&self, _msg: msgs::UnsignedGossipMessage) -> Result { 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 { + self.backing.sign_bolt12_invoice_request(invoice_request) + } + + fn sign_bolt12_invoice( + &self, invoice: &UnsignedBolt12Invoice, + ) -> Result { + self.backing.sign_bolt12_invoice(invoice) + } + fn sign_gossip_message(&self, msg: msgs::UnsignedGossipMessage) -> Result { self.backing.sign_gossip_message(msg) } -- 2.30.2