///
/// 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> {
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;
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 {
///
/// 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> {
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.
///
/// [`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]);
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)
}
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)
}
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;
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)
}
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 {
},
};
- 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 {
.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),
}
use crate::io;
use crate::blinded_path::BlindedPath;
use crate::ln::PaymentHash;
+use crate::ln::channelmanager::PaymentId;
use crate::ln::features::InvoiceRequestFeatures;
use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce};
use crate::ln::msgs::DecodeError;
}
pub(super) fn deriving_metadata<ES: Deref>(
- offer: &'a Offer, payer_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES
+ offer: &'a Offer, payer_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES,
+ payment_id: PaymentId,
) -> Self where ES::Target: EntropySource {
let nonce = Nonce::from_entropy_source(entropy_source);
- let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES);
+ let payment_id = Some(payment_id);
+ let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES, payment_id);
let metadata = Metadata::Derived(derivation_material);
Self {
offer,
impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T> {
pub(super) fn deriving_payer_id<ES: Deref>(
- offer: &'a Offer, expanded_key: &ExpandedKey, entropy_source: ES, secp_ctx: &'b Secp256k1<T>
+ offer: &'a Offer, expanded_key: &ExpandedKey, entropy_source: ES,
+ secp_ctx: &'b Secp256k1<T>, payment_id: PaymentId
) -> Self where ES::Target: EntropySource {
let nonce = Nonce::from_entropy_source(entropy_source);
- let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES);
+ let payment_id = Some(payment_id);
+ let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES, payment_id);
let metadata = Metadata::DerivedSigningPubkey(derivation_material);
Self {
offer,
let mut tlv_stream = self.invoice_request.as_tlv_stream();
debug_assert!(tlv_stream.2.payer_id.is_none());
tlv_stream.0.metadata = None;
- if !metadata.derives_keys() {
+ if !metadata.derives_payer_keys() {
tlv_stream.2.payer_id = self.payer_id.as_ref();
}
signature: Signature,
}
+/// An [`InvoiceRequest`] that has been verified by [`InvoiceRequest::verify`] and exposes different
+/// ways to respond depending on whether the signing keys were derived.
+#[derive(Clone, Debug)]
+pub struct VerifiedInvoiceRequest {
+ /// The verified request.
+ inner: InvoiceRequest,
+
+ /// Keys used for signing a [`Bolt12Invoice`] if they can be derived.
+ ///
+ /// If `Some`, must call [`respond_using_derived_keys`] when responding. Otherwise, call
+ /// [`respond_with`].
+ ///
+ /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
+ /// [`respond_using_derived_keys`]: Self::respond_using_derived_keys
+ /// [`respond_with`]: Self::respond_with
+ pub keys: Option<KeyPair>,
+}
+
/// The contents of an [`InvoiceRequest`], which may be shared with an [`Bolt12Invoice`].
///
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
///
/// Errors if the request contains unknown required features.
///
+ /// # Note
+ ///
+ /// If the originating [`Offer`] was created using [`OfferBuilder::deriving_signing_pubkey`],
+ /// then use [`InvoiceRequest::verify`] and [`VerifiedInvoiceRequest`] methods instead.
+ ///
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
///
/// [`Bolt12Invoice::created_at`]: crate::offers::invoice::Bolt12Invoice::created_at
+ /// [`OfferBuilder::deriving_signing_pubkey`]: crate::offers::offer::OfferBuilder::deriving_signing_pubkey
pub fn respond_with_no_std(
&self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
created_at: core::time::Duration
InvoiceBuilder::for_offer(self, payment_paths, created_at, payment_hash)
}
+ /// Verifies that the request was for an offer created using the given key. Returns the verified
+ /// request which contains the derived keys needed to sign a [`Bolt12Invoice`] for the request
+ /// if they could be extracted from the metadata.
+ ///
+ /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
+ pub fn verify<T: secp256k1::Signing>(
+ self, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
+ ) -> Result<VerifiedInvoiceRequest, ()> {
+ let keys = self.contents.inner.offer.verify(&self.bytes, key, secp_ctx)?;
+ Ok(VerifiedInvoiceRequest {
+ inner: self,
+ keys,
+ })
+ }
+
+ #[cfg(test)]
+ fn as_tlv_stream(&self) -> FullInvoiceRequestTlvStreamRef {
+ let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) =
+ self.contents.as_tlv_stream();
+ let signature_tlv_stream = SignatureTlvStreamRef {
+ signature: Some(&self.signature),
+ };
+ (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, signature_tlv_stream)
+ }
+}
+
+impl VerifiedInvoiceRequest {
+ offer_accessors!(self, self.inner.contents.inner.offer);
+ invoice_request_accessors!(self, self.inner.contents);
+
+ /// Creates an [`InvoiceBuilder`] for the request with the given required fields and using the
+ /// [`Duration`] since [`std::time::SystemTime::UNIX_EPOCH`] as the creation time.
+ ///
+ /// See [`InvoiceRequest::respond_with_no_std`] for further details.
+ ///
+ /// This is not exported to bindings users as builder patterns don't map outside of move semantics.
+ ///
+ /// [`Duration`]: core::time::Duration
+ #[cfg(feature = "std")]
+ pub fn respond_with(
+ &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash
+ ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, Bolt12SemanticError> {
+ self.inner.respond_with(payment_paths, payment_hash)
+ }
+
+ /// Creates an [`InvoiceBuilder`] for the request with the given required fields.
+ ///
+ /// See [`InvoiceRequest::respond_with_no_std`] for further details.
+ ///
+ /// This is not exported to bindings users as builder patterns don't map outside of move semantics.
+ pub fn respond_with_no_std(
+ &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
+ created_at: core::time::Duration
+ ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, Bolt12SemanticError> {
+ self.inner.respond_with_no_std(payment_paths, payment_hash, created_at)
+ }
+
/// Creates an [`InvoiceBuilder`] for the request using the given required fields and that uses
/// derived signing keys from the originating [`Offer`] to sign the [`Bolt12Invoice`]. Must use
/// the same [`ExpandedKey`] as the one used to create the offer.
///
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
#[cfg(feature = "std")]
- pub fn verify_and_respond_using_derived_keys<T: secp256k1::Signing>(
- &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
- expanded_key: &ExpandedKey, secp_ctx: &Secp256k1<T>
+ pub fn respond_using_derived_keys(
+ &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash
) -> Result<InvoiceBuilder<DerivedSigningPubkey>, Bolt12SemanticError> {
let created_at = std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
- self.verify_and_respond_using_derived_keys_no_std(
- payment_paths, payment_hash, created_at, expanded_key, secp_ctx
- )
+ self.respond_using_derived_keys_no_std(payment_paths, payment_hash, created_at)
}
/// Creates an [`InvoiceBuilder`] for the request using the given required fields and that uses
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
///
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
- pub fn verify_and_respond_using_derived_keys_no_std<T: secp256k1::Signing>(
+ pub fn respond_using_derived_keys_no_std(
&self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
- created_at: core::time::Duration, expanded_key: &ExpandedKey, secp_ctx: &Secp256k1<T>
+ created_at: core::time::Duration
) -> Result<InvoiceBuilder<DerivedSigningPubkey>, Bolt12SemanticError> {
- if self.invoice_request_features().requires_unknown_bits() {
+ if self.inner.invoice_request_features().requires_unknown_bits() {
return Err(Bolt12SemanticError::UnknownRequiredFeatures);
}
- let keys = match self.verify(expanded_key, secp_ctx) {
- Err(()) => return Err(Bolt12SemanticError::InvalidMetadata),
- Ok(None) => return Err(Bolt12SemanticError::InvalidMetadata),
- Ok(Some(keys)) => keys,
+ let keys = match self.keys {
+ None => return Err(Bolt12SemanticError::InvalidMetadata),
+ Some(keys) => keys,
};
- InvoiceBuilder::for_offer_using_keys(self, payment_paths, created_at, payment_hash, keys)
- }
-
- /// Verifies that the request was for an offer created using the given key. Returns the derived
- /// keys need to sign an [`Bolt12Invoice`] for the request if they could be extracted from the
- /// metadata.
- ///
- /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
- pub fn verify<T: secp256k1::Signing>(
- &self, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
- ) -> Result<Option<KeyPair>, ()> {
- self.contents.inner.offer.verify(&self.bytes, key, secp_ctx)
- }
-
- #[cfg(test)]
- fn as_tlv_stream(&self) -> FullInvoiceRequestTlvStreamRef {
- let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) =
- self.contents.as_tlv_stream();
- let signature_tlv_stream = SignatureTlvStreamRef {
- signature: Some(&self.signature),
- };
- (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, signature_tlv_stream)
+ InvoiceBuilder::for_offer_using_keys(
+ &self.inner, payment_paths, created_at, payment_hash, keys
+ )
}
}
}
pub(super) fn derives_keys(&self) -> bool {
- self.inner.payer.0.derives_keys()
+ self.inner.payer.0.derives_payer_keys()
}
pub(super) fn chain(&self) -> ChainHash {
#[cfg(feature = "std")]
use core::time::Duration;
use crate::sign::KeyMaterial;
+ use crate::ln::channelmanager::PaymentId;
use crate::ln::features::{InvoiceRequestFeatures, OfferFeatures};
use crate::ln::inbound_payment::ExpandedKey;
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
let entropy = FixedEntropy {};
let secp_ctx = Secp256k1::new();
+ let payment_id = PaymentId([1; 32]);
let offer = OfferBuilder::new("foo".into(), recipient_pubkey())
.amount_msats(1000)
.build().unwrap();
let invoice_request = offer
- .request_invoice_deriving_metadata(payer_id, &expanded_key, &entropy)
+ .request_invoice_deriving_metadata(payer_id, &expanded_key, &entropy, payment_id)
.unwrap()
.build().unwrap()
.sign(payer_sign).unwrap();
.unwrap()
.build().unwrap()
.sign(recipient_sign).unwrap();
- assert!(invoice.verify(&expanded_key, &secp_ctx));
+ match invoice.verify(&expanded_key, &secp_ctx) {
+ Ok(payment_id) => assert_eq!(payment_id, PaymentId([1; 32])),
+ Err(()) => panic!("verification failed"),
+ }
// Fails verification with altered fields
let (
signature_tlv_stream.write(&mut encoded_invoice).unwrap();
let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap();
- assert!(!invoice.verify(&expanded_key, &secp_ctx));
+ assert!(invoice.verify(&expanded_key, &secp_ctx).is_err());
// Fails verification with altered metadata
let (
signature_tlv_stream.write(&mut encoded_invoice).unwrap();
let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap();
- assert!(!invoice.verify(&expanded_key, &secp_ctx));
+ assert!(invoice.verify(&expanded_key, &secp_ctx).is_err());
}
#[test]
let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
let entropy = FixedEntropy {};
let secp_ctx = Secp256k1::new();
+ let payment_id = PaymentId([1; 32]);
let offer = OfferBuilder::new("foo".into(), recipient_pubkey())
.amount_msats(1000)
.build().unwrap();
let invoice_request = offer
- .request_invoice_deriving_payer_id(&expanded_key, &entropy, &secp_ctx)
+ .request_invoice_deriving_payer_id(&expanded_key, &entropy, &secp_ctx, payment_id)
.unwrap()
.build_and_sign()
.unwrap();
.unwrap()
.build().unwrap()
.sign(recipient_sign).unwrap();
- assert!(invoice.verify(&expanded_key, &secp_ctx));
+ match invoice.verify(&expanded_key, &secp_ctx) {
+ Ok(payment_id) => assert_eq!(payment_id, PaymentId([1; 32])),
+ Err(()) => panic!("verification failed"),
+ }
// Fails verification with altered fields
let (
signature_tlv_stream.write(&mut encoded_invoice).unwrap();
let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap();
- assert!(!invoice.verify(&expanded_key, &secp_ctx));
+ assert!(invoice.verify(&expanded_key, &secp_ctx).is_err());
// Fails verification with altered payer id
let (
signature_tlv_stream.write(&mut encoded_invoice).unwrap();
let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap();
- assert!(!invoice.verify(&expanded_key, &secp_ctx));
+ assert!(invoice.verify(&expanded_key, &secp_ctx).is_err());
}
#[test]
pub mod parse;
mod payer;
pub mod refund;
-#[allow(unused)]
pub(crate) mod signer;
#[cfg(test)]
mod test_utils;
use crate::sign::EntropySource;
use crate::io;
use crate::blinded_path::BlindedPath;
+use crate::ln::channelmanager::PaymentId;
use crate::ln::features::OfferFeatures;
use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce};
use crate::ln::msgs::MAX_VALUE_MSAT;
secp_ctx: &'a Secp256k1<T>
) -> Self where ES::Target: EntropySource {
let nonce = Nonce::from_entropy_source(entropy_source);
- let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES);
+ let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES, None);
let metadata = Metadata::DerivedSigningPubkey(derivation_material);
OfferBuilder {
offer: OfferContents {
let mut tlv_stream = self.offer.as_tlv_stream();
debug_assert_eq!(tlv_stream.metadata, None);
tlv_stream.metadata = None;
- if metadata.derives_keys() {
+ if metadata.derives_recipient_keys() {
tlv_stream.node_id = None;
}
/// Similar to [`Offer::request_invoice`] except it:
/// - derives the [`InvoiceRequest::payer_id`] such that a different key can be used for each
- /// request, and
- /// - sets the [`InvoiceRequest::payer_metadata`] when [`InvoiceRequestBuilder::build`] is
- /// called such that it can be used by [`Bolt12Invoice::verify`] to determine if the invoice
- /// was requested using a base [`ExpandedKey`] from which the payer id was derived.
+ /// request,
+ /// - sets [`InvoiceRequest::payer_metadata`] when [`InvoiceRequestBuilder::build`] is called
+ /// such that it can be used by [`Bolt12Invoice::verify`] to determine if the invoice was
+ /// requested using a base [`ExpandedKey`] from which the payer id was derived, and
+ /// - includes the [`PaymentId`] encrypted in [`InvoiceRequest::payer_metadata`] so that it can
+ /// be used when sending the payment for the requested invoice.
///
/// Useful to protect the sender's privacy.
///
/// [`Bolt12Invoice::verify`]: crate::offers::invoice::Bolt12Invoice::verify
/// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey
pub fn request_invoice_deriving_payer_id<'a, 'b, ES: Deref, T: secp256k1::Signing>(
- &'a self, expanded_key: &ExpandedKey, entropy_source: ES, secp_ctx: &'b Secp256k1<T>
+ &'a self, expanded_key: &ExpandedKey, entropy_source: ES, secp_ctx: &'b Secp256k1<T>,
+ payment_id: PaymentId
) -> Result<InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T>, Bolt12SemanticError>
where
ES::Target: EntropySource,
return Err(Bolt12SemanticError::UnknownRequiredFeatures);
}
- Ok(InvoiceRequestBuilder::deriving_payer_id(self, expanded_key, entropy_source, secp_ctx))
+ Ok(InvoiceRequestBuilder::deriving_payer_id(
+ self, expanded_key, entropy_source, secp_ctx, payment_id
+ ))
}
/// Similar to [`Offer::request_invoice_deriving_payer_id`] except uses `payer_id` for the
///
/// [`InvoiceRequest::payer_id`]: crate::offers::invoice_request::InvoiceRequest::payer_id
pub fn request_invoice_deriving_metadata<ES: Deref>(
- &self, payer_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES
+ &self, payer_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES,
+ payment_id: PaymentId
) -> Result<InvoiceRequestBuilder<ExplicitPayerId, secp256k1::SignOnly>, Bolt12SemanticError>
where
ES::Target: EntropySource,
return Err(Bolt12SemanticError::UnknownRequiredFeatures);
}
- Ok(InvoiceRequestBuilder::deriving_metadata(self, payer_id, expanded_key, entropy_source))
+ Ok(InvoiceRequestBuilder::deriving_metadata(
+ self, payer_id, expanded_key, entropy_source, payment_id
+ ))
}
/// Creates an [`InvoiceRequestBuilder`] for the offer with the given `metadata` and `payer_id`,
let tlv_stream = TlvStream::new(bytes).range(OFFER_TYPES).filter(|record| {
match record.r#type {
OFFER_METADATA_TYPE => false,
- OFFER_NODE_ID_TYPE => !self.metadata.as_ref().unwrap().derives_keys(),
+ OFFER_NODE_ID_TYPE => {
+ !self.metadata.as_ref().unwrap().derives_recipient_keys()
+ },
_ => true,
}
});
- signer::verify_metadata(
+ signer::verify_recipient_metadata(
metadata, key, IV_BYTES, self.signing_pubkey(), tlv_stream, secp_ctx
)
},
use crate::io;
use crate::blinded_path::BlindedPath;
use crate::ln::PaymentHash;
+use crate::ln::channelmanager::PaymentId;
use crate::ln::features::InvoiceRequestFeatures;
use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce};
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
/// Also, sets the metadata when [`RefundBuilder::build`] is called such that it can be used to
/// verify that an [`InvoiceRequest`] was produced for the refund given an [`ExpandedKey`].
///
+ /// The `payment_id` is encrypted in the metadata and should be unique. This ensures that only
+ /// one invoice will be paid for the refund and that payments can be uniquely identified.
+ ///
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
/// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey
pub fn deriving_payer_id<ES: Deref>(
description: String, node_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES,
- secp_ctx: &'a Secp256k1<T>, amount_msats: u64
+ secp_ctx: &'a Secp256k1<T>, amount_msats: u64, payment_id: PaymentId
) -> Result<Self, Bolt12SemanticError> where ES::Target: EntropySource {
if amount_msats > MAX_VALUE_MSAT {
return Err(Bolt12SemanticError::InvalidAmount);
}
let nonce = Nonce::from_entropy_source(entropy_source);
- let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES);
+ let payment_id = Some(payment_id);
+ let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES, payment_id);
let metadata = Metadata::DerivedSigningPubkey(derivation_material);
Ok(Self {
refund: RefundContents {
let mut tlv_stream = self.refund.as_tlv_stream();
tlv_stream.0.metadata = None;
- if metadata.derives_keys() {
+ if metadata.derives_payer_keys() {
tlv_stream.2.payer_id = None;
}
}
pub(super) fn derives_keys(&self) -> bool {
- self.payer.0.derives_keys()
+ self.payer.0.derives_payer_keys()
}
pub(super) fn as_tlv_stream(&self) -> RefundTlvStreamRef {
use core::time::Duration;
use crate::blinded_path::{BlindedHop, BlindedPath};
use crate::sign::KeyMaterial;
+ use crate::ln::channelmanager::PaymentId;
use crate::ln::features::{InvoiceRequestFeatures, OfferFeatures};
use crate::ln::inbound_payment::ExpandedKey;
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
let entropy = FixedEntropy {};
let secp_ctx = Secp256k1::new();
+ let payment_id = PaymentId([1; 32]);
let refund = RefundBuilder
- ::deriving_payer_id(desc, node_id, &expanded_key, &entropy, &secp_ctx, 1000)
+ ::deriving_payer_id(desc, node_id, &expanded_key, &entropy, &secp_ctx, 1000, payment_id)
.unwrap()
.build().unwrap();
assert_eq!(refund.payer_id(), node_id);
.unwrap()
.build().unwrap()
.sign(recipient_sign).unwrap();
- assert!(invoice.verify(&expanded_key, &secp_ctx));
+ match invoice.verify(&expanded_key, &secp_ctx) {
+ Ok(payment_id) => assert_eq!(payment_id, PaymentId([1; 32])),
+ Err(()) => panic!("verification failed"),
+ }
let mut tlv_stream = refund.as_tlv_stream();
tlv_stream.2.amount = Some(2000);
.unwrap()
.build().unwrap()
.sign(recipient_sign).unwrap();
- assert!(!invoice.verify(&expanded_key, &secp_ctx));
+ assert!(invoice.verify(&expanded_key, &secp_ctx).is_err());
// Fails verification with altered metadata
let mut tlv_stream = refund.as_tlv_stream();
.unwrap()
.build().unwrap()
.sign(recipient_sign).unwrap();
- assert!(!invoice.verify(&expanded_key, &secp_ctx));
+ assert!(invoice.verify(&expanded_key, &secp_ctx).is_err());
}
#[test]
let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
let entropy = FixedEntropy {};
let secp_ctx = Secp256k1::new();
+ let payment_id = PaymentId([1; 32]);
let blinded_path = BlindedPath {
introduction_node_id: pubkey(40),
};
let refund = RefundBuilder
- ::deriving_payer_id(desc, node_id, &expanded_key, &entropy, &secp_ctx, 1000)
+ ::deriving_payer_id(desc, node_id, &expanded_key, &entropy, &secp_ctx, 1000, payment_id)
.unwrap()
.path(blinded_path)
.build().unwrap();
.unwrap()
.build().unwrap()
.sign(recipient_sign).unwrap();
- assert!(invoice.verify(&expanded_key, &secp_ctx));
+ match invoice.verify(&expanded_key, &secp_ctx) {
+ Ok(payment_id) => assert_eq!(payment_id, PaymentId([1; 32])),
+ Err(()) => panic!("verification failed"),
+ }
// Fails verification with altered fields
let mut tlv_stream = refund.as_tlv_stream();
.unwrap()
.build().unwrap()
.sign(recipient_sign).unwrap();
- assert!(!invoice.verify(&expanded_key, &secp_ctx));
+ assert!(invoice.verify(&expanded_key, &secp_ctx).is_err());
// Fails verification with altered payer_id
let mut tlv_stream = refund.as_tlv_stream();
.unwrap()
.build().unwrap()
.sign(recipient_sign).unwrap();
- assert!(!invoice.verify(&expanded_key, &secp_ctx));
+ assert!(invoice.verify(&expanded_key, &secp_ctx).is_err());
}
#[test]
use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey, self};
use core::convert::TryFrom;
use core::fmt;
+use crate::ln::channelmanager::PaymentId;
use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce};
use crate::offers::merkle::TlvRecord;
use crate::util::ser::Writeable;
use crate::prelude::*;
+// Use a different HMAC input for each derivation. Otherwise, an attacker could:
+// - take an Offer that has metadata consisting of a nonce and HMAC
+// - strip off the HMAC and replace the signing_pubkey where the privkey is the HMAC,
+// - generate and sign an invoice using the new signing_pubkey, and
+// - claim they paid it since they would know the preimage of the invoice's payment_hash
const DERIVED_METADATA_HMAC_INPUT: &[u8; 16] = &[1; 16];
const DERIVED_METADATA_AND_KEYS_HMAC_INPUT: &[u8; 16] = &[2; 16];
+// Additional HMAC inputs to distinguish use cases, either Offer or Refund/InvoiceRequest, where
+// metadata for the latter contain an encrypted PaymentId.
+const WITHOUT_ENCRYPTED_PAYMENT_ID_HMAC_INPUT: &[u8; 16] = &[3; 16];
+const WITH_ENCRYPTED_PAYMENT_ID_HMAC_INPUT: &[u8; 16] = &[4; 16];
+
/// Message metadata which possibly is derived from [`MetadataMaterial`] such that it can be
/// verified.
#[derive(Clone)]
}
}
- pub fn derives_keys(&self) -> bool {
+ pub fn derives_payer_keys(&self) -> bool {
+ match self {
+ // Infer whether Metadata::derived_from was called on Metadata::DerivedSigningPubkey to
+ // produce Metadata::Bytes. This is merely to determine which fields should be included
+ // when verifying a message. It doesn't necessarily indicate that keys were in fact
+ // derived, as wouldn't be the case if a Metadata::Bytes with length PaymentId::LENGTH +
+ // Nonce::LENGTH had been set explicitly.
+ Metadata::Bytes(bytes) => bytes.len() == PaymentId::LENGTH + Nonce::LENGTH,
+ Metadata::Derived(_) => false,
+ Metadata::DerivedSigningPubkey(_) => true,
+ }
+ }
+
+ pub fn derives_recipient_keys(&self) -> bool {
match self {
// Infer whether Metadata::derived_from was called on Metadata::DerivedSigningPubkey to
// produce Metadata::Bytes. This is merely to determine which fields should be included
pub(super) struct MetadataMaterial {
nonce: Nonce,
hmac: HmacEngine<Sha256>,
+ // Some for payer metadata and None for offer metadata
+ encrypted_payment_id: Option<[u8; PaymentId::LENGTH]>,
}
impl MetadataMaterial {
- pub fn new(nonce: Nonce, expanded_key: &ExpandedKey, iv_bytes: &[u8; IV_LEN]) -> Self {
+ pub fn new(
+ nonce: Nonce, expanded_key: &ExpandedKey, iv_bytes: &[u8; IV_LEN],
+ payment_id: Option<PaymentId>
+ ) -> Self {
+ // Encrypt payment_id
+ let encrypted_payment_id = payment_id.map(|payment_id| {
+ expanded_key.crypt_for_offer(payment_id.0, nonce)
+ });
+
Self {
nonce,
hmac: expanded_key.hmac_for_offer(nonce, iv_bytes),
+ encrypted_payment_id,
}
}
fn derive_metadata(mut self) -> Vec<u8> {
self.hmac.input(DERIVED_METADATA_HMAC_INPUT);
+ self.maybe_include_encrypted_payment_id();
- let mut bytes = self.nonce.as_slice().to_vec();
+ let mut bytes = self.encrypted_payment_id.map(|id| id.to_vec()).unwrap_or(vec![]);
+ bytes.extend_from_slice(self.nonce.as_slice());
bytes.extend_from_slice(&Hmac::from_engine(self.hmac).into_inner());
bytes
}
mut self, secp_ctx: &Secp256k1<T>
) -> (Vec<u8>, KeyPair) {
self.hmac.input(DERIVED_METADATA_AND_KEYS_HMAC_INPUT);
+ self.maybe_include_encrypted_payment_id();
+
+ let mut bytes = self.encrypted_payment_id.map(|id| id.to_vec()).unwrap_or(vec![]);
+ bytes.extend_from_slice(self.nonce.as_slice());
let hmac = Hmac::from_engine(self.hmac);
let privkey = SecretKey::from_slice(hmac.as_inner()).unwrap();
let keys = KeyPair::from_secret_key(secp_ctx, &privkey);
- (self.nonce.as_slice().to_vec(), keys)
+
+ (bytes, keys)
+ }
+
+ fn maybe_include_encrypted_payment_id(&mut self) {
+ match self.encrypted_payment_id {
+ None => self.hmac.input(WITHOUT_ENCRYPTED_PAYMENT_ID_HMAC_INPUT),
+ Some(encrypted_payment_id) => {
+ self.hmac.input(WITH_ENCRYPTED_PAYMENT_ID_HMAC_INPUT);
+ self.hmac.input(&encrypted_payment_id)
+ },
+ }
}
}
KeyPair::from_secret_key(&secp_ctx, &privkey)
}
+/// Verifies data given in a TLV stream was used to produce the given metadata, consisting of:
+/// - a 256-bit [`PaymentId`],
+/// - a 128-bit [`Nonce`], and possibly
+/// - a [`Sha256`] hash of the nonce and the TLV records using the [`ExpandedKey`].
+///
+/// If the latter is not included in the metadata, the TLV stream is used to check if the given
+/// `signing_pubkey` can be derived from it.
+///
+/// Returns the [`PaymentId`] that should be used for sending the payment.
+pub(super) fn verify_payer_metadata<'a, T: secp256k1::Signing>(
+ metadata: &[u8], expanded_key: &ExpandedKey, iv_bytes: &[u8; IV_LEN],
+ signing_pubkey: PublicKey, tlv_stream: impl core::iter::Iterator<Item = TlvRecord<'a>>,
+ secp_ctx: &Secp256k1<T>
+) -> Result<PaymentId, ()> {
+ if metadata.len() < PaymentId::LENGTH {
+ return Err(());
+ }
+
+ let mut encrypted_payment_id = [0u8; PaymentId::LENGTH];
+ encrypted_payment_id.copy_from_slice(&metadata[..PaymentId::LENGTH]);
+
+ let mut hmac = hmac_for_message(
+ &metadata[PaymentId::LENGTH..], expanded_key, iv_bytes, tlv_stream
+ )?;
+ hmac.input(WITH_ENCRYPTED_PAYMENT_ID_HMAC_INPUT);
+ hmac.input(&encrypted_payment_id);
+
+ verify_metadata(
+ &metadata[PaymentId::LENGTH..], Hmac::from_engine(hmac), signing_pubkey, secp_ctx
+ )?;
+
+ let nonce = Nonce::try_from(&metadata[PaymentId::LENGTH..][..Nonce::LENGTH]).unwrap();
+ let payment_id = expanded_key.crypt_for_offer(encrypted_payment_id, nonce);
+
+ Ok(PaymentId(payment_id))
+}
+
/// Verifies data given in a TLV stream was used to produce the given metadata, consisting of:
/// - a 128-bit [`Nonce`] and possibly
/// - a [`Sha256`] hash of the nonce and the TLV records using the [`ExpandedKey`].
///
/// If the latter is not included in the metadata, the TLV stream is used to check if the given
/// `signing_pubkey` can be derived from it.
-pub(super) fn verify_metadata<'a, T: secp256k1::Signing>(
+///
+/// Returns the [`KeyPair`] for signing the invoice, if it can be derived from the metadata.
+pub(super) fn verify_recipient_metadata<'a, T: secp256k1::Signing>(
metadata: &[u8], expanded_key: &ExpandedKey, iv_bytes: &[u8; IV_LEN],
signing_pubkey: PublicKey, tlv_stream: impl core::iter::Iterator<Item = TlvRecord<'a>>,
secp_ctx: &Secp256k1<T>
) -> Result<Option<KeyPair>, ()> {
- let hmac = hmac_for_message(metadata, expanded_key, iv_bytes, tlv_stream)?;
+ let mut hmac = hmac_for_message(metadata, expanded_key, iv_bytes, tlv_stream)?;
+ hmac.input(WITHOUT_ENCRYPTED_PAYMENT_ID_HMAC_INPUT);
+
+ verify_metadata(metadata, Hmac::from_engine(hmac), signing_pubkey, secp_ctx)
+}
+fn verify_metadata<T: secp256k1::Signing>(
+ metadata: &[u8], hmac: Hmac<Sha256>, signing_pubkey: PublicKey, secp_ctx: &Secp256k1<T>
+) -> Result<Option<KeyPair>, ()> {
if metadata.len() == Nonce::LENGTH {
let derived_keys = KeyPair::from_secret_key(
secp_ctx, &SecretKey::from_slice(hmac.as_inner()).unwrap()
fn hmac_for_message<'a>(
metadata: &[u8], expanded_key: &ExpandedKey, iv_bytes: &[u8; IV_LEN],
tlv_stream: impl core::iter::Iterator<Item = TlvRecord<'a>>
-) -> Result<Hmac<Sha256>, ()> {
+) -> Result<HmacEngine<Sha256>, ()> {
if metadata.len() < Nonce::LENGTH {
return Err(());
}
hmac.input(DERIVED_METADATA_HMAC_INPUT);
}
- Ok(Hmac::from_engine(hmac))
+ Ok(hmac)
}
chacha_bytes
}
+ /// Encrypts `src` into `dest` using a single block from a ChaCha stream. Passing `dest` as
+ /// `src` in a second call will decrypt it.
+ pub fn encrypt_single_block(
+ key: &[u8; 32], nonce: &[u8; 16], dest: &mut [u8], src: &[u8]
+ ) {
+ debug_assert_eq!(dest.len(), src.len());
+ debug_assert!(dest.len() <= 32);
+
+ let block = ChaCha20::get_single_block(key, nonce);
+ for i in 0..dest.len() {
+ dest[i] = block[i] ^ src[i];
+ }
+ }
+
+ /// Same as `encrypt_single_block` only operates on a fixed-size input in-place.
+ pub fn encrypt_single_block_in_place(
+ key: &[u8; 32], nonce: &[u8; 16], bytes: &mut [u8; 32]
+ ) {
+ let block = ChaCha20::get_single_block(key, nonce);
+ for i in 0..bytes.len() {
+ bytes[i] = block[i] ^ bytes[i];
+ }
+ }
+
fn expand(key: &[u8], nonce: &[u8]) -> ChaChaState {
let constant = match key.len() {
16 => b"expand 16-byte k",
[0; 32]
}
+ pub fn encrypt_single_block(
+ _key: &[u8; 32], _nonce: &[u8; 16], dest: &mut [u8], src: &[u8]
+ ) {
+ debug_assert_eq!(dest.len(), src.len());
+ debug_assert!(dest.len() <= 32);
+ }
+
+ pub fn encrypt_single_block_in_place(
+ _key: &[u8; 32], _nonce: &[u8; 16], _bytes: &mut [u8; 32]
+ ) {}
+
pub fn process(&mut self, input: &[u8], output: &mut [u8]) {
output.copy_from_slice(input);
}
assert_eq!(ChaCha20::get_single_block(&key, &nonce_16bytes), block_bytes);
}
+
+ #[test]
+ fn encrypt_single_block() {
+ let key = [
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+ ];
+ let nonce = [
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ ];
+ let bytes = [1; 32];
+
+ let mut encrypted_bytes = [0; 32];
+ ChaCha20::encrypt_single_block(&key, &nonce, &mut encrypted_bytes, &bytes);
+
+ let mut decrypted_bytes = [0; 32];
+ ChaCha20::encrypt_single_block(&key, &nonce, &mut decrypted_bytes, &encrypted_bytes);
+
+ assert_eq!(bytes, decrypted_bytes);
+ }
+
+ #[test]
+ fn encrypt_single_block_in_place() {
+ let key = [
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+ ];
+ let nonce = [
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ ];
+ let unencrypted_bytes = [1; 32];
+ let mut bytes = unencrypted_bytes;
+
+ ChaCha20::encrypt_single_block_in_place(&key, &nonce, &mut bytes);
+ assert_ne!(bytes, unencrypted_bytes);
+
+ ChaCha20::encrypt_single_block_in_place(&key, &nonce, &mut bytes);
+ assert_eq!(bytes, unencrypted_bytes);
+ }
}
let (k1, k2, _) = hkdf_extract_expand!($salt, $ikm);
(k1, k2)
}};
- ($salt: expr, $ikm: expr, 4) => {{
+ ($salt: expr, $ikm: expr, 5) => {{
let (k1, k2, prk) = hkdf_extract_expand!($salt, $ikm);
let mut hmac = HmacEngine::<Sha256>::new(&prk[..]);
let mut hmac = HmacEngine::<Sha256>::new(&prk[..]);
hmac.input(&k3);
hmac.input(&[4; 1]);
- (k1, k2, k3, Hmac::from_engine(hmac).into_inner())
+ let k4 = Hmac::from_engine(hmac).into_inner();
+
+ let mut hmac = HmacEngine::<Sha256>::new(&prk[..]);
+ hmac.input(&k4);
+ hmac.input(&[5; 1]);
+ let k5 = Hmac::from_engine(hmac).into_inner();
+
+ (k1, k2, k3, k4, k5)
}}
}
hkdf_extract_expand!(salt, ikm, 2)
}
-pub fn hkdf_extract_expand_4x(salt: &[u8], ikm: &[u8]) -> ([u8; 32], [u8; 32], [u8; 32], [u8; 32]) {
- hkdf_extract_expand!(salt, ikm, 4)
+pub fn hkdf_extract_expand_5x(salt: &[u8], ikm: &[u8]) -> ([u8; 32], [u8; 32], [u8; 32], [u8; 32], [u8; 32]) {
+ hkdf_extract_expand!(salt, ikm, 5)
}
#[inline]