Skip to content

Commit 2f6b469

Browse files
committed
f - abstraction for metadata material
1 parent f4be2b2 commit 2f6b469

File tree

2 files changed

+88
-38
lines changed

2 files changed

+88
-38
lines changed

lightning/src/offers/offer.rs

Lines changed: 15 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,9 @@
6767
//! ```
6868
6969
use bitcoin::blockdata::constants::ChainHash;
70-
use bitcoin::hashes::{Hash, HashEngine};
71-
use bitcoin::hashes::hmac::{Hmac, HmacEngine};
72-
use bitcoin::hashes::sha256::Hash as Sha256;
7370
use bitcoin::network::constants::Network;
7471
use bitcoin::secp256k1::PublicKey;
75-
use core::convert::{TryFrom, TryInto};
72+
use core::convert::TryFrom;
7673
use core::num::NonZeroU64;
7774
use core::str::FromStr;
7875
use core::time::Duration;
@@ -83,7 +80,7 @@ use crate::ln::msgs::MAX_VALUE_MSAT;
8380
use crate::offers::invoice_request::InvoiceRequestBuilder;
8481
use crate::offers::merkle::TlvStream;
8582
use crate::offers::parse::{Bech32Encode, ParseError, ParsedMessage, SemanticError};
86-
use crate::offers::signer::SigningPubkey;
83+
use crate::offers::signer::{MetadataMaterial, SigningPubkey, self};
8784
use crate::onion_message::BlindedPath;
8885
use crate::util::ser::{HighZeroBytesDroppedBigSize, WithoutLength, Writeable, Writer};
8986
use crate::util::string::PrintableString;
@@ -100,7 +97,7 @@ use std::time::SystemTime;
10097
/// [module-level documentation]: self
10198
pub struct OfferBuilder {
10299
offer: OfferContents,
103-
metadata_material: Option<(Nonce, HmacEngine<Sha256>)>,
100+
metadata_material: Option<MetadataMaterial>,
104101
}
105102

106103
impl OfferBuilder {
@@ -110,14 +107,7 @@ impl OfferBuilder {
110107
///
111108
/// Use a different pubkey per offer to avoid correlating offers.
112109
pub fn new(description: String, signing_pubkey: SigningPubkey) -> Self {
113-
let (metadata_material, signing_pubkey) = match signing_pubkey {
114-
SigningPubkey::Explicit(pubkey) => (None, pubkey),
115-
SigningPubkey::Derived(expanded_key, nonce) => {
116-
let metadata_material = (nonce, expanded_key.hmac_for_offer(nonce));
117-
let signing_pubkey = expanded_key.signing_pubkey_for_offer(nonce);
118-
(Some(metadata_material), signing_pubkey)
119-
},
120-
};
110+
let (metadata_material, signing_pubkey) = signing_pubkey.into_parts();
121111
let offer = OfferContents {
122112
chains: None, metadata: None, amount: None, description,
123113
features: OfferFeatures::empty(), absolute_expiry: None, issuer: None, paths: None,
@@ -167,7 +157,7 @@ impl OfferBuilder {
167157
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
168158
pub fn metadata_derived(mut self, key: &ExpandedKey, nonce: Nonce) -> Self {
169159
self.offer.metadata = None;
170-
self.metadata_material = Some((nonce, key.hmac_for_offer(nonce)));
160+
self.metadata_material = Some(MetadataMaterial::new(nonce, key));
171161
self
172162
}
173163

@@ -240,18 +230,14 @@ impl OfferBuilder {
240230
}
241231
}
242232

243-
// Create the metadata for stateless verification. It consists of a 16-byte nonce and a
244-
// 32-byte HMAC of the offer bytes excluding the signing pubkey.
245-
if let Some((nonce, mut hmac)) = self.metadata_material {
233+
// Create the metadata for stateless verification of an InvoiceRequest.
234+
if let Some(mut metadata_material) = self.metadata_material {
246235
debug_assert!(self.offer.metadata.is_none());
247236
let mut tlv_stream = self.offer.as_tlv_stream();
248237
tlv_stream.node_id = None;
249-
tlv_stream.write(&mut hmac).unwrap();
250-
251-
let mut metadata = nonce.as_slice().to_vec();
252-
metadata.extend_from_slice(&Hmac::from_engine(hmac).into_inner());
238+
tlv_stream.write(&mut metadata_material).unwrap();
253239

254-
self.offer.metadata = Some(metadata);
240+
self.offer.metadata = Some(metadata_material.into_metadata());
255241
}
256242

257243
let mut bytes = Vec::new();
@@ -536,23 +522,15 @@ impl OfferContents {
536522
pub(super) fn verify(&self, tlv_stream: TlvStream<'_>, key: &ExpandedKey) -> bool {
537523
match &self.metadata {
538524
Some(metadata) => {
539-
let mut hmac = if metadata.len() < Nonce::LENGTH {
540-
return false;
541-
} else {
542-
let nonce = Nonce(metadata[..Nonce::LENGTH].try_into().unwrap());
543-
key.hmac_for_offer(nonce)
544-
};
545-
546-
for record in tlv_stream.range(OFFER_TYPES) {
525+
let tlv_stream = tlv_stream.range(OFFER_TYPES).filter(|record| {
547526
match record.r#type {
548527
// TODO: Assert value bytes == metadata?
549-
OFFER_METADATA_TYPE => {},
550-
OFFER_NODE_ID_TYPE => {},
551-
_ => hmac.input(record.record_bytes),
528+
OFFER_METADATA_TYPE => false,
529+
OFFER_NODE_ID_TYPE => false,
530+
_ => true,
552531
}
553-
}
554-
555-
&metadata[Nonce::LENGTH..] == &Hmac::from_engine(hmac).into_inner()
532+
});
533+
signer::verify_metadata(metadata, key, tlv_stream)
556534
},
557535
None => false,
558536
}

lightning/src/offers/signer.rs

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,16 @@
77
// You may not use this file except in accordance with one or both of these
88
// licenses.
99

10-
//! Data structures for signing offer messages.
10+
//! Utilities for signing offer messages and verifying metadata.
1111
12+
use bitcoin::hashes::{Hash, HashEngine};
13+
use bitcoin::hashes::hmac::{Hmac, HmacEngine};
14+
use bitcoin::hashes::sha256::Hash as Sha256;
15+
use core::convert::TryInto;
1216
use bitcoin::secp256k1::PublicKey;
17+
use crate::io;
1318
use crate::ln::inbound_payment::{ExpandedKey, Nonce};
19+
use crate::offers::merkle::TlvRecord;
1420

1521
/// A pubkey for signing messages, either given explicitly or derived.
1622
pub enum SigningPubkey<'a> {
@@ -21,8 +27,74 @@ pub enum SigningPubkey<'a> {
2127
Derived(&'a ExpandedKey, Nonce),
2228
}
2329

30+
impl<'a> SigningPubkey<'a> {
31+
pub(super) fn into_parts(self) -> (Option<MetadataMaterial>, PublicKey) {
32+
match self {
33+
SigningPubkey::Explicit(pubkey) => (None, pubkey),
34+
SigningPubkey::Derived(expanded_key, nonce) => {
35+
let metadata_material = MetadataMaterial::new(nonce, expanded_key);
36+
let signing_pubkey = expanded_key.signing_pubkey_for_offer(nonce);
37+
(Some(metadata_material), signing_pubkey)
38+
},
39+
}
40+
}
41+
}
42+
2443
impl<'a> From<PublicKey> for SigningPubkey<'a> {
2544
fn from(pubkey: PublicKey) -> Self {
2645
SigningPubkey::Explicit(pubkey)
2746
}
2847
}
48+
49+
/// Material used to create metadata for a message. Once initialized, write the applicable data from
50+
/// the message into it and call [`MetadataMaterial::into_metadata`].
51+
pub(super) struct MetadataMaterial {
52+
nonce: Nonce,
53+
hmac: HmacEngine<Sha256>,
54+
}
55+
56+
impl MetadataMaterial {
57+
pub fn new(nonce: Nonce, expanded_key: &ExpandedKey) -> Self {
58+
Self {
59+
nonce,
60+
hmac: expanded_key.hmac_for_offer(nonce),
61+
}
62+
}
63+
64+
pub fn into_metadata(self) -> Vec<u8> {
65+
let mut bytes = self.nonce.as_slice().to_vec();
66+
bytes.extend_from_slice(&Hmac::from_engine(self.hmac).into_inner());
67+
bytes
68+
}
69+
}
70+
71+
impl io::Write for MetadataMaterial {
72+
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
73+
self.hmac.write(buf)
74+
}
75+
76+
fn flush(&mut self) -> io::Result<()> {
77+
self.hmac.flush()
78+
}
79+
}
80+
81+
/// Verifies data given in a TLV stream was used to produce the given metadata, consisting of:
82+
/// - a 128-bit [`Nonce`] and
83+
/// - a [`Sha256Hash`] of the nonce and the TLV records using the [`ExpandedKey`].
84+
pub(super) fn verify_metadata<'a>(
85+
metadata: &Vec<u8>, expanded_key: &ExpandedKey,
86+
tlv_stream: impl core::iter::Iterator<Item = TlvRecord<'a>>
87+
) -> bool {
88+
let mut hmac = if metadata.len() < Nonce::LENGTH {
89+
return false;
90+
} else {
91+
let nonce = Nonce(metadata[..Nonce::LENGTH].try_into().unwrap());
92+
expanded_key.hmac_for_offer(nonce)
93+
};
94+
95+
for record in tlv_stream {
96+
hmac.input(record.record_bytes);
97+
}
98+
99+
&metadata[Nonce::LENGTH..] == &Hmac::from_engine(hmac).into_inner()
100+
}

0 commit comments

Comments
 (0)