diff --git a/lightning-invoice/src/lib.rs b/lightning-invoice/src/lib.rs index 37c74922ee6..523777455cf 100644 --- a/lightning-invoice/src/lib.rs +++ b/lightning-invoice/src/lib.rs @@ -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::(&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::(&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"); @@ -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. /// /// ``` @@ -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. @@ -236,9 +238,11 @@ pub struct InvoiceBuilder(&str)` +/// 1. using [`InvoiceBuilder`] +/// 2. using [`Invoice::from_signed`] +/// 3. using `str::parse::(&str)` (see [`Invoice::from_str`]) +/// +/// [`Invoice::from_str`]: crate::Invoice#impl-FromStr #[derive(Eq, PartialEq, Debug, Clone, Hash)] pub struct Invoice { signed_invoice: SignedRawInvoice, @@ -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 @@ -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` #[derive(Eq, PartialEq, Debug, Clone, Hash)] @@ -310,7 +314,7 @@ pub struct RawHrp { pub si_prefix: Option, } -/// 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 @@ -564,7 +568,8 @@ impl InvoiceBui } impl InvoiceBuilder { - /// 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 { // If an error occurred at any time before, return it now @@ -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 } @@ -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 @@ -1021,6 +1026,11 @@ impl From 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 @@ -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::*; /// @@ -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 { @@ -1326,7 +1336,7 @@ impl Description { } } - /// Returns the underlying description `String` + /// Returns the underlying description [`String`] pub fn into_inner(self) -> String { self.0 } @@ -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()) } @@ -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 } @@ -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 @@ -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 { @@ -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 { diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index 49c03a44347..48b8cec3536 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -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) = @@ -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(), ( diff --git a/lightning/src/offers/merkle.rs b/lightning/src/offers/merkle.rs index 9782dc7d1e8..94a1eac0ca4 100644 --- a/lightning/src/offers/merkle.rs +++ b/lightning/src/offers/merkle.rs @@ -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()