]> git.bitcoin.ninja Git - rust-lightning/commitdiff
Support BOLT 12 signing in c_bindings
authorJeffrey Czyz <jkczyz@gmail.com>
Fri, 1 Mar 2024 16:17:08 +0000 (10:17 -0600)
committerJeffrey Czyz <jkczyz@gmail.com>
Fri, 8 Mar 2024 16:39:22 +0000 (10:39 -0600)
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
fuzz/src/offer_deser.rs
fuzz/src/refund_deser.rs
lightning/src/ln/channelmanager.rs
lightning/src/offers/invoice.rs
lightning/src/offers/invoice_request.rs
lightning/src/offers/merkle.rs
lightning/src/offers/test_utils.rs

index 22a2258f4e25f9df7f413bec0b1ab3d07d9a847d..27178deacf017361b0a9fd9aed1d60d240126047 100644 (file)
@@ -37,17 +37,17 @@ pub fn do_test<Out: test_logger::Output>(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();
                        }
                }
index e16c3b4103b4d568640ce3ff77967d02e5ca6f5b..1b3289b3322b5125208d96d54aed5aeae825800f 100644 (file)
@@ -29,9 +29,9 @@ pub fn do_test<Out: test_logger::Output>(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();
index fd273d7e0284573e13899db7e8f510a0da49e0a7..9c231aa5234104158603097cdf7cd05d966fc8ad 100644 (file)
@@ -33,9 +33,9 @@ pub fn do_test<Out: test_logger::Output>(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();
index a56793fa16d33a24258e63b679fa07d4c825c5d7..364150645e52b118422f9ea31db888d411c267f8 100644 (file)
@@ -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())
index f665bd3b26e81dda6c2181e15dc2f46758f84a9f..50406738f36ff9d3e8d8bd783fd7196bf78d335d 100644 (file)
@@ -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<Signature, Self::Error>;
+}
+
+impl<F, E> SignBolt12InvoiceFn for F
+where
+       F: Fn(&UnsignedBolt12Invoice) -> Result<Signature, E>,
+{
+       type Error = E;
+
+       fn sign_invoice(&self, message: &UnsignedBolt12Invoice) -> Result<Signature, E> {
+               self(message)
+       }
+}
+
+impl<F, E> SignFn<UnsignedBolt12Invoice> for F
+where
+       F: SignBolt12InvoiceFn<Error = E>,
+{
+       type Error = E;
+
+       fn sign(&self, message: &UnsignedBolt12Invoice) -> Result<Signature, Self::Error> {
+               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<F, E>($($self_mut)* $self: $self_type, sign: F) -> Result<Bolt12Invoice, SignError<E>>
-       where
-               F: FnOnce(&Self) -> Result<Signature, E>
-       {
+       pub fn sign<F: SignBolt12InvoiceFn>(
+               $($self_mut)* $self: $self_type, sign: F
+       ) -> Result<Bolt12Invoice, SignError<F::Error>> {
                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(())),
index 5f6d846ed10949093a8fe9aed12fc9ec3766c218..4eed9fb7ee68f1ef51621c00173998167b56dc1f 100644 (file)
@@ -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<Signature, Self::Error>;
+}
+
+impl<F, E> SignInvoiceRequestFn for F
+where
+       F: Fn(&UnsignedInvoiceRequest) -> Result<Signature, E>,
+{
+       type Error = E;
+
+       fn sign_invoice_request(&self, message: &UnsignedInvoiceRequest) -> Result<Signature, E> {
+               self(message)
+       }
+}
+
+impl<F, E> SignFn<UnsignedInvoiceRequest> for F
+where
+       F: SignInvoiceRequestFn<Error = E>,
+{
+       type Error = E;
+
+       fn sign(&self, message: &UnsignedInvoiceRequest) -> Result<Signature, Self::Error> {
+               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<F, E>($($self_mut)* $self: $self_type, sign: F) -> Result<InvoiceRequest, SignError<E>>
-       where
-               F: FnOnce(&Self) -> Result<Signature, E>
-       {
+       pub fn sign<F: SignInvoiceRequestFn>(
+               $($self_mut)* $self: $self_type, sign: F
+       ) -> Result<InvoiceRequest, SignError<F::Error>> {
                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();
index ced3b08b7c2f3a33becd7b2e4f2a444f909a66c9..5a9424500b37c8e037f22b0e9ea2ee7cd561c911 100644 (file)
@@ -76,13 +76,33 @@ impl AsRef<TaggedHash> for TaggedHash {
 
 /// Error when signing messages.
 #[derive(Debug, PartialEq)]
-pub enum SignError<E> {
+pub enum SignError<E = ()> {
        /// 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<T: AsRef<TaggedHash>> {
+       /// 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<Signature, Self::Error>;
+}
+
+impl<F, E> SignFn<TaggedHash> for F
+where
+       F: Fn(&TaggedHash) -> Result<Signature, E>,
+{
+       type Error = E;
+
+       fn sign(&self, message: &TaggedHash) -> Result<Signature, E> {
+               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<E> {
 ///
 /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
 /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
-pub(super) fn sign_message<F, E, T>(
-       sign: F, message: &T, pubkey: PublicKey,
+pub(super) fn sign_message<F, T, E>(
+       f: F, message: &T, pubkey: PublicKey,
 ) -> Result<Signature, SignError<E>>
 where
-       F: FnOnce(&T) -> Result<Signature, E>,
+       F: SignFn<T, Error = E>,
        T: AsRef<TaggedHash>,
 {
-       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)
index 39122472eacb5513640f7fd78020a10ea783957d..701eb69c3e15b6a96db097859dde46d0072fa953 100644 (file)
@@ -20,6 +20,10 @@ use crate::ln::features::BlindedHopFeatures;
 use crate::offers::invoice::BlindedPayInfo;
 use crate::offers::merkle::TaggedHash;
 
+pub(crate) fn fail_sign<T: AsRef<TaggedHash>>(_message: &T) -> Result<Signature, ()> {
+       Err(())
+}
+
 pub(crate) fn payer_keys() -> KeyPair {
        let secp_ctx = Secp256k1::new();
        KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap())