Skip to content

Commit 230f081

Browse files
committed
Unsigned BOLT 12 message parsing and serialization
1 parent 889848d commit 230f081

File tree

3 files changed

+110
-10
lines changed

3 files changed

+110
-10
lines changed

lightning/src/offers/invoice.rs

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,11 @@ impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> {
368368
}
369369

370370
/// A semantically valid [`Bolt12Invoice`] that hasn't been signed.
371+
///
372+
/// # Serialization
373+
///
374+
/// This is serialized as a TLV stream, which includes TLV records from the originating message. As
375+
/// such, it may include unknown, odd TLV records.
371376
pub struct UnsignedBolt12Invoice {
372377
bytes: Vec<u8>,
373378
contents: InvoiceContents,
@@ -396,7 +401,9 @@ impl UnsignedBolt12Invoice {
396401
self.contents.fields().signing_pubkey
397402
}
398403

399-
/// Signs the invoice using the given function.
404+
/// Signs the [`TaggedHash`] of the invoice using the given function.
405+
///
406+
/// Note: The hash computation may have included unknown, odd TLV records.
400407
///
401408
/// This is not exported to bindings users as functions aren't currently mapped.
402409
pub fn sign<F, E>(mut self, sign: F) -> Result<Bolt12Invoice, SignError<E>>
@@ -733,6 +740,12 @@ impl InvoiceFields {
733740
}
734741
}
735742

743+
impl Writeable for UnsignedBolt12Invoice {
744+
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
745+
WithoutLength(&self.bytes).write(writer)
746+
}
747+
}
748+
736749
impl Writeable for Bolt12Invoice {
737750
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
738751
WithoutLength(&self.bytes).write(writer)
@@ -745,6 +758,25 @@ impl Writeable for InvoiceContents {
745758
}
746759
}
747760

761+
impl TryFrom<Vec<u8>> for UnsignedBolt12Invoice {
762+
type Error = Bolt12ParseError;
763+
764+
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
765+
let invoice = ParsedMessage::<PartialInvoiceTlvStream>::try_from(bytes)?;
766+
let ParsedMessage { bytes, tlv_stream } = invoice;
767+
let (
768+
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
769+
) = tlv_stream;
770+
let contents = InvoiceContents::try_from(
771+
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream)
772+
)?;
773+
774+
let tagged_hash = TaggedHash::new(SIGNATURE_TAG, &bytes);
775+
776+
Ok(UnsignedBolt12Invoice { bytes, contents, tagged_hash })
777+
}
778+
}
779+
748780
impl TryFrom<Vec<u8>> for Bolt12Invoice {
749781
type Error = Bolt12ParseError;
750782

@@ -857,6 +889,17 @@ type PartialInvoiceTlvStreamRef<'a> = (
857889
InvoiceTlvStreamRef<'a>,
858890
);
859891

892+
impl SeekReadable for PartialInvoiceTlvStream {
893+
fn read<R: io::Read + io::Seek>(r: &mut R) -> Result<Self, DecodeError> {
894+
let payer = SeekReadable::read(r)?;
895+
let offer = SeekReadable::read(r)?;
896+
let invoice_request = SeekReadable::read(r)?;
897+
let invoice = SeekReadable::read(r)?;
898+
899+
Ok((payer, offer, invoice_request, invoice))
900+
}
901+
}
902+
860903
impl TryFrom<ParsedMessage<FullInvoiceTlvStream>> for Bolt12Invoice {
861904
type Error = Bolt12ParseError;
862905

@@ -961,7 +1004,7 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
9611004

9621005
#[cfg(test)]
9631006
mod tests {
964-
use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, FallbackAddress, FullInvoiceTlvStreamRef, InvoiceTlvStreamRef, SIGNATURE_TAG};
1007+
use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, FallbackAddress, FullInvoiceTlvStreamRef, InvoiceTlvStreamRef, SIGNATURE_TAG, UnsignedBolt12Invoice};
9651008

9661009
use bitcoin::blockdata::script::Script;
9671010
use bitcoin::hashes::Hash;
@@ -1007,15 +1050,27 @@ mod tests {
10071050
let payment_paths = payment_paths();
10081051
let payment_hash = payment_hash();
10091052
let now = now();
1010-
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
1053+
let unsigned_invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
10111054
.amount_msats(1000)
10121055
.build().unwrap()
10131056
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
10141057
.build().unwrap()
10151058
.sign(payer_sign).unwrap()
10161059
.respond_with_no_std(payment_paths.clone(), payment_hash, now).unwrap()
1017-
.build().unwrap()
1018-
.sign(recipient_sign).unwrap();
1060+
.build().unwrap();
1061+
1062+
let mut buffer = Vec::new();
1063+
unsigned_invoice.write(&mut buffer).unwrap();
1064+
1065+
match UnsignedBolt12Invoice::try_from(buffer) {
1066+
Err(e) => panic!("error parsing unsigned invoice: {:?}", e),
1067+
Ok(parsed) => {
1068+
assert_eq!(parsed.bytes, unsigned_invoice.bytes);
1069+
assert_eq!(parsed.tagged_hash, unsigned_invoice.tagged_hash);
1070+
},
1071+
}
1072+
1073+
let invoice = unsigned_invoice.sign(recipient_sign).unwrap();
10191074

10201075
let mut buffer = Vec::new();
10211076
invoice.write(&mut buffer).unwrap();

lightning/src/offers/invoice_request.rs

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,11 @@ impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a
344344
}
345345

346346
/// A semantically valid [`InvoiceRequest`] that hasn't been signed.
347+
///
348+
/// # Serialization
349+
///
350+
/// This is serialized as a TLV stream, which includes TLV records from the originating message. As
351+
/// such, it may include unknown, odd TLV records.
347352
pub struct UnsignedInvoiceRequest {
348353
bytes: Vec<u8>,
349354
contents: InvoiceRequestContents,
@@ -367,7 +372,9 @@ impl UnsignedInvoiceRequest {
367372
Self { bytes, contents, tagged_hash }
368373
}
369374

370-
/// Signs the invoice request using the given function.
375+
/// Signs the [`TaggedHash`] of the invoice request using the given function.
376+
///
377+
/// Note: The hash computation may have included unknown, odd TLV records.
371378
///
372379
/// This is not exported to bindings users as functions are not yet mapped.
373380
pub fn sign<F, E>(mut self, sign: F) -> Result<InvoiceRequest, SignError<E>>
@@ -664,6 +671,12 @@ impl InvoiceRequestContentsWithoutPayerId {
664671
}
665672
}
666673

674+
impl Writeable for UnsignedInvoiceRequest {
675+
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
676+
WithoutLength(&self.bytes).write(writer)
677+
}
678+
}
679+
667680
impl Writeable for InvoiceRequest {
668681
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
669682
WithoutLength(&self.bytes).write(writer)
@@ -723,6 +736,25 @@ type PartialInvoiceRequestTlvStreamRef<'a> = (
723736
InvoiceRequestTlvStreamRef<'a>,
724737
);
725738

739+
impl TryFrom<Vec<u8>> for UnsignedInvoiceRequest {
740+
type Error = Bolt12ParseError;
741+
742+
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
743+
let invoice_request = ParsedMessage::<PartialInvoiceRequestTlvStream>::try_from(bytes)?;
744+
let ParsedMessage { bytes, tlv_stream } = invoice_request;
745+
let (
746+
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream,
747+
) = tlv_stream;
748+
let contents = InvoiceRequestContents::try_from(
749+
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream)
750+
)?;
751+
752+
let tagged_hash = TaggedHash::new(SIGNATURE_TAG, &bytes);
753+
754+
Ok(UnsignedInvoiceRequest { bytes, contents, tagged_hash })
755+
}
756+
}
757+
726758
impl TryFrom<Vec<u8>> for InvoiceRequest {
727759
type Error = Bolt12ParseError;
728760

@@ -792,7 +824,7 @@ impl TryFrom<PartialInvoiceRequestTlvStream> for InvoiceRequestContents {
792824

793825
#[cfg(test)]
794826
mod tests {
795-
use super::{InvoiceRequest, InvoiceRequestTlvStreamRef, SIGNATURE_TAG};
827+
use super::{InvoiceRequest, InvoiceRequestTlvStreamRef, SIGNATURE_TAG, UnsignedInvoiceRequest};
796828

797829
use bitcoin::blockdata::constants::ChainHash;
798830
use bitcoin::network::constants::Network;
@@ -816,12 +848,24 @@ mod tests {
816848

817849
#[test]
818850
fn builds_invoice_request_with_defaults() {
819-
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
851+
let unsigned_invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
820852
.amount_msats(1000)
821853
.build().unwrap()
822854
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
823-
.build().unwrap()
824-
.sign(payer_sign).unwrap();
855+
.build().unwrap();
856+
857+
let mut buffer = Vec::new();
858+
unsigned_invoice_request.write(&mut buffer).unwrap();
859+
860+
match UnsignedInvoiceRequest::try_from(buffer) {
861+
Err(e) => panic!("error parsing unsigned invoice request: {:?}", e),
862+
Ok(parsed) => {
863+
assert_eq!(parsed.bytes, unsigned_invoice_request.bytes);
864+
assert_eq!(parsed.tagged_hash, unsigned_invoice_request.tagged_hash);
865+
},
866+
}
867+
868+
let invoice_request = unsigned_invoice_request.sign(payer_sign).unwrap();
825869

826870
let mut buffer = Vec::new();
827871
invoice_request.write(&mut buffer).unwrap();

lightning/src/offers/merkle.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ tlv_stream!(SignatureTlvStream, SignatureTlvStreamRef, SIGNATURE_TYPES, {
3030
///
3131
/// [BIP 340]: https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki
3232
/// [BOLT 12]: https://github.com/rustyrussell/lightning-rfc/blob/guilt/offers/12-offer-encoding.md#signature-calculation
33+
#[derive(Debug, PartialEq)]
3334
pub struct TaggedHash(Message);
3435

3536
impl TaggedHash {

0 commit comments

Comments
 (0)