Skip to content

Commit ea5685c

Browse files
offer: make the merkle tree signature public
This is helpfull for the users that want to use the merkle tree signature in their own code, for example to verify the signature of bolt12 invoices or recreate it. Very useful for people that are building command line tools for the bolt12 offers. Signed-off-by: Vincenzo Palazzo <[email protected]>
1 parent f5dd77c commit ea5685c

File tree

2 files changed

+98
-34
lines changed

2 files changed

+98
-34
lines changed

lightning/src/offers/invoice.rs

Lines changed: 95 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,6 +1032,11 @@ impl Bolt12Invoice {
10321032
InvoiceContents::ForRefund { .. } => self.message_paths().is_empty(),
10331033
}
10341034
}
1035+
1036+
/// Returns the [`TaggedHash`] of the invoice that was signed.
1037+
pub fn tagged_hash(&self) -> &TaggedHash {
1038+
&self.tagged_hash
1039+
}
10351040
}
10361041

10371042
impl PartialEq for Bolt12Invoice {
@@ -1777,6 +1782,14 @@ mod tests {
17771782
EXPERIMENTAL_INVOICE_TYPES, INVOICE_TYPES, SIGNATURE_TAG,
17781783
};
17791784

1785+
use crate::ln::channelmanager::PaymentId;
1786+
use crate::ln::inbound_payment::ExpandedKey;
1787+
use crate::offers::merkle;
1788+
use crate::offers::nonce::Nonce;
1789+
use crate::offers::offer::OfferBuilder;
1790+
use crate::offers::test_utils::{
1791+
payment_hash, payment_paths, recipient_pubkey, recipient_sign, FixedEntropy,
1792+
};
17801793
use bitcoin::address::Address;
17811794
use bitcoin::constants::ChainHash;
17821795
use bitcoin::hashes::Hash;
@@ -1785,37 +1798,8 @@ mod tests {
17851798
use bitcoin::script::ScriptBuf;
17861799
use bitcoin::secp256k1::{self, Keypair, Message, Secp256k1, SecretKey, XOnlyPublicKey};
17871800
use bitcoin::{CompressedPublicKey, WitnessProgram, WitnessVersion};
1788-
17891801
use core::time::Duration;
17901802

1791-
use crate::blinded_path::message::BlindedMessagePath;
1792-
use crate::blinded_path::BlindedHop;
1793-
use crate::ln::channelmanager::PaymentId;
1794-
use crate::ln::inbound_payment::ExpandedKey;
1795-
use crate::ln::msgs::DecodeError;
1796-
use crate::offers::invoice_request::{
1797-
ExperimentalInvoiceRequestTlvStreamRef, InvoiceRequestTlvStreamRef,
1798-
};
1799-
use crate::offers::merkle::{self, SignError, SignatureTlvStreamRef, TaggedHash, TlvStream};
1800-
use crate::offers::nonce::Nonce;
1801-
use crate::offers::offer::{
1802-
Amount, ExperimentalOfferTlvStreamRef, OfferTlvStreamRef, Quantity,
1803-
};
1804-
use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError};
1805-
use crate::offers::payer::PayerTlvStreamRef;
1806-
use crate::offers::test_utils::*;
1807-
use crate::prelude::*;
1808-
use crate::types::features::{Bolt12InvoiceFeatures, InvoiceRequestFeatures, OfferFeatures};
1809-
use crate::types::string::PrintableString;
1810-
use crate::util::ser::{BigSize, Iterable, Writeable};
1811-
#[cfg(not(c_bindings))]
1812-
use {crate::offers::offer::OfferBuilder, crate::offers::refund::RefundBuilder};
1813-
#[cfg(c_bindings)]
1814-
use {
1815-
crate::offers::offer::OfferWithExplicitMetadataBuilder as OfferBuilder,
1816-
crate::offers::refund::RefundMaybeWithDerivedMetadataBuilder as RefundBuilder,
1817-
};
1818-
18191803
trait ToBytes {
18201804
fn to_bytes(&self) -> Vec<u8>;
18211805
}
@@ -3560,4 +3544,86 @@ mod tests {
35603544
),
35613545
}
35623546
}
3547+
3548+
#[test]
3549+
fn invoice_offer_id_matches_offer_id() {
3550+
let expanded_key = ExpandedKey::new([42; 32]);
3551+
let entropy = FixedEntropy {};
3552+
let nonce = Nonce::from_entropy_source(&entropy);
3553+
let secp_ctx = Secp256k1::new();
3554+
let payment_id = PaymentId([1; 32]);
3555+
3556+
let offer = OfferBuilder::new(recipient_pubkey()).amount_msats(1000).build().unwrap();
3557+
3558+
let offer_id = offer.id();
3559+
3560+
let invoice_request = offer
3561+
.request_invoice(&expanded_key, nonce, &secp_ctx, payment_id)
3562+
.unwrap()
3563+
.build_and_sign()
3564+
.unwrap();
3565+
3566+
let invoice = invoice_request
3567+
.respond_with_no_std(payment_paths(), payment_hash(), now())
3568+
.unwrap()
3569+
.build()
3570+
.unwrap()
3571+
.sign(recipient_sign)
3572+
.unwrap();
3573+
3574+
assert_eq!(invoice.offer_id(), Some(offer_id));
3575+
}
3576+
3577+
#[test]
3578+
fn refund_invoice_has_no_offer_id() {
3579+
let refund =
3580+
RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap().build().unwrap();
3581+
3582+
let invoice = refund
3583+
.respond_with_no_std(payment_paths(), payment_hash(), recipient_pubkey(), now())
3584+
.unwrap()
3585+
.build()
3586+
.unwrap()
3587+
.sign(recipient_sign)
3588+
.unwrap();
3589+
3590+
assert_eq!(invoice.offer_id(), None);
3591+
}
3592+
3593+
#[test]
3594+
fn verifies_invoice_signature_with_tagged_hash() {
3595+
let secp_ctx = Secp256k1::new();
3596+
let expanded_key = ExpandedKey::new([42; 32]);
3597+
let entropy = FixedEntropy {};
3598+
let nonce = Nonce::from_entropy_source(&entropy);
3599+
let node_id = recipient_pubkey();
3600+
let payment_paths = payment_paths();
3601+
let now = Duration::from_secs(123456);
3602+
let payment_id = PaymentId([1; 32]);
3603+
3604+
let offer = OfferBuilder::new(node_id)
3605+
.amount_msats(1000)
3606+
.path(crate::offers::test_utils::blinded_path())
3607+
.build()
3608+
.unwrap();
3609+
3610+
let invoice_request = offer
3611+
.request_invoice(&expanded_key, nonce, &secp_ctx, payment_id)
3612+
.unwrap()
3613+
.build_and_sign()
3614+
.unwrap();
3615+
3616+
let invoice = invoice_request
3617+
.respond_with_no_std(payment_paths, payment_hash(), now)
3618+
.unwrap()
3619+
.build()
3620+
.unwrap()
3621+
.sign(recipient_sign)
3622+
.unwrap();
3623+
3624+
let issuer_sign_pubkey = offer.issuer_signing_pubkey().unwrap();
3625+
let tagged_hash = invoice.tagged_hash();
3626+
let signature = invoice.signature();
3627+
assert!(merkle::verify_signature(&signature, tagged_hash, issuer_sign_pubkey).is_ok());
3628+
}
35633629
}

lightning/src/offers/merkle.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ pub enum SignError {
9494
}
9595

9696
/// A function for signing a [`TaggedHash`].
97-
pub(super) trait SignFn<T: AsRef<TaggedHash>> {
97+
pub trait SignFn<T: AsRef<TaggedHash>> {
9898
/// Signs a [`TaggedHash`] computed over the merkle root of `message`'s TLV stream.
9999
fn sign(&self, message: &T) -> Result<Signature, ()>;
100100
}
@@ -117,9 +117,7 @@ where
117117
///
118118
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
119119
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
120-
pub(super) fn sign_message<F, T>(
121-
f: F, message: &T, pubkey: PublicKey,
122-
) -> Result<Signature, SignError>
120+
pub fn sign_message<F, T>(f: F, message: &T, pubkey: PublicKey) -> Result<Signature, SignError>
123121
where
124122
F: SignFn<T>,
125123
T: AsRef<TaggedHash>,
@@ -136,7 +134,7 @@ where
136134

137135
/// Verifies the signature with a pubkey over the given message using a tagged hash as the message
138136
/// digest.
139-
pub(super) fn verify_signature(
137+
pub fn verify_signature(
140138
signature: &Signature, message: &TaggedHash, pubkey: PublicKey,
141139
) -> Result<(), secp256k1::Error> {
142140
let digest = message.as_digest();

0 commit comments

Comments
 (0)