Skip to content

Commit 4f0dae4

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 b2dc34d commit 4f0dae4

File tree

7 files changed

+60
-29
lines changed

7 files changed

+60
-29
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: 16 additions & 6 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();
@@ -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+
|digest, _, _, _| Ok(secp_ctx.sign_schnorr_no_aux_rand(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(&Message, &str, &[u8], &[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,9 @@ impl<'a> UnsignedInvoice<'a> {
393395
let mut bytes = Vec::new();
394396
unsigned_tlv_stream.write(&mut bytes).unwrap();
395397

398+
let metadata = self.invoice.metadata();
396399
let pubkey = self.invoice.fields().signing_pubkey;
397-
let signature = merkle::sign_message(sign, SIGNATURE_TAG, &bytes, pubkey)?;
400+
let signature = merkle::sign_message(sign, SIGNATURE_TAG, &bytes, metadata, pubkey)?;
398401

399402
// Append the signature TLV record to the bytes.
400403
let signature_tlv_stream = SignatureTlvStreamRef {
@@ -609,6 +612,13 @@ impl Invoice {
609612
}
610613

611614
impl InvoiceContents {
615+
fn metadata(&self) -> &[u8] {
616+
match self {
617+
InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.metadata(),
618+
InvoiceContents::ForRefund { refund, .. } => refund.metadata(),
619+
}
620+
}
621+
612622
/// Whether the original offer or refund has expired.
613623
#[cfg(feature = "std")]
614624
fn is_offer_or_refund_expired(&self) -> bool {
@@ -1458,7 +1468,7 @@ mod tests {
14581468
.sign(payer_sign).unwrap()
14591469
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
14601470
.build().unwrap()
1461-
.sign(|_| Err(()))
1471+
.sign(|_, _, _, _| Err(()))
14621472
{
14631473
Ok(_) => panic!("expected error"),
14641474
Err(e) => assert_eq!(e, SignError::Signing(())),

lightning/src/offers/invoice_request.rs

Lines changed: 19 additions & 11 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();
@@ -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+
|digest, _, _, _| Ok(secp_ctx.sign_schnorr_no_aux_rand(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(&Message, &str, &[u8], &[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,9 @@ impl<'a> UnsignedInvoiceRequest<'a> {
364366
let mut bytes = Vec::new();
365367
unsigned_tlv_stream.write(&mut bytes).unwrap();
366368

369+
let metadata = self.offer.metadata().map(|metadata| metadata.as_slice()).unwrap_or(&[]);
367370
let pubkey = self.invoice_request.payer_id;
368-
let signature = merkle::sign_message(sign, SIGNATURE_TAG, &bytes, pubkey)?;
371+
let signature = merkle::sign_message(sign, SIGNATURE_TAG, &bytes, metadata, pubkey)?;
369372

370373
// Append the signature TLV record to the bytes.
371374
let signature_tlv_stream = SignatureTlvStreamRef {
@@ -591,7 +594,7 @@ impl InvoiceRequest {
591594
}
592595

593596
impl InvoiceRequestContents {
594-
pub fn metadata(&self) -> &[u8] {
597+
pub(super) fn metadata(&self) -> &[u8] {
595598
self.inner.metadata()
596599
}
597600

@@ -923,7 +926,8 @@ mod tests {
923926
tlv_stream.write(&mut bytes).unwrap();
924927

925928
let signature = merkle::sign_message(
926-
recipient_sign, INVOICE_SIGNATURE_TAG, &bytes, recipient_pubkey()
929+
recipient_sign, INVOICE_SIGNATURE_TAG, &bytes, invoice_request.metadata(),
930+
recipient_pubkey()
927931
).unwrap();
928932
signature_tlv_stream.signature = Some(&signature);
929933

@@ -947,7 +951,7 @@ mod tests {
947951
tlv_stream.write(&mut bytes).unwrap();
948952

949953
let signature = merkle::sign_message(
950-
recipient_sign, INVOICE_SIGNATURE_TAG, &bytes, recipient_pubkey()
954+
recipient_sign, INVOICE_SIGNATURE_TAG, &bytes, &metadata, recipient_pubkey()
951955
).unwrap();
952956
signature_tlv_stream.signature = Some(&signature);
953957

@@ -993,7 +997,8 @@ mod tests {
993997
tlv_stream.write(&mut bytes).unwrap();
994998

995999
let signature = merkle::sign_message(
996-
recipient_sign, INVOICE_SIGNATURE_TAG, &bytes, recipient_pubkey()
1000+
recipient_sign, INVOICE_SIGNATURE_TAG, &bytes, invoice_request.metadata(),
1001+
recipient_pubkey()
9971002
).unwrap();
9981003
signature_tlv_stream.signature = Some(&signature);
9991004

@@ -1017,7 +1022,8 @@ mod tests {
10171022
tlv_stream.write(&mut bytes).unwrap();
10181023

10191024
let signature = merkle::sign_message(
1020-
recipient_sign, INVOICE_SIGNATURE_TAG, &bytes, recipient_pubkey()
1025+
recipient_sign, INVOICE_SIGNATURE_TAG, &bytes, invoice_request.metadata(),
1026+
recipient_pubkey()
10211027
).unwrap();
10221028
signature_tlv_stream.signature = Some(&signature);
10231029

@@ -1357,7 +1363,7 @@ mod tests {
13571363
.build().unwrap()
13581364
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
13591365
.build().unwrap()
1360-
.sign(|_| Err(()))
1366+
.sign(|_, _, _, _| Err(()))
13611367
{
13621368
Ok(_) => panic!("expected error"),
13631369
Err(e) => assert_eq!(e, SignError::Signing(())),
@@ -1771,7 +1777,9 @@ mod tests {
17711777
.build().unwrap()
17721778
.request_invoice(vec![1; 32], keys.public_key()).unwrap()
17731779
.build().unwrap()
1774-
.sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)))
1780+
.sign::<_, Infallible>(
1781+
|digest, _, _, _| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys))
1782+
)
17751783
.unwrap();
17761784

17771785
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();
@@ -271,7 +274,9 @@ mod tests {
271274
.build_unchecked()
272275
.request_invoice(vec![0; 8], payer_keys.public_key()).unwrap()
273276
.build_unchecked()
274-
.sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &payer_keys)))
277+
.sign::<_, Infallible>(
278+
|digest, _, _, _| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &payer_keys))
279+
)
275280
.unwrap();
276281
assert_eq!(
277282
invoice_request.to_string(),
@@ -304,7 +309,9 @@ mod tests {
304309
.build_unchecked()
305310
.request_invoice(vec![0; 8], payer_keys.public_key()).unwrap()
306311
.build_unchecked()
307-
.sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &payer_keys)))
312+
.sign::<_, Infallible>(
313+
|digest, _, _, _| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &payer_keys))
314+
)
308315
.unwrap();
309316

310317
let mut bytes_without_signature = Vec::new();
@@ -334,7 +341,9 @@ mod tests {
334341
.build_unchecked()
335342
.request_invoice(vec![0; 8], payer_keys.public_key()).unwrap()
336343
.build_unchecked()
337-
.sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &payer_keys)))
344+
.sign::<_, Infallible>(
345+
|digest, _, _, _| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &payer_keys))
346+
)
338347
.unwrap();
339348

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

lightning/src/offers/test_utils.rs

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

27-
pub(super) fn payer_sign(digest: &Message) -> Result<Signature, Infallible> {
27+
pub(super) fn payer_sign(
28+
digest: &Message, _tag: &str, _bytes: &[u8], _metadata: &[u8]
29+
) -> Result<Signature, Infallible> {
2830
let secp_ctx = Secp256k1::new();
2931
let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
3032
Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys))
@@ -39,7 +41,9 @@ pub(super) fn recipient_keys() -> KeyPair {
3941
KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[43; 32]).unwrap())
4042
}
4143

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

0 commit comments

Comments
 (0)