Skip to content

Commit 226dbf9

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 b23eacf commit 226dbf9

File tree

8 files changed

+63
-27
lines changed

8 files changed

+63
-27
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+
|digest, _, _, _| Ok(secp_ctx.sign_schnorr_no_aux_rand(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+
|digest, _, _, _| Ok(secp_ctx.sign_schnorr_no_aux_rand(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+
|digest, _, _, _| Ok(secp_ctx.sign_schnorr_no_aux_rand(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+
|digest, _, _, _| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys))
3838
)
3939
.unwrap()
4040
.write(&mut buffer)

lightning/src/offers/invoice.rs

Lines changed: 13 additions & 5 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>(|digest, _, _, _| Ok(secp_ctx.sign_schnorr_no_aux_rand(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>(|digest, _, _, _| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)))
8888
//! .expect("failed verifying signature")
8989
//! .write(&mut buffer)
9090
//! .unwrap();
@@ -277,7 +277,7 @@ impl<'a> UnsignedInvoice<'a> {
277277
/// Signs the invoice using the given function.
278278
pub fn sign<F, E>(self, sign: F) -> Result<Invoice, SignError<E>>
279279
where
280-
F: FnOnce(&Message) -> Result<Signature, E>
280+
F: FnOnce(&Message, &str, &[u8], &[u8]) -> Result<Signature, E>
281281
{
282282
// Use the invoice_request bytes instead of the invoice_request TLV stream as the latter may
283283
// have contained unknown TLV records, which are not stored in `InvoiceRequestContents` or
@@ -289,8 +289,9 @@ impl<'a> UnsignedInvoice<'a> {
289289
let mut bytes = Vec::new();
290290
unsigned_tlv_stream.write(&mut bytes).unwrap();
291291

292+
let metadata = self.invoice.metadata();
292293
let pubkey = self.invoice.fields().signing_pubkey;
293-
let signature = merkle::sign_message(sign, SIGNATURE_TAG, &bytes, pubkey)?;
294+
let signature = merkle::sign_message(sign, SIGNATURE_TAG, &bytes, metadata, pubkey)?;
294295

295296
// Append the signature TLV record to the bytes.
296297
let signature_tlv_stream = SignatureTlvStreamRef {
@@ -489,6 +490,13 @@ impl Invoice {
489490
}
490491

491492
impl InvoiceContents {
493+
fn metadata(&self) -> &[u8] {
494+
match self {
495+
InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.metadata(),
496+
InvoiceContents::ForRefund { refund, .. } => refund.metadata(),
497+
}
498+
}
499+
492500
/// Whether the original offer or refund has expired.
493501
#[cfg(feature = "std")]
494502
fn is_offer_or_refund_expired(&self) -> bool {
@@ -1212,7 +1220,7 @@ mod tests {
12121220
.sign(payer_sign).unwrap()
12131221
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
12141222
.build().unwrap()
1215-
.sign(|_| Err(()))
1223+
.sign(|_, _, _, _| Err(()))
12161224
{
12171225
Ok(_) => panic!("expected error"),
12181226
Err(e) => assert_eq!(e, SignError::Signing(())),

lightning/src/offers/invoice_request.rs

Lines changed: 20 additions & 9 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>(|digest, _, _, _| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)))
4848
//! .expect("failed verifying signature")
4949
//! .write(&mut buffer)
5050
//! .unwrap();
@@ -268,7 +268,7 @@ impl<'a> UnsignedInvoiceRequest<'a> {
268268
/// Signs the invoice request using the given function.
269269
pub fn sign<F, E>(self, sign: F) -> Result<InvoiceRequest, SignError<E>>
270270
where
271-
F: FnOnce(&Message) -> Result<Signature, E>
271+
F: FnOnce(&Message, &str, &[u8], &[u8]) -> Result<Signature, E>
272272
{
273273
// Use the offer bytes instead of the offer TLV stream as the offer may have contained
274274
// unknown TLV records, which are not stored in `OfferContents`.
@@ -280,8 +280,9 @@ impl<'a> UnsignedInvoiceRequest<'a> {
280280
let mut bytes = Vec::new();
281281
unsigned_tlv_stream.write(&mut bytes).unwrap();
282282

283+
let metadata = self.offer.metadata().map(|metadata| metadata.as_slice()).unwrap_or(&[]);
283284
let pubkey = self.invoice_request.payer_id;
284-
let signature = merkle::sign_message(sign, SIGNATURE_TAG, &bytes, pubkey)?;
285+
let signature = merkle::sign_message(sign, SIGNATURE_TAG, &bytes, metadata, pubkey)?;
285286

286287
// Append the signature TLV record to the bytes.
287288
let signature_tlv_stream = SignatureTlvStreamRef {
@@ -332,7 +333,7 @@ impl InvoiceRequest {
332333
///
333334
/// [`payer_id`]: Self::payer_id
334335
pub fn metadata(&self) -> &[u8] {
335-
&self.contents.payer.0[..]
336+
self.contents.metadata()
336337
}
337338

338339
/// A chain from [`Offer::chains`] that the offer is valid for.
@@ -442,6 +443,10 @@ impl InvoiceRequest {
442443
}
443444

444445
impl InvoiceRequestContents {
446+
pub(super) fn metadata(&self) -> &[u8] {
447+
&self.payer.0
448+
}
449+
445450
pub(super) fn chain(&self) -> ChainHash {
446451
self.chain.unwrap_or_else(|| self.offer.implied_chain())
447452
}
@@ -755,7 +760,8 @@ mod tests {
755760
tlv_stream.write(&mut bytes).unwrap();
756761

757762
let signature = merkle::sign_message(
758-
recipient_sign, INVOICE_SIGNATURE_TAG, &bytes, recipient_pubkey()
763+
recipient_sign, INVOICE_SIGNATURE_TAG, &bytes, invoice_request.metadata(),
764+
recipient_pubkey()
759765
).unwrap();
760766
signature_tlv_stream.signature = Some(&signature);
761767

@@ -790,7 +796,9 @@ mod tests {
790796
.build().unwrap();
791797
let invoice_request = offer.request_invoice_deriving_payer_id(payer_pubkey).unwrap()
792798
.build().unwrap()
793-
.sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)))
799+
.sign::<_, Infallible>(
800+
|digest, _, _, _| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys))
801+
)
794802
.unwrap();
795803
assert_eq!(invoice_request.metadata()[..Nonce::LENGTH], nonce.0);
796804
assert_eq!(invoice_request.payer_id(), keys.public_key());
@@ -814,7 +822,8 @@ mod tests {
814822
tlv_stream.write(&mut bytes).unwrap();
815823

816824
let signature = merkle::sign_message(
817-
recipient_sign, INVOICE_SIGNATURE_TAG, &bytes, recipient_pubkey()
825+
recipient_sign, INVOICE_SIGNATURE_TAG, &bytes, invoice_request.metadata(),
826+
recipient_pubkey()
818827
).unwrap();
819828
signature_tlv_stream.signature = Some(&signature);
820829

@@ -1164,7 +1173,7 @@ mod tests {
11641173
.build().unwrap()
11651174
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
11661175
.build().unwrap()
1167-
.sign(|_| Err(()))
1176+
.sign(|_, _, _, _| Err(()))
11681177
{
11691178
Ok(_) => panic!("expected error"),
11701179
Err(e) => assert_eq!(e, SignError::Signing(())),
@@ -1578,7 +1587,9 @@ mod tests {
15781587
.build().unwrap()
15791588
.request_invoice(vec![1; 32], keys.public_key()).unwrap()
15801589
.build().unwrap()
1581-
.sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)))
1590+
.sign::<_, Infallible>(
1591+
|digest, _, _, _| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys))
1592+
)
15821593
.unwrap();
15831594

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

lightning/src/offers/merkle.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,18 @@ pub enum SignError<E> {
3636
/// Signs a message digest consisting of a tagged hash of the given bytes, checking if it can be
3737
/// verified with the supplied pubkey.
3838
///
39+
/// `metadata` is either the payer or offer metadata, depending on the message type and origin, and
40+
/// may be used by `sign` to derive the signing keys.
41+
///
3942
/// Panics if `bytes` is not a well-formed TLV stream containing at least one TLV record.
4043
pub(super) fn sign_message<F, E>(
41-
sign: F, tag: &str, bytes: &[u8], pubkey: PublicKey,
44+
sign: F, tag: &str, bytes: &[u8], metadata: &[u8], pubkey: PublicKey,
4245
) -> Result<Signature, SignError<E>>
4346
where
44-
F: FnOnce(&Message) -> Result<Signature, E>
47+
F: FnOnce(&Message, &str, &[u8], &[u8]) -> Result<Signature, E>
4548
{
4649
let digest = message_digest(tag, bytes);
47-
let signature = sign(&digest).map_err(|e| SignError::Signing(e))?;
50+
let signature = sign(&digest, tag, bytes, metadata).map_err(|e| SignError::Signing(e))?;
4851

4952
let pubkey = pubkey.into();
5053
let secp_ctx = Secp256k1::verification_only();
@@ -270,7 +273,9 @@ mod tests {
270273
.build_unchecked()
271274
.request_invoice(vec![0; 8], payer_keys.public_key()).unwrap()
272275
.build_unchecked()
273-
.sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &payer_keys)))
276+
.sign::<_, Infallible>(
277+
|digest, _, _, _| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &payer_keys))
278+
)
274279
.unwrap();
275280
assert_eq!(
276281
invoice_request.to_string(),
@@ -299,7 +304,9 @@ mod tests {
299304
.build_unchecked()
300305
.request_invoice(vec![0; 8], payer_keys.public_key()).unwrap()
301306
.build_unchecked()
302-
.sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &payer_keys)))
307+
.sign::<_, Infallible>(
308+
|digest, _, _, _| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &payer_keys))
309+
)
303310
.unwrap();
304311

305312
let mut bytes_without_signature = Vec::new();
@@ -329,7 +336,9 @@ mod tests {
329336
.build_unchecked()
330337
.request_invoice(vec![0; 8], payer_keys.public_key()).unwrap()
331338
.build_unchecked()
332-
.sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &payer_keys)))
339+
.sign::<_, Infallible>(
340+
|digest, _, _, _| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &payer_keys))
341+
)
333342
.unwrap();
334343

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

lightning/src/offers/refund.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ impl Refund {
340340
///
341341
/// [`payer_id`]: Self::payer_id
342342
pub fn metadata(&self) -> &[u8] {
343-
&self.contents.payer.0
343+
self.contents.metadata()
344344
}
345345

346346
/// A chain that the refund is valid for.
@@ -454,6 +454,10 @@ impl RefundContents {
454454
}
455455
}
456456

457+
pub(super) fn metadata(&self) -> &[u8] {
458+
&self.payer.0
459+
}
460+
457461
pub(super) fn chain(&self) -> ChainHash {
458462
self.chain.unwrap_or_else(|| self.implied_chain())
459463
}

lightning/src/offers/test_utils.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ pub(super) fn payer_keys() -> KeyPair {
2323
KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap())
2424
}
2525

26-
pub(super) fn payer_sign(digest: &Message) -> Result<Signature, Infallible> {
26+
pub(super) fn payer_sign(
27+
digest: &Message, tag: &str, bytes: &[u8], metadata: &[u8]
28+
) -> Result<Signature, Infallible> {
2729
let secp_ctx = Secp256k1::new();
2830
let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
2931
Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys))
@@ -38,7 +40,9 @@ pub(super) fn recipient_keys() -> KeyPair {
3840
KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[43; 32]).unwrap())
3941
}
4042

41-
pub(super) fn recipient_sign(digest: &Message) -> Result<Signature, Infallible> {
43+
pub(super) fn recipient_sign(
44+
digest: &Message, tag: &str, bytes: &[u8], metadata: &[u8]
45+
) -> Result<Signature, Infallible> {
4246
let secp_ctx = Secp256k1::new();
4347
let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[43; 32]).unwrap());
4448
Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys))

0 commit comments

Comments
 (0)