Skip to content
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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Expose signable hashes for BOLT 11 and BOLT 12 invoices by jkczyz · Pull Request #2162 · lightningdevkit/rust-lightning · GitHub
Skip to content
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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Expose signable hashes for BOLT 11 and BOLT 12 invoices by jkczyz · Pull Request #2162 · lightningdevkit/rust-lightning · GitHub
Skip to content
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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Expose signable hashes for BOLT 11 and BOLT 12 invoices by jkczyz · Pull Request #2162 · lightningdevkit/rust-lightning · GitHub
Skip to content
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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Expose signable hashes for BOLT 11 and BOLT 12 invoices by jkczyz · Pull Request #2162 · lightningdevkit/rust-lightning · GitHub
Skip to content
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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Expose signable hashes for BOLT 11 and BOLT 12 invoices by jkczyz · Pull Request #2162 · lightningdevkit/rust-lightning · GitHub
Skip to content
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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); Expose signable hashes for BOLT 11 and BOLT 12 invoices by jkczyz · Pull Request #2162 · lightningdevkit/rust-lightning · GitHub
Skip to content
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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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