From 407762446cd4abaab5ea9c1c96e8c181a0f63b86 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Fri, 1 Mar 2024 10:17:08 -0600 Subject: [PATCH] Support BOLT 12 signing in c_bindings Replace the Fn trait bound on signing methods with a dedicated trait since Fn is not supported in bindings. Implement the trait for Fn so that closures can still be used in Rust. --- fuzz/src/invoice_request_deser.rs | 12 ++--- fuzz/src/offer_deser.rs | 6 +-- fuzz/src/refund_deser.rs | 6 +-- lightning/src/ln/channelmanager.rs | 6 ++- lightning/src/offers/invoice.rs | 63 ++++++++++++++++++------- lightning/src/offers/invoice_request.rs | 63 ++++++++++++++++++------- lightning/src/offers/merkle.rs | 50 ++++++++++++++------ lightning/src/offers/test_utils.rs | 4 ++ 8 files changed, 147 insertions(+), 63 deletions(-) diff --git a/fuzz/src/invoice_request_deser.rs b/fuzz/src/invoice_request_deser.rs index 22a2258f4..27178deac 100644 --- a/fuzz/src/invoice_request_deser.rs +++ b/fuzz/src/invoice_request_deser.rs @@ -37,17 +37,17 @@ pub fn do_test(data: &[u8], _out: Out) { let even_pubkey = x_only_pubkey.public_key(Parity::Even); if signing_pubkey == odd_pubkey || signing_pubkey == even_pubkey { unsigned_invoice - .sign::<_, Infallible>( - |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) - ) + .sign(|message: &UnsignedBolt12Invoice| -> Result<_, Infallible> { + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) + }) .unwrap() .write(&mut buffer) .unwrap(); } else { unsigned_invoice - .sign::<_, Infallible>( - |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) - ) + .sign(|message: &UnsignedBolt12Invoice| -> Result<_, Infallible> { + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) + }) .unwrap_err(); } } diff --git a/fuzz/src/offer_deser.rs b/fuzz/src/offer_deser.rs index e16c3b410..1b3289b33 100644 --- a/fuzz/src/offer_deser.rs +++ b/fuzz/src/offer_deser.rs @@ -29,9 +29,9 @@ pub fn do_test(data: &[u8], _out: Out) { if let Ok(invoice_request) = build_response(&offer, pubkey) { invoice_request - .sign::<_, Infallible>( - |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) - ) + .sign(|message: &UnsignedInvoiceRequest| -> Result<_, Infallible> { + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) + }) .unwrap() .write(&mut buffer) .unwrap(); diff --git a/fuzz/src/refund_deser.rs b/fuzz/src/refund_deser.rs index fd273d7e0..9c231aa52 100644 --- a/fuzz/src/refund_deser.rs +++ b/fuzz/src/refund_deser.rs @@ -33,9 +33,9 @@ pub fn do_test(data: &[u8], _out: Out) { if let Ok(invoice) = build_response(&refund, pubkey, &secp_ctx) { invoice - .sign::<_, Infallible>( - |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) - ) + .sign(|message: &UnsignedBolt12Invoice| -> Result<_, Infallible> { + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) + }) .unwrap() .write(&mut buffer) .unwrap(); diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index a56793fa1..364150645 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -57,7 +57,7 @@ use crate::ln::msgs::{ChannelMessageHandler, DecodeError, LightningError}; use crate::ln::outbound_payment; use crate::ln::outbound_payment::{Bolt12PaymentError, OutboundPayments, PaymentAttempts, PendingOutboundPayment, SendAlongPathArgs, StaleExpiration}; use crate::ln::wire::Encode; -use crate::offers::invoice::{BlindedPayInfo, Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder}; +use crate::offers::invoice::{BlindedPayInfo, Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder, UnsignedBolt12Invoice}; use crate::offers::invoice_error::InvoiceError; use crate::offers::invoice_request::{DerivedPayerId, InvoiceRequestBuilder}; use crate::offers::merkle::SignError; @@ -9302,7 +9302,9 @@ where .and_then(|invoice| { #[cfg(c_bindings)] let mut invoice = invoice; - match invoice.sign(|invoice| self.node_signer.sign_bolt12_invoice(invoice)) { + match invoice.sign(|invoice: &UnsignedBolt12Invoice| + self.node_signer.sign_bolt12_invoice(invoice) + ) { Ok(invoice) => Ok(OffersMessage::Invoice(invoice)), Err(SignError::Signing(())) => Err(OffersMessage::InvoiceError( InvoiceError::from_string("Failed signing invoice".to_string()) diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index f665bd3b2..50406738f 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -23,6 +23,7 @@ //! use bitcoin::hashes::Hash; //! use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey}; //! use core::convert::{Infallible, TryFrom}; +//! use lightning::offers::invoice::UnsignedBolt12Invoice; //! use lightning::offers::invoice_request::InvoiceRequest; //! use lightning::offers::refund::Refund; //! use lightning::util::ser::Writeable; @@ -57,9 +58,9 @@ //! .allow_mpp() //! .fallback_v0_p2wpkh(&wpubkey_hash) //! .build()? -//! .sign::<_, Infallible>( -//! |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) -//! ) +//! .sign(|message: &UnsignedBolt12Invoice| -> Result<_, Infallible> { +//! Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) +//! }) //! .expect("failed verifying signature") //! .write(&mut buffer) //! .unwrap(); @@ -90,9 +91,9 @@ //! .allow_mpp() //! .fallback_v0_p2wpkh(&wpubkey_hash) //! .build()? -//! .sign::<_, Infallible>( -//! |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) -//! ) +//! .sign(|message: &UnsignedBolt12Invoice| -> Result<_, Infallible> { +//! Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) +//! }) //! .expect("failed verifying signature") //! .write(&mut buffer) //! .unwrap(); @@ -119,7 +120,7 @@ use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures, InvoiceRequ use crate::ln::inbound_payment::ExpandedKey; use crate::ln::msgs::DecodeError; use crate::offers::invoice_request::{INVOICE_REQUEST_PAYER_ID_TYPE, INVOICE_REQUEST_TYPES, IV_BYTES as INVOICE_REQUEST_IV_BYTES, InvoiceRequest, InvoiceRequestContents, InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef}; -use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream, WithoutSignatures, self}; +use crate::offers::merkle::{SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream, WithoutSignatures, self}; use crate::offers::offer::{Amount, OFFER_TYPES, OfferTlvStream, OfferTlvStreamRef, Quantity}; use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError, ParsedMessage}; use crate::offers::payer::{PAYER_METADATA_TYPE, PayerTlvStream, PayerTlvStreamRef}; @@ -324,9 +325,9 @@ macro_rules! invoice_derived_signing_pubkey_builder_methods { ($self: ident, $se let mut unsigned_invoice = UnsignedBolt12Invoice::new(invreq_bytes, invoice.clone()); let invoice = unsigned_invoice - .sign::<_, Infallible>( - |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) - ) + .sign(|message: &UnsignedBolt12Invoice| -> Result<_, Infallible> { + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) + }) .unwrap(); Ok(invoice) } @@ -507,6 +508,37 @@ pub struct UnsignedBolt12Invoice { tagged_hash: TaggedHash, } +/// A function for signing an [`UnsignedBolt12Invoice`]. +pub trait SignBolt12InvoiceFn { + /// Error type returned by the function. + type Error; + + /// Signs a [`TaggedHash`] computed over the merkle root of `message`'s TLV stream. + fn sign_invoice(&self, message: &UnsignedBolt12Invoice) -> Result; +} + +impl SignBolt12InvoiceFn for F +where + F: Fn(&UnsignedBolt12Invoice) -> Result, +{ + type Error = E; + + fn sign_invoice(&self, message: &UnsignedBolt12Invoice) -> Result { + self(message) + } +} + +impl SignFn for F +where + F: SignBolt12InvoiceFn, +{ + type Error = E; + + fn sign(&self, message: &UnsignedBolt12Invoice) -> Result { + self.sign_invoice(message) + } +} + impl UnsignedBolt12Invoice { fn new(invreq_bytes: &[u8], contents: InvoiceContents) -> Self { // Use the invoice_request bytes instead of the invoice_request TLV stream as the latter may @@ -534,12 +566,9 @@ macro_rules! unsigned_invoice_sign_method { ($self: ident, $self_type: ty $(, $s /// Signs the [`TaggedHash`] of the invoice using the given function. /// /// Note: The hash computation may have included unknown, odd TLV records. - /// - /// This is not exported to bindings users as functions aren't currently mapped. - pub fn sign($($self_mut)* $self: $self_type, sign: F) -> Result> - where - F: FnOnce(&Self) -> Result - { + pub fn sign( + $($self_mut)* $self: $self_type, sign: F + ) -> Result> { let pubkey = $self.contents.fields().signing_pubkey; let signature = merkle::sign_message(sign, &$self, pubkey)?; @@ -2013,7 +2042,7 @@ mod tests { .sign(payer_sign).unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .build().unwrap() - .sign(|_| Err(())) + .sign(fail_sign) { Ok(_) => panic!("expected error"), Err(e) => assert_eq!(e, SignError::Signing(())), diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index 5f6d846ed..4eed9fb7e 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -27,6 +27,7 @@ //! use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey}; //! use core::convert::Infallible; //! use lightning::ln::features::OfferFeatures; +//! use lightning::offers::invoice_request::UnsignedInvoiceRequest; //! use lightning::offers::offer::Offer; //! use lightning::util::ser::Writeable; //! @@ -47,9 +48,9 @@ //! .quantity(5)? //! .payer_note("foo".to_string()) //! .build()? -//! .sign::<_, Infallible>( -//! |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) -//! ) +//! .sign(|message: &UnsignedInvoiceRequest| -> Result<_, Infallible> { +//! Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) +//! }) //! .expect("failed verifying signature") //! .write(&mut buffer) //! .unwrap(); @@ -72,7 +73,7 @@ use crate::ln::features::InvoiceRequestFeatures; use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce}; use crate::ln::msgs::DecodeError; use crate::offers::invoice::BlindedPayInfo; -use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, self}; +use crate::offers::merkle::{SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, self}; use crate::offers::offer::{Offer, OfferContents, OfferTlvStream, OfferTlvStreamRef}; use crate::offers::parse::{Bolt12ParseError, ParsedMessage, Bolt12SemanticError}; use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef}; @@ -227,9 +228,9 @@ macro_rules! invoice_request_derived_payer_id_builder_methods { ( let secp_ctx = secp_ctx.unwrap(); let keys = keys.unwrap(); let invoice_request = unsigned_invoice_request - .sign::<_, Infallible>( - |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) - ) + .sign(|message: &UnsignedInvoiceRequest| -> Result<_, Infallible> { + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) + }) .unwrap(); Ok(invoice_request) } @@ -493,6 +494,37 @@ pub struct UnsignedInvoiceRequest { tagged_hash: TaggedHash, } +/// A function for signing an [`UnsignedInvoiceRequest`]. +pub trait SignInvoiceRequestFn { + /// Error type returned by the function. + type Error; + + /// Signs a [`TaggedHash`] computed over the merkle root of `message`'s TLV stream. + fn sign_invoice_request(&self, message: &UnsignedInvoiceRequest) -> Result; +} + +impl SignInvoiceRequestFn for F +where + F: Fn(&UnsignedInvoiceRequest) -> Result, +{ + type Error = E; + + fn sign_invoice_request(&self, message: &UnsignedInvoiceRequest) -> Result { + self(message) + } +} + +impl SignFn for F +where + F: SignInvoiceRequestFn, +{ + type Error = E; + + fn sign(&self, message: &UnsignedInvoiceRequest) -> Result { + self.sign_invoice_request(message) + } +} + impl UnsignedInvoiceRequest { fn new(offer: &Offer, contents: InvoiceRequestContents) -> Self { // Use the offer bytes instead of the offer TLV stream as the offer may have contained @@ -522,12 +554,9 @@ macro_rules! unsigned_invoice_request_sign_method { ( /// Signs the [`TaggedHash`] of the invoice request using the given function. /// /// Note: The hash computation may have included unknown, odd TLV records. - /// - /// This is not exported to bindings users as functions are not yet mapped. - pub fn sign($($self_mut)* $self: $self_type, sign: F) -> Result> - where - F: FnOnce(&Self) -> Result - { + pub fn sign( + $($self_mut)* $self: $self_type, sign: F + ) -> Result> { let pubkey = $self.contents.payer_id; let signature = merkle::sign_message(sign, &$self, pubkey)?; @@ -1712,7 +1741,7 @@ mod tests { .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() .build().unwrap() - .sign(|_| Err(())) + .sign(fail_sign) { Ok(_) => panic!("expected error"), Err(e) => assert_eq!(e, SignError::Signing(())), @@ -2126,9 +2155,9 @@ mod tests { .build().unwrap() .request_invoice(vec![1; 32], keys.public_key()).unwrap() .build().unwrap() - .sign::<_, Infallible>( - |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) - ) + .sign(|message: &UnsignedInvoiceRequest| -> Result<_, Infallible> { + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) + }) .unwrap(); let mut encoded_invoice_request = Vec::new(); diff --git a/lightning/src/offers/merkle.rs b/lightning/src/offers/merkle.rs index ced3b08b7..5a9424500 100644 --- a/lightning/src/offers/merkle.rs +++ b/lightning/src/offers/merkle.rs @@ -76,13 +76,33 @@ impl AsRef for TaggedHash { /// Error when signing messages. #[derive(Debug, PartialEq)] -pub enum SignError { +pub enum SignError { /// User-defined error when signing the message. Signing(E), /// Error when verifying the produced signature using the given pubkey. Verification(secp256k1::Error), } +/// A function for signing a [`TaggedHash`]. +pub(super) trait SignFn> { + /// Error type returned by the function. + type Error; + + /// Signs a [`TaggedHash`] computed over the merkle root of `message`'s TLV stream. + fn sign(&self, message: &T) -> Result; +} + +impl SignFn for F +where + F: Fn(&TaggedHash) -> Result, +{ + type Error = E; + + fn sign(&self, message: &TaggedHash) -> Result { + self(message) + } +} + /// Signs a [`TaggedHash`] computed over the merkle root of `message`'s TLV stream, checking if it /// can be verified with the supplied `pubkey`. /// @@ -92,14 +112,14 @@ pub enum SignError { /// /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest -pub(super) fn sign_message( - sign: F, message: &T, pubkey: PublicKey, +pub(super) fn sign_message( + f: F, message: &T, pubkey: PublicKey, ) -> Result> where - F: FnOnce(&T) -> Result, + F: SignFn, T: AsRef, { - let signature = sign(message).map_err(|e| SignError::Signing(e))?; + let signature = f.sign(message).map_err(|e| SignError::Signing(e))?; let digest = message.as_ref().as_digest(); let pubkey = pubkey.into(); @@ -278,7 +298,7 @@ mod tests { use bitcoin::secp256k1::schnorr::Signature; use core::convert::Infallible; use crate::offers::offer::{Amount, OfferBuilder}; - use crate::offers::invoice_request::InvoiceRequest; + use crate::offers::invoice_request::{InvoiceRequest, UnsignedInvoiceRequest}; use crate::offers::parse::Bech32Encode; use crate::offers::test_utils::{payer_pubkey, recipient_pubkey}; use crate::util::ser::Writeable; @@ -321,9 +341,9 @@ mod tests { .build_unchecked() .request_invoice(vec![0; 8], payer_keys.public_key()).unwrap() .build_unchecked() - .sign::<_, Infallible>( - |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys)) - ) + .sign(|message: &UnsignedInvoiceRequest| -> Result<_, Infallible> { + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys)) + }) .unwrap(); assert_eq!( invoice_request.to_string(), @@ -375,9 +395,9 @@ mod tests { .build_unchecked() .request_invoice(vec![0; 8], payer_keys.public_key()).unwrap() .build_unchecked() - .sign::<_, Infallible>( - |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys)) - ) + .sign(|message: &UnsignedInvoiceRequest| -> Result<_, Infallible> { + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys)) + }) .unwrap(); let mut bytes_without_signature = Vec::new(); @@ -407,9 +427,9 @@ mod tests { .build_unchecked() .request_invoice(vec![0; 8], payer_keys.public_key()).unwrap() .build_unchecked() - .sign::<_, Infallible>( - |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys)) - ) + .sign(|message: &UnsignedInvoiceRequest| -> Result<_, Infallible> { + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys)) + }) .unwrap(); let tlv_stream = TlvStream::new(&invoice_request.bytes).range(0..1) diff --git a/lightning/src/offers/test_utils.rs b/lightning/src/offers/test_utils.rs index 39122472e..701eb69c3 100644 --- a/lightning/src/offers/test_utils.rs +++ b/lightning/src/offers/test_utils.rs @@ -20,6 +20,10 @@ use crate::ln::features::BlindedHopFeatures; use crate::offers::invoice::BlindedPayInfo; use crate::offers::merkle::TaggedHash; +pub(crate) fn fail_sign>(_message: &T) -> Result { + Err(()) +} + pub(crate) fn payer_keys() -> KeyPair { let secp_ctx = Secp256k1::new(); KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()) -- 2.39.5