Skip to content

Commit

Permalink
Merge pull request lightningdevkit#2468 from jkczyz/2023-08-offer-pay…
Browse files Browse the repository at this point in the history
…ment-id

Offer outbound payments
  • Loading branch information
TheBlueMatt authored Aug 29, 2023
2 parents 1f2ee21 + 7a3e06b commit 073f078
Show file tree
Hide file tree
Showing 10 changed files with 412 additions and 121 deletions.
7 changes: 6 additions & 1 deletion lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,12 @@ impl From<&ClaimableHTLC> for events::ClaimedHTLC {
///
/// This is not exported to bindings users as we just use [u8; 32] directly
#[derive(Hash, Copy, Clone, PartialEq, Eq, Debug)]
pub struct PaymentId(pub [u8; 32]);
pub struct PaymentId(pub [u8; Self::LENGTH]);

impl PaymentId {
/// Number of bytes in the id.
pub const LENGTH: usize = 32;
}

impl Writeable for PaymentId {
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
Expand Down
37 changes: 24 additions & 13 deletions lightning/src/ln/inbound_payment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use crate::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
use crate::ln::msgs;
use crate::ln::msgs::MAX_VALUE_MSAT;
use crate::util::chacha20::ChaCha20;
use crate::util::crypto::hkdf_extract_expand_4x;
use crate::util::crypto::hkdf_extract_expand_5x;
use crate::util::errors::APIError;
use crate::util::logger::Logger;

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

impl ExpandedKey {
/// Create a new [`ExpandedKey`] for generating an inbound payment hash and secret.
///
/// It is recommended to cache this value and not regenerate it for each new inbound payment.
pub fn new(key_material: &KeyMaterial) -> ExpandedKey {
let (metadata_key, ldk_pmt_hash_key, user_pmt_hash_key, offers_base_key) =
hkdf_extract_expand_4x(b"LDK Inbound Payment Key Expansion", &key_material.0);
let (
metadata_key,
ldk_pmt_hash_key,
user_pmt_hash_key,
offers_base_key,
offers_encryption_key,
) = hkdf_extract_expand_5x(b"LDK Inbound Payment Key Expansion", &key_material.0);
Self {
metadata_key,
ldk_pmt_hash_key,
user_pmt_hash_key,
offers_base_key,
offers_encryption_key,
}
}

/// Returns an [`HmacEngine`] used to construct [`Offer::metadata`].
///
/// [`Offer::metadata`]: crate::offers::offer::Offer::metadata
#[allow(unused)]
pub(crate) fn hmac_for_offer(
&self, nonce: Nonce, iv_bytes: &[u8; IV_LEN]
) -> HmacEngine<Sha256> {
Expand All @@ -79,6 +86,13 @@ impl ExpandedKey {
hmac.input(&nonce.0);
hmac
}

/// Encrypts or decrypts the given `bytes`. Used for data included in an offer message's
/// metadata (e.g., payment id).
pub(crate) fn crypt_for_offer(&self, mut bytes: [u8; 32], nonce: Nonce) -> [u8; 32] {
ChaCha20::encrypt_single_block_in_place(&self.offers_encryption_key, &nonce.0, &mut bytes);
bytes
}
}

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

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

let chacha_block = ChaCha20::get_single_block(metadata_key, iv_bytes);
for i in 0..METADATA_LEN {
encrypted_metadata_slice[i] = chacha_block[i] ^ metadata_bytes[i];
}
ChaCha20::encrypt_single_block(
metadata_key, iv_bytes, encrypted_metadata_slice, metadata_bytes
);
PaymentSecret(payment_secret_bytes)
}

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

let chacha_block = ChaCha20::get_single_block(&keys.metadata_key, &iv_bytes);
let mut metadata_bytes: [u8; METADATA_LEN] = [0; METADATA_LEN];
for i in 0..METADATA_LEN {
metadata_bytes[i] = chacha_block[i] ^ encrypted_metadata_bytes[i];
}
ChaCha20::encrypt_single_block(
&keys.metadata_key, &iv_bytes, &mut metadata_bytes, encrypted_metadata_bytes
);

(iv_bytes, metadata_bytes)
}
Expand Down
36 changes: 15 additions & 21 deletions lightning/src/offers/invoice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ use core::time::Duration;
use crate::io;
use crate::blinded_path::BlindedPath;
use crate::ln::PaymentHash;
use crate::ln::channelmanager::PaymentId;
use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures, InvoiceRequestFeatures, OfferFeatures};
use crate::ln::inbound_payment::ExpandedKey;
use crate::ln::msgs::DecodeError;
Expand Down Expand Up @@ -695,10 +696,11 @@ impl Bolt12Invoice {
merkle::message_digest(SIGNATURE_TAG, &self.bytes).as_ref().clone()
}

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

Expand Down Expand Up @@ -947,7 +949,7 @@ impl InvoiceContents {

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

match signer::verify_metadata(metadata, key, iv_bytes, payer_id, tlv_stream, secp_ctx) {
Ok(_) => true,
Err(()) => false,
}
signer::verify_payer_metadata(metadata, key, iv_bytes, payer_id, tlv_stream, secp_ctx)
}

fn derives_keys(&self) -> bool {
Expand Down Expand Up @@ -1642,36 +1641,31 @@ mod tests {
.build().unwrap()
.sign(payer_sign).unwrap();

if let Err(e) = invoice_request
.verify_and_respond_using_derived_keys_no_std(
payment_paths(), payment_hash(), now(), &expanded_key, &secp_ctx
)
.unwrap()
if let Err(e) = invoice_request.clone()
.verify(&expanded_key, &secp_ctx).unwrap()
.respond_using_derived_keys_no_std(payment_paths(), payment_hash(), now()).unwrap()
.build_and_sign(&secp_ctx)
{
panic!("error building invoice: {:?}", e);
}

let expanded_key = ExpandedKey::new(&KeyMaterial([41; 32]));
match invoice_request.verify_and_respond_using_derived_keys_no_std(
payment_paths(), payment_hash(), now(), &expanded_key, &secp_ctx
) {
Ok(_) => panic!("expected error"),
Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidMetadata),
}
assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_err());

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

match invoice_request.verify_and_respond_using_derived_keys_no_std(
payment_paths(), payment_hash(), now(), &expanded_key, &secp_ctx
) {
match invoice_request
.verify(&expanded_key, &secp_ctx).unwrap()
.respond_using_derived_keys_no_std(payment_paths(), payment_hash(), now())
{
Ok(_) => panic!("expected error"),
Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidMetadata),
}
Expand Down
Loading

0 comments on commit 073f078

Please sign in to comment.