X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Foffers%2Finvoice_request.rs;h=92fabd6fdf0c7ab5f49c46cfd0793e02a8886774;hb=8afe6940200769b9df9e9ecfda2a8390919a6cf2;hp=f617383fdcfa364c38bbc521b91a43554d6a35f4;hpb=9bd43e077fd00add0491960aeb5533a75d9d71d3;p=rust-lightning diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index f617383f..92fabd6f 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -64,8 +64,8 @@ use crate::ln::PaymentHash; use crate::ln::features::InvoiceRequestFeatures; use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce}; use crate::ln::msgs::DecodeError; -use crate::offers::invoice::{BlindedPayInfo, InvoiceBuilder}; -use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, TlvStream, self}; +use crate::offers::invoice::{BlindedPayInfo, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder}; +use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, self}; use crate::offers::offer::{Offer, OfferContents, OfferTlvStream, OfferTlvStreamRef}; use crate::offers::parse::{ParseError, ParsedMessage, SemanticError}; use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef}; @@ -78,7 +78,7 @@ use crate::prelude::*; const SIGNATURE_TAG: &'static str = concat!("lightning", "invoice_request", "signature"); -const IV_BYTES: &[u8; IV_LEN] = b"LDK Invreq ~~~~~"; +pub(super) const IV_BYTES: &[u8; IV_LEN] = b"LDK Invreq ~~~~~"; /// Builds an [`InvoiceRequest`] from an [`Offer`] for the "offer to be paid" flow. /// @@ -469,7 +469,7 @@ impl InvoiceRequest { #[cfg(feature = "std")] pub fn respond_with( &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash - ) -> Result { + ) -> Result, SemanticError> { let created_at = std::time::SystemTime::now() .duration_since(std::time::SystemTime::UNIX_EPOCH) .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"); @@ -497,7 +497,7 @@ impl InvoiceRequest { pub fn respond_with_no_std( &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash, created_at: core::time::Duration - ) -> Result { + ) -> Result, SemanticError> { if self.features().requires_unknown_bits() { return Err(SemanticError::UnknownRequiredFeatures); } @@ -505,11 +505,60 @@ impl InvoiceRequest { InvoiceBuilder::for_offer(self, payment_paths, created_at, payment_hash) } - /// Verifies that the request was for an offer created using the given key. + /// Creates an [`InvoiceBuilder`] for the request using the given required fields and that uses + /// derived signing keys from the originating [`Offer`] to sign the [`Invoice`]. Must use the + /// same [`ExpandedKey`] as the one used to create the offer. + /// + /// See [`InvoiceRequest::respond_with`] for further details. + /// + /// [`Invoice`]: crate::offers::invoice::Invoice + #[cfg(feature = "std")] + pub fn verify_and_respond_using_derived_keys( + &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash, + expanded_key: &ExpandedKey, secp_ctx: &Secp256k1 + ) -> Result, SemanticError> { + 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 + ) + } + + /// Creates an [`InvoiceBuilder`] for the request using the given required fields and that uses + /// derived signing keys from the originating [`Offer`] to sign the [`Invoice`]. Must use the + /// same [`ExpandedKey`] as the one used to create the offer. + /// + /// See [`InvoiceRequest::respond_with_no_std`] for further details. + /// + /// [`Invoice`]: crate::offers::invoice::Invoice + pub fn verify_and_respond_using_derived_keys_no_std( + &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash, + created_at: core::time::Duration, expanded_key: &ExpandedKey, secp_ctx: &Secp256k1 + ) -> Result, SemanticError> { + if self.features().requires_unknown_bits() { + return Err(SemanticError::UnknownRequiredFeatures); + } + + let keys = match self.verify(expanded_key, secp_ctx) { + Err(()) => return Err(SemanticError::InvalidMetadata), + Ok(None) => return Err(SemanticError::InvalidMetadata), + Ok(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 [`Invoice`] for the request if they could be extracted from the + /// metadata. + /// + /// [`Invoice`]: crate::offers::invoice::Invoice pub fn verify( &self, key: &ExpandedKey, secp_ctx: &Secp256k1 - ) -> bool { - self.contents.inner.offer.verify(TlvStream::new(&self.bytes), key, secp_ctx) + ) -> Result, ()> { + self.contents.inner.offer.verify(&self.bytes, key, secp_ctx) } #[cfg(test)] @@ -528,10 +577,18 @@ impl InvoiceRequestContents { self.inner.metadata() } + pub(super) fn derives_keys(&self) -> bool { + self.inner.payer.0.derives_keys() + } + pub(super) fn chain(&self) -> ChainHash { self.inner.chain() } + pub(super) fn payer_id(&self) -> PublicKey { + self.payer_id + } + 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); @@ -585,12 +642,20 @@ impl Writeable for InvoiceRequestContents { } } -tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, 80..160, { +/// Valid type range for invoice_request TLV records. +pub(super) const INVOICE_REQUEST_TYPES: core::ops::Range = 80..160; + +/// TLV record type for [`InvoiceRequest::payer_id`] and [`Refund::payer_id`]. +/// +/// [`Refund::payer_id`]: crate::offers::refund::Refund::payer_id +pub(super) 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)), }); @@ -702,8 +767,11 @@ mod tests { 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}; @@ -800,6 +868,148 @@ mod tests { } } + #[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);