TaggedHash for BOLT 12 signing function
authorJeffrey Czyz <jkczyz@gmail.com>
Mon, 27 Feb 2023 20:23:05 +0000 (14:23 -0600)
committerJeffrey Czyz <jkczyz@gmail.com>
Tue, 22 Aug 2023 00:14:27 +0000 (19:14 -0500)
The function used to sign BOLT 12 messages only takes a message digest.
This doesn't allow signers to independently verify the message before
signing nor does it allow them to derive the necessary signing keys, if
needed.

Introduce a TaggedHash wrapper for a message digest, which each unsigned
BOLT 12 message type constructs upon initialization. Change the signing
function to take AsRef<TaggedHash>, which each unsigned type implements.
This allows the signing function to take any unsigned message and obtain
its tagged hash.

fuzz/src/invoice_request_deser.rs
fuzz/src/offer_deser.rs
fuzz/src/refund_deser.rs
lightning/src/offers/invoice.rs
lightning/src/offers/invoice_request.rs
lightning/src/offers/merkle.rs
lightning/src/offers/test_utils.rs

index ca9d06ab1f8612d3f6c5bf212c191d27c0cb9cc6..22a2258f4e25f9df7f413bec0b1ab3d07d9a847d 100644 (file)
@@ -38,7 +38,7 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], _out: Out) {
                        if signing_pubkey == odd_pubkey || signing_pubkey == even_pubkey {
                                unsigned_invoice
                                        .sign::<_, Infallible>(
-                                               |digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys))
+                                               |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
                                        )
                                        .unwrap()
                                        .write(&mut buffer)
@@ -46,7 +46,7 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], _out: Out) {
                        } else {
                                unsigned_invoice
                                        .sign::<_, Infallible>(
-                                               |digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys))
+                                               |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
                                        )
                                        .unwrap_err();
                        }
@@ -69,9 +69,9 @@ fn privkey(byte: u8) -> SecretKey {
        SecretKey::from_slice(&[byte; 32]).unwrap()
 }
 
-fn build_response<'a, T: secp256k1::Signing + secp256k1::Verification>(
-       invoice_request: &'a InvoiceRequest, secp_ctx: &Secp256k1<T>
-) -> Result<UnsignedBolt12Invoice<'a>, Bolt12SemanticError> {
+fn build_response<T: secp256k1::Signing + secp256k1::Verification>(
+       invoice_request: &InvoiceRequest, secp_ctx: &Secp256k1<T>
+) -> Result<UnsignedBolt12Invoice, Bolt12SemanticError> {
        let entropy_source = Randomness {};
        let paths = vec![
                BlindedPath::new_for_message(&[pubkey(43), pubkey(44), pubkey(42)], &entropy_source, secp_ctx).unwrap(),
index 53f67a3380db07d322f8b1fe1c72a75301e34cc0..e16c3b4103b4d568640ce3ff77967d02e5ca6f5b 100644 (file)
@@ -30,7 +30,7 @@ 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>(
-                                       |digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys))
+                                       |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
                                )
                                .unwrap()
                                .write(&mut buffer)
@@ -39,9 +39,9 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], _out: Out) {
        }
 }
 
-fn build_response<'a>(
-       offer: &'a Offer, pubkey: PublicKey
-) -> Result<UnsignedInvoiceRequest<'a>, Bolt12SemanticError> {
+fn build_response(
+       offer: &Offer, pubkey: PublicKey
+) -> Result<UnsignedInvoiceRequest, Bolt12SemanticError> {
        let mut builder = offer.request_invoice(vec![42; 64], pubkey)?;
 
        builder = match offer.amount() {
index 81b614d602b269259a9ce67fadbc86307c583334..fd273d7e0284573e13899db7e8f510a0da49e0a7 100644 (file)
@@ -34,7 +34,7 @@ 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>(
-                                       |digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys))
+                                       |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
                                )
                                .unwrap()
                                .write(&mut buffer)
@@ -58,9 +58,9 @@ fn privkey(byte: u8) -> SecretKey {
        SecretKey::from_slice(&[byte; 32]).unwrap()
 }
 
-fn build_response<'a, T: secp256k1::Signing + secp256k1::Verification>(
-       refund: &'a Refund, signing_pubkey: PublicKey, secp_ctx: &Secp256k1<T>
-) -> Result<UnsignedBolt12Invoice<'a>, Bolt12SemanticError> {
+fn build_response<T: secp256k1::Signing + secp256k1::Verification>(
+       refund: &Refund, signing_pubkey: PublicKey, secp_ctx: &Secp256k1<T>
+) -> Result<UnsignedBolt12Invoice, Bolt12SemanticError> {
        let entropy_source = Randomness {};
        let paths = vec![
                BlindedPath::new_for_message(&[pubkey(43), pubkey(44), pubkey(42)], &entropy_source, secp_ctx).unwrap(),
index c3d4500aaebbb0b8f0c2d6abdb4f4179bfad3cf0..773e1ead26c8bbfa84eec3731519007e12c40de2 100644 (file)
@@ -55,7 +55,9 @@
 //!     .allow_mpp()
 //!     .fallback_v0_p2wpkh(&wpubkey_hash)
 //!     .build()?
-//!     .sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)))
+//!     .sign::<_, Infallible>(
+//!         |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
+//!     )
 //!     .expect("failed verifying signature")
 //!     .write(&mut buffer)
 //!     .unwrap();
@@ -84,7 +86,9 @@
 //!     .allow_mpp()
 //!     .fallback_v0_p2wpkh(&wpubkey_hash)
 //!     .build()?
-//!     .sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)))
+//!     .sign::<_, Infallible>(
+//!         |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
+//!     )
 //!     .expect("failed verifying signature")
 //!     .write(&mut buffer)
 //!     .unwrap();
@@ -97,11 +101,11 @@ use bitcoin::blockdata::constants::ChainHash;
 use bitcoin::hash_types::{WPubkeyHash, WScriptHash};
 use bitcoin::hashes::Hash;
 use bitcoin::network::constants::Network;
-use bitcoin::secp256k1::{KeyPair, Message, PublicKey, Secp256k1, self};
+use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, self};
 use bitcoin::secp256k1::schnorr::Signature;
 use bitcoin::util::address::{Address, Payload, WitnessVersion};
 use bitcoin::util::schnorr::TweakedPublicKey;
-use core::convert::{Infallible, TryFrom};
+use core::convert::{AsRef, Infallible, TryFrom};
 use core::time::Duration;
 use crate::io;
 use crate::blinded_path::BlindedPath;
@@ -110,7 +114,7 @@ use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures};
 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, TlvStream, WithoutSignatures, self};
+use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream, WithoutSignatures, self};
 use crate::offers::offer::{Amount, OFFER_TYPES, OfferTlvStream, OfferTlvStreamRef};
 use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError, ParsedMessage};
 use crate::offers::payer::{PAYER_METADATA_TYPE, PayerTlvStream, PayerTlvStreamRef};
@@ -126,7 +130,8 @@ use std::time::SystemTime;
 
 const DEFAULT_RELATIVE_EXPIRY: Duration = Duration::from_secs(7200);
 
-pub(super) const SIGNATURE_TAG: &'static str = concat!("lightning", "invoice", "signature");
+/// Tag for the hash function used when signing a [`Bolt12Invoice`]'s merkle root.
+pub const SIGNATURE_TAG: &'static str = concat!("lightning", "invoice", "signature");
 
 /// Builds a [`Bolt12Invoice`] from either:
 /// - an [`InvoiceRequest`] for the "offer to be paid" flow or
@@ -331,7 +336,7 @@ impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> {
 impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> {
        /// Builds an unsigned [`Bolt12Invoice`] after checking for valid semantics. It can be signed by
        /// [`UnsignedBolt12Invoice::sign`].
-       pub fn build(self) -> Result<UnsignedBolt12Invoice<'a>, Bolt12SemanticError> {
+       pub fn build(self) -> Result<UnsignedBolt12Invoice, Bolt12SemanticError> {
                #[cfg(feature = "std")] {
                        if self.invoice.is_offer_or_refund_expired() {
                                return Err(Bolt12SemanticError::AlreadyExpired);
@@ -339,7 +344,7 @@ impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> {
                }
 
                let InvoiceBuilder { invreq_bytes, invoice, .. } = self;
-               Ok(UnsignedBolt12Invoice { invreq_bytes, invoice })
+               Ok(UnsignedBolt12Invoice::new(invreq_bytes, invoice))
        }
 }
 
@@ -355,23 +360,42 @@ impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> {
                }
 
                let InvoiceBuilder { invreq_bytes, invoice, keys, .. } = self;
-               let unsigned_invoice = UnsignedBolt12Invoice { invreq_bytes, invoice };
+               let unsigned_invoice = UnsignedBolt12Invoice::new(invreq_bytes, invoice);
 
                let keys = keys.unwrap();
                let invoice = unsigned_invoice
-                       .sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)))
+                       .sign::<_, Infallible>(
+                               |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
+                       )
                        .unwrap();
                Ok(invoice)
        }
 }
 
 /// A semantically valid [`Bolt12Invoice`] that hasn't been signed.
-pub struct UnsignedBolt12Invoice<'a> {
-       invreq_bytes: &'a Vec<u8>,
+pub struct UnsignedBolt12Invoice {
+       bytes: Vec<u8>,
        invoice: InvoiceContents,
+       tagged_hash: TaggedHash,
 }
 
-impl<'a> UnsignedBolt12Invoice<'a> {
+impl UnsignedBolt12Invoice {
+       fn new(invreq_bytes: &[u8], invoice: InvoiceContents) -> Self {
+               // Use the invoice_request bytes instead of the invoice_request TLV stream as the latter may
+               // have contained unknown TLV records, which are not stored in `InvoiceRequestContents` or
+               // `RefundContents`.
+               let (_, _, _, invoice_tlv_stream) = invoice.as_tlv_stream();
+               let invoice_request_bytes = WithoutSignatures(invreq_bytes);
+               let unsigned_tlv_stream = (invoice_request_bytes, invoice_tlv_stream);
+
+               let mut bytes = Vec::new();
+               unsigned_tlv_stream.write(&mut bytes).unwrap();
+
+               let tagged_hash = TaggedHash::new(SIGNATURE_TAG, &bytes);
+
+               Self { bytes, invoice, tagged_hash }
+       }
+
        /// The public key corresponding to the key needed to sign the invoice.
        pub fn signing_pubkey(&self) -> PublicKey {
                self.invoice.fields().signing_pubkey
@@ -380,37 +404,33 @@ impl<'a> UnsignedBolt12Invoice<'a> {
        /// Signs the invoice using the given function.
        ///
        /// This is not exported to bindings users as functions aren't currently mapped.
-       pub fn sign<F, E>(self, sign: F) -> Result<Bolt12Invoice, SignError<E>>
+       pub fn sign<F, E>(mut self, sign: F) -> Result<Bolt12Invoice, SignError<E>>
        where
-               F: FnOnce(&Message) -> Result<Signature, E>
+               F: FnOnce(&Self) -> Result<Signature, E>
        {
-               // Use the invoice_request bytes instead of the invoice_request TLV stream as the latter may
-               // have contained unknown TLV records, which are not stored in `InvoiceRequestContents` or
-               // `RefundContents`.
-               let (_, _, _, invoice_tlv_stream) = self.invoice.as_tlv_stream();
-               let invoice_request_bytes = WithoutSignatures(self.invreq_bytes);
-               let unsigned_tlv_stream = (invoice_request_bytes, invoice_tlv_stream);
-
-               let mut bytes = Vec::new();
-               unsigned_tlv_stream.write(&mut bytes).unwrap();
-
                let pubkey = self.invoice.fields().signing_pubkey;
-               let signature = merkle::sign_message(sign, SIGNATURE_TAG, &bytes, pubkey)?;
+               let signature = merkle::sign_message(sign, &self, pubkey)?;
 
                // Append the signature TLV record to the bytes.
                let signature_tlv_stream = SignatureTlvStreamRef {
                        signature: Some(&signature),
                };
-               signature_tlv_stream.write(&mut bytes).unwrap();
+               signature_tlv_stream.write(&mut self.bytes).unwrap();
 
                Ok(Bolt12Invoice {
-                       bytes,
+                       bytes: self.bytes,
                        contents: self.invoice,
                        signature,
                })
        }
 }
 
+impl AsRef<TaggedHash> for UnsignedBolt12Invoice {
+       fn as_ref(&self) -> &TaggedHash {
+               &self.tagged_hash
+       }
+}
+
 /// A `Bolt12Invoice` is a payment request, typically corresponding to an [`Offer`] or a [`Refund`].
 ///
 /// An invoice may be sent in response to an [`InvoiceRequest`] in the case of an offer or sent
@@ -1686,15 +1706,14 @@ mod tests {
                        .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
                        .build().unwrap()
                        .sign(payer_sign).unwrap();
-               let mut unsigned_invoice = invoice_request
+               let mut invoice_builder = invoice_request
                        .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
                        .fallback_v0_p2wsh(&script.wscript_hash())
                        .fallback_v0_p2wpkh(&pubkey.wpubkey_hash().unwrap())
-                       .fallback_v1_p2tr_tweaked(&tweaked_pubkey)
-                       .build().unwrap();
+                       .fallback_v1_p2tr_tweaked(&tweaked_pubkey);
 
                // Only standard addresses will be included.
-               let fallbacks = unsigned_invoice.invoice.fields_mut().fallbacks.as_mut().unwrap();
+               let fallbacks = invoice_builder.invoice.fields_mut().fallbacks.as_mut().unwrap();
                // Non-standard addresses
                fallbacks.push(FallbackAddress { version: 1, program: vec![0u8; 41] });
                fallbacks.push(FallbackAddress { version: 2, program: vec![0u8; 1] });
@@ -1703,7 +1722,7 @@ mod tests {
                fallbacks.push(FallbackAddress { version: 1, program: vec![0u8; 33] });
                fallbacks.push(FallbackAddress { version: 2, program: vec![0u8; 40] });
 
-               let invoice = unsigned_invoice.sign(recipient_sign).unwrap();
+               let invoice = invoice_builder.build().unwrap().sign(recipient_sign).unwrap();
                let mut buffer = Vec::new();
                invoice.write(&mut buffer).unwrap();
 
index f014bf120021b52b613a6726f988e8fb6b076f1b..1dea6503f58fd23913874d0aa33c234cc3d50bce 100644 (file)
@@ -44,7 +44,9 @@
 //!     .quantity(5)?
 //!     .payer_note("foo".to_string())
 //!     .build()?
-//!     .sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)))
+//!     .sign::<_, Infallible>(
+//!         |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
+//!     )
 //!     .expect("failed verifying signature")
 //!     .write(&mut buffer)
 //!     .unwrap();
@@ -54,9 +56,9 @@
 
 use bitcoin::blockdata::constants::ChainHash;
 use bitcoin::network::constants::Network;
-use bitcoin::secp256k1::{KeyPair, Message, PublicKey, Secp256k1, self};
+use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, self};
 use bitcoin::secp256k1::schnorr::Signature;
-use core::convert::{Infallible, TryFrom};
+use core::convert::{AsRef, Infallible, TryFrom};
 use core::ops::Deref;
 use crate::sign::EntropySource;
 use crate::io;
@@ -66,7 +68,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, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder};
-use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, self};
+use crate::offers::merkle::{SignError, 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};
@@ -76,7 +78,8 @@ use crate::util::string::PrintableString;
 
 use crate::prelude::*;
 
-const SIGNATURE_TAG: &'static str = concat!("lightning", "invoice_request", "signature");
+/// Tag for the hash function used when signing an [`InvoiceRequest`]'s merkle root.
+pub const SIGNATURE_TAG: &'static str = concat!("lightning", "invoice_request", "signature");
 
 pub(super) const IV_BYTES: &[u8; IV_LEN] = b"LDK Invreq ~~~~~";
 
@@ -214,7 +217,7 @@ impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a
        }
 
        fn build_with_checks(mut self) -> Result<
-               (UnsignedInvoiceRequest<'a>, Option<KeyPair>, Option<&'b Secp256k1<T>>),
+               (UnsignedInvoiceRequest, Option<KeyPair>, Option<&'b Secp256k1<T>>),
                Bolt12SemanticError
        > {
                #[cfg(feature = "std")] {
@@ -245,7 +248,7 @@ impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a
        }
 
        fn build_without_checks(mut self) ->
-               (UnsignedInvoiceRequest<'a>, Option<KeyPair>, Option<&'b Secp256k1<T>>)
+               (UnsignedInvoiceRequest, Option<KeyPair>, Option<&'b Secp256k1<T>>)
        {
                // Create the metadata for stateless verification of a Bolt12Invoice.
                let mut keys = None;
@@ -275,22 +278,20 @@ impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a
                debug_assert!(self.payer_id.is_some());
                let payer_id = self.payer_id.unwrap();
 
-               let unsigned_invoice = UnsignedInvoiceRequest {
-                       offer: self.offer,
-                       invoice_request: InvoiceRequestContents {
-                               inner: self.invoice_request,
-                               payer_id,
-                       },
+               let invoice_request = InvoiceRequestContents {
+                       inner: self.invoice_request,
+                       payer_id,
                };
+               let unsigned_invoice_request = UnsignedInvoiceRequest::new(self.offer, invoice_request);
 
-               (unsigned_invoice, keys, secp_ctx)
+               (unsigned_invoice_request, keys, secp_ctx)
        }
 }
 
 impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, ExplicitPayerId, T> {
        /// Builds an unsigned [`InvoiceRequest`] after checking for valid semantics. It can be signed
        /// by [`UnsignedInvoiceRequest::sign`].
-       pub fn build(self) -> Result<UnsignedInvoiceRequest<'a>, Bolt12SemanticError> {
+       pub fn build(self) -> Result<UnsignedInvoiceRequest, Bolt12SemanticError> {
                let (unsigned_invoice_request, keys, _) = self.build_with_checks()?;
                debug_assert!(keys.is_none());
                Ok(unsigned_invoice_request)
@@ -306,7 +307,9 @@ impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, DerivedPayerId
                let secp_ctx = secp_ctx.unwrap();
                let keys = keys.unwrap();
                let invoice_request = unsigned_invoice_request
-                       .sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)))
+                       .sign::<_, Infallible>(
+                               |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
+                       )
                        .unwrap();
                Ok(invoice_request)
        }
@@ -335,52 +338,65 @@ impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a
                self
        }
 
-       pub(super) fn build_unchecked(self) -> UnsignedInvoiceRequest<'a> {
+       pub(super) fn build_unchecked(self) -> UnsignedInvoiceRequest {
                self.build_without_checks().0
        }
 }
 
 /// A semantically valid [`InvoiceRequest`] that hasn't been signed.
-pub struct UnsignedInvoiceRequest<'a> {
-       offer: &'a Offer,
+pub struct UnsignedInvoiceRequest {
+       bytes: Vec<u8>,
        invoice_request: InvoiceRequestContents,
+       tagged_hash: TaggedHash,
 }
 
-impl<'a> UnsignedInvoiceRequest<'a> {
-       /// Signs the invoice request using the given function.
-       ///
-       /// This is not exported to bindings users as functions are not yet mapped.
-       pub fn sign<F, E>(self, sign: F) -> Result<InvoiceRequest, SignError<E>>
-       where
-               F: FnOnce(&Message) -> Result<Signature, E>
-       {
+impl UnsignedInvoiceRequest {
+       fn new(offer: &Offer, invoice_request: InvoiceRequestContents) -> Self {
                // Use the offer bytes instead of the offer TLV stream as the offer may have contained
                // unknown TLV records, which are not stored in `OfferContents`.
                let (payer_tlv_stream, _offer_tlv_stream, invoice_request_tlv_stream) =
-                       self.invoice_request.as_tlv_stream();
-               let offer_bytes = WithoutLength(&self.offer.bytes);
+                       invoice_request.as_tlv_stream();
+               let offer_bytes = WithoutLength(&offer.bytes);
                let unsigned_tlv_stream = (payer_tlv_stream, offer_bytes, invoice_request_tlv_stream);
 
                let mut bytes = Vec::new();
                unsigned_tlv_stream.write(&mut bytes).unwrap();
 
+               let tagged_hash = TaggedHash::new(SIGNATURE_TAG, &bytes);
+
+               Self { bytes, invoice_request, tagged_hash }
+       }
+
+       /// Signs the invoice request using the given function.
+       ///
+       /// This is not exported to bindings users as functions are not yet mapped.
+       pub fn sign<F, E>(mut self, sign: F) -> Result<InvoiceRequest, SignError<E>>
+       where
+               F: FnOnce(&Self) -> Result<Signature, E>
+       {
                let pubkey = self.invoice_request.payer_id;
-               let signature = merkle::sign_message(sign, SIGNATURE_TAG, &bytes, pubkey)?;
+               let signature = merkle::sign_message(sign, &self, pubkey)?;
 
                // Append the signature TLV record to the bytes.
                let signature_tlv_stream = SignatureTlvStreamRef {
                        signature: Some(&signature),
                };
-               signature_tlv_stream.write(&mut bytes).unwrap();
+               signature_tlv_stream.write(&mut self.bytes).unwrap();
 
                Ok(InvoiceRequest {
-                       bytes,
+                       bytes: self.bytes,
                        contents: self.invoice_request,
                        signature,
                })
        }
 }
 
+impl AsRef<TaggedHash> for UnsignedInvoiceRequest {
+       fn as_ref(&self) -> &TaggedHash {
+               &self.tagged_hash
+       }
+}
+
 /// An `InvoiceRequest` is a request for a [`Bolt12Invoice`] formulated from an [`Offer`].
 ///
 /// An offer may provide choices such as quantity, amount, chain, features, etc. An invoice request
@@ -591,7 +607,7 @@ impl InvoiceRequest {
 }
 
 impl InvoiceRequestContents {
-       pub fn metadata(&self) -> &[u8] {
+       pub(super) fn metadata(&self) -> &[u8] {
                self.inner.metadata()
        }
 
@@ -790,7 +806,7 @@ mod tests {
        use crate::ln::inbound_payment::ExpandedKey;
        use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
        use crate::offers::invoice::{Bolt12Invoice, SIGNATURE_TAG as INVOICE_SIGNATURE_TAG};
-       use crate::offers::merkle::{SignError, SignatureTlvStreamRef, self};
+       use crate::offers::merkle::{SignError, SignatureTlvStreamRef, TaggedHash, self};
        use crate::offers::offer::{Amount, OfferBuilder, OfferTlvStreamRef, Quantity};
        use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError};
        use crate::offers::payer::PayerTlvStreamRef;
@@ -922,9 +938,8 @@ mod tests {
                let mut bytes = Vec::new();
                tlv_stream.write(&mut bytes).unwrap();
 
-               let signature = merkle::sign_message(
-                       recipient_sign, INVOICE_SIGNATURE_TAG, &bytes, recipient_pubkey()
-               ).unwrap();
+               let message = TaggedHash::new(INVOICE_SIGNATURE_TAG, &bytes);
+               let signature = merkle::sign_message(recipient_sign, &message, recipient_pubkey()).unwrap();
                signature_tlv_stream.signature = Some(&signature);
 
                let mut encoded_invoice = bytes;
@@ -946,9 +961,8 @@ mod tests {
                let mut bytes = Vec::new();
                tlv_stream.write(&mut bytes).unwrap();
 
-               let signature = merkle::sign_message(
-                       recipient_sign, INVOICE_SIGNATURE_TAG, &bytes, recipient_pubkey()
-               ).unwrap();
+               let message = TaggedHash::new(INVOICE_SIGNATURE_TAG, &bytes);
+               let signature = merkle::sign_message(recipient_sign, &message, recipient_pubkey()).unwrap();
                signature_tlv_stream.signature = Some(&signature);
 
                let mut encoded_invoice = bytes;
@@ -992,9 +1006,8 @@ mod tests {
                let mut bytes = Vec::new();
                tlv_stream.write(&mut bytes).unwrap();
 
-               let signature = merkle::sign_message(
-                       recipient_sign, INVOICE_SIGNATURE_TAG, &bytes, recipient_pubkey()
-               ).unwrap();
+               let message = TaggedHash::new(INVOICE_SIGNATURE_TAG, &bytes);
+               let signature = merkle::sign_message(recipient_sign, &message, recipient_pubkey()).unwrap();
                signature_tlv_stream.signature = Some(&signature);
 
                let mut encoded_invoice = bytes;
@@ -1016,9 +1029,8 @@ mod tests {
                let mut bytes = Vec::new();
                tlv_stream.write(&mut bytes).unwrap();
 
-               let signature = merkle::sign_message(
-                       recipient_sign, INVOICE_SIGNATURE_TAG, &bytes, recipient_pubkey()
-               ).unwrap();
+               let message = TaggedHash::new(INVOICE_SIGNATURE_TAG, &bytes);
+               let signature = merkle::sign_message(recipient_sign, &message, recipient_pubkey()).unwrap();
                signature_tlv_stream.signature = Some(&signature);
 
                let mut encoded_invoice = bytes;
@@ -1771,7 +1783,9 @@ mod tests {
                        .build().unwrap()
                        .request_invoice(vec![1; 32], keys.public_key()).unwrap()
                        .build().unwrap()
-                       .sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)))
+                       .sign::<_, Infallible>(
+                               |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
+                       )
                        .unwrap();
 
                let mut encoded_invoice_request = Vec::new();
index f7c33902c51441cd3e2a358637c9ddb4d2cc31e6..d15039cd317d3ed8589ed6230cd3ccdd6487c7b6 100644 (file)
@@ -12,6 +12,7 @@
 use bitcoin::hashes::{Hash, HashEngine, sha256};
 use bitcoin::secp256k1::{Message, PublicKey, Secp256k1, self};
 use bitcoin::secp256k1::schnorr::Signature;
+use core::convert::AsRef;
 use crate::io;
 use crate::util::ser::{BigSize, Readable, Writeable, Writer};
 
@@ -24,6 +25,33 @@ tlv_stream!(SignatureTlvStream, SignatureTlvStreamRef, SIGNATURE_TYPES, {
        (240, signature: Signature),
 });
 
+/// A hash for use in a specific context by tweaking with a context-dependent tag as per [BIP 340]
+/// and computed over the merkle root of a TLV stream to sign as defined in [BOLT 12].
+///
+/// [BIP 340]: https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki
+/// [BOLT 12]: https://github.com/rustyrussell/lightning-rfc/blob/guilt/offers/12-offer-encoding.md#signature-calculation
+pub struct TaggedHash(Message);
+
+impl TaggedHash {
+       /// Creates a tagged hash with the given parameters.
+       ///
+       /// Panics if `tlv_stream` is not a well-formed TLV stream containing at least one TLV record.
+       pub(super) fn new(tag: &str, tlv_stream: &[u8]) -> Self {
+               Self(message_digest(tag, tlv_stream))
+       }
+
+       /// Returns the digest to sign.
+       pub fn as_digest(&self) -> &Message {
+               &self.0
+       }
+}
+
+impl AsRef<TaggedHash> for TaggedHash {
+       fn as_ref(&self) -> &TaggedHash {
+               self
+       }
+}
+
 /// Error when signing messages.
 #[derive(Debug, PartialEq)]
 pub enum SignError<E> {
@@ -33,22 +61,28 @@ pub enum SignError<E> {
        Verification(secp256k1::Error),
 }
 
-/// Signs a message digest consisting of a tagged hash of the given bytes, checking if it can be
-/// verified with the supplied pubkey.
+/// Signs a [`TaggedHash`] computed over the merkle root of `message`'s TLV stream, checking if it
+/// can be verified with the supplied `pubkey`.
 ///
-/// Panics if `bytes` is not a well-formed TLV stream containing at least one TLV record.
-pub(super) fn sign_message<F, E>(
-       sign: F, tag: &str, bytes: &[u8], pubkey: PublicKey,
+/// Since `message` is any type that implements [`AsRef<TaggedHash>`], `sign` may be a closure that
+/// takes a message such as [`Bolt12Invoice`] or [`InvoiceRequest`]. This allows further message
+/// verification before signing its [`TaggedHash`].
+///
+/// [`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,
 ) -> Result<Signature, SignError<E>>
 where
-       F: FnOnce(&Message) -> Result<Signature, E>
+       F: FnOnce(&T) -> Result<Signature, E>,
+       T: AsRef<TaggedHash>,
 {
-       let digest = message_digest(tag, bytes);
-       let signature = sign(&digest).map_err(|e| SignError::Signing(e))?;
+       let signature = sign(message).map_err(|e| SignError::Signing(e))?;
 
+       let digest = message.as_ref().as_digest();
        let pubkey = pubkey.into();
        let secp_ctx = Secp256k1::verification_only();
-       secp_ctx.verify_schnorr(&signature, &digest, &pubkey).map_err(|e| SignError::Verification(e))?;
+       secp_ctx.verify_schnorr(&signature, digest, &pubkey).map_err(|e| SignError::Verification(e))?;
 
        Ok(signature)
 }
@@ -207,12 +241,12 @@ impl<'a> Iterator for TlvStream<'a> {
 /// Encoding for a pre-serialized TLV stream that excludes any signature TLV records.
 ///
 /// Panics if the wrapped bytes are not a well-formed TLV stream.
-pub(super) struct WithoutSignatures<'a>(pub &'a Vec<u8>);
+pub(super) struct WithoutSignatures<'a>(pub &'a [u8]);
 
 impl<'a> Writeable for WithoutSignatures<'a> {
        #[inline]
        fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
-               let tlv_stream = TlvStream::new(&self.0[..]);
+               let tlv_stream = TlvStream::new(self.0);
                for record in tlv_stream.skip_signatures() {
                        writer.write_all(record.record_bytes)?;
                }
@@ -271,7 +305,9 @@ mod tests {
                        .build_unchecked()
                        .request_invoice(vec![0; 8], payer_keys.public_key()).unwrap()
                        .build_unchecked()
-                       .sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &payer_keys)))
+                       .sign::<_, Infallible>(
+                               |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys))
+                       )
                        .unwrap();
                assert_eq!(
                        invoice_request.to_string(),
@@ -304,7 +340,9 @@ mod tests {
                        .build_unchecked()
                        .request_invoice(vec![0; 8], payer_keys.public_key()).unwrap()
                        .build_unchecked()
-                       .sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &payer_keys)))
+                       .sign::<_, Infallible>(
+                               |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys))
+                       )
                        .unwrap();
 
                let mut bytes_without_signature = Vec::new();
@@ -334,7 +372,9 @@ mod tests {
                        .build_unchecked()
                        .request_invoice(vec![0; 8], payer_keys.public_key()).unwrap()
                        .build_unchecked()
-                       .sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &payer_keys)))
+                       .sign::<_, Infallible>(
+                               |message| 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 230c6aa1628d96a180276d228d5742369062886f..f1b3c79edc0ec15cd5f68a406c5f6a8e7f7c3f91 100644 (file)
@@ -9,25 +9,26 @@
 
 //! Utilities for testing BOLT 12 Offers interfaces
 
-use bitcoin::secp256k1::{KeyPair, Message, PublicKey, Secp256k1, SecretKey};
+use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey};
 use bitcoin::secp256k1::schnorr::Signature;
-use core::convert::Infallible;
+use core::convert::{AsRef, Infallible};
 use core::time::Duration;
 use crate::blinded_path::{BlindedHop, BlindedPath};
 use crate::sign::EntropySource;
 use crate::ln::PaymentHash;
 use crate::ln::features::BlindedHopFeatures;
 use crate::offers::invoice::BlindedPayInfo;
+use crate::offers::merkle::TaggedHash;
 
 pub(super) fn payer_keys() -> KeyPair {
        let secp_ctx = Secp256k1::new();
        KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap())
 }
 
-pub(super) fn payer_sign(digest: &Message) -> Result<Signature, Infallible> {
+pub(super) fn payer_sign<T: AsRef<TaggedHash>>(message: &T) -> Result<Signature, Infallible> {
        let secp_ctx = Secp256k1::new();
        let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
-       Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys))
+       Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
 }
 
 pub(super) fn payer_pubkey() -> PublicKey {
@@ -39,10 +40,10 @@ pub(super) fn recipient_keys() -> KeyPair {
        KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[43; 32]).unwrap())
 }
 
-pub(super) fn recipient_sign(digest: &Message) -> Result<Signature, Infallible> {
+pub(super) fn recipient_sign<T: AsRef<TaggedHash>>(message: &T) -> Result<Signature, Infallible> {
        let secp_ctx = Secp256k1::new();
        let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[43; 32]).unwrap());
-       Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys))
+       Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
 }
 
 pub(super) fn recipient_pubkey() -> PublicKey {