Skip to content

Commit ead5b22

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 ead5b22

File tree

2 files changed

+101
-5
lines changed

2 files changed

+101
-5
lines changed

lightning/src/offers/invoice.rs

Lines changed: 98 additions & 0 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 {
@@ -3560,4 +3565,97 @@ mod tests {
35603565
),
35613566
}
35623567
}
3568+
3569+
#[test]
3570+
fn invoice_offer_id_matches_offer_id() {
3571+
let expanded_key = ExpandedKey::new([42; 32]);
3572+
let entropy = FixedEntropy {};
3573+
let nonce = Nonce::from_entropy_source(&entropy);
3574+
let secp_ctx = Secp256k1::new();
3575+
let payment_id = PaymentId([1; 32]);
3576+
3577+
let offer = OfferBuilder::new(recipient_pubkey()).amount_msats(1000).build().unwrap();
3578+
3579+
let offer_id = offer.id();
3580+
3581+
let invoice_request = offer
3582+
.request_invoice(&expanded_key, nonce, &secp_ctx, payment_id)
3583+
.unwrap()
3584+
.build_and_sign()
3585+
.unwrap();
3586+
3587+
let invoice = invoice_request
3588+
.respond_with_no_std(payment_paths(), payment_hash(), now())
3589+
.unwrap()
3590+
.build()
3591+
.unwrap()
3592+
.sign(recipient_sign)
3593+
.unwrap();
3594+
3595+
assert_eq!(invoice.offer_id(), Some(offer_id));
3596+
}
3597+
3598+
#[test]
3599+
fn refund_invoice_has_no_offer_id() {
3600+
let refund =
3601+
RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap().build().unwrap();
3602+
3603+
let invoice = refund
3604+
.respond_with_no_std(payment_paths(), payment_hash(), recipient_pubkey(), now())
3605+
.unwrap()
3606+
.build()
3607+
.unwrap()
3608+
.sign(recipient_sign)
3609+
.unwrap();
3610+
3611+
assert_eq!(invoice.offer_id(), None);
3612+
}
3613+
3614+
#[test]
3615+
fn verifies_invoice_signature_with_tagged_hash() {
3616+
use crate::ln::channelmanager::PaymentId;
3617+
use crate::ln::inbound_payment::ExpandedKey;
3618+
use crate::offers::merkle;
3619+
use crate::offers::nonce::Nonce;
3620+
use crate::offers::offer::OfferBuilder;
3621+
use crate::offers::test_utils::{
3622+
payment_hash, payment_paths, recipient_pubkey, recipient_sign, FixedEntropy,
3623+
};
3624+
use bitcoin::secp256k1::Secp256k1;
3625+
use core::time::Duration;
3626+
3627+
let secp_ctx = Secp256k1::new();
3628+
let expanded_key = ExpandedKey::new([42; 32]);
3629+
let entropy = FixedEntropy {};
3630+
let nonce = Nonce::from_entropy_source(&entropy);
3631+
let node_id = recipient_pubkey();
3632+
let payment_paths = payment_paths();
3633+
let now = Duration::from_secs(123456);
3634+
let payment_id = PaymentId([1; 32]);
3635+
3636+
let offer = OfferBuilder::new(node_id)
3637+
.amount_msats(1000)
3638+
.path(crate::offers::test_utils::blinded_path())
3639+
.build()
3640+
.unwrap();
3641+
3642+
let invoice_request = offer
3643+
.request_invoice(&expanded_key, nonce, &secp_ctx, payment_id)
3644+
.unwrap()
3645+
.build_and_sign()
3646+
.unwrap();
3647+
3648+
let invoice = invoice_request
3649+
.respond_with_no_std(payment_paths, payment_hash(), now)
3650+
.unwrap()
3651+
.build()
3652+
.unwrap()
3653+
.sign(recipient_sign)
3654+
.unwrap();
3655+
3656+
let issuer_sign_pubkey = offer.issuer_signing_pubkey().unwrap();
3657+
let tagged_hash = invoice.tagged_hash();
3658+
let signature = invoice.signature();
3659+
assert!(merkle::verify_signature(&signature, tagged_hash, issuer_sign_pubkey).is_ok());
3660+
}
35633661
}

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)