Skip to content

Commit dc52c54

Browse files
committed
Add more parameters to BOLT 12 signing function
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 they had been derived. Include additional parameters to support these use cases.
1 parent c4db058 commit dc52c54

File tree

7 files changed

+93
-40
lines changed

7 files changed

+93
-40
lines changed

fuzz/src/invoice_request_deser.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,15 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], _out: Out) {
3838
if signing_pubkey == odd_pubkey || signing_pubkey == even_pubkey {
3939
unsigned_invoice
4040
.sign::<_, Infallible>(
41-
|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys))
41+
|message, _| Ok(secp_ctx.sign_schnorr_no_aux_rand(&message.digest(), &keys))
4242
)
4343
.unwrap()
4444
.write(&mut buffer)
4545
.unwrap();
4646
} else {
4747
unsigned_invoice
4848
.sign::<_, Infallible>(
49-
|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys))
49+
|message, _| Ok(secp_ctx.sign_schnorr_no_aux_rand(&message.digest(), &keys))
5050
)
5151
.unwrap_err();
5252
}

fuzz/src/offer_deser.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], _out: Out) {
3030
if let Ok(invoice_request) = build_response(&offer, pubkey) {
3131
invoice_request
3232
.sign::<_, Infallible>(
33-
|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys))
33+
|message, _| Ok(secp_ctx.sign_schnorr_no_aux_rand(&message.digest(), &keys))
3434
)
3535
.unwrap()
3636
.write(&mut buffer)

fuzz/src/refund_deser.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], _out: Out) {
3434
if let Ok(invoice) = build_response(&refund, pubkey, &secp_ctx) {
3535
invoice
3636
.sign::<_, Infallible>(
37-
|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys))
37+
|message, _| Ok(secp_ctx.sign_schnorr_no_aux_rand(&message.digest(), &keys))
3838
)
3939
.unwrap()
4040
.write(&mut buffer)

lightning/src/offers/invoice.rs

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
//! .allow_mpp()
5656
//! .fallback_v0_p2wpkh(&wpubkey_hash)
5757
//! .build()?
58-
//! .sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)))
58+
//! .sign::<_, Infallible>(|message, _| Ok(secp_ctx.sign_schnorr_no_aux_rand(&message.digest(), &keys)))
5959
//! .expect("failed verifying signature")
6060
//! .write(&mut buffer)
6161
//! .unwrap();
@@ -84,7 +84,7 @@
8484
//! .allow_mpp()
8585
//! .fallback_v0_p2wpkh(&wpubkey_hash)
8686
//! .build()?
87-
//! .sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)))
87+
//! .sign::<_, Infallible>(|message, _| Ok(secp_ctx.sign_schnorr_no_aux_rand(&message.digest(), &keys)))
8888
//! .expect("failed verifying signature")
8989
//! .write(&mut buffer)
9090
//! .unwrap();
@@ -97,7 +97,7 @@ use bitcoin::blockdata::constants::ChainHash;
9797
use bitcoin::hash_types::{WPubkeyHash, WScriptHash};
9898
use bitcoin::hashes::Hash;
9999
use bitcoin::network::constants::Network;
100-
use bitcoin::secp256k1::{KeyPair, Message, PublicKey, Secp256k1, self};
100+
use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, self};
101101
use bitcoin::secp256k1::schnorr::Signature;
102102
use bitcoin::util::address::{Address, Payload, WitnessVersion};
103103
use bitcoin::util::schnorr::TweakedPublicKey;
@@ -110,7 +110,7 @@ use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures};
110110
use crate::ln::inbound_payment::ExpandedKey;
111111
use crate::ln::msgs::DecodeError;
112112
use crate::offers::invoice_request::{INVOICE_REQUEST_PAYER_ID_TYPE, INVOICE_REQUEST_TYPES, IV_BYTES as INVOICE_REQUEST_IV_BYTES, InvoiceRequest, InvoiceRequestContents, InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef};
113-
use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, TlvStream, WithoutSignatures, self};
113+
use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, TaggedBytes, TlvStream, WithoutSignatures, self};
114114
use crate::offers::offer::{Amount, OFFER_TYPES, OfferTlvStream, OfferTlvStreamRef};
115115
use crate::offers::parse::{ParseError, ParsedMessage, SemanticError};
116116
use crate::offers::payer::{PAYER_METADATA_TYPE, PayerTlvStream, PayerTlvStreamRef};
@@ -358,7 +358,9 @@ impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> {
358358

359359
let keys = keys.unwrap();
360360
let invoice = unsigned_invoice
361-
.sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)))
361+
.sign::<_, Infallible>(
362+
|message, _| Ok(secp_ctx.sign_schnorr_no_aux_rand(&message.digest(), &keys))
363+
)
362364
.unwrap();
363365
Ok(invoice)
364366
}
@@ -381,7 +383,7 @@ impl<'a> UnsignedInvoice<'a> {
381383
/// This is not exported to bindings users as functions aren't currently mapped.
382384
pub fn sign<F, E>(self, sign: F) -> Result<Invoice, SignError<E>>
383385
where
384-
F: FnOnce(&Message) -> Result<Signature, E>
386+
F: FnOnce(&TaggedBytes, &[u8]) -> Result<Signature, E>
385387
{
386388
// Use the invoice_request bytes instead of the invoice_request TLV stream as the latter may
387389
// have contained unknown TLV records, which are not stored in `InvoiceRequestContents` or
@@ -393,8 +395,10 @@ impl<'a> UnsignedInvoice<'a> {
393395
let mut bytes = Vec::new();
394396
unsigned_tlv_stream.write(&mut bytes).unwrap();
395397

398+
let message = TaggedBytes::new(SIGNATURE_TAG, &bytes);
399+
let metadata = self.invoice.metadata();
396400
let pubkey = self.invoice.fields().signing_pubkey;
397-
let signature = merkle::sign_message(sign, SIGNATURE_TAG, &bytes, pubkey)?;
401+
let signature = merkle::sign_message(sign, message, metadata, pubkey)?;
398402

399403
// Append the signature TLV record to the bytes.
400404
let signature_tlv_stream = SignatureTlvStreamRef {
@@ -609,6 +613,13 @@ impl Invoice {
609613
}
610614

611615
impl InvoiceContents {
616+
fn metadata(&self) -> &[u8] {
617+
match self {
618+
InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.metadata(),
619+
InvoiceContents::ForRefund { refund, .. } => refund.metadata(),
620+
}
621+
}
622+
612623
/// Whether the original offer or refund has expired.
613624
#[cfg(feature = "std")]
614625
fn is_offer_or_refund_expired(&self) -> bool {
@@ -1458,7 +1469,7 @@ mod tests {
14581469
.sign(payer_sign).unwrap()
14591470
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
14601471
.build().unwrap()
1461-
.sign(|_| Err(()))
1472+
.sign(|_, _| Err(()))
14621473
{
14631474
Ok(_) => panic!("expected error"),
14641475
Err(e) => assert_eq!(e, SignError::Signing(())),

lightning/src/offers/invoice_request.rs

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
//! .quantity(5)?
4545
//! .payer_note("foo".to_string())
4646
//! .build()?
47-
//! .sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)))
47+
//! .sign::<_, Infallible>(|message, _| Ok(secp_ctx.sign_schnorr_no_aux_rand(&message.digest(), &keys)))
4848
//! .expect("failed verifying signature")
4949
//! .write(&mut buffer)
5050
//! .unwrap();
@@ -54,7 +54,7 @@
5454
5555
use bitcoin::blockdata::constants::ChainHash;
5656
use bitcoin::network::constants::Network;
57-
use bitcoin::secp256k1::{KeyPair, Message, PublicKey, Secp256k1, self};
57+
use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, self};
5858
use bitcoin::secp256k1::schnorr::Signature;
5959
use core::convert::{Infallible, TryFrom};
6060
use core::ops::Deref;
@@ -66,7 +66,7 @@ use crate::ln::features::InvoiceRequestFeatures;
6666
use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce};
6767
use crate::ln::msgs::DecodeError;
6868
use crate::offers::invoice::{BlindedPayInfo, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder};
69-
use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, self};
69+
use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, TaggedBytes, self};
7070
use crate::offers::offer::{Offer, OfferContents, OfferTlvStream, OfferTlvStreamRef};
7171
use crate::offers::parse::{ParseError, ParsedMessage, SemanticError};
7272
use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef};
@@ -306,7 +306,9 @@ impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, DerivedPayerId
306306
let secp_ctx = secp_ctx.unwrap();
307307
let keys = keys.unwrap();
308308
let invoice_request = unsigned_invoice_request
309-
.sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)))
309+
.sign::<_, Infallible>(
310+
|message, _| Ok(secp_ctx.sign_schnorr_no_aux_rand(&message.digest(), &keys))
311+
)
310312
.unwrap();
311313
Ok(invoice_request)
312314
}
@@ -352,7 +354,7 @@ impl<'a> UnsignedInvoiceRequest<'a> {
352354
/// This is not exported to bindings users as functions are not yet mapped.
353355
pub fn sign<F, E>(self, sign: F) -> Result<InvoiceRequest, SignError<E>>
354356
where
355-
F: FnOnce(&Message) -> Result<Signature, E>
357+
F: FnOnce(&TaggedBytes, &[u8]) -> Result<Signature, E>
356358
{
357359
// Use the offer bytes instead of the offer TLV stream as the offer may have contained
358360
// unknown TLV records, which are not stored in `OfferContents`.
@@ -364,8 +366,10 @@ impl<'a> UnsignedInvoiceRequest<'a> {
364366
let mut bytes = Vec::new();
365367
unsigned_tlv_stream.write(&mut bytes).unwrap();
366368

369+
let message = TaggedBytes::new(SIGNATURE_TAG, &bytes);
370+
let metadata = self.offer.metadata().map(|metadata| metadata.as_slice()).unwrap_or(&[]);
367371
let pubkey = self.invoice_request.payer_id;
368-
let signature = merkle::sign_message(sign, SIGNATURE_TAG, &bytes, pubkey)?;
372+
let signature = merkle::sign_message(sign, message, metadata, pubkey)?;
369373

370374
// Append the signature TLV record to the bytes.
371375
let signature_tlv_stream = SignatureTlvStreamRef {
@@ -591,7 +595,7 @@ impl InvoiceRequest {
591595
}
592596

593597
impl InvoiceRequestContents {
594-
pub fn metadata(&self) -> &[u8] {
598+
pub(super) fn metadata(&self) -> &[u8] {
595599
self.inner.metadata()
596600
}
597601

@@ -790,7 +794,7 @@ mod tests {
790794
use crate::ln::inbound_payment::ExpandedKey;
791795
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
792796
use crate::offers::invoice::{Invoice, SIGNATURE_TAG as INVOICE_SIGNATURE_TAG};
793-
use crate::offers::merkle::{SignError, SignatureTlvStreamRef, self};
797+
use crate::offers::merkle::{SignError, SignatureTlvStreamRef, TaggedBytes, self};
794798
use crate::offers::offer::{Amount, OfferBuilder, OfferTlvStreamRef, Quantity};
795799
use crate::offers::parse::{ParseError, SemanticError};
796800
use crate::offers::payer::PayerTlvStreamRef;
@@ -922,8 +926,9 @@ mod tests {
922926
let mut bytes = Vec::new();
923927
tlv_stream.write(&mut bytes).unwrap();
924928

929+
let message = TaggedBytes::new(INVOICE_SIGNATURE_TAG, &bytes);
925930
let signature = merkle::sign_message(
926-
recipient_sign, INVOICE_SIGNATURE_TAG, &bytes, recipient_pubkey()
931+
recipient_sign, message, invoice_request.metadata(), recipient_pubkey()
927932
).unwrap();
928933
signature_tlv_stream.signature = Some(&signature);
929934

@@ -946,8 +951,9 @@ mod tests {
946951
let mut bytes = Vec::new();
947952
tlv_stream.write(&mut bytes).unwrap();
948953

954+
let message = TaggedBytes::new(INVOICE_SIGNATURE_TAG, &bytes);
949955
let signature = merkle::sign_message(
950-
recipient_sign, INVOICE_SIGNATURE_TAG, &bytes, recipient_pubkey()
956+
recipient_sign, message, &metadata, recipient_pubkey()
951957
).unwrap();
952958
signature_tlv_stream.signature = Some(&signature);
953959

@@ -992,8 +998,9 @@ mod tests {
992998
let mut bytes = Vec::new();
993999
tlv_stream.write(&mut bytes).unwrap();
9941000

1001+
let message = TaggedBytes::new(INVOICE_SIGNATURE_TAG, &bytes);
9951002
let signature = merkle::sign_message(
996-
recipient_sign, INVOICE_SIGNATURE_TAG, &bytes, recipient_pubkey()
1003+
recipient_sign, message, invoice_request.metadata(), recipient_pubkey()
9971004
).unwrap();
9981005
signature_tlv_stream.signature = Some(&signature);
9991006

@@ -1016,8 +1023,9 @@ mod tests {
10161023
let mut bytes = Vec::new();
10171024
tlv_stream.write(&mut bytes).unwrap();
10181025

1026+
let message = TaggedBytes::new(INVOICE_SIGNATURE_TAG, &bytes);
10191027
let signature = merkle::sign_message(
1020-
recipient_sign, INVOICE_SIGNATURE_TAG, &bytes, recipient_pubkey()
1028+
recipient_sign, message, invoice_request.metadata(), recipient_pubkey()
10211029
).unwrap();
10221030
signature_tlv_stream.signature = Some(&signature);
10231031

@@ -1357,7 +1365,7 @@ mod tests {
13571365
.build().unwrap()
13581366
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
13591367
.build().unwrap()
1360-
.sign(|_| Err(()))
1368+
.sign(|_, _| Err(()))
13611369
{
13621370
Ok(_) => panic!("expected error"),
13631371
Err(e) => assert_eq!(e, SignError::Signing(())),
@@ -1771,7 +1779,9 @@ mod tests {
17711779
.build().unwrap()
17721780
.request_invoice(vec![1; 32], keys.public_key()).unwrap()
17731781
.build().unwrap()
1774-
.sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)))
1782+
.sign::<_, Infallible>(
1783+
|message, _| Ok(secp_ctx.sign_schnorr_no_aux_rand(&message.digest(), &keys))
1784+
)
17751785
.unwrap();
17761786

17771787
let mut encoded_invoice_request = Vec::new();

lightning/src/offers/merkle.rs

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use bitcoin::hashes::{Hash, HashEngine, sha256};
1313
use bitcoin::secp256k1::{Message, PublicKey, Secp256k1, self};
1414
use bitcoin::secp256k1::schnorr::Signature;
15+
use core::cell::RefCell;
1516
use crate::io;
1617
use crate::util::ser::{BigSize, Readable, Writeable, Writer};
1718

@@ -24,6 +25,25 @@ tlv_stream!(SignatureTlvStream, SignatureTlvStreamRef, SIGNATURE_TYPES, {
2425
(240, signature: Signature),
2526
});
2627

28+
/// Bytes associated with a tag, which are used to produced a [`Message`] digest to sign.
29+
pub struct TaggedBytes<'a> {
30+
tag: &'a str,
31+
bytes: &'a [u8],
32+
digest: RefCell<Option<Message>>,
33+
}
34+
35+
impl<'a> TaggedBytes<'a> {
36+
/// Creates tagged bytes with the given parameters.
37+
pub fn new(tag: &'a str, bytes: &'a [u8]) -> Self {
38+
Self { tag, bytes, digest: RefCell::new(None) }
39+
}
40+
41+
/// Returns the digest to sign.
42+
pub fn digest(&self) -> Message {
43+
*self.digest.borrow_mut().get_or_insert_with(|| message_digest(self.tag, self.bytes))
44+
}
45+
}
46+
2747
/// Error when signing messages.
2848
#[derive(Debug, PartialEq)]
2949
pub enum SignError<E> {
@@ -36,19 +56,22 @@ pub enum SignError<E> {
3656
/// Signs a message digest consisting of a tagged hash of the given bytes, checking if it can be
3757
/// verified with the supplied pubkey.
3858
///
39-
/// Panics if `bytes` is not a well-formed TLV stream containing at least one TLV record.
59+
/// `metadata` is either the payer or offer metadata, depending on the message type and origin, and
60+
/// may be used by `sign` to derive the signing keys.
61+
///
62+
/// Panics if `message` is not a well-formed TLV stream containing at least one TLV record.
4063
pub(super) fn sign_message<F, E>(
41-
sign: F, tag: &str, bytes: &[u8], pubkey: PublicKey,
64+
sign: F, message: TaggedBytes, metadata: &[u8], pubkey: PublicKey,
4265
) -> Result<Signature, SignError<E>>
4366
where
44-
F: FnOnce(&Message) -> Result<Signature, E>
67+
F: FnOnce(&TaggedBytes, &[u8]) -> Result<Signature, E>
4568
{
46-
let digest = message_digest(tag, bytes);
47-
let signature = sign(&digest).map_err(|e| SignError::Signing(e))?;
69+
let signature = sign(&message, metadata).map_err(|e| SignError::Signing(e))?;
4870

4971
let pubkey = pubkey.into();
5072
let secp_ctx = Secp256k1::verification_only();
51-
secp_ctx.verify_schnorr(&signature, &digest, &pubkey).map_err(|e| SignError::Verification(e))?;
73+
secp_ctx.verify_schnorr(&signature, &message.digest(), &pubkey)
74+
.map_err(|e| SignError::Verification(e))?;
5275

5376
Ok(signature)
5477
}
@@ -271,7 +294,9 @@ mod tests {
271294
.build_unchecked()
272295
.request_invoice(vec![0; 8], payer_keys.public_key()).unwrap()
273296
.build_unchecked()
274-
.sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &payer_keys)))
297+
.sign::<_, Infallible>(
298+
|message, _| Ok(secp_ctx.sign_schnorr_no_aux_rand(&message.digest(), &payer_keys))
299+
)
275300
.unwrap();
276301
assert_eq!(
277302
invoice_request.to_string(),
@@ -304,7 +329,9 @@ mod tests {
304329
.build_unchecked()
305330
.request_invoice(vec![0; 8], payer_keys.public_key()).unwrap()
306331
.build_unchecked()
307-
.sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &payer_keys)))
332+
.sign::<_, Infallible>(
333+
|message, _| Ok(secp_ctx.sign_schnorr_no_aux_rand(&message.digest(), &payer_keys))
334+
)
308335
.unwrap();
309336

310337
let mut bytes_without_signature = Vec::new();
@@ -334,7 +361,9 @@ mod tests {
334361
.build_unchecked()
335362
.request_invoice(vec![0; 8], payer_keys.public_key()).unwrap()
336363
.build_unchecked()
337-
.sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &payer_keys)))
364+
.sign::<_, Infallible>(
365+
|message, _| Ok(secp_ctx.sign_schnorr_no_aux_rand(&message.digest(), &payer_keys))
366+
)
338367
.unwrap();
339368

340369
let tlv_stream = TlvStream::new(&invoice_request.bytes).range(0..1)

0 commit comments

Comments
 (0)