Skip to content

Commit c17d677

Browse files
committed
Add InvoiceRequest::verify_using_nonce
Invoice requests are authenticated by checking the metadata in the corresponding offer. For offers using blinded paths, this will simply be a 128-bit nonce. Allows checking this nonce explicitly instead of the metadata. This will be used by an upcoming change that includes the nonce in the offer's blinded paths instead of the metadata, which mitigate de-anonymization attacks.
1 parent 099c7d5 commit c17d677

File tree

3 files changed

+85
-9
lines changed

3 files changed

+85
-9
lines changed

lightning/src/offers/invoice_request.rs

+34-3
Original file line numberDiff line numberDiff line change
@@ -774,9 +774,11 @@ macro_rules! invoice_request_respond_with_explicit_signing_pubkey_methods { (
774774
} }
775775

776776
macro_rules! invoice_request_verify_method { ($self: ident, $self_type: ty) => {
777-
/// Verifies that the request was for an offer created using the given key. Returns the verified
778-
/// request which contains the derived keys needed to sign a [`Bolt12Invoice`] for the request
779-
/// if they could be extracted from the metadata.
777+
/// Verifies that the request was for an offer created using the given key by checking the
778+
/// metadata from the offer.
779+
///
780+
/// Returns the verified request which contains the derived keys needed to sign a
781+
/// [`Bolt12Invoice`] for the request if they could be extracted from the metadata.
780782
///
781783
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
782784
pub fn verify<
@@ -800,6 +802,35 @@ macro_rules! invoice_request_verify_method { ($self: ident, $self_type: ty) => {
800802
})
801803
}
802804

805+
/// Verifies that the request was for an offer created using the given key by checking a nonce
806+
/// included with the [`BlindedPath`] for which the request was sent through.
807+
///
808+
/// Returns the verified request which contains the derived keys needed to sign a
809+
/// [`Bolt12Invoice`] for the request if they could be extracted from the metadata.
810+
///
811+
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
812+
pub fn verify_using_nonce<
813+
#[cfg(not(c_bindings))]
814+
T: secp256k1::Signing
815+
>(
816+
$self: $self_type, nonce: Nonce, key: &ExpandedKey,
817+
#[cfg(not(c_bindings))]
818+
secp_ctx: &Secp256k1<T>,
819+
#[cfg(c_bindings)]
820+
secp_ctx: &Secp256k1<secp256k1::All>,
821+
) -> Result<VerifiedInvoiceRequest, ()> {
822+
let (offer_id, keys) = $self.contents.inner.offer.verify_using_nonce(
823+
&$self.bytes, nonce, key, secp_ctx
824+
)?;
825+
Ok(VerifiedInvoiceRequest {
826+
offer_id,
827+
#[cfg(not(c_bindings))]
828+
inner: $self,
829+
#[cfg(c_bindings)]
830+
inner: $self.clone(),
831+
keys,
832+
})
833+
}
803834
} }
804835

805836
#[cfg(not(c_bindings))]

lightning/src/offers/offer.rs

+29-6
Original file line numberDiff line numberDiff line change
@@ -912,18 +912,28 @@ impl OfferContents {
912912
self.signing_pubkey
913913
}
914914

915-
/// Verifies that the offer metadata was produced from the offer in the TLV stream.
916915
pub(super) fn verify<T: secp256k1::Signing>(
917916
&self, bytes: &[u8], key: &ExpandedKey, secp_ctx: &Secp256k1<T>
918917
) -> Result<(OfferId, Option<Keypair>), ()> {
919-
match self.metadata() {
918+
self.verify_using_metadata(bytes, self.metadata.as_ref(), key, secp_ctx)
919+
}
920+
921+
pub(super) fn verify_using_nonce<T: secp256k1::Signing>(
922+
&self, bytes: &[u8], nonce: Nonce, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
923+
) -> Result<(OfferId, Option<Keypair>), ()> {
924+
self.verify_using_metadata(bytes, Some(&Metadata::Nonce(nonce)), key, secp_ctx)
925+
}
926+
927+
/// Verifies that the offer metadata was produced from the offer in the TLV stream.
928+
fn verify_using_metadata<T: secp256k1::Signing>(
929+
&self, bytes: &[u8], metadata: Option<&Metadata>, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
930+
) -> Result<(OfferId, Option<Keypair>), ()> {
931+
match metadata {
920932
Some(metadata) => {
921933
let tlv_stream = TlvStream::new(bytes).range(OFFER_TYPES).filter(|record| {
922934
match record.r#type {
923935
OFFER_METADATA_TYPE => false,
924-
OFFER_NODE_ID_TYPE => {
925-
!self.metadata.as_ref().unwrap().derives_recipient_keys()
926-
},
936+
OFFER_NODE_ID_TYPE => !metadata.derives_recipient_keys(),
927937
_ => true,
928938
}
929939
});
@@ -932,7 +942,7 @@ impl OfferContents {
932942
None => return Err(()),
933943
};
934944
let keys = signer::verify_recipient_metadata(
935-
metadata, key, IV_BYTES, signing_pubkey, tlv_stream, secp_ctx
945+
metadata.as_ref(), key, IV_BYTES, signing_pubkey, tlv_stream, secp_ctx
936946
)?;
937947

938948
let offer_id = OfferId::from_valid_invreq_tlv_stream(bytes);
@@ -1295,6 +1305,11 @@ mod tests {
12951305
Err(_) => panic!("unexpected error"),
12961306
}
12971307

1308+
let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
1309+
.build().unwrap()
1310+
.sign(payer_sign).unwrap();
1311+
assert!(invoice_request.verify_using_nonce(nonce, &expanded_key, &secp_ctx).is_err());
1312+
12981313
// Fails verification with altered offer field
12991314
let mut tlv_stream = offer.as_tlv_stream();
13001315
tlv_stream.amount = Some(100);
@@ -1356,6 +1371,14 @@ mod tests {
13561371
Err(_) => panic!("unexpected error"),
13571372
}
13581373

1374+
let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
1375+
.build().unwrap()
1376+
.sign(payer_sign).unwrap();
1377+
match invoice_request.verify_using_nonce(nonce, &expanded_key, &secp_ctx) {
1378+
Ok(invoice_request) => assert_eq!(invoice_request.offer_id, offer.id()),
1379+
Err(_) => panic!("unexpected error"),
1380+
}
1381+
13591382
// Fails verification with altered offer field
13601383
let mut tlv_stream = offer.as_tlv_stream();
13611384
tlv_stream.amount = Some(100);

lightning/src/offers/signer.rs

+22
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ pub(super) enum Metadata {
4343
/// Metadata as parsed, supplied by the user, or derived from the message contents.
4444
Bytes(Vec<u8>),
4545

46+
/// Metadata for deriving keys included as recipient data in a blinded path.
47+
Nonce(Nonce),
48+
4649
/// Metadata to be derived from message contents and given material.
4750
Derived(MetadataMaterial),
4851

@@ -54,6 +57,7 @@ impl Metadata {
5457
pub fn as_bytes(&self) -> Option<&Vec<u8>> {
5558
match self {
5659
Metadata::Bytes(bytes) => Some(bytes),
60+
Metadata::Nonce(_) => None,
5761
Metadata::Derived(_) => None,
5862
Metadata::DerivedSigningPubkey(_) => None,
5963
}
@@ -62,6 +66,7 @@ impl Metadata {
6266
pub fn has_derivation_material(&self) -> bool {
6367
match self {
6468
Metadata::Bytes(_) => false,
69+
Metadata::Nonce(_) => false,
6570
Metadata::Derived(_) => true,
6671
Metadata::DerivedSigningPubkey(_) => true,
6772
}
@@ -75,6 +80,7 @@ impl Metadata {
7580
// derived, as wouldn't be the case if a Metadata::Bytes with length PaymentId::LENGTH +
7681
// Nonce::LENGTH had been set explicitly.
7782
Metadata::Bytes(bytes) => bytes.len() == PaymentId::LENGTH + Nonce::LENGTH,
83+
Metadata::Nonce(_) => false,
7884
Metadata::Derived(_) => false,
7985
Metadata::DerivedSigningPubkey(_) => true,
8086
}
@@ -88,6 +94,7 @@ impl Metadata {
8894
// derived, as wouldn't be the case if a Metadata::Bytes with length Nonce::LENGTH had
8995
// been set explicitly.
9096
Metadata::Bytes(bytes) => bytes.len() == Nonce::LENGTH,
97+
Metadata::Nonce(_) => true,
9198
Metadata::Derived(_) => false,
9299
Metadata::DerivedSigningPubkey(_) => true,
93100
}
@@ -96,6 +103,7 @@ impl Metadata {
96103
pub fn without_keys(self) -> Self {
97104
match self {
98105
Metadata::Bytes(_) => self,
106+
Metadata::Nonce(_) => self,
99107
Metadata::Derived(_) => self,
100108
Metadata::DerivedSigningPubkey(material) => Metadata::Derived(material),
101109
}
@@ -106,6 +114,7 @@ impl Metadata {
106114
) -> (Self, Option<Keypair>) {
107115
match self {
108116
Metadata::Bytes(_) => (self, None),
117+
Metadata::Nonce(_) => (self, None),
109118
Metadata::Derived(mut metadata_material) => {
110119
tlv_stream.write(&mut metadata_material.hmac).unwrap();
111120
(Metadata::Bytes(metadata_material.derive_metadata()), None)
@@ -126,10 +135,22 @@ impl Default for Metadata {
126135
}
127136
}
128137

138+
impl AsRef<[u8]> for Metadata {
139+
fn as_ref(&self) -> &[u8] {
140+
match self {
141+
Metadata::Bytes(bytes) => &bytes,
142+
Metadata::Nonce(nonce) => &nonce.0,
143+
Metadata::Derived(_) => &[],
144+
Metadata::DerivedSigningPubkey(_) => &[],
145+
}
146+
}
147+
}
148+
129149
impl fmt::Debug for Metadata {
130150
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131151
match self {
132152
Metadata::Bytes(bytes) => bytes.fmt(f),
153+
Metadata::Nonce(Nonce(bytes)) => bytes.fmt(f),
133154
Metadata::Derived(_) => f.write_str("Derived"),
134155
Metadata::DerivedSigningPubkey(_) => f.write_str("DerivedSigningPubkey"),
135156
}
@@ -145,6 +166,7 @@ impl PartialEq for Metadata {
145166
} else {
146167
false
147168
},
169+
Metadata::Nonce(_) => false,
148170
Metadata::Derived(_) => false,
149171
Metadata::DerivedSigningPubkey(_) => false,
150172
}

0 commit comments

Comments
 (0)