X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Foffers%2Finvoice.rs;h=48b8cec3536b2ab0576feca6826ed960f46d7a60;hb=1ceb41e08b2d76b23d2505a10a88db8d840895ca;hp=7a3438b6410f8ff73fe7fd5f47bc28479aef1d39;hpb=e049e97993b292337e32864f47fa0a445d37e047;p=rust-lightning diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index 7a3438b6..48b8cec3 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -148,7 +148,8 @@ impl<'a> InvoiceBuilder<'a> { Some(amount_msats) => amount_msats, None => match invoice_request.contents.offer.amount() { Some(Amount::Bitcoin { amount_msats }) => { - amount_msats * invoice_request.quantity().unwrap_or(1) + amount_msats.checked_mul(invoice_request.quantity().unwrap_or(1)) + .ok_or(SemanticError::InvalidAmount)? }, Some(Amount::Currency { .. }) => return Err(SemanticError::UnsupportedCurrency), None => return Err(SemanticError::MissingAmount), @@ -468,6 +469,11 @@ impl Invoice { self.signature } + /// Hash that was used for signing the invoice. + pub fn signable_hash(&self) -> [u8; 32] { + merkle::message_digest(SIGNATURE_TAG, &self.bytes).as_ref().clone() + } + #[cfg(test)] fn as_tlv_stream(&self) -> FullInvoiceTlvStreamRef { let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream) = @@ -787,7 +793,7 @@ mod tests { use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures}; use crate::offers::invoice_request::InvoiceRequestTlvStreamRef; use crate::offers::merkle::{SignError, SignatureTlvStreamRef, self}; - use crate::offers::offer::{OfferBuilder, OfferTlvStreamRef}; + use crate::offers::offer::{OfferBuilder, OfferTlvStreamRef, Quantity}; use crate::offers::parse::{ParseError, SemanticError}; use crate::offers::payer::PayerTlvStreamRef; use crate::offers::refund::RefundBuilder; @@ -936,6 +942,11 @@ mod tests { ).is_ok() ); + let digest = Message::from_slice(&invoice.signable_hash()).unwrap(); + let pubkey = recipient_pubkey().into(); + let secp_ctx = Secp256k1::verification_only(); + assert!(secp_ctx.verify_schnorr(&invoice.signature, &digest, &pubkey).is_ok()); + assert_eq!( invoice.as_tlv_stream(), ( @@ -1177,6 +1188,38 @@ mod tests { assert_eq!(tlv_stream.amount, Some(1001)); } + #[test] + fn builds_invoice_with_quantity_from_request() { + let invoice = OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .supported_quantity(Quantity::Unbounded) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .quantity(2).unwrap() + .build().unwrap() + .sign(payer_sign).unwrap() + .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() + .build().unwrap() + .sign(recipient_sign).unwrap(); + let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream(); + assert_eq!(invoice.amount_msats(), 2000); + assert_eq!(tlv_stream.amount, Some(2000)); + + match OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .supported_quantity(Quantity::Unbounded) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .quantity(u64::max_value()).unwrap() + .build_unchecked() + .sign(payer_sign).unwrap() + .respond_with_no_std(payment_paths(), payment_hash(), now()) + { + Ok(_) => panic!("expected error"), + Err(e) => assert_eq!(e, SemanticError::InvalidAmount), + } + } + #[test] fn builds_invoice_with_fallback_address() { let script = Script::new();