Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose signable hashes for BOLT 11 and BOLT 12 invoices #2162

Merged
merged 3 commits into from
Apr 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 43 additions & 33 deletions lightning-invoice/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@
//! invoices and functions to create, encode and decode these. If you just want to use the standard
//! en-/decoding functionality this should get you started:
//!
//! * For parsing use `str::parse::<Invoice>(&self)` (see the docs of `impl FromStr for Invoice`)
//! * For constructing invoices use the `InvoiceBuilder`
//! * For serializing invoices use the `Display`/`ToString` traits
//! * For parsing use `str::parse::<Invoice>(&self)` (see [`Invoice::from_str`])
//! * For constructing invoices use the [`InvoiceBuilder`]
//! * For serializing invoices use the [`Display`]/[`ToString`] traits
//!
//! [`Invoice::from_str`]: crate::Invoice#impl-FromStr

#[cfg(not(any(feature = "std", feature = "no-std")))]
compile_error!("at least one of the `std` or `no-std` features must be enabled");
Expand Down Expand Up @@ -160,7 +162,7 @@ pub const DEFAULT_EXPIRY_TIME: u64 = 3600;
/// [`MIN_FINAL_CLTV_EXPIRY_DELTA`]: lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA
pub const DEFAULT_MIN_FINAL_CLTV_EXPIRY_DELTA: u64 = 18;

/// Builder for `Invoice`s. It's the most convenient and advised way to use this library. It ensures
/// Builder for [`Invoice`]s. It's the most convenient and advised way to use this library. It ensures
/// that only a semantically and syntactically correct Invoice can be built using it.
///
/// ```
Expand Down Expand Up @@ -212,8 +214,8 @@ pub const DEFAULT_MIN_FINAL_CLTV_EXPIRY_DELTA: u64 = 18;
/// # Type parameters
/// The two parameters `D` and `H` signal if the builder already contains the correct amount of the
/// given field:
/// * `D`: exactly one `Description` or `DescriptionHash`
/// * `H`: exactly one `PaymentHash`
/// * `D`: exactly one [`TaggedField::Description`] or [`TaggedField::DescriptionHash`]
/// * `H`: exactly one [`TaggedField::PaymentHash`]
/// * `T`: the timestamp is set
///
/// (C-not exported) as we likely need to manually select one set of boolean type parameters.
Expand All @@ -236,9 +238,11 @@ pub struct InvoiceBuilder<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S:
/// Represents a syntactically and semantically correct lightning BOLT11 invoice.
///
/// There are three ways to construct an `Invoice`:
/// 1. using `InvoiceBuilder`
/// 2. using `Invoice::from_signed(SignedRawInvoice)`
/// 3. using `str::parse::<Invoice>(&str)`
/// 1. using [`InvoiceBuilder`]
/// 2. using [`Invoice::from_signed`]
/// 3. using `str::parse::<Invoice>(&str)` (see [`Invoice::from_str`])
///
/// [`Invoice::from_str`]: crate::Invoice#impl-FromStr
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
pub struct Invoice {
signed_invoice: SignedRawInvoice,
Expand All @@ -258,34 +262,34 @@ pub enum InvoiceDescription<'f> {
Hash(&'f Sha256),
}

/// Represents a signed `RawInvoice` with cached hash. The signature is not checked and may be
/// Represents a signed [`RawInvoice`] with cached hash. The signature is not checked and may be
/// invalid.
///
/// # Invariants
/// The hash has to be either from the deserialized invoice or from the serialized `raw_invoice`.
/// The hash has to be either from the deserialized invoice or from the serialized [`RawInvoice`].
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
pub struct SignedRawInvoice {
/// The rawInvoice that the signature belongs to
raw_invoice: RawInvoice,

/// Hash of the `RawInvoice` that will be used to check the signature.
/// Hash of the [`RawInvoice`] that will be used to check the signature.
///
/// * if the `SignedRawInvoice` was deserialized the hash is of from the original encoded form,
/// since it's not guaranteed that encoding it again will lead to the same result since integers
/// could have been encoded with leading zeroes etc.
/// * if the `SignedRawInvoice` was constructed manually the hash will be the calculated hash
/// from the `RawInvoice`
/// from the [`RawInvoice`]
hash: [u8; 32],

/// signature of the payment request
signature: InvoiceSignature,
}

/// Represents an syntactically correct Invoice for a payment on the lightning network,
/// Represents an syntactically correct [`Invoice`] for a payment on the lightning network,
/// but without the signature information.
/// De- and encoding should not lead to information loss but may lead to different hashes.
/// Decoding and encoding should not lead to information loss but may lead to different hashes.
///
/// For methods without docs see the corresponding methods in `Invoice`.
/// For methods without docs see the corresponding methods in [`Invoice`].
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
pub struct RawInvoice {
/// human readable part
Expand All @@ -295,7 +299,7 @@ pub struct RawInvoice {
pub data: RawDataPart,
}

/// Data of the `RawInvoice` that is encoded in the human readable part
/// Data of the [`RawInvoice`] that is encoded in the human readable part.
///
/// (C-not exported) As we don't yet support `Option<Enum>`
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
Expand All @@ -310,7 +314,7 @@ pub struct RawHrp {
pub si_prefix: Option<SiPrefix>,
}

/// Data of the `RawInvoice` that is encoded in the data part
/// Data of the [`RawInvoice`] that is encoded in the data part
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
pub struct RawDataPart {
/// generation time of the invoice
Expand Down Expand Up @@ -564,7 +568,8 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBui
}

impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb::True, C, S> {
/// Builds a `RawInvoice` if no `CreationError` occurred while construction any of the fields.
/// Builds a [`RawInvoice`] if no [`CreationError`] occurred while construction any of the
/// fields.
pub fn build_raw(self) -> Result<RawInvoice, CreationError> {

// If an error occurred at any time before, return it now
Expand Down Expand Up @@ -741,17 +746,17 @@ impl SignedRawInvoice {
(self.raw_invoice, self.hash, self.signature)
}

/// The `RawInvoice` which was signed.
/// The [`RawInvoice`] which was signed.
pub fn raw_invoice(&self) -> &RawInvoice {
&self.raw_invoice
}

/// The hash of the `RawInvoice` that was signed.
/// The hash of the [`RawInvoice`] that was signed.
pub fn signable_hash(&self) -> &[u8; 32] {
&self.hash
}

/// InvoiceSignature for the invoice.
/// Signature for the invoice.
pub fn signature(&self) -> &InvoiceSignature {
&self.signature
}
Expand Down Expand Up @@ -869,8 +874,8 @@ impl RawInvoice {
)
}

/// Signs the invoice using the supplied `sign_function`. This function MAY fail with an error
/// of type `E`. Since the signature of a `SignedRawInvoice` is not required to be valid there
/// Signs the invoice using the supplied `sign_method`. This function MAY fail with an error of
/// type `E`. Since the signature of a [`SignedRawInvoice`] is not required to be valid there
/// are no constraints regarding the validity of the produced signature.
///
/// (C-not exported) As we don't currently support passing function pointers into methods
Expand Down Expand Up @@ -1021,6 +1026,11 @@ impl From<PositiveTimestamp> for SystemTime {
}

impl Invoice {
/// The hash of the [`RawInvoice`] that was signed.
pub fn signable_hash(&self) -> [u8; 32] {
self.signed_invoice.hash
}

/// Transform the `Invoice` into it's unchecked version
pub fn into_signed_raw(self) -> SignedRawInvoice {
self.signed_invoice
Expand Down Expand Up @@ -1125,7 +1135,7 @@ impl Invoice {
Ok(())
}

/// Constructs an `Invoice` from a `SignedRawInvoice` by checking all its invariants.
/// Constructs an `Invoice` from a [`SignedRawInvoice`] by checking all its invariants.
/// ```
/// use lightning_invoice::*;
///
Expand Down Expand Up @@ -1315,7 +1325,7 @@ impl TaggedField {
impl Description {

/// Creates a new `Description` if `description` is at most 1023 __bytes__ long,
/// returns `CreationError::DescriptionTooLong` otherwise
/// returns [`CreationError::DescriptionTooLong`] otherwise
///
/// Please note that single characters may use more than one byte due to UTF8 encoding.
pub fn new(description: String) -> Result<Description, CreationError> {
Expand All @@ -1326,7 +1336,7 @@ impl Description {
}
}

/// Returns the underlying description `String`
/// Returns the underlying description [`String`]
pub fn into_inner(self) -> String {
self.0
}
Expand Down Expand Up @@ -1366,7 +1376,7 @@ impl ExpiryTime {
ExpiryTime(Duration::from_secs(seconds))
}

/// Construct an `ExpiryTime` from a `Duration`, dropping the sub-second part.
/// Construct an `ExpiryTime` from a [`Duration`], dropping the sub-second part.
pub fn from_duration(duration: Duration) -> ExpiryTime {
Self::from_seconds(duration.as_secs())
}
Expand All @@ -1376,7 +1386,7 @@ impl ExpiryTime {
self.0.as_secs()
}

/// Returns a reference to the underlying `Duration` (=expiry time)
/// Returns a reference to the underlying [`Duration`] (=expiry time)
pub fn as_duration(&self) -> &Duration {
&self.0
}
Expand Down Expand Up @@ -1428,10 +1438,10 @@ impl Deref for SignedRawInvoice {
}
}

/// Errors that may occur when constructing a new `RawInvoice` or `Invoice`
/// Errors that may occur when constructing a new [`RawInvoice`] or [`Invoice`]
#[derive(Eq, PartialEq, Debug, Clone)]
pub enum CreationError {
/// The supplied description string was longer than 639 __bytes__ (see [`Description::new(…)`](./struct.Description.html#method.new))
/// The supplied description string was longer than 639 __bytes__ (see [`Description::new`])
DescriptionTooLong,

/// The specified route has too many hops and can't be encoded
Expand Down Expand Up @@ -1472,7 +1482,7 @@ impl Display for CreationError {
#[cfg(feature = "std")]
impl std::error::Error for CreationError { }

/// Errors that may occur when converting a `RawInvoice` to an `Invoice`. They relate to the
/// Errors that may occur when converting a [`RawInvoice`] to an [`Invoice`]. They relate to the
/// requirements sections in BOLT #11
#[derive(Eq, PartialEq, Debug, Clone)]
pub enum SemanticError {
Expand Down Expand Up @@ -1528,7 +1538,7 @@ impl Display for SemanticError {
#[cfg(feature = "std")]
impl std::error::Error for SemanticError { }

/// When signing using a fallible method either an user-supplied `SignError` or a `CreationError`
/// When signing using a fallible method either an user-supplied `SignError` or a [`CreationError`]
/// may occur.
#[derive(Eq, PartialEq, Debug, Clone)]
pub enum SignOrCreationError<S = ()> {
Expand Down
10 changes: 10 additions & 0 deletions lightning/src/offers/invoice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -469,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) =
Expand Down Expand Up @@ -937,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(),
(
Expand Down
2 changes: 1 addition & 1 deletion lightning/src/offers/merkle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ pub(super) fn verify_signature(
secp_ctx.verify_schnorr(signature, &digest, &pubkey)
}

fn message_digest(tag: &str, bytes: &[u8]) -> Message {
pub(super) fn message_digest(tag: &str, bytes: &[u8]) -> Message {
let tag = sha256::Hash::hash(tag.as_bytes());
let merkle_root = root_hash(bytes);
Message::from_slice(&tagged_hash(tag, merkle_root)).unwrap()
Expand Down