diff --git a/fuzz/src/invoice_request_deser.rs b/fuzz/src/invoice_request_deser.rs index ca9d06ab1..22a2258f4 100644 --- a/fuzz/src/invoice_request_deser.rs +++ b/fuzz/src/invoice_request_deser.rs @@ -38,7 +38,7 @@ pub fn do_test(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(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 -) -> Result, Bolt12SemanticError> { +fn build_response( + invoice_request: &InvoiceRequest, secp_ctx: &Secp256k1 +) -> Result { let entropy_source = Randomness {}; let paths = vec![ BlindedPath::new_for_message(&[pubkey(43), pubkey(44), pubkey(42)], &entropy_source, secp_ctx).unwrap(), diff --git a/fuzz/src/offer_deser.rs b/fuzz/src/offer_deser.rs index 53f67a338..e16c3b410 100644 --- a/fuzz/src/offer_deser.rs +++ b/fuzz/src/offer_deser.rs @@ -30,7 +30,7 @@ pub fn do_test(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(data: &[u8], _out: Out) { } } -fn build_response<'a>( - offer: &'a Offer, pubkey: PublicKey -) -> Result, Bolt12SemanticError> { +fn build_response( + offer: &Offer, pubkey: PublicKey +) -> Result { let mut builder = offer.request_invoice(vec![42; 64], pubkey)?; builder = match offer.amount() { diff --git a/fuzz/src/refund_deser.rs b/fuzz/src/refund_deser.rs index 81b614d60..fd273d7e0 100644 --- a/fuzz/src/refund_deser.rs +++ b/fuzz/src/refund_deser.rs @@ -34,7 +34,7 @@ pub fn do_test(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 -) -> Result, Bolt12SemanticError> { +fn build_response( + refund: &Refund, signing_pubkey: PublicKey, secp_ctx: &Secp256k1 +) -> Result { let entropy_source = Randomness {}; let paths = vec![ BlindedPath::new_for_message(&[pubkey(43), pubkey(44), pubkey(42)], &entropy_source, secp_ctx).unwrap(), diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index c3d4500aa..773e1ead2 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -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, Bolt12SemanticError> { + pub fn build(self) -> Result { #[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, +pub struct UnsignedBolt12Invoice { + bytes: Vec, 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(self, sign: F) -> Result> + pub fn sign(mut self, sign: F) -> Result> where - F: FnOnce(&Message) -> Result + F: FnOnce(&Self) -> Result { - // 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 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(); diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index f014bf120..1dea6503f 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -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, Option<&'b Secp256k1>), + (UnsignedInvoiceRequest, Option, Option<&'b Secp256k1>), 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, Option<&'b Secp256k1>) + (UnsignedInvoiceRequest, Option, Option<&'b Secp256k1>) { // 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, Bolt12SemanticError> { + pub fn build(self) -> Result { 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, 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(self, sign: F) -> Result> - where - F: FnOnce(&Message) -> Result - { +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(mut self, sign: F) -> Result> + where + F: FnOnce(&Self) -> Result + { 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 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(); diff --git a/lightning/src/offers/merkle.rs b/lightning/src/offers/merkle.rs index f7c33902c..d15039cd3 100644 --- a/lightning/src/offers/merkle.rs +++ b/lightning/src/offers/merkle.rs @@ -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 for TaggedHash { + fn as_ref(&self) -> &TaggedHash { + self + } +} + /// Error when signing messages. #[derive(Debug, PartialEq)] pub enum SignError { @@ -33,22 +61,28 @@ pub enum SignError { 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( - sign: F, tag: &str, bytes: &[u8], pubkey: PublicKey, +/// Since `message` is any type that implements [`AsRef`], `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( + sign: F, message: &T, pubkey: PublicKey, ) -> Result> where - F: FnOnce(&Message) -> Result + F: FnOnce(&T) -> Result, + T: AsRef, { - 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); +pub(super) struct WithoutSignatures<'a>(pub &'a [u8]); impl<'a> Writeable for WithoutSignatures<'a> { #[inline] fn write(&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) diff --git a/lightning/src/offers/test_utils.rs b/lightning/src/offers/test_utils.rs index 230c6aa16..f1b3c79ed 100644 --- a/lightning/src/offers/test_utils.rs +++ b/lightning/src/offers/test_utils.rs @@ -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 { +pub(super) fn payer_sign>(message: &T) -> Result { 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 { +pub(super) fn recipient_sign>(message: &T) -> Result { 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 {