From: Jeffrey Czyz Date: Sat, 12 Aug 2023 03:13:36 +0000 (-0500) Subject: Unsigned BOLT 12 message parsing and serialization X-Git-Tag: v0.0.117-alpha1~49^2~10 X-Git-Url: http://git.bitcoin.ninja/?a=commitdiff_plain;h=230f081e5a63161c3396adfb509ccfe045e921f8;p=rust-lightning Unsigned BOLT 12 message parsing and serialization --- diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index bb62e71c5..4bc2ac6e7 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -368,6 +368,11 @@ impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> { } /// A semantically valid [`Bolt12Invoice`] that hasn't been signed. +/// +/// # Serialization +/// +/// This is serialized as a TLV stream, which includes TLV records from the originating message. As +/// such, it may include unknown, odd TLV records. pub struct UnsignedBolt12Invoice { bytes: Vec, contents: InvoiceContents, @@ -396,7 +401,9 @@ impl UnsignedBolt12Invoice { self.contents.fields().signing_pubkey } - /// Signs the invoice using the given function. + /// Signs the [`TaggedHash`] of the invoice using the given function. + /// + /// Note: The hash computation may have included unknown, odd TLV records. /// /// This is not exported to bindings users as functions aren't currently mapped. pub fn sign(mut self, sign: F) -> Result> @@ -733,6 +740,12 @@ impl InvoiceFields { } } +impl Writeable for UnsignedBolt12Invoice { + fn write(&self, writer: &mut W) -> Result<(), io::Error> { + WithoutLength(&self.bytes).write(writer) + } +} + impl Writeable for Bolt12Invoice { fn write(&self, writer: &mut W) -> Result<(), io::Error> { WithoutLength(&self.bytes).write(writer) @@ -745,6 +758,25 @@ impl Writeable for InvoiceContents { } } +impl TryFrom> for UnsignedBolt12Invoice { + type Error = Bolt12ParseError; + + fn try_from(bytes: Vec) -> Result { + let invoice = ParsedMessage::::try_from(bytes)?; + let ParsedMessage { bytes, tlv_stream } = invoice; + let ( + payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream, + ) = tlv_stream; + let contents = InvoiceContents::try_from( + (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream) + )?; + + let tagged_hash = TaggedHash::new(SIGNATURE_TAG, &bytes); + + Ok(UnsignedBolt12Invoice { bytes, contents, tagged_hash }) + } +} + impl TryFrom> for Bolt12Invoice { type Error = Bolt12ParseError; @@ -857,6 +889,17 @@ type PartialInvoiceTlvStreamRef<'a> = ( InvoiceTlvStreamRef<'a>, ); +impl SeekReadable for PartialInvoiceTlvStream { + fn read(r: &mut R) -> Result { + let payer = SeekReadable::read(r)?; + let offer = SeekReadable::read(r)?; + let invoice_request = SeekReadable::read(r)?; + let invoice = SeekReadable::read(r)?; + + Ok((payer, offer, invoice_request, invoice)) + } +} + impl TryFrom> for Bolt12Invoice { type Error = Bolt12ParseError; @@ -961,7 +1004,7 @@ impl TryFrom for InvoiceContents { #[cfg(test)] mod tests { - use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, FallbackAddress, FullInvoiceTlvStreamRef, InvoiceTlvStreamRef, SIGNATURE_TAG}; + use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, FallbackAddress, FullInvoiceTlvStreamRef, InvoiceTlvStreamRef, SIGNATURE_TAG, UnsignedBolt12Invoice}; use bitcoin::blockdata::script::Script; use bitcoin::hashes::Hash; @@ -1007,15 +1050,27 @@ mod tests { let payment_paths = payment_paths(); let payment_hash = payment_hash(); let now = now(); - let invoice = OfferBuilder::new("foo".into(), recipient_pubkey()) + let unsigned_invoice = OfferBuilder::new("foo".into(), recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() .build().unwrap() .sign(payer_sign).unwrap() .respond_with_no_std(payment_paths.clone(), payment_hash, now).unwrap() - .build().unwrap() - .sign(recipient_sign).unwrap(); + .build().unwrap(); + + let mut buffer = Vec::new(); + unsigned_invoice.write(&mut buffer).unwrap(); + + match UnsignedBolt12Invoice::try_from(buffer) { + Err(e) => panic!("error parsing unsigned invoice: {:?}", e), + Ok(parsed) => { + assert_eq!(parsed.bytes, unsigned_invoice.bytes); + assert_eq!(parsed.tagged_hash, unsigned_invoice.tagged_hash); + }, + } + + let invoice = unsigned_invoice.sign(recipient_sign).unwrap(); let mut buffer = Vec::new(); invoice.write(&mut buffer).unwrap(); diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index 978ae77bf..b05f9af84 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -344,6 +344,11 @@ impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a } /// A semantically valid [`InvoiceRequest`] that hasn't been signed. +/// +/// # Serialization +/// +/// This is serialized as a TLV stream, which includes TLV records from the originating message. As +/// such, it may include unknown, odd TLV records. pub struct UnsignedInvoiceRequest { bytes: Vec, contents: InvoiceRequestContents, @@ -367,7 +372,9 @@ impl UnsignedInvoiceRequest { Self { bytes, contents, tagged_hash } } - /// Signs the invoice request using the given function. + /// Signs the [`TaggedHash`] of the invoice request using the given function. + /// + /// Note: The hash computation may have included unknown, odd TLV records. /// /// This is not exported to bindings users as functions are not yet mapped. pub fn sign(mut self, sign: F) -> Result> @@ -664,6 +671,12 @@ impl InvoiceRequestContentsWithoutPayerId { } } +impl Writeable for UnsignedInvoiceRequest { + fn write(&self, writer: &mut W) -> Result<(), io::Error> { + WithoutLength(&self.bytes).write(writer) + } +} + impl Writeable for InvoiceRequest { fn write(&self, writer: &mut W) -> Result<(), io::Error> { WithoutLength(&self.bytes).write(writer) @@ -723,6 +736,25 @@ type PartialInvoiceRequestTlvStreamRef<'a> = ( InvoiceRequestTlvStreamRef<'a>, ); +impl TryFrom> for UnsignedInvoiceRequest { + type Error = Bolt12ParseError; + + fn try_from(bytes: Vec) -> Result { + let invoice_request = ParsedMessage::::try_from(bytes)?; + let ParsedMessage { bytes, tlv_stream } = invoice_request; + let ( + payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, + ) = tlv_stream; + let contents = InvoiceRequestContents::try_from( + (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) + )?; + + let tagged_hash = TaggedHash::new(SIGNATURE_TAG, &bytes); + + Ok(UnsignedInvoiceRequest { bytes, contents, tagged_hash }) + } +} + impl TryFrom> for InvoiceRequest { type Error = Bolt12ParseError; @@ -792,7 +824,7 @@ impl TryFrom for InvoiceRequestContents { #[cfg(test)] mod tests { - use super::{InvoiceRequest, InvoiceRequestTlvStreamRef, SIGNATURE_TAG}; + use super::{InvoiceRequest, InvoiceRequestTlvStreamRef, SIGNATURE_TAG, UnsignedInvoiceRequest}; use bitcoin::blockdata::constants::ChainHash; use bitcoin::network::constants::Network; @@ -816,12 +848,24 @@ mod tests { #[test] fn builds_invoice_request_with_defaults() { - let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + let unsigned_invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap(); + .build().unwrap(); + + let mut buffer = Vec::new(); + unsigned_invoice_request.write(&mut buffer).unwrap(); + + match UnsignedInvoiceRequest::try_from(buffer) { + Err(e) => panic!("error parsing unsigned invoice request: {:?}", e), + Ok(parsed) => { + assert_eq!(parsed.bytes, unsigned_invoice_request.bytes); + assert_eq!(parsed.tagged_hash, unsigned_invoice_request.tagged_hash); + }, + } + + let invoice_request = unsigned_invoice_request.sign(payer_sign).unwrap(); let mut buffer = Vec::new(); invoice_request.write(&mut buffer).unwrap(); diff --git a/lightning/src/offers/merkle.rs b/lightning/src/offers/merkle.rs index d15039cd3..b3867bf6f 100644 --- a/lightning/src/offers/merkle.rs +++ b/lightning/src/offers/merkle.rs @@ -30,6 +30,7 @@ tlv_stream!(SignatureTlvStream, SignatureTlvStreamRef, SIGNATURE_TYPES, { /// /// [BIP 340]: https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki /// [BOLT 12]: https://github.com/rustyrussell/lightning-rfc/blob/guilt/offers/12-offer-encoding.md#signature-calculation +#[derive(Debug, PartialEq)] pub struct TaggedHash(Message); impl TaggedHash {