Skip to content

Commit 431e3ea

Browse files
committed
f - derive signing pubkey from nonce
1 parent 7f81e67 commit 431e3ea

File tree

2 files changed

+104
-26
lines changed

2 files changed

+104
-26
lines changed

lightning/src/ln/inbound_payment.rs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ use bitcoin::hashes::{Hash, HashEngine};
1414
use bitcoin::hashes::cmp::fixed_time_eq;
1515
use bitcoin::hashes::hmac::{Hmac, HmacEngine};
1616
use bitcoin::hashes::sha256::Hash as Sha256;
17+
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
18+
use bitcoin::secp256k1::scalar::Scalar;
1719
use crate::chain::keysinterface::{KeyMaterial, EntropySource};
1820
use crate::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
1921
use crate::ln::msgs;
@@ -67,11 +69,45 @@ impl ExpandedKey {
6769
/// Returns an [`HmacEngine`] used to construct [`Offer::metadata`].
6870
///
6971
/// [`Offer::metadata`]: crate::offers::offer::Offer::metadata
70-
pub(crate) fn hmac_for_offer(&self) -> HmacEngine<Sha256> {
72+
pub(crate) fn hmac_for_offer(&self, nonce: Nonce) -> HmacEngine<Sha256> {
7173
let mut hmac = HmacEngine::<Sha256>::new(&self.ldk_pmt_hash_key);
72-
//hmac.input(&entropy_source.get_secure_random_bytes());
74+
hmac.input(&nonce.0);
7375
hmac
7476
}
77+
78+
pub(crate) fn signing_pubkey_for_offer(&self, nonce: Nonce) -> PublicKey {
79+
let secp_ctx = Secp256k1::new();
80+
let mut tweak = [0; 32];
81+
tweak[..Nonce::LENGTH].copy_from_slice(&nonce.0);
82+
83+
SecretKey::from_slice(&self.ldk_pmt_hash_key)
84+
.unwrap()
85+
.mul_tweak(&Scalar::from_be_bytes(tweak).unwrap())
86+
.unwrap()
87+
.public_key(&secp_ctx)
88+
}
89+
}
90+
91+
///
92+
#[derive(Clone, Copy)]
93+
pub struct Nonce(pub(crate) [u8; Self::LENGTH]);
94+
95+
impl Nonce {
96+
///
97+
pub const LENGTH: usize = 16;
98+
99+
///
100+
pub fn from_entropy_source<ES: EntropySource>(entropy_source: &ES) -> Self {
101+
let mut bytes = [0 as u8; Self::LENGTH];
102+
let rand_bytes = entropy_source.get_secure_random_bytes();
103+
bytes.copy_from_slice(&rand_bytes[..Self::LENGTH]);
104+
Nonce(bytes)
105+
}
106+
107+
///
108+
pub fn as_slice(&self) -> &[u8] {
109+
&self.0
110+
}
75111
}
76112

77113
enum Method {

lightning/src/offers/offer.rs

Lines changed: 66 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,13 @@ use bitcoin::hashes::hmac::{Hmac, HmacEngine};
7272
use bitcoin::hashes::sha256::Hash as Sha256;
7373
use bitcoin::network::constants::Network;
7474
use bitcoin::secp256k1::PublicKey;
75-
use core::convert::TryFrom;
75+
use core::convert::{TryFrom, TryInto};
7676
use core::num::NonZeroU64;
7777
use core::str::FromStr;
7878
use core::time::Duration;
7979
use crate::io;
8080
use crate::ln::features::OfferFeatures;
81-
use crate::ln::inbound_payment::ExpandedKey;
81+
use crate::ln::inbound_payment::{ExpandedKey, Nonce};
8282
use crate::ln::msgs::MAX_VALUE_MSAT;
8383
use crate::offers::invoice_request::InvoiceRequestBuilder;
8484
use crate::offers::merkle::TlvStream;
@@ -99,16 +99,16 @@ use std::time::SystemTime;
9999
/// [module-level documentation]: self
100100
pub struct OfferBuilder {
101101
offer: OfferContents,
102-
hmac: Option<HmacEngine<Sha256>>,
102+
metadata_material: Option<(Nonce, HmacEngine<Sha256>)>,
103103
}
104104

105105
///
106-
pub enum SigningPubkey {
106+
pub enum SigningPubkey<'a> {
107107
Explicit(PublicKey),
108-
Derived,
108+
Derived(&'a ExpandedKey, Nonce),
109109
}
110110

111-
impl From<PublicKey> for SigningPubkey {
111+
impl<'a> From<PublicKey> for SigningPubkey<'a> {
112112
fn from(pubkey: PublicKey) -> Self {
113113
SigningPubkey::Explicit(pubkey)
114114
}
@@ -121,16 +121,20 @@ impl OfferBuilder {
121121
///
122122
/// Use a different pubkey per offer to avoid correlating offers.
123123
pub fn new(description: String, signing_pubkey: SigningPubkey) -> Self {
124-
let signing_pubkey = match signing_pubkey {
125-
SigningPubkey::Explicit(pubkey) => Some(pubkey),
126-
SigningPubkey::Derived => None,
124+
let (metadata_material, signing_pubkey) = match signing_pubkey {
125+
SigningPubkey::Explicit(pubkey) => (None, Some(pubkey)),
126+
SigningPubkey::Derived(expanded_key, nonce) => {
127+
let metadata_material = (nonce, expanded_key.hmac_for_offer(nonce));
128+
let signing_pubkey = expanded_key.signing_pubkey_for_offer(nonce);
129+
(Some(metadata_material), Some(signing_pubkey))
130+
},
127131
};
128132
let offer = OfferContents {
129133
chains: None, metadata: None, amount: None, description,
130134
features: OfferFeatures::empty(), absolute_expiry: None, issuer: None, paths: None,
131135
supported_quantity: Quantity::One, signing_pubkey,
132136
};
133-
OfferBuilder { offer, hmac: None }
137+
OfferBuilder { offer, metadata_material }
134138
}
135139

136140
/// Adds the chain hash of the given [`Network`] to [`Offer::chains`]. If not called,
@@ -159,21 +163,21 @@ impl OfferBuilder {
159163
}
160164

161165
self.offer.metadata = Some(metadata);
162-
self.hmac = None;
166+
self.metadata_material = None;
163167
Ok(self)
164168
}
165169

166170
/// Sets the [`Offer::metadata`] derived from the given `key` and any fields set prior to
167171
/// calling [`OfferBuilder::build`]. Allows for stateless verification of an [`InvoiceRequest`].
168172
///
169173
/// Successive calls to this method will override the previous setting and any previous calls to
170-
/// [`OfferBuilder::metadata`]. Must be called if the builder was constructed with
171-
/// [`SigningPubkey::Derived`] in order to derive [`Offer::signing_pubkey`].
174+
/// [`OfferBuilder::metadata`]. Does not need to be called if the builder was constructed with
175+
/// [`SigningPubkey::Derived`].
172176
///
173177
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
174-
pub fn metadata_derived(mut self, key: &ExpandedKey) -> Self {
178+
pub fn metadata_derived(mut self, key: &ExpandedKey, nonce: Nonce) -> Self {
175179
self.offer.metadata = None;
176-
self.hmac = Some(key.hmac_for_offer());
180+
self.metadata_material = Some((nonce, key.hmac_for_offer(nonce)));
177181
self
178182
}
179183

@@ -246,14 +250,17 @@ impl OfferBuilder {
246250
}
247251
}
248252

249-
// Created the metadata for stateless verification and derive the signing_pubkey, if needed.
250-
if let Some(mut hmac) = self.hmac {
253+
// Created the metadata for stateless verification.
254+
if let Some((nonce, mut hmac)) = self.metadata_material {
251255
debug_assert!(self.offer.metadata.is_none());
252256
let mut tlv_stream = self.offer.as_tlv_stream();
253257
tlv_stream.node_id = None;
254258
tlv_stream.write(&mut hmac).unwrap();
255259

256-
self.offer.metadata = Some(Hmac::from_engine(hmac).into_inner().to_vec());
260+
let mut metadata = nonce.as_slice().to_vec();
261+
metadata.extend_from_slice(&Hmac::from_engine(hmac).into_inner());
262+
263+
self.offer.metadata = Some(metadata);
257264
}
258265

259266
if self.offer.signing_pubkey.is_none() {
@@ -542,7 +549,12 @@ impl OfferContents {
542549
pub(super) fn verify(&self, tlv_stream: TlvStream<'_>, key: &ExpandedKey) -> bool {
543550
match &self.metadata {
544551
Some(metadata) => {
545-
let mut hmac = key.hmac_for_offer();
552+
let mut hmac = if metadata.len() < Nonce::LENGTH {
553+
return false;
554+
} else {
555+
let nonce = Nonce(metadata[..Nonce::LENGTH].try_into().unwrap());
556+
key.hmac_for_offer(nonce)
557+
};
546558

547559
for record in tlv_stream.range(OFFER_TYPES) {
548560
match record.r#type {
@@ -553,7 +565,7 @@ impl OfferContents {
553565
}
554566
}
555567

556-
metadata == &Hmac::from_engine(hmac).into_inner()
568+
&metadata[Nonce::LENGTH..] == &Hmac::from_engine(hmac).into_inner()
557569
},
558570
None => false,
559571
}
@@ -746,7 +758,7 @@ impl core::fmt::Display for Offer {
746758

747759
#[cfg(test)]
748760
mod tests {
749-
use super::{Amount, Offer, OfferBuilder, OfferTlvStreamRef, Quantity};
761+
use super::{Amount, Offer, OfferBuilder, OfferTlvStreamRef, Quantity, SigningPubkey};
750762

751763
use bitcoin::blockdata::constants::ChainHash;
752764
use bitcoin::network::constants::Network;
@@ -757,7 +769,7 @@ mod tests {
757769
use core::time::Duration;
758770
use crate::chain::keysinterface::KeyMaterial;
759771
use crate::ln::features::OfferFeatures;
760-
use crate::ln::inbound_payment::ExpandedKey;
772+
use crate::ln::inbound_payment::{ExpandedKey, Nonce};
761773
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
762774
use crate::offers::parse::{ParseError, SemanticError};
763775
use crate::onion_message::{BlindedHop, BlindedPath};
@@ -900,7 +912,7 @@ mod tests {
900912
fn builds_offer_with_metadata_derived() {
901913
let keys = ExpandedKey::new(&KeyMaterial([42; 32]));
902914
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey().into())
903-
.metadata_derived(&keys)
915+
.metadata_derived(&keys, Nonce([42; Nonce::LENGTH]))
904916
.amount_msats(1000)
905917
.build().unwrap()
906918
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
@@ -909,7 +921,7 @@ mod tests {
909921
assert!(invoice_request.verify(&keys));
910922

911923
let offer = OfferBuilder::new("foo".into(), recipient_pubkey().into())
912-
.metadata_derived(&keys)
924+
.metadata_derived(&keys, Nonce([42; Nonce::LENGTH]))
913925
.amount_msats(1000)
914926
.build().unwrap();
915927
let mut tlv_stream = offer.as_tlv_stream();
@@ -925,6 +937,36 @@ mod tests {
925937
assert!(!invoice_request.verify(&keys));
926938
}
927939

940+
#[test]
941+
fn builds_offer_with_signing_pubkey_derived() {
942+
let keys = ExpandedKey::new(&KeyMaterial([42; 32]));
943+
let nonce = Nonce([42; Nonce::LENGTH]);
944+
945+
let recipient_pubkey = SigningPubkey::Derived(&keys, nonce);
946+
let offer = OfferBuilder::new("foo".into(), recipient_pubkey)
947+
.amount_msats(1000)
948+
.build().unwrap();
949+
assert_eq!(offer.metadata().unwrap()[..Nonce::LENGTH], nonce.0);
950+
assert_eq!(offer.signing_pubkey(), keys.signing_pubkey_for_offer(nonce));
951+
952+
let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
953+
.build().unwrap()
954+
.sign(payer_sign).unwrap();
955+
assert!(invoice_request.verify(&keys));
956+
957+
let mut tlv_stream = offer.as_tlv_stream();
958+
tlv_stream.amount = Some(100);
959+
960+
let mut encoded_offer = Vec::new();
961+
tlv_stream.write(&mut encoded_offer).unwrap();
962+
963+
let invoice_request = Offer::try_from(encoded_offer).unwrap()
964+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
965+
.build().unwrap()
966+
.sign(payer_sign).unwrap();
967+
assert!(!invoice_request.verify(&keys));
968+
}
969+
928970
#[test]
929971
fn builds_offer_with_amount() {
930972
let bitcoin_amount = Amount::Bitcoin { amount_msats: 1000 };

0 commit comments

Comments
 (0)