From: Jeffrey Czyz Date: Mon, 27 Feb 2023 18:10:32 +0000 (-0600) Subject: Support signing BOLT 12 invoices in NodeSigner X-Git-Tag: v0.0.117-alpha1~49^2 X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=commitdiff_plain;h=39012e35957922eea239c6ed33a6aaf16e7dee9c;p=rust-lightning 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. --- diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index e923ef882..296b3a03e 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 1fbd7dbec..cf8060ab6 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 d323ecb21..0ffc090ea 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 745b389fa..75a844cd1 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 41c86171b..03af068d1 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 eb0b9cb6c..65df55617 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 187abe19f..e7e29600d 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) }