Skip to content

Commit a83cf94

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 40f9e86 commit a83cf94

File tree

3 files changed

+85
-9
lines changed

3 files changed

+85
-9
lines changed

lightning/src/offers/invoice_request.rs

Lines changed: 34 additions & 3 deletions
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

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -913,18 +913,28 @@ impl OfferContents {
913913
self.signing_pubkey
914914
}
915915

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

939949
let offer_id = OfferId::from_valid_invreq_tlv_stream(bytes);
@@ -1296,6 +1306,11 @@ mod tests {
12961306
Err(_) => panic!("unexpected error"),
12971307
}
12981308

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

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

lightning/src/offers/signer.rs

Lines changed: 22 additions & 0 deletions
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)