Skip to content

Commit 830a2e2

Browse files
committed
WIP: Offer stateless verification
1 parent 9c5f76a commit 830a2e2

File tree

3 files changed

+125
-8
lines changed

3 files changed

+125
-8
lines changed

lightning/src/ln/inbound_payment.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,15 @@ impl ExpandedKey {
6363
user_pmt_hash_key,
6464
}
6565
}
66+
67+
/// Returns an [`HmacEngine`] used to construct [`Offer::metadata`].
68+
///
69+
/// [`Offer::metadata`]: crate::offers::offer::Offer::metadata
70+
pub(crate) fn hmac_for_offer(&self) -> HmacEngine<Sha256> {
71+
let mut hmac = HmacEngine::<Sha256>::new(&self.ldk_pmt_hash_key);
72+
//hmac.input(&entropy_source.get_secure_random_bytes());
73+
hmac
74+
}
6675
}
6776

6877
enum Method {

lightning/src/offers/invoice_request.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,10 @@ use core::convert::TryFrom;
6060
use crate::io;
6161
use crate::ln::PaymentHash;
6262
use crate::ln::features::InvoiceRequestFeatures;
63+
use crate::ln::inbound_payment::ExpandedKey;
6364
use crate::ln::msgs::DecodeError;
6465
use crate::offers::invoice::{BlindedPayInfo, InvoiceBuilder};
65-
use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, self};
66+
use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, TlvStream, self};
6667
use crate::offers::offer::{Offer, OfferContents, OfferTlvStream, OfferTlvStreamRef};
6768
use crate::offers::parse::{ParseError, ParsedMessage, SemanticError};
6869
use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef};
@@ -322,6 +323,11 @@ impl InvoiceRequest {
322323
self.signature
323324
}
324325

326+
/// Verifies that the request was for an offer created using the given key.
327+
pub(crate) fn verify(&self, key: &ExpandedKey) -> bool {
328+
self.contents.offer.verify(TlvStream::new(&self.bytes), key)
329+
}
330+
325331
/// Creates an [`Invoice`] for the request with the given required fields.
326332
///
327333
/// Unless [`InvoiceBuilder::relative_expiry`] is set, the invoice will expire two hours after

lightning/src/offers/offer.rs

Lines changed: 109 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +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;
7073
use bitcoin::network::constants::Network;
7174
use bitcoin::secp256k1::PublicKey;
7275
use core::convert::TryFrom;
@@ -75,8 +78,10 @@ use core::str::FromStr;
7578
use core::time::Duration;
7679
use crate::io;
7780
use crate::ln::features::OfferFeatures;
81+
use crate::ln::inbound_payment::ExpandedKey;
7882
use crate::ln::msgs::MAX_VALUE_MSAT;
7983
use crate::offers::invoice_request::InvoiceRequestBuilder;
84+
use crate::offers::merkle::TlvStream;
8085
use crate::offers::parse::{Bech32Encode, ParseError, ParsedMessage, SemanticError};
8186
use crate::onion_message::BlindedPath;
8287
use crate::util::ser::{HighZeroBytesDroppedBigSize, WithoutLength, Writeable, Writer};
@@ -94,6 +99,7 @@ use std::time::SystemTime;
9499
/// [module-level documentation]: self
95100
pub struct OfferBuilder {
96101
offer: OfferContents,
102+
hmac: Option<HmacEngine<Sha256>>,
97103
}
98104

99105
impl OfferBuilder {
@@ -108,7 +114,7 @@ impl OfferBuilder {
108114
features: OfferFeatures::empty(), absolute_expiry: None, issuer: None, paths: None,
109115
supported_quantity: Quantity::One, signing_pubkey,
110116
};
111-
OfferBuilder { offer }
117+
OfferBuilder { offer, hmac: None }
112118
}
113119

114120
/// Adds the chain hash of the given [`Network`] to [`Offer::chains`]. If not called,
@@ -127,11 +133,24 @@ impl OfferBuilder {
127133
self
128134
}
129135

130-
/// Sets the [`Offer::metadata`].
136+
/// Sets the [`Offer::metadata`] to the given bytes.
131137
///
132-
/// Successive calls to this method will override the previous setting.
138+
/// Successive calls to this method will override the previous setting and any previous calls to
139+
/// [`OfferBuilder::metadata_derived`].
133140
pub fn metadata(mut self, metadata: Vec<u8>) -> Self {
134141
self.offer.metadata = Some(metadata);
142+
self.hmac = None;
143+
self
144+
}
145+
146+
/// Sets the [`Offer::metadata`] derived from the given `key` and any fields set prior to
147+
/// calling [`OfferBuilder::build`].
148+
///
149+
/// Successive calls to this method will override the previous setting and any previous calls to
150+
/// [`OfferBuilder::metadata`].
151+
pub fn metadata_derived(mut self, key: &ExpandedKey) -> Self {
152+
self.offer.metadata = None;
153+
self.hmac = Some(key.hmac_for_offer());
135154
self
136155
}
137156

@@ -204,6 +223,11 @@ impl OfferBuilder {
204223
}
205224
}
206225

226+
if let Some(mut hmac) = self.hmac {
227+
self.offer.write(&mut hmac).unwrap();
228+
self.offer.metadata = Some(Hmac::from_engine(hmac).into_inner().to_vec());
229+
}
230+
207231
let mut bytes = Vec::new();
208232
self.offer.write(&mut bytes).unwrap();
209233

@@ -482,6 +506,26 @@ impl OfferContents {
482506
self.signing_pubkey
483507
}
484508

509+
/// Verifies that the offer metadata was produced from the offer in the TLV stream.
510+
pub(super) fn verify(&self, tlv_stream: TlvStream<'_>, key: &ExpandedKey) -> bool {
511+
match &self.metadata {
512+
Some(metadata) => {
513+
let mut hmac = key.hmac_for_offer();
514+
515+
for record in tlv_stream.range(OFFER_TYPES) {
516+
if record.r#type != OFFER_METADATA_TYPE {
517+
hmac.input(record.record_bytes);
518+
} else {
519+
// TODO: Assert value bytes == metadata?
520+
}
521+
}
522+
523+
metadata == &Hmac::from_engine(hmac).into_inner()
524+
},
525+
None => false,
526+
}
527+
}
528+
485529
pub(super) fn as_tlv_stream(&self) -> OfferTlvStreamRef {
486530
let (currency, amount) = match &self.amount {
487531
None => (None, None),
@@ -565,9 +609,15 @@ impl Quantity {
565609
}
566610
}
567611

568-
tlv_stream!(OfferTlvStream, OfferTlvStreamRef, 1..80, {
612+
/// Valid type range for offer TLV records.
613+
const OFFER_TYPES: core::ops::Range<u64> = 1..80;
614+
615+
/// TLV record type for [`Offer::metadata`].
616+
const OFFER_METADATA_TYPE: u64 = 4;
617+
618+
tlv_stream!(OfferTlvStream, OfferTlvStreamRef, OFFER_TYPES, {
569619
(2, chains: (Vec<ChainHash>, WithoutLength)),
570-
(4, metadata: (Vec<u8>, WithoutLength)),
620+
(OFFER_METADATA_TYPE, metadata: (Vec<u8>, WithoutLength)),
571621
(6, currency: CurrencyCode),
572622
(8, amount: (u64, HighZeroBytesDroppedBigSize)),
573623
(10, description: (String, WithoutLength)),
@@ -661,17 +711,40 @@ mod tests {
661711

662712
use bitcoin::blockdata::constants::ChainHash;
663713
use bitcoin::network::constants::Network;
664-
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
665-
use core::convert::TryFrom;
714+
use bitcoin::secp256k1::{KeyPair, Message, PublicKey, Secp256k1, SecretKey};
715+
use bitcoin::secp256k1::schnorr::Signature;
716+
use core::convert::{Infallible, TryFrom};
666717
use core::num::NonZeroU64;
667718
use core::time::Duration;
719+
use crate::chain::keysinterface::KeyMaterial;
668720
use crate::ln::features::OfferFeatures;
721+
use crate::ln::inbound_payment::ExpandedKey;
669722
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
670723
use crate::offers::parse::{ParseError, SemanticError};
671724
use crate::onion_message::{BlindedHop, BlindedPath};
672725
use crate::util::ser::{BigSize, Writeable};
673726
use crate::util::string::PrintableString;
674727

728+
fn payer_keys() -> KeyPair {
729+
let secp_ctx = Secp256k1::new();
730+
KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap())
731+
}
732+
733+
fn payer_sign(digest: &Message) -> Result<Signature, Infallible> {
734+
let secp_ctx = Secp256k1::new();
735+
let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
736+
Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys))
737+
}
738+
739+
fn payer_pubkey() -> PublicKey {
740+
payer_keys().public_key()
741+
}
742+
743+
fn recipient_pubkey() -> PublicKey {
744+
let secp_ctx = Secp256k1::new();
745+
KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[43; 32]).unwrap()).public_key()
746+
}
747+
675748
fn pubkey(byte: u8) -> PublicKey {
676749
let secp_ctx = Secp256k1::new();
677750
PublicKey::from_secret_key(&secp_ctx, &privkey(byte))
@@ -784,6 +857,35 @@ mod tests {
784857
assert_eq!(offer.as_tlv_stream().metadata, Some(&vec![43; 32]));
785858
}
786859

860+
#[test]
861+
fn builds_offer_with_metadata_derived() {
862+
let keys = ExpandedKey::new(&KeyMaterial([42; 32]));
863+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
864+
.metadata_derived(&keys)
865+
.amount_msats(1000)
866+
.build().unwrap()
867+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
868+
.build().unwrap()
869+
.sign(payer_sign).unwrap();
870+
assert!(invoice_request.verify(&keys));
871+
872+
let offer = OfferBuilder::new("foo".into(), recipient_pubkey())
873+
.metadata_derived(&keys)
874+
.amount_msats(1000)
875+
.build().unwrap();
876+
let mut tlv_stream = offer.as_tlv_stream();
877+
tlv_stream.amount = Some(100);
878+
879+
let mut encoded_offer = Vec::new();
880+
tlv_stream.write(&mut encoded_offer).unwrap();
881+
882+
let invoice_request = Offer::try_from(encoded_offer).unwrap()
883+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
884+
.build().unwrap()
885+
.sign(payer_sign).unwrap();
886+
assert!(!invoice_request.verify(&keys));
887+
}
888+
787889
#[test]
788890
fn builds_offer_with_amount() {
789891
let bitcoin_amount = Amount::Bitcoin { amount_msats: 1000 };

0 commit comments

Comments
 (0)