Skip to content

Commit 360643c

Browse files
committed
WIP: Offer stateless verification
1 parent 11a722e commit 360643c

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),
@@ -568,9 +612,15 @@ impl Quantity {
568612
}
569613
}
570614

571-
tlv_stream!(OfferTlvStream, OfferTlvStreamRef, 1..80, {
615+
/// Valid type range for offer TLV records.
616+
const OFFER_TYPES: core::ops::Range<u64> = 1..80;
617+
618+
/// TLV record type for [`Offer::metadata`].
619+
const OFFER_METADATA_TYPE: u64 = 4;
620+
621+
tlv_stream!(OfferTlvStream, OfferTlvStreamRef, OFFER_TYPES, {
572622
(2, chains: (Vec<ChainHash>, WithoutLength)),
573-
(4, metadata: (Vec<u8>, WithoutLength)),
623+
(OFFER_METADATA_TYPE, metadata: (Vec<u8>, WithoutLength)),
574624
(6, currency: CurrencyCode),
575625
(8, amount: (u64, HighZeroBytesDroppedBigSize)),
576626
(10, description: (String, WithoutLength)),
@@ -664,17 +714,40 @@ mod tests {
664714

665715
use bitcoin::blockdata::constants::ChainHash;
666716
use bitcoin::network::constants::Network;
667-
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
668-
use core::convert::TryFrom;
717+
use bitcoin::secp256k1::{KeyPair, Message, PublicKey, Secp256k1, SecretKey};
718+
use bitcoin::secp256k1::schnorr::Signature;
719+
use core::convert::{Infallible, TryFrom};
669720
use core::num::NonZeroU64;
670721
use core::time::Duration;
722+
use crate::chain::keysinterface::KeyMaterial;
671723
use crate::ln::features::OfferFeatures;
724+
use crate::ln::inbound_payment::ExpandedKey;
672725
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
673726
use crate::offers::parse::{ParseError, SemanticError};
674727
use crate::onion_message::{BlindedHop, BlindedPath};
675728
use crate::util::ser::{BigSize, Writeable};
676729
use crate::util::string::PrintableString;
677730

731+
fn payer_keys() -> KeyPair {
732+
let secp_ctx = Secp256k1::new();
733+
KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap())
734+
}
735+
736+
fn payer_sign(digest: &Message) -> Result<Signature, Infallible> {
737+
let secp_ctx = Secp256k1::new();
738+
let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
739+
Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys))
740+
}
741+
742+
fn payer_pubkey() -> PublicKey {
743+
payer_keys().public_key()
744+
}
745+
746+
fn recipient_pubkey() -> PublicKey {
747+
let secp_ctx = Secp256k1::new();
748+
KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[43; 32]).unwrap()).public_key()
749+
}
750+
678751
fn pubkey(byte: u8) -> PublicKey {
679752
let secp_ctx = Secp256k1::new();
680753
PublicKey::from_secret_key(&secp_ctx, &privkey(byte))
@@ -787,6 +860,35 @@ mod tests {
787860
assert_eq!(offer.as_tlv_stream().metadata, Some(&vec![43; 32]));
788861
}
789862

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

0 commit comments

Comments
 (0)