Skip to content

Commit 825eb2f

Browse files
committed
Bolt12Invoice for Offer without signing_pubkey
When parsing a Bolt12Invoice use both the Offer's signing_pubkey and paths to determine if it is for an Offer or a Refund. Previously, an Offer was required to have a signing_pubkey. But now that it is optional, the Offers paths can be used to make the determination. Additionally, check that the invoice matches one of the blinded node ids from the paths' last hops.
1 parent cc50f8d commit 825eb2f

File tree

2 files changed

+108
-4
lines changed

2 files changed

+108
-4
lines changed

lightning/src/offers/invoice.rs

+93-4
Original file line numberDiff line numberDiff line change
@@ -1434,8 +1434,8 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
14341434
features, signing_pubkey,
14351435
};
14361436

1437-
match offer_tlv_stream.node_id {
1438-
Some(expected_signing_pubkey) => {
1437+
match (offer_tlv_stream.node_id, &offer_tlv_stream.paths) {
1438+
(Some(expected_signing_pubkey), _) => {
14391439
if fields.signing_pubkey != expected_signing_pubkey {
14401440
return Err(Bolt12SemanticError::InvalidSigningPubkey);
14411441
}
@@ -1445,7 +1445,21 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
14451445
)?;
14461446
Ok(InvoiceContents::ForOffer { invoice_request, fields })
14471447
},
1448-
None => {
1448+
(None, Some(paths)) => {
1449+
if !paths
1450+
.iter()
1451+
.filter_map(|path| path.blinded_hops.last())
1452+
.any(|last_hop| fields.signing_pubkey == last_hop.blinded_node_id)
1453+
{
1454+
return Err(Bolt12SemanticError::InvalidSigningPubkey);
1455+
}
1456+
1457+
let invoice_request = InvoiceRequestContents::try_from(
1458+
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream)
1459+
)?;
1460+
Ok(InvoiceContents::ForOffer { invoice_request, fields })
1461+
},
1462+
(None, None) => {
14491463
let refund = RefundContents::try_from(
14501464
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream)
14511465
)?;
@@ -1463,7 +1477,7 @@ mod tests {
14631477
use bitcoin::blockdata::script::ScriptBuf;
14641478
use bitcoin::hashes::Hash;
14651479
use bitcoin::network::constants::Network;
1466-
use bitcoin::secp256k1::{Message, Secp256k1, XOnlyPublicKey, self};
1480+
use bitcoin::secp256k1::{KeyPair, Message, Secp256k1, SecretKey, XOnlyPublicKey, self};
14671481
use bitcoin::address::{Address, Payload, WitnessProgram, WitnessVersion};
14681482
use bitcoin::key::TweakedPublicKey;
14691483

@@ -2366,6 +2380,81 @@ mod tests {
23662380
}
23672381
}
23682382

2383+
#[test]
2384+
fn parses_invoice_with_node_id_from_blinded_path() {
2385+
let paths = vec![
2386+
BlindedPath {
2387+
introduction_node: IntroductionNode::NodeId(pubkey(40)),
2388+
blinding_point: pubkey(41),
2389+
blinded_hops: vec![
2390+
BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] },
2391+
BlindedHop { blinded_node_id: pubkey(44), encrypted_payload: vec![0; 44] },
2392+
],
2393+
},
2394+
BlindedPath {
2395+
introduction_node: IntroductionNode::NodeId(pubkey(40)),
2396+
blinding_point: pubkey(41),
2397+
blinded_hops: vec![
2398+
BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] },
2399+
BlindedHop { blinded_node_id: pubkey(46), encrypted_payload: vec![0; 46] },
2400+
],
2401+
},
2402+
];
2403+
2404+
let blinded_node_id_sign = |message: &UnsignedBolt12Invoice| {
2405+
let secp_ctx = Secp256k1::new();
2406+
let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[46; 32]).unwrap());
2407+
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
2408+
};
2409+
2410+
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
2411+
.clear_signing_pubkey()
2412+
.amount_msats(1000)
2413+
.path(paths[0].clone())
2414+
.path(paths[1].clone())
2415+
.build().unwrap()
2416+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
2417+
.build().unwrap()
2418+
.sign(payer_sign).unwrap()
2419+
.respond_with_no_std_using_signing_pubkey(
2420+
payment_paths(), payment_hash(), now(), pubkey(46)
2421+
).unwrap()
2422+
.build().unwrap()
2423+
.sign(blinded_node_id_sign).unwrap();
2424+
2425+
let mut buffer = Vec::new();
2426+
invoice.write(&mut buffer).unwrap();
2427+
2428+
if let Err(e) = Bolt12Invoice::try_from(buffer) {
2429+
panic!("error parsing invoice: {:?}", e);
2430+
}
2431+
2432+
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
2433+
.clear_signing_pubkey()
2434+
.amount_msats(1000)
2435+
.path(paths[0].clone())
2436+
.path(paths[1].clone())
2437+
.build().unwrap()
2438+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
2439+
.build().unwrap()
2440+
.sign(payer_sign).unwrap()
2441+
.respond_with_no_std_using_signing_pubkey(
2442+
payment_paths(), payment_hash(), now(), recipient_pubkey()
2443+
).unwrap()
2444+
.build().unwrap()
2445+
.sign(recipient_sign).unwrap();
2446+
2447+
let mut buffer = Vec::new();
2448+
invoice.write(&mut buffer).unwrap();
2449+
2450+
match Bolt12Invoice::try_from(buffer) {
2451+
Ok(_) => panic!("expected error"),
2452+
Err(e) => {
2453+
assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InvalidSigningPubkey));
2454+
},
2455+
}
2456+
}
2457+
23692458
#[test]
23702459
fn fails_parsing_invoice_without_signature() {
23712460
let mut buffer = Vec::new();

lightning/src/offers/invoice_request.rs

+15
Original file line numberDiff line numberDiff line change
@@ -757,6 +757,21 @@ macro_rules! invoice_request_respond_with_explicit_signing_pubkey_methods { (
757757

758758
<$builder>::for_offer(&$contents, payment_paths, created_at, payment_hash, signing_pubkey)
759759
}
760+
761+
#[cfg(test)]
762+
#[allow(dead_code)]
763+
pub(super) fn respond_with_no_std_using_signing_pubkey(
764+
&$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
765+
created_at: core::time::Duration, signing_pubkey: PublicKey
766+
) -> Result<$builder, Bolt12SemanticError> {
767+
debug_assert!($contents.contents.inner.offer.signing_pubkey().is_none());
768+
769+
if $contents.invoice_request_features().requires_unknown_bits() {
770+
return Err(Bolt12SemanticError::UnknownRequiredFeatures);
771+
}
772+
773+
<$builder>::for_offer(&$contents, payment_paths, created_at, payment_hash, signing_pubkey)
774+
}
760775
} }
761776

762777
macro_rules! invoice_request_verify_method { ($self: ident, $self_type: ty) => {

0 commit comments

Comments
 (0)