Skip to content

Commit 3137eb9

Browse files
committed
Offer raw byte encoding and decoding
Offers are typically encoded as bech32 strings for use in QR codes. However, a more compact encoding is useful when storing offers long-term. Implement Writeable for Offer and have a corresponding TryFrom implementation for decoding the raw bytes.
1 parent e39f648 commit 3137eb9

File tree

1 file changed

+44
-3
lines changed

1 file changed

+44
-3
lines changed

lightning/src/offers/offer.rs

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@
1818
//! extern crate core;
1919
//! extern crate lightning;
2020
//!
21+
//! use core::convert::TryFrom;
2122
//! use core::num::NonZeroU64;
2223
//! use core::time::Duration;
2324
//!
2425
//! use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey};
2526
//! use lightning::offers::offer::{Amount, Offer, OfferBuilder};
2627
//! use lightning::offers::parse::ParseError;
28+
//! use lightning::util::ser::{Readable, Writeable};
2729
//!
2830
//! # use lightning::onion_message::BlindedPath;
2931
//! # #[cfg(feature = "std")]
@@ -54,6 +56,13 @@
5456
//!
5557
//! // Parse from a bech32 string after scanning from a QR code.
5658
//! let offer = encoded_offer.parse::<Offer>()?;
59+
//!
60+
//! // Encode offer as raw bytes.
61+
//! let mut bytes = Vec::new();
62+
//! offer.write(&mut bytes).unwrap();
63+
//!
64+
//! // Decode raw bytes into an offer.
65+
//! let offer = Offer::try_from(bytes)?;
5766
//! # Ok(())
5867
//! # }
5968
//! ```
@@ -71,7 +80,7 @@ use ln::features::OfferFeatures;
7180
use ln::msgs::MAX_VALUE_MSAT;
7281
use offers::parse::{Bech32Encode, ParseError, SemanticError};
7382
use onion_message::BlindedPath;
74-
use util::ser::{HighZeroBytesDroppedBigSize, WithoutLength, Writeable, Writer};
83+
use util::ser::{HighZeroBytesDroppedBigSize, Readable, WithoutLength, Writeable, Writer};
7584

7685
use prelude::*;
7786

@@ -408,12 +417,27 @@ impl OfferContents {
408417
}
409418
}
410419

420+
impl Writeable for Offer {
421+
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
422+
WithoutLength(&self.bytes).write(writer)
423+
}
424+
}
425+
411426
impl Writeable for OfferContents {
412427
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
413428
self.as_tlv_stream().write(writer)
414429
}
415430
}
416431

432+
impl TryFrom<Vec<u8>> for Offer {
433+
type Error = ParseError;
434+
435+
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
436+
let tlv_stream: OfferTlvStream = Readable::read(&mut &bytes[..])?;
437+
Offer::try_from((bytes, tlv_stream))
438+
}
439+
}
440+
417441
/// The minimum amount required for an item in an [`Offer`], denominated in either bitcoin or
418442
/// another currency.
419443
#[derive(Clone, Debug, PartialEq)]
@@ -466,6 +490,8 @@ impl Bech32Encode for Offer {
466490
const BECH32_HRP: &'static str = "lno";
467491
}
468492

493+
type ParsedOffer = (Vec<u8>, OfferTlvStream);
494+
469495
impl FromStr for Offer {
470496
type Err = ParseError;
471497

@@ -476,6 +502,16 @@ impl FromStr for Offer {
476502
}
477503
}
478504

505+
impl TryFrom<ParsedOffer> for Offer {
506+
type Error = ParseError;
507+
508+
fn try_from(offer: ParsedOffer) -> Result<Self, Self::Error> {
509+
let (bytes, tlv_stream) = offer;
510+
let contents = OfferContents::try_from(tlv_stream)?;
511+
Ok(Offer { bytes, contents })
512+
}
513+
}
514+
479515
impl TryFrom<OfferTlvStream> for OfferContents {
480516
type Error = SemanticError;
481517

@@ -544,11 +580,12 @@ impl core::fmt::Display for Offer {
544580

545581
#[cfg(test)]
546582
mod tests {
547-
use super::{Amount, OfferBuilder};
583+
use super::{Amount, Offer, OfferBuilder};
548584

549585
use bitcoin::blockdata::constants::ChainHash;
550586
use bitcoin::network::constants::Network;
551587
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
588+
use core::convert::TryFrom;
552589
use core::num::NonZeroU64;
553590
use core::time::Duration;
554591
use ln::features::OfferFeatures;
@@ -571,7 +608,7 @@ mod tests {
571608
let offer = OfferBuilder::new("foo".into(), pubkey(42)).build().unwrap();
572609
let tlv_stream = offer.as_tlv_stream();
573610
let mut buffer = Vec::new();
574-
offer.contents.write(&mut buffer).unwrap();
611+
offer.write(&mut buffer).unwrap();
575612

576613
assert_eq!(offer.bytes, buffer.as_slice());
577614
assert_eq!(offer.chains(), vec![ChainHash::using_genesis_block(Network::Bitcoin)]);
@@ -600,6 +637,10 @@ mod tests {
600637
assert_eq!(tlv_stream.quantity_min, None);
601638
assert_eq!(tlv_stream.quantity_max, None);
602639
assert_eq!(tlv_stream.node_id, Some(&pubkey(42)));
640+
641+
if let Err(e) = Offer::try_from(buffer) {
642+
panic!("error parsing offer: {:?}", e);
643+
}
603644
}
604645

605646
#[test]

0 commit comments

Comments
 (0)