use bitcoin::blockdata::constants::ChainHash;
use bitcoin::secp256k1::PublicKey;
use bitcoin::secp256k1::schnorr::Signature;
+use core::convert::TryFrom;
+use crate::io;
use crate::ln::features::InvoiceRequestFeatures;
-use crate::offers::offer::OfferContents;
-use crate::offers::payer::PayerContents;
+use crate::ln::msgs::DecodeError;
+use crate::offers::merkle::{SignatureTlvStream, self};
+use crate::offers::offer::{Amount, OfferContents, OfferTlvStream};
+use crate::offers::parse::{ParseError, ParsedMessage, SemanticError};
+use crate::offers::payer::{PayerContents, PayerTlvStream};
+use crate::util::ser::{HighZeroBytesDroppedBigSize, SeekReadable, WithoutLength, Writeable, Writer};
use crate::util::string::PrintableString;
use crate::prelude::*;
/// The contents of an [`InvoiceRequest`], which may be shared with an `Invoice`.
#[derive(Clone, Debug)]
-pub(crate) struct InvoiceRequestContents {
+pub(super) struct InvoiceRequestContents {
payer: PayerContents,
offer: OfferContents,
chain: Option<ChainHash>,
&self.contents.features
}
- /// The quantity of the offer's item conforming to [`Offer::supported_quantity`].
+ /// The quantity of the offer's item conforming to [`Offer::is_valid_quantity`].
///
- /// [`Offer::supported_quantity`]: crate::offers::offer::Offer::supported_quantity
+ /// [`Offer::is_valid_quantity`]: crate::offers::offer::Offer::is_valid_quantity
pub fn quantity(&self) -> Option<u64> {
self.contents.quantity
}
self.signature
}
}
+
+impl Writeable for InvoiceRequest {
+ fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
+ WithoutLength(&self.bytes).write(writer)
+ }
+}
+
+tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, 80..160, {
+ (80, chain: ChainHash),
+ (82, amount: (u64, HighZeroBytesDroppedBigSize)),
+ (84, features: InvoiceRequestFeatures),
+ (86, quantity: (u64, HighZeroBytesDroppedBigSize)),
+ (88, payer_id: PublicKey),
+ (89, payer_note: (String, WithoutLength)),
+});
+
+type FullInvoiceRequestTlvStream =
+ (PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, SignatureTlvStream);
+
+impl SeekReadable for FullInvoiceRequestTlvStream {
+ fn read<R: io::Read + io::Seek>(r: &mut R) -> Result<Self, DecodeError> {
+ let payer = SeekReadable::read(r)?;
+ let offer = SeekReadable::read(r)?;
+ let invoice_request = SeekReadable::read(r)?;
+ let signature = SeekReadable::read(r)?;
+
+ Ok((payer, offer, invoice_request, signature))
+ }
+}
+
+type PartialInvoiceRequestTlvStream = (PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream);
+
+impl TryFrom<Vec<u8>> for InvoiceRequest {
+ type Error = ParseError;
+
+ fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
+ let invoice_request = ParsedMessage::<FullInvoiceRequestTlvStream>::try_from(bytes)?;
+ let ParsedMessage { bytes, tlv_stream } = invoice_request;
+ let (
+ payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream,
+ SignatureTlvStream { signature },
+ ) = tlv_stream;
+ let contents = InvoiceRequestContents::try_from(
+ (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream)
+ )?;
+
+ if let Some(signature) = &signature {
+ let tag = concat!("lightning", "invoice_request", "signature");
+ merkle::verify_signature(signature, tag, &bytes, contents.payer_id)?;
+ }
+
+ Ok(InvoiceRequest { bytes, contents, signature })
+ }
+}
+
+impl TryFrom<PartialInvoiceRequestTlvStream> for InvoiceRequestContents {
+ type Error = SemanticError;
+
+ fn try_from(tlv_stream: PartialInvoiceRequestTlvStream) -> Result<Self, Self::Error> {
+ let (
+ PayerTlvStream { metadata },
+ offer_tlv_stream,
+ InvoiceRequestTlvStream { chain, amount, features, quantity, payer_id, payer_note },
+ ) = tlv_stream;
+
+ let payer = match metadata {
+ None => return Err(SemanticError::MissingPayerMetadata),
+ Some(metadata) => PayerContents(metadata),
+ };
+ let offer = OfferContents::try_from(offer_tlv_stream)?;
+
+ if !offer.supports_chain(chain.unwrap_or_else(|| offer.implied_chain())) {
+ return Err(SemanticError::UnsupportedChain);
+ }
+
+ let amount_msats = match (offer.amount(), amount) {
+ (None, None) => return Err(SemanticError::MissingAmount),
+ (Some(Amount::Currency { .. }), _) => return Err(SemanticError::UnsupportedCurrency),
+ (_, amount_msats) => amount_msats,
+ };
+
+ let features = features.unwrap_or_else(InvoiceRequestFeatures::empty);
+
+ let expects_quantity = offer.expects_quantity();
+ let quantity = match quantity {
+ None if expects_quantity => return Err(SemanticError::MissingQuantity),
+ Some(_) if !expects_quantity => return Err(SemanticError::UnexpectedQuantity),
+ Some(quantity) if !offer.is_valid_quantity(quantity) => {
+ return Err(SemanticError::InvalidQuantity);
+ }
+ quantity => quantity,
+ };
+
+ {
+ let amount_msats = amount_msats.unwrap_or(offer.amount_msats());
+ let quantity = quantity.unwrap_or(1);
+ if amount_msats < offer.expected_invoice_amount_msats(quantity) {
+ return Err(SemanticError::InsufficientAmount);
+ }
+ }
+
+ let payer_id = match payer_id {
+ None => return Err(SemanticError::MissingPayerId),
+ Some(payer_id) => payer_id,
+ };
+
+ Ok(InvoiceRequestContents {
+ payer, offer, chain, amount_msats, features, quantity, payer_id, payer_note,
+ })
+ }
+}
//! Tagged hashes for use in signature calculation and verification.
use bitcoin::hashes::{Hash, HashEngine, sha256};
+use bitcoin::secp256k1::{Message, PublicKey, Secp256k1, self};
+use bitcoin::secp256k1::schnorr::Signature;
use crate::io;
use crate::util::ser::{BigSize, Readable};
/// Valid type range for signature TLV records.
const SIGNATURE_TYPES: core::ops::RangeInclusive<u64> = 240..=1000;
+tlv_stream!(SignatureTlvStream, SignatureTlvStreamRef, SIGNATURE_TYPES, {
+ (240, signature: Signature),
+});
+
+/// Verifies the signature with a pubkey over the given bytes using a tagged hash as the message
+/// digest.
+///
+/// Panics if `bytes` is not a well-formed TLV stream containing at least one TLV record.
+pub(super) fn verify_signature(
+ signature: &Signature, tag: &str, bytes: &[u8], pubkey: PublicKey,
+) -> Result<(), secp256k1::Error> {
+ let tag = sha256::Hash::hash(tag.as_bytes());
+ let merkle_root = root_hash(bytes);
+ let digest = Message::from_slice(&tagged_hash(tag, merkle_root)).unwrap();
+ let pubkey = pubkey.into();
+ let secp_ctx = Secp256k1::verification_only();
+ secp_ctx.verify_schnorr(signature, &digest, &pubkey)
+}
+
/// Computes a merkle root hash for the given data, which must be a well-formed TLV stream
/// containing at least one TLV record.
fn root_hash(data: &[u8]) -> sha256::Hash {
self.contents.chains()
}
+ /// Returns whether the given chain is supported by the offer.
+ pub fn supports_chain(&self, chain: ChainHash) -> bool {
+ self.contents.supports_chain(chain)
+ }
+
// TODO: Link to corresponding method in `InvoiceRequest`.
/// Opaque bytes set by the originator. Useful for authentication and validating fields since it
/// is reflected in `invoice_request` messages along with all the other fields from the `offer`.
/// The minimum amount required for a successful payment of a single item.
pub fn amount(&self) -> Option<&Amount> {
- self.contents.amount.as_ref()
+ self.contents.amount()
}
/// A complete description of the purpose of the payment. Intended to be displayed to the user
self.contents.supported_quantity()
}
+ /// Returns whether the given quantity is valid for the offer.
+ pub fn is_valid_quantity(&self, quantity: u64) -> bool {
+ self.contents.is_valid_quantity(quantity)
+ }
+
+ /// Returns whether a quantity is expected in an [`InvoiceRequest`] for the offer.
+ ///
+ /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
+ pub fn expects_quantity(&self) -> bool {
+ self.contents.expects_quantity()
+ }
+
/// The public key used by the recipient to sign invoices.
pub fn signing_pubkey(&self) -> PublicKey {
self.contents.signing_pubkey.unwrap()
ChainHash::using_genesis_block(Network::Bitcoin)
}
+ pub fn supports_chain(&self, chain: ChainHash) -> bool {
+ self.chains().contains(&chain)
+ }
+
+ pub fn amount(&self) -> Option<&Amount> {
+ self.amount.as_ref()
+ }
+
+ pub fn amount_msats(&self) -> u64 {
+ match self.amount() {
+ None => 0,
+ Some(&Amount::Bitcoin { amount_msats }) => amount_msats,
+ Some(&Amount::Currency { .. }) => unreachable!(),
+ }
+ }
+
+ pub fn expected_invoice_amount_msats(&self, quantity: u64) -> u64 {
+ self.amount_msats() * quantity
+ }
+
pub fn supported_quantity(&self) -> Quantity {
self.supported_quantity
}
+ pub fn is_valid_quantity(&self, quantity: u64) -> bool {
+ match self.supported_quantity {
+ Quantity::Bounded(n) => {
+ let n = n.get();
+ if n == 1 { false }
+ else { quantity > 0 && quantity <= n }
+ },
+ Quantity::Unbounded => quantity > 0,
+ }
+ }
+
+ pub fn expects_quantity(&self) -> bool {
+ match self.supported_quantity {
+ Quantity::Bounded(n) => n.get() != 1,
+ Quantity::Unbounded => true,
+ }
+ }
+
fn as_tlv_stream(&self) -> OfferTlvStreamRef {
let (currency, amount) = match &self.amount {
None => (None, None),
assert_eq!(offer.bytes, buffer.as_slice());
assert_eq!(offer.chains(), vec![ChainHash::using_genesis_block(Network::Bitcoin)]);
+ assert!(offer.supports_chain(ChainHash::using_genesis_block(Network::Bitcoin)));
assert_eq!(offer.metadata(), None);
assert_eq!(offer.amount(), None);
assert_eq!(offer.description(), PrintableString("foo"));
.chain(Network::Bitcoin)
.build()
.unwrap();
+ assert!(offer.supports_chain(mainnet));
assert_eq!(offer.chains(), vec![mainnet]);
assert_eq!(offer.as_tlv_stream().chains, None);
.chain(Network::Testnet)
.build()
.unwrap();
+ assert!(offer.supports_chain(testnet));
assert_eq!(offer.chains(), vec![testnet]);
assert_eq!(offer.as_tlv_stream().chains, Some(&vec![testnet]));
.chain(Network::Testnet)
.build()
.unwrap();
+ assert!(offer.supports_chain(testnet));
assert_eq!(offer.chains(), vec![testnet]);
assert_eq!(offer.as_tlv_stream().chains, Some(&vec![testnet]));
.chain(Network::Testnet)
.build()
.unwrap();
+ assert!(offer.supports_chain(mainnet));
+ assert!(offer.supports_chain(testnet));
assert_eq!(offer.chains(), vec![mainnet, testnet]);
assert_eq!(offer.as_tlv_stream().chains, Some(&vec![mainnet, testnet]));
}
use bitcoin::bech32;
use bitcoin::bech32::{FromBase32, ToBase32};
+use bitcoin::secp256k1;
use core::convert::TryFrom;
use core::fmt;
use crate::io;
Decode(DecodeError),
/// The parsed message has invalid semantics.
InvalidSemantics(SemanticError),
+ /// The parsed message has an invalid signature.
+ InvalidSignature(secp256k1::Error),
}
/// Error when interpreting a TLV stream as a specific type.
#[derive(Debug, PartialEq)]
pub enum SemanticError {
+ /// The provided chain hash does not correspond to a supported chain.
+ UnsupportedChain,
/// An amount was expected but was missing.
MissingAmount,
/// The amount exceeded the total bitcoin supply.
InvalidAmount,
+ /// An amount was provided but was not sufficient in value.
+ InsufficientAmount,
/// A currency was provided that is not supported.
UnsupportedCurrency,
/// A required description was not provided.
MissingDescription,
/// A signing pubkey was not provided.
MissingSigningPubkey,
+ /// A quantity was expected but was missing.
+ MissingQuantity,
/// An unsupported quantity was provided.
InvalidQuantity,
+ /// A quantity or quantity bounds was provided but was not expected.
+ UnexpectedQuantity,
+ /// Payer metadata was expected but was missing.
+ MissingPayerMetadata,
+ /// A payer id was expected but was missing.
+ MissingPayerId,
}
impl From<bech32::Error> for ParseError {
Self::InvalidSemantics(error)
}
}
+
+impl From<secp256k1::Error> for ParseError {
+ fn from(error: secp256k1::Error) -> Self {
+ Self::InvalidSignature(error)
+ }
+}
//! Data structures and encoding for `invoice_request_metadata` records.
+use crate::util::ser::WithoutLength;
+
use crate::prelude::*;
/// An unpredictable sequence of bytes typically containing information needed to derive
///
/// [`InvoiceRequest::payer_id`]: crate::offers::invoice_request::InvoiceRequest::payer_id
#[derive(Clone, Debug)]
-pub(crate) struct PayerContents(pub Vec<u8>);
+pub(super) struct PayerContents(pub Vec<u8>);
+
+tlv_stream!(PayerTlvStream, PayerTlvStreamRef, 0..1, {
+ (0, metadata: (Vec<u8>, WithoutLength)),
+});
#[derive(Debug)]
pub(super) struct $name {
$(
- $field: Option<tlv_record_type!($fieldty)>,
+ pub(super) $field: Option<tlv_record_type!($fieldty)>,
)*
}