Skip to content

Commit 073f078

Browse files
authored
Merge pull request #2468 from jkczyz/2023-08-offer-payment-id
Offer outbound payments
2 parents 1f2ee21 + 7a3e06b commit 073f078

File tree

10 files changed

+412
-121
lines changed

10 files changed

+412
-121
lines changed

lightning/src/ln/channelmanager.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,12 @@ impl From<&ClaimableHTLC> for events::ClaimedHTLC {
237237
///
238238
/// This is not exported to bindings users as we just use [u8; 32] directly
239239
#[derive(Hash, Copy, Clone, PartialEq, Eq, Debug)]
240-
pub struct PaymentId(pub [u8; 32]);
240+
pub struct PaymentId(pub [u8; Self::LENGTH]);
241+
242+
impl PaymentId {
243+
/// Number of bytes in the id.
244+
pub const LENGTH: usize = 32;
245+
}
241246

242247
impl Writeable for PaymentId {
243248
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {

lightning/src/ln/inbound_payment.rs

+24-13
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use crate::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
1919
use crate::ln::msgs;
2020
use crate::ln::msgs::MAX_VALUE_MSAT;
2121
use crate::util::chacha20::ChaCha20;
22-
use crate::util::crypto::hkdf_extract_expand_4x;
22+
use crate::util::crypto::hkdf_extract_expand_5x;
2323
use crate::util::errors::APIError;
2424
use crate::util::logger::Logger;
2525

@@ -50,27 +50,34 @@ pub struct ExpandedKey {
5050
user_pmt_hash_key: [u8; 32],
5151
/// The base key used to derive signing keys and authenticate messages for BOLT 12 Offers.
5252
offers_base_key: [u8; 32],
53+
/// The key used to encrypt message metadata for BOLT 12 Offers.
54+
offers_encryption_key: [u8; 32],
5355
}
5456

5557
impl ExpandedKey {
5658
/// Create a new [`ExpandedKey`] for generating an inbound payment hash and secret.
5759
///
5860
/// It is recommended to cache this value and not regenerate it for each new inbound payment.
5961
pub fn new(key_material: &KeyMaterial) -> ExpandedKey {
60-
let (metadata_key, ldk_pmt_hash_key, user_pmt_hash_key, offers_base_key) =
61-
hkdf_extract_expand_4x(b"LDK Inbound Payment Key Expansion", &key_material.0);
62+
let (
63+
metadata_key,
64+
ldk_pmt_hash_key,
65+
user_pmt_hash_key,
66+
offers_base_key,
67+
offers_encryption_key,
68+
) = hkdf_extract_expand_5x(b"LDK Inbound Payment Key Expansion", &key_material.0);
6269
Self {
6370
metadata_key,
6471
ldk_pmt_hash_key,
6572
user_pmt_hash_key,
6673
offers_base_key,
74+
offers_encryption_key,
6775
}
6876
}
6977

7078
/// Returns an [`HmacEngine`] used to construct [`Offer::metadata`].
7179
///
7280
/// [`Offer::metadata`]: crate::offers::offer::Offer::metadata
73-
#[allow(unused)]
7481
pub(crate) fn hmac_for_offer(
7582
&self, nonce: Nonce, iv_bytes: &[u8; IV_LEN]
7683
) -> HmacEngine<Sha256> {
@@ -79,6 +86,13 @@ impl ExpandedKey {
7986
hmac.input(&nonce.0);
8087
hmac
8188
}
89+
90+
/// Encrypts or decrypts the given `bytes`. Used for data included in an offer message's
91+
/// metadata (e.g., payment id).
92+
pub(crate) fn crypt_for_offer(&self, mut bytes: [u8; 32], nonce: Nonce) -> [u8; 32] {
93+
ChaCha20::encrypt_single_block_in_place(&self.offers_encryption_key, &nonce.0, &mut bytes);
94+
bytes
95+
}
8296
}
8397

8498
/// A 128-bit number used only once.
@@ -88,7 +102,6 @@ impl ExpandedKey {
88102
///
89103
/// [`Offer::metadata`]: crate::offers::offer::Offer::metadata
90104
/// [`Offer::signing_pubkey`]: crate::offers::offer::Offer::signing_pubkey
91-
#[allow(unused)]
92105
#[derive(Clone, Copy, Debug, PartialEq)]
93106
pub(crate) struct Nonce(pub(crate) [u8; Self::LENGTH]);
94107

@@ -271,10 +284,9 @@ fn construct_payment_secret(iv_bytes: &[u8; IV_LEN], metadata_bytes: &[u8; METAD
271284
let (iv_slice, encrypted_metadata_slice) = payment_secret_bytes.split_at_mut(IV_LEN);
272285
iv_slice.copy_from_slice(iv_bytes);
273286

274-
let chacha_block = ChaCha20::get_single_block(metadata_key, iv_bytes);
275-
for i in 0..METADATA_LEN {
276-
encrypted_metadata_slice[i] = chacha_block[i] ^ metadata_bytes[i];
277-
}
287+
ChaCha20::encrypt_single_block(
288+
metadata_key, iv_bytes, encrypted_metadata_slice, metadata_bytes
289+
);
278290
PaymentSecret(payment_secret_bytes)
279291
}
280292

@@ -406,11 +418,10 @@ fn decrypt_metadata(payment_secret: PaymentSecret, keys: &ExpandedKey) -> ([u8;
406418
let (iv_slice, encrypted_metadata_bytes) = payment_secret.0.split_at(IV_LEN);
407419
iv_bytes.copy_from_slice(iv_slice);
408420

409-
let chacha_block = ChaCha20::get_single_block(&keys.metadata_key, &iv_bytes);
410421
let mut metadata_bytes: [u8; METADATA_LEN] = [0; METADATA_LEN];
411-
for i in 0..METADATA_LEN {
412-
metadata_bytes[i] = chacha_block[i] ^ encrypted_metadata_bytes[i];
413-
}
422+
ChaCha20::encrypt_single_block(
423+
&keys.metadata_key, &iv_bytes, &mut metadata_bytes, encrypted_metadata_bytes
424+
);
414425

415426
(iv_bytes, metadata_bytes)
416427
}

lightning/src/offers/invoice.rs

+15-21
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ use core::time::Duration;
110110
use crate::io;
111111
use crate::blinded_path::BlindedPath;
112112
use crate::ln::PaymentHash;
113+
use crate::ln::channelmanager::PaymentId;
113114
use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures, InvoiceRequestFeatures, OfferFeatures};
114115
use crate::ln::inbound_payment::ExpandedKey;
115116
use crate::ln::msgs::DecodeError;
@@ -695,10 +696,11 @@ impl Bolt12Invoice {
695696
merkle::message_digest(SIGNATURE_TAG, &self.bytes).as_ref().clone()
696697
}
697698

698-
/// Verifies that the invoice was for a request or refund created using the given key.
699+
/// Verifies that the invoice was for a request or refund created using the given key. Returns
700+
/// the associated [`PaymentId`] to use when sending the payment.
699701
pub fn verify<T: secp256k1::Signing>(
700702
&self, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
701-
) -> bool {
703+
) -> Result<PaymentId, ()> {
702704
self.contents.verify(TlvStream::new(&self.bytes), key, secp_ctx)
703705
}
704706

@@ -947,7 +949,7 @@ impl InvoiceContents {
947949

948950
fn verify<T: secp256k1::Signing>(
949951
&self, tlv_stream: TlvStream<'_>, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
950-
) -> bool {
952+
) -> Result<PaymentId, ()> {
951953
let offer_records = tlv_stream.clone().range(OFFER_TYPES);
952954
let invreq_records = tlv_stream.range(INVOICE_REQUEST_TYPES).filter(|record| {
953955
match record.r#type {
@@ -967,10 +969,7 @@ impl InvoiceContents {
967969
},
968970
};
969971

970-
match signer::verify_metadata(metadata, key, iv_bytes, payer_id, tlv_stream, secp_ctx) {
971-
Ok(_) => true,
972-
Err(()) => false,
973-
}
972+
signer::verify_payer_metadata(metadata, key, iv_bytes, payer_id, tlv_stream, secp_ctx)
974973
}
975974

976975
fn derives_keys(&self) -> bool {
@@ -1642,36 +1641,31 @@ mod tests {
16421641
.build().unwrap()
16431642
.sign(payer_sign).unwrap();
16441643

1645-
if let Err(e) = invoice_request
1646-
.verify_and_respond_using_derived_keys_no_std(
1647-
payment_paths(), payment_hash(), now(), &expanded_key, &secp_ctx
1648-
)
1649-
.unwrap()
1644+
if let Err(e) = invoice_request.clone()
1645+
.verify(&expanded_key, &secp_ctx).unwrap()
1646+
.respond_using_derived_keys_no_std(payment_paths(), payment_hash(), now()).unwrap()
16501647
.build_and_sign(&secp_ctx)
16511648
{
16521649
panic!("error building invoice: {:?}", e);
16531650
}
16541651

16551652
let expanded_key = ExpandedKey::new(&KeyMaterial([41; 32]));
1656-
match invoice_request.verify_and_respond_using_derived_keys_no_std(
1657-
payment_paths(), payment_hash(), now(), &expanded_key, &secp_ctx
1658-
) {
1659-
Ok(_) => panic!("expected error"),
1660-
Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidMetadata),
1661-
}
1653+
assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_err());
16621654

16631655
let desc = "foo".to_string();
16641656
let offer = OfferBuilder
16651657
::deriving_signing_pubkey(desc, node_id, &expanded_key, &entropy, &secp_ctx)
16661658
.amount_msats(1000)
1659+
// Omit the path so that node_id is used for the signing pubkey instead of deriving
16671660
.build().unwrap();
16681661
let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
16691662
.build().unwrap()
16701663
.sign(payer_sign).unwrap();
16711664

1672-
match invoice_request.verify_and_respond_using_derived_keys_no_std(
1673-
payment_paths(), payment_hash(), now(), &expanded_key, &secp_ctx
1674-
) {
1665+
match invoice_request
1666+
.verify(&expanded_key, &secp_ctx).unwrap()
1667+
.respond_using_derived_keys_no_std(payment_paths(), payment_hash(), now())
1668+
{
16751669
Ok(_) => panic!("expected error"),
16761670
Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidMetadata),
16771671
}

0 commit comments

Comments
 (0)