Skip to content

Commit

Permalink
Add InvoiceRequest::verify_using_nonce
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jkczyz committed Jun 20, 2024
1 parent 099c7d5 commit c17d677
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 9 deletions.
37 changes: 34 additions & 3 deletions lightning/src/offers/invoice_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -774,9 +774,11 @@ macro_rules! invoice_request_respond_with_explicit_signing_pubkey_methods { (
} }

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

/// Verifies that the request was for an offer created using the given key by checking a nonce
/// included with the [`BlindedPath`] for which the request was sent through.
///
/// Returns the verified request which contains the derived keys needed to sign a
/// [`Bolt12Invoice`] for the request if they could be extracted from the metadata.
///
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
pub fn verify_using_nonce<
#[cfg(not(c_bindings))]
T: secp256k1::Signing
>(
$self: $self_type, nonce: Nonce, key: &ExpandedKey,
#[cfg(not(c_bindings))]
secp_ctx: &Secp256k1<T>,
#[cfg(c_bindings)]
secp_ctx: &Secp256k1<secp256k1::All>,
) -> Result<VerifiedInvoiceRequest, ()> {
let (offer_id, keys) = $self.contents.inner.offer.verify_using_nonce(
&$self.bytes, nonce, key, secp_ctx
)?;
Ok(VerifiedInvoiceRequest {
offer_id,
#[cfg(not(c_bindings))]
inner: $self,
#[cfg(c_bindings)]
inner: $self.clone(),
keys,
})
}
} }

#[cfg(not(c_bindings))]
Expand Down
35 changes: 29 additions & 6 deletions lightning/src/offers/offer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -912,18 +912,28 @@ impl OfferContents {
self.signing_pubkey
}

/// Verifies that the offer metadata was produced from the offer in the TLV stream.
pub(super) fn verify<T: secp256k1::Signing>(
&self, bytes: &[u8], key: &ExpandedKey, secp_ctx: &Secp256k1<T>
) -> Result<(OfferId, Option<Keypair>), ()> {
match self.metadata() {
self.verify_using_metadata(bytes, self.metadata.as_ref(), key, secp_ctx)
}

pub(super) fn verify_using_nonce<T: secp256k1::Signing>(
&self, bytes: &[u8], nonce: Nonce, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
) -> Result<(OfferId, Option<Keypair>), ()> {
self.verify_using_metadata(bytes, Some(&Metadata::Nonce(nonce)), key, secp_ctx)
}

/// Verifies that the offer metadata was produced from the offer in the TLV stream.
fn verify_using_metadata<T: secp256k1::Signing>(
&self, bytes: &[u8], metadata: Option<&Metadata>, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
) -> Result<(OfferId, Option<Keypair>), ()> {
match metadata {
Some(metadata) => {
let tlv_stream = TlvStream::new(bytes).range(OFFER_TYPES).filter(|record| {
match record.r#type {
OFFER_METADATA_TYPE => false,
OFFER_NODE_ID_TYPE => {
!self.metadata.as_ref().unwrap().derives_recipient_keys()
},
OFFER_NODE_ID_TYPE => !metadata.derives_recipient_keys(),
_ => true,
}
});
Expand All @@ -932,7 +942,7 @@ impl OfferContents {
None => return Err(()),
};
let keys = signer::verify_recipient_metadata(
metadata, key, IV_BYTES, signing_pubkey, tlv_stream, secp_ctx
metadata.as_ref(), key, IV_BYTES, signing_pubkey, tlv_stream, secp_ctx
)?;

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

let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap();
assert!(invoice_request.verify_using_nonce(nonce, &expanded_key, &secp_ctx).is_err());

// Fails verification with altered offer field
let mut tlv_stream = offer.as_tlv_stream();
tlv_stream.amount = Some(100);
Expand Down Expand Up @@ -1356,6 +1371,14 @@ mod tests {
Err(_) => panic!("unexpected error"),
}

let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap();
match invoice_request.verify_using_nonce(nonce, &expanded_key, &secp_ctx) {
Ok(invoice_request) => assert_eq!(invoice_request.offer_id, offer.id()),
Err(_) => panic!("unexpected error"),
}

// Fails verification with altered offer field
let mut tlv_stream = offer.as_tlv_stream();
tlv_stream.amount = Some(100);
Expand Down
22 changes: 22 additions & 0 deletions lightning/src/offers/signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ pub(super) enum Metadata {
/// Metadata as parsed, supplied by the user, or derived from the message contents.
Bytes(Vec<u8>),

/// Metadata for deriving keys included as recipient data in a blinded path.
Nonce(Nonce),

/// Metadata to be derived from message contents and given material.
Derived(MetadataMaterial),

Expand All @@ -54,6 +57,7 @@ impl Metadata {
pub fn as_bytes(&self) -> Option<&Vec<u8>> {
match self {
Metadata::Bytes(bytes) => Some(bytes),
Metadata::Nonce(_) => None,
Metadata::Derived(_) => None,
Metadata::DerivedSigningPubkey(_) => None,
}
Expand All @@ -62,6 +66,7 @@ impl Metadata {
pub fn has_derivation_material(&self) -> bool {
match self {
Metadata::Bytes(_) => false,
Metadata::Nonce(_) => false,
Metadata::Derived(_) => true,
Metadata::DerivedSigningPubkey(_) => true,
}
Expand All @@ -75,6 +80,7 @@ impl Metadata {
// derived, as wouldn't be the case if a Metadata::Bytes with length PaymentId::LENGTH +
// Nonce::LENGTH had been set explicitly.
Metadata::Bytes(bytes) => bytes.len() == PaymentId::LENGTH + Nonce::LENGTH,
Metadata::Nonce(_) => false,
Metadata::Derived(_) => false,
Metadata::DerivedSigningPubkey(_) => true,
}
Expand All @@ -88,6 +94,7 @@ impl Metadata {
// derived, as wouldn't be the case if a Metadata::Bytes with length Nonce::LENGTH had
// been set explicitly.
Metadata::Bytes(bytes) => bytes.len() == Nonce::LENGTH,
Metadata::Nonce(_) => true,
Metadata::Derived(_) => false,
Metadata::DerivedSigningPubkey(_) => true,
}
Expand All @@ -96,6 +103,7 @@ impl Metadata {
pub fn without_keys(self) -> Self {
match self {
Metadata::Bytes(_) => self,
Metadata::Nonce(_) => self,
Metadata::Derived(_) => self,
Metadata::DerivedSigningPubkey(material) => Metadata::Derived(material),
}
Expand All @@ -106,6 +114,7 @@ impl Metadata {
) -> (Self, Option<Keypair>) {
match self {
Metadata::Bytes(_) => (self, None),
Metadata::Nonce(_) => (self, None),
Metadata::Derived(mut metadata_material) => {
tlv_stream.write(&mut metadata_material.hmac).unwrap();
(Metadata::Bytes(metadata_material.derive_metadata()), None)
Expand All @@ -126,10 +135,22 @@ impl Default for Metadata {
}
}

impl AsRef<[u8]> for Metadata {
fn as_ref(&self) -> &[u8] {
match self {
Metadata::Bytes(bytes) => &bytes,
Metadata::Nonce(nonce) => &nonce.0,
Metadata::Derived(_) => &[],
Metadata::DerivedSigningPubkey(_) => &[],
}
}
}

impl fmt::Debug for Metadata {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Metadata::Bytes(bytes) => bytes.fmt(f),
Metadata::Nonce(Nonce(bytes)) => bytes.fmt(f),
Metadata::Derived(_) => f.write_str("Derived"),
Metadata::DerivedSigningPubkey(_) => f.write_str("DerivedSigningPubkey"),
}
Expand All @@ -145,6 +166,7 @@ impl PartialEq for Metadata {
} else {
false
},
Metadata::Nonce(_) => false,
Metadata::Derived(_) => false,
Metadata::DerivedSigningPubkey(_) => false,
}
Expand Down

0 comments on commit c17d677

Please sign in to comment.