use bitcoin::hash_types::{WPubkeyHash, WScriptHash};
use bitcoin::hashes::Hash;
use bitcoin::network::constants::Network;
-use bitcoin::secp256k1::{Message, PublicKey};
+use bitcoin::secp256k1::{Message, PublicKey, Secp256k1, self};
use bitcoin::secp256k1::schnorr::Signature;
use bitcoin::util::address::{Address, Payload, WitnessVersion};
use bitcoin::util::schnorr::TweakedPublicKey;
use crate::io;
use crate::ln::PaymentHash;
use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures};
+use crate::ln::inbound_payment::ExpandedKey;
use crate::ln::msgs::DecodeError;
use crate::offers::invoice_request::{InvoiceRequest, InvoiceRequestContents, InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef};
-use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, WithoutSignatures, self};
+use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, TlvStream, WithoutSignatures, self};
use crate::offers::offer::{Amount, OfferTlvStream, OfferTlvStreamRef};
use crate::offers::parse::{ParseError, ParsedMessage, SemanticError};
use crate::offers::payer::{PayerTlvStream, PayerTlvStreamRef};
const DEFAULT_RELATIVE_EXPIRY: Duration = Duration::from_secs(7200);
-const SIGNATURE_TAG: &'static str = concat!("lightning", "invoice", "signature");
+pub(super) const SIGNATURE_TAG: &'static str = concat!("lightning", "invoice", "signature");
/// Builds an [`Invoice`] from either:
/// - an [`InvoiceRequest`] for the "offer to be paid" flow or
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.
+ pub fn verify<T: secp256k1::Signing>(
+ &self, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
+ ) -> bool {
+ self.contents.verify(TlvStream::new(&self.bytes), key, secp_ctx)
+ }
+
#[cfg(test)]
- fn as_tlv_stream(&self) -> FullInvoiceTlvStreamRef {
+ pub(super) fn as_tlv_stream(&self) -> FullInvoiceTlvStreamRef {
let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream) =
self.contents.as_tlv_stream();
let signature_tlv_stream = SignatureTlvStreamRef {
}
}
+ fn verify<T: secp256k1::Signing>(
+ &self, tlv_stream: TlvStream<'_>, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
+ ) -> bool {
+ match self {
+ InvoiceContents::ForOffer { invoice_request, .. } => {
+ invoice_request.verify(tlv_stream, key, secp_ctx)
+ },
+ _ => todo!(),
+ }
+ }
+
fn as_tlv_stream(&self) -> PartialInvoiceTlvStreamRef {
let (payer, offer, invoice_request) = match self {
InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.as_tlv_stream(),
use crate::ln::msgs::DecodeError;
use crate::offers::invoice::{BlindedPayInfo, InvoiceBuilder};
use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, TlvStream, self};
-use crate::offers::offer::{Offer, OfferContents, OfferTlvStream, OfferTlvStreamRef};
+use crate::offers::offer::{OFFER_TYPES, Offer, OfferContents, OfferTlvStream, OfferTlvStreamRef};
use crate::offers::parse::{ParseError, ParsedMessage, SemanticError};
-use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef};
-use crate::offers::signer::{Metadata, MetadataMaterial};
+use crate::offers::payer::{PAYER_METADATA_TYPE, PayerContents, PayerTlvStream, PayerTlvStreamRef};
+use crate::offers::signer::{Metadata, MetadataMaterial, self};
use crate::onion_message::BlindedPath;
use crate::util::ser::{HighZeroBytesDroppedBigSize, SeekReadable, WithoutLength, Writeable, Writer};
use crate::util::string::PrintableString;
self.inner.chain()
}
+ /// Verifies that the payer metadata was produced from the invoice request in the TLV stream.
+ pub(super) fn verify<T: secp256k1::Signing>(
+ &self, tlv_stream: TlvStream<'_>, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
+ ) -> bool {
+ let offer_records = tlv_stream.clone().range(OFFER_TYPES);
+ let invreq_records = tlv_stream.range(INVOICE_REQUEST_TYPES).filter(|record| {
+ match record.r#type {
+ PAYER_METADATA_TYPE => false, // Should be outside range
+ INVOICE_REQUEST_PAYER_ID_TYPE => !self.inner.payer.0.derives_keys(),
+ _ => true,
+ }
+ });
+ let tlv_stream = offer_records.chain(invreq_records);
+ signer::verify_metadata(self.metadata(), key, IV_BYTES, self.payer_id, tlv_stream, secp_ctx)
+ }
+
pub(super) fn as_tlv_stream(&self) -> PartialInvoiceRequestTlvStreamRef {
let (payer, offer, mut invoice_request) = self.inner.as_tlv_stream();
invoice_request.payer_id = Some(&self.payer_id);
}
}
-tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, 80..160, {
+/// Valid type range for invoice_request TLV records.
+const INVOICE_REQUEST_TYPES: core::ops::Range<u64> = 80..160;
+
+/// TLV record type for [`InvoiceRequest::payer_id`] and [`Refund::payer_id`].
+///
+/// [`Refund::payer_id`]: crate::offers::refund::Refund::payer_id
+const INVOICE_REQUEST_PAYER_ID_TYPE: u64 = 88;
+
+tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, INVOICE_REQUEST_TYPES, {
(80, chain: ChainHash),
(82, amount: (u64, HighZeroBytesDroppedBigSize)),
(84, features: (InvoiceRequestFeatures, WithoutLength)),
(86, quantity: (u64, HighZeroBytesDroppedBigSize)),
- (88, payer_id: PublicKey),
+ (INVOICE_REQUEST_PAYER_ID_TYPE, payer_id: PublicKey),
(89, payer_note: (String, WithoutLength)),
});
use core::num::NonZeroU64;
#[cfg(feature = "std")]
use core::time::Duration;
+ use crate::chain::keysinterface::KeyMaterial;
use crate::ln::features::InvoiceRequestFeatures;
+ use crate::ln::inbound_payment::ExpandedKey;
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
+ use crate::offers::invoice::{Invoice, SIGNATURE_TAG as INVOICE_SIGNATURE_TAG};
use crate::offers::merkle::{SignError, SignatureTlvStreamRef, self};
use crate::offers::offer::{Amount, OfferBuilder, OfferTlvStreamRef, Quantity};
use crate::offers::parse::{ParseError, SemanticError};
}
}
+ #[test]
+ fn builds_invoice_request_with_derived_metadata() {
+ let payer_id = payer_pubkey();
+ let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
+ let entropy = FixedEntropy {};
+ let secp_ctx = Secp256k1::new();
+
+ 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)
+ .unwrap()
+ .build().unwrap()
+ .sign(payer_sign).unwrap();
+ assert_eq!(invoice_request.payer_id(), payer_pubkey());
+
+ let invoice = invoice_request.respond_with_no_std(payment_paths(), payment_hash(), now())
+ .unwrap()
+ .build().unwrap()
+ .sign(recipient_sign).unwrap();
+ assert!(invoice.verify(&expanded_key, &secp_ctx));
+
+ // Fails verification with altered fields
+ let (
+ payer_tlv_stream, offer_tlv_stream, mut invoice_request_tlv_stream,
+ mut invoice_tlv_stream, mut signature_tlv_stream
+ ) = invoice.as_tlv_stream();
+ invoice_request_tlv_stream.amount = Some(2000);
+ invoice_tlv_stream.amount = Some(2000);
+
+ let tlv_stream =
+ (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream);
+ let mut bytes = Vec::new();
+ tlv_stream.write(&mut bytes).unwrap();
+
+ let signature = merkle::sign_message(
+ recipient_sign, INVOICE_SIGNATURE_TAG, &bytes, recipient_pubkey()
+ ).unwrap();
+ signature_tlv_stream.signature = Some(&signature);
+
+ let mut encoded_invoice = bytes;
+ signature_tlv_stream.write(&mut encoded_invoice).unwrap();
+
+ let invoice = Invoice::try_from(encoded_invoice).unwrap();
+ assert!(!invoice.verify(&expanded_key, &secp_ctx));
+
+ // Fails verification with altered metadata
+ let (
+ mut payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
+ mut signature_tlv_stream
+ ) = invoice.as_tlv_stream();
+ let metadata = payer_tlv_stream.metadata.unwrap().iter().copied().rev().collect();
+ payer_tlv_stream.metadata = Some(&metadata);
+
+ let tlv_stream =
+ (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream);
+ let mut bytes = Vec::new();
+ tlv_stream.write(&mut bytes).unwrap();
+
+ let signature = merkle::sign_message(
+ recipient_sign, INVOICE_SIGNATURE_TAG, &bytes, recipient_pubkey()
+ ).unwrap();
+ signature_tlv_stream.signature = Some(&signature);
+
+ let mut encoded_invoice = bytes;
+ signature_tlv_stream.write(&mut encoded_invoice).unwrap();
+
+ let invoice = Invoice::try_from(encoded_invoice).unwrap();
+ assert!(!invoice.verify(&expanded_key, &secp_ctx));
+ }
+
+ #[test]
+ fn builds_invoice_request_with_derived_payer_id() {
+ let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
+ let entropy = FixedEntropy {};
+ let secp_ctx = Secp256k1::new();
+
+ 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)
+ .unwrap()
+ .build_and_sign()
+ .unwrap();
+
+ let invoice = invoice_request.respond_with_no_std(payment_paths(), payment_hash(), now())
+ .unwrap()
+ .build().unwrap()
+ .sign(recipient_sign).unwrap();
+ assert!(invoice.verify(&expanded_key, &secp_ctx));
+
+ // Fails verification with altered fields
+ let (
+ payer_tlv_stream, offer_tlv_stream, mut invoice_request_tlv_stream,
+ mut invoice_tlv_stream, mut signature_tlv_stream
+ ) = invoice.as_tlv_stream();
+ invoice_request_tlv_stream.amount = Some(2000);
+ invoice_tlv_stream.amount = Some(2000);
+
+ let tlv_stream =
+ (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream);
+ let mut bytes = Vec::new();
+ tlv_stream.write(&mut bytes).unwrap();
+
+ let signature = merkle::sign_message(
+ recipient_sign, INVOICE_SIGNATURE_TAG, &bytes, recipient_pubkey()
+ ).unwrap();
+ signature_tlv_stream.signature = Some(&signature);
+
+ let mut encoded_invoice = bytes;
+ signature_tlv_stream.write(&mut encoded_invoice).unwrap();
+
+ let invoice = Invoice::try_from(encoded_invoice).unwrap();
+ assert!(!invoice.verify(&expanded_key, &secp_ctx));
+
+ // Fails verification with altered payer id
+ let (
+ payer_tlv_stream, offer_tlv_stream, mut invoice_request_tlv_stream, invoice_tlv_stream,
+ mut signature_tlv_stream
+ ) = invoice.as_tlv_stream();
+ let payer_id = pubkey(1);
+ invoice_request_tlv_stream.payer_id = Some(&payer_id);
+
+ let tlv_stream =
+ (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream);
+ let mut bytes = Vec::new();
+ tlv_stream.write(&mut bytes).unwrap();
+
+ let signature = merkle::sign_message(
+ recipient_sign, INVOICE_SIGNATURE_TAG, &bytes, recipient_pubkey()
+ ).unwrap();
+ signature_tlv_stream.signature = Some(&signature);
+
+ let mut encoded_invoice = bytes;
+ signature_tlv_stream.write(&mut encoded_invoice).unwrap();
+
+ let invoice = Invoice::try_from(encoded_invoice).unwrap();
+ assert!(!invoice.verify(&expanded_key, &secp_ctx));
+ }
+
#[test]
fn builds_invoice_request_with_chain() {
let mainnet = ChainHash::using_genesis_block(Network::Bitcoin);