mirror of
https://github.com/lightningdevkit/rust-lightning.git
synced 2025-02-24 06:57:53 +01:00
Merge pull request #3017 from jkczyz/2024-04-optional-pubkey
Sending to `Offer` without `signing_pubkey`
This commit is contained in:
commit
2b14cc40a6
7 changed files with 227 additions and 56 deletions
|
@ -8757,14 +8757,7 @@ where
|
|||
.map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?;
|
||||
|
||||
let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap();
|
||||
if offer.paths().is_empty() {
|
||||
let message = new_pending_onion_message(
|
||||
OffersMessage::InvoiceRequest(invoice_request),
|
||||
Destination::Node(offer.signing_pubkey()),
|
||||
Some(reply_path),
|
||||
);
|
||||
pending_offers_messages.push(message);
|
||||
} else {
|
||||
if !offer.paths().is_empty() {
|
||||
// Send as many invoice requests as there are paths in the offer (with an upper bound).
|
||||
// Using only one path could result in a failure if the path no longer exists. But only
|
||||
// one invoice for a given payment id will be paid, even if more than one is received.
|
||||
|
@ -8777,6 +8770,16 @@ where
|
|||
);
|
||||
pending_offers_messages.push(message);
|
||||
}
|
||||
} else if let Some(signing_pubkey) = offer.signing_pubkey() {
|
||||
let message = new_pending_onion_message(
|
||||
OffersMessage::InvoiceRequest(invoice_request),
|
||||
Destination::Node(signing_pubkey),
|
||||
Some(reply_path),
|
||||
);
|
||||
pending_offers_messages.push(message);
|
||||
} else {
|
||||
debug_assert!(false);
|
||||
return Err(Bolt12SemanticError::MissingSigningPubkey);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -270,7 +270,7 @@ fn prefers_non_tor_nodes_in_blinded_paths() {
|
|||
.create_offer_builder("coffee".to_string()).unwrap()
|
||||
.amount_msats(10_000_000)
|
||||
.build().unwrap();
|
||||
assert_ne!(offer.signing_pubkey(), bob_id);
|
||||
assert_ne!(offer.signing_pubkey(), Some(bob_id));
|
||||
assert!(!offer.paths().is_empty());
|
||||
for path in offer.paths() {
|
||||
assert_ne!(path.introduction_node, IntroductionNode::NodeId(bob_id));
|
||||
|
@ -285,7 +285,7 @@ fn prefers_non_tor_nodes_in_blinded_paths() {
|
|||
.create_offer_builder("coffee".to_string()).unwrap()
|
||||
.amount_msats(10_000_000)
|
||||
.build().unwrap();
|
||||
assert_ne!(offer.signing_pubkey(), bob_id);
|
||||
assert_ne!(offer.signing_pubkey(), Some(bob_id));
|
||||
assert!(!offer.paths().is_empty());
|
||||
for path in offer.paths() {
|
||||
assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id));
|
||||
|
@ -335,7 +335,7 @@ fn prefers_more_connected_nodes_in_blinded_paths() {
|
|||
.create_offer_builder("coffee".to_string()).unwrap()
|
||||
.amount_msats(10_000_000)
|
||||
.build().unwrap();
|
||||
assert_ne!(offer.signing_pubkey(), bob_id);
|
||||
assert_ne!(offer.signing_pubkey(), Some(bob_id));
|
||||
assert!(!offer.paths().is_empty());
|
||||
for path in offer.paths() {
|
||||
assert_eq!(path.introduction_node, IntroductionNode::NodeId(nodes[4].node.get_our_node_id()));
|
||||
|
@ -385,7 +385,7 @@ fn creates_and_pays_for_offer_using_two_hop_blinded_path() {
|
|||
.unwrap()
|
||||
.amount_msats(10_000_000)
|
||||
.build().unwrap();
|
||||
assert_ne!(offer.signing_pubkey(), alice_id);
|
||||
assert_ne!(offer.signing_pubkey(), Some(alice_id));
|
||||
assert!(!offer.paths().is_empty());
|
||||
for path in offer.paths() {
|
||||
assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id));
|
||||
|
@ -544,7 +544,7 @@ fn creates_and_pays_for_offer_using_one_hop_blinded_path() {
|
|||
.create_offer_builder("coffee".to_string()).unwrap()
|
||||
.amount_msats(10_000_000)
|
||||
.build().unwrap();
|
||||
assert_ne!(offer.signing_pubkey(), alice_id);
|
||||
assert_ne!(offer.signing_pubkey(), Some(alice_id));
|
||||
assert!(!offer.paths().is_empty());
|
||||
for path in offer.paths() {
|
||||
assert_eq!(path.introduction_node, IntroductionNode::NodeId(alice_id));
|
||||
|
@ -667,7 +667,7 @@ fn pays_for_offer_without_blinded_paths() {
|
|||
.clear_paths()
|
||||
.amount_msats(10_000_000)
|
||||
.build().unwrap();
|
||||
assert_eq!(offer.signing_pubkey(), alice_id);
|
||||
assert_eq!(offer.signing_pubkey(), Some(alice_id));
|
||||
assert!(offer.paths().is_empty());
|
||||
|
||||
let payment_id = PaymentId([1; 32]);
|
||||
|
|
|
@ -210,10 +210,9 @@ macro_rules! invoice_explicit_signing_pubkey_builder_methods { ($self: ident, $s
|
|||
#[cfg_attr(c_bindings, allow(dead_code))]
|
||||
pub(super) fn for_offer(
|
||||
invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>,
|
||||
created_at: Duration, payment_hash: PaymentHash
|
||||
created_at: Duration, payment_hash: PaymentHash, signing_pubkey: PublicKey
|
||||
) -> Result<Self, Bolt12SemanticError> {
|
||||
let amount_msats = Self::amount_msats(invoice_request)?;
|
||||
let signing_pubkey = invoice_request.contents.inner.offer.signing_pubkey();
|
||||
let contents = InvoiceContents::ForOffer {
|
||||
invoice_request: invoice_request.contents.clone(),
|
||||
fields: Self::fields(
|
||||
|
@ -272,7 +271,7 @@ macro_rules! invoice_derived_signing_pubkey_builder_methods { ($self: ident, $se
|
|||
created_at: Duration, payment_hash: PaymentHash, keys: KeyPair
|
||||
) -> Result<Self, Bolt12SemanticError> {
|
||||
let amount_msats = Self::amount_msats(invoice_request)?;
|
||||
let signing_pubkey = invoice_request.contents.inner.offer.signing_pubkey();
|
||||
let signing_pubkey = keys.public_key();
|
||||
let contents = InvoiceContents::ForOffer {
|
||||
invoice_request: invoice_request.contents.clone(),
|
||||
fields: Self::fields(
|
||||
|
@ -1435,8 +1434,8 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
|
|||
features, signing_pubkey,
|
||||
};
|
||||
|
||||
match offer_tlv_stream.node_id {
|
||||
Some(expected_signing_pubkey) => {
|
||||
match (offer_tlv_stream.node_id, &offer_tlv_stream.paths) {
|
||||
(Some(expected_signing_pubkey), _) => {
|
||||
if fields.signing_pubkey != expected_signing_pubkey {
|
||||
return Err(Bolt12SemanticError::InvalidSigningPubkey);
|
||||
}
|
||||
|
@ -1446,7 +1445,21 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
|
|||
)?;
|
||||
Ok(InvoiceContents::ForOffer { invoice_request, fields })
|
||||
},
|
||||
None => {
|
||||
(None, Some(paths)) => {
|
||||
if !paths
|
||||
.iter()
|
||||
.filter_map(|path| path.blinded_hops.last())
|
||||
.any(|last_hop| fields.signing_pubkey == last_hop.blinded_node_id)
|
||||
{
|
||||
return Err(Bolt12SemanticError::InvalidSigningPubkey);
|
||||
}
|
||||
|
||||
let invoice_request = InvoiceRequestContents::try_from(
|
||||
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream)
|
||||
)?;
|
||||
Ok(InvoiceContents::ForOffer { invoice_request, fields })
|
||||
},
|
||||
(None, None) => {
|
||||
let refund = RefundContents::try_from(
|
||||
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream)
|
||||
)?;
|
||||
|
@ -1464,7 +1477,7 @@ mod tests {
|
|||
use bitcoin::blockdata::script::ScriptBuf;
|
||||
use bitcoin::hashes::Hash;
|
||||
use bitcoin::network::constants::Network;
|
||||
use bitcoin::secp256k1::{Message, Secp256k1, XOnlyPublicKey, self};
|
||||
use bitcoin::secp256k1::{KeyPair, Message, Secp256k1, SecretKey, XOnlyPublicKey, self};
|
||||
use bitcoin::address::{Address, Payload, WitnessProgram, WitnessVersion};
|
||||
use bitcoin::key::TweakedPublicKey;
|
||||
|
||||
|
@ -1633,6 +1646,7 @@ mod tests {
|
|||
quantity: None,
|
||||
payer_id: Some(&payer_pubkey()),
|
||||
payer_note: None,
|
||||
paths: None,
|
||||
},
|
||||
InvoiceTlvStreamRef {
|
||||
paths: Some(Iterable(payment_paths.iter().map(|(_, path)| path))),
|
||||
|
@ -1725,6 +1739,7 @@ mod tests {
|
|||
quantity: None,
|
||||
payer_id: Some(&payer_pubkey()),
|
||||
payer_note: None,
|
||||
paths: None,
|
||||
},
|
||||
InvoiceTlvStreamRef {
|
||||
paths: Some(Iterable(payment_paths.iter().map(|(_, path)| path))),
|
||||
|
@ -2365,6 +2380,81 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_invoice_with_node_id_from_blinded_path() {
|
||||
let paths = vec![
|
||||
BlindedPath {
|
||||
introduction_node: IntroductionNode::NodeId(pubkey(40)),
|
||||
blinding_point: pubkey(41),
|
||||
blinded_hops: vec![
|
||||
BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] },
|
||||
BlindedHop { blinded_node_id: pubkey(44), encrypted_payload: vec![0; 44] },
|
||||
],
|
||||
},
|
||||
BlindedPath {
|
||||
introduction_node: IntroductionNode::NodeId(pubkey(40)),
|
||||
blinding_point: pubkey(41),
|
||||
blinded_hops: vec![
|
||||
BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] },
|
||||
BlindedHop { blinded_node_id: pubkey(46), encrypted_payload: vec![0; 46] },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
let blinded_node_id_sign = |message: &UnsignedBolt12Invoice| {
|
||||
let secp_ctx = Secp256k1::new();
|
||||
let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[46; 32]).unwrap());
|
||||
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
|
||||
};
|
||||
|
||||
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
|
||||
.clear_signing_pubkey()
|
||||
.amount_msats(1000)
|
||||
.path(paths[0].clone())
|
||||
.path(paths[1].clone())
|
||||
.build().unwrap()
|
||||
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
|
||||
.build().unwrap()
|
||||
.sign(payer_sign).unwrap()
|
||||
.respond_with_no_std_using_signing_pubkey(
|
||||
payment_paths(), payment_hash(), now(), pubkey(46)
|
||||
).unwrap()
|
||||
.build().unwrap()
|
||||
.sign(blinded_node_id_sign).unwrap();
|
||||
|
||||
let mut buffer = Vec::new();
|
||||
invoice.write(&mut buffer).unwrap();
|
||||
|
||||
if let Err(e) = Bolt12Invoice::try_from(buffer) {
|
||||
panic!("error parsing invoice: {:?}", e);
|
||||
}
|
||||
|
||||
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
|
||||
.clear_signing_pubkey()
|
||||
.amount_msats(1000)
|
||||
.path(paths[0].clone())
|
||||
.path(paths[1].clone())
|
||||
.build().unwrap()
|
||||
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
|
||||
.build().unwrap()
|
||||
.sign(payer_sign).unwrap()
|
||||
.respond_with_no_std_using_signing_pubkey(
|
||||
payment_paths(), payment_hash(), now(), recipient_pubkey()
|
||||
).unwrap()
|
||||
.build().unwrap()
|
||||
.sign(recipient_sign).unwrap();
|
||||
|
||||
let mut buffer = Vec::new();
|
||||
invoice.write(&mut buffer).unwrap();
|
||||
|
||||
match Bolt12Invoice::try_from(buffer) {
|
||||
Ok(_) => panic!("expected error"),
|
||||
Err(e) => {
|
||||
assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InvalidSigningPubkey));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_parsing_invoice_without_signature() {
|
||||
let mut buffer = Vec::new();
|
||||
|
|
|
@ -747,7 +747,27 @@ macro_rules! invoice_request_respond_with_explicit_signing_pubkey_methods { (
|
|||
return Err(Bolt12SemanticError::UnknownRequiredFeatures);
|
||||
}
|
||||
|
||||
<$builder>::for_offer(&$contents, payment_paths, created_at, payment_hash)
|
||||
let signing_pubkey = match $contents.contents.inner.offer.signing_pubkey() {
|
||||
Some(signing_pubkey) => signing_pubkey,
|
||||
None => return Err(Bolt12SemanticError::MissingSigningPubkey),
|
||||
};
|
||||
|
||||
<$builder>::for_offer(&$contents, payment_paths, created_at, payment_hash, signing_pubkey)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(dead_code)]
|
||||
pub(super) fn respond_with_no_std_using_signing_pubkey(
|
||||
&$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
|
||||
created_at: core::time::Duration, signing_pubkey: PublicKey
|
||||
) -> Result<$builder, Bolt12SemanticError> {
|
||||
debug_assert!($contents.contents.inner.offer.signing_pubkey().is_none());
|
||||
|
||||
if $contents.invoice_request_features().requires_unknown_bits() {
|
||||
return Err(Bolt12SemanticError::UnknownRequiredFeatures);
|
||||
}
|
||||
|
||||
<$builder>::for_offer(&$contents, payment_paths, created_at, payment_hash, signing_pubkey)
|
||||
}
|
||||
} }
|
||||
|
||||
|
@ -855,6 +875,11 @@ macro_rules! invoice_request_respond_with_derived_signing_pubkey_methods { (
|
|||
Some(keys) => keys,
|
||||
};
|
||||
|
||||
match $contents.contents.inner.offer.signing_pubkey() {
|
||||
Some(signing_pubkey) => debug_assert_eq!(signing_pubkey, keys.public_key()),
|
||||
None => return Err(Bolt12SemanticError::MissingSigningPubkey),
|
||||
}
|
||||
|
||||
<$builder>::for_offer_using_keys(
|
||||
&$self.inner, payment_paths, created_at, payment_hash, keys
|
||||
)
|
||||
|
@ -959,6 +984,7 @@ impl InvoiceRequestContentsWithoutPayerId {
|
|||
quantity: self.quantity,
|
||||
payer_id: None,
|
||||
payer_note: self.payer_note.as_ref(),
|
||||
paths: None,
|
||||
};
|
||||
|
||||
(payer, offer, invoice_request)
|
||||
|
@ -991,6 +1017,8 @@ pub(super) const INVOICE_REQUEST_TYPES: core::ops::Range<u64> = 80..160;
|
|||
/// [`Refund::payer_id`]: crate::offers::refund::Refund::payer_id
|
||||
pub(super) const INVOICE_REQUEST_PAYER_ID_TYPE: u64 = 88;
|
||||
|
||||
// This TLV stream is used for both InvoiceRequest and Refund, but not all TLV records are valid for
|
||||
// InvoiceRequest as noted below.
|
||||
tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, INVOICE_REQUEST_TYPES, {
|
||||
(80, chain: ChainHash),
|
||||
(82, amount: (u64, HighZeroBytesDroppedBigSize)),
|
||||
|
@ -998,6 +1026,8 @@ tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, INVOICE_REQUEST
|
|||
(86, quantity: (u64, HighZeroBytesDroppedBigSize)),
|
||||
(INVOICE_REQUEST_PAYER_ID_TYPE, payer_id: PublicKey),
|
||||
(89, payer_note: (String, WithoutLength)),
|
||||
// Only used for Refund since the onion message of an InvoiceRequest has a reply path.
|
||||
(90, paths: (Vec<BlindedPath>, WithoutLength)),
|
||||
});
|
||||
|
||||
type FullInvoiceRequestTlvStream =
|
||||
|
@ -1080,7 +1110,9 @@ impl TryFrom<PartialInvoiceRequestTlvStream> for InvoiceRequestContents {
|
|||
let (
|
||||
PayerTlvStream { metadata },
|
||||
offer_tlv_stream,
|
||||
InvoiceRequestTlvStream { chain, amount, features, quantity, payer_id, payer_note },
|
||||
InvoiceRequestTlvStream {
|
||||
chain, amount, features, quantity, payer_id, payer_note, paths,
|
||||
},
|
||||
) = tlv_stream;
|
||||
|
||||
let payer = match metadata {
|
||||
|
@ -1107,6 +1139,10 @@ impl TryFrom<PartialInvoiceRequestTlvStream> for InvoiceRequestContents {
|
|||
Some(payer_id) => payer_id,
|
||||
};
|
||||
|
||||
if paths.is_some() {
|
||||
return Err(Bolt12SemanticError::UnexpectedPaths);
|
||||
}
|
||||
|
||||
Ok(InvoiceRequestContents {
|
||||
inner: InvoiceRequestContentsWithoutPayerId {
|
||||
payer, offer, chain, amount_msats: amount, features, quantity, payer_note,
|
||||
|
@ -1218,7 +1254,7 @@ mod tests {
|
|||
assert_eq!(unsigned_invoice_request.paths(), &[]);
|
||||
assert_eq!(unsigned_invoice_request.issuer(), None);
|
||||
assert_eq!(unsigned_invoice_request.supported_quantity(), Quantity::One);
|
||||
assert_eq!(unsigned_invoice_request.signing_pubkey(), recipient_pubkey());
|
||||
assert_eq!(unsigned_invoice_request.signing_pubkey(), Some(recipient_pubkey()));
|
||||
assert_eq!(unsigned_invoice_request.chain(), ChainHash::using_genesis_block(Network::Bitcoin));
|
||||
assert_eq!(unsigned_invoice_request.amount_msats(), None);
|
||||
assert_eq!(unsigned_invoice_request.invoice_request_features(), &InvoiceRequestFeatures::empty());
|
||||
|
@ -1250,7 +1286,7 @@ mod tests {
|
|||
assert_eq!(invoice_request.paths(), &[]);
|
||||
assert_eq!(invoice_request.issuer(), None);
|
||||
assert_eq!(invoice_request.supported_quantity(), Quantity::One);
|
||||
assert_eq!(invoice_request.signing_pubkey(), recipient_pubkey());
|
||||
assert_eq!(invoice_request.signing_pubkey(), Some(recipient_pubkey()));
|
||||
assert_eq!(invoice_request.chain(), ChainHash::using_genesis_block(Network::Bitcoin));
|
||||
assert_eq!(invoice_request.amount_msats(), None);
|
||||
assert_eq!(invoice_request.invoice_request_features(), &InvoiceRequestFeatures::empty());
|
||||
|
@ -1285,6 +1321,7 @@ mod tests {
|
|||
quantity: None,
|
||||
payer_id: Some(&payer_pubkey()),
|
||||
payer_note: None,
|
||||
paths: None,
|
||||
},
|
||||
SignatureTlvStreamRef { signature: Some(&invoice_request.signature()) },
|
||||
),
|
||||
|
@ -2245,7 +2282,7 @@ mod tests {
|
|||
.amount_msats(1000)
|
||||
.supported_quantity(Quantity::Unbounded)
|
||||
.build().unwrap();
|
||||
assert_eq!(offer.signing_pubkey(), node_id);
|
||||
assert_eq!(offer.signing_pubkey(), Some(node_id));
|
||||
|
||||
let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
|
||||
.chain(Network::Testnet).unwrap()
|
||||
|
|
|
@ -226,7 +226,7 @@ macro_rules! offer_explicit_metadata_builder_methods { (
|
|||
offer: OfferContents {
|
||||
chains: None, metadata: None, amount: None, description,
|
||||
features: OfferFeatures::empty(), absolute_expiry: None, issuer: None, paths: None,
|
||||
supported_quantity: Quantity::One, signing_pubkey,
|
||||
supported_quantity: Quantity::One, signing_pubkey: Some(signing_pubkey),
|
||||
},
|
||||
metadata_strategy: core::marker::PhantomData,
|
||||
secp_ctx: None,
|
||||
|
@ -265,7 +265,7 @@ macro_rules! offer_derived_metadata_builder_methods { ($secp_context: ty) => {
|
|||
offer: OfferContents {
|
||||
chains: None, metadata: Some(metadata), amount: None, description,
|
||||
features: OfferFeatures::empty(), absolute_expiry: None, issuer: None, paths: None,
|
||||
supported_quantity: Quantity::One, signing_pubkey: node_id,
|
||||
supported_quantity: Quantity::One, signing_pubkey: Some(node_id),
|
||||
},
|
||||
metadata_strategy: core::marker::PhantomData,
|
||||
secp_ctx: Some(secp_ctx),
|
||||
|
@ -391,7 +391,7 @@ macro_rules! offer_builder_methods { (
|
|||
let (derived_metadata, keys) = metadata.derive_from(tlv_stream, $self.secp_ctx);
|
||||
metadata = derived_metadata;
|
||||
if let Some(keys) = keys {
|
||||
$self.offer.signing_pubkey = keys.public_key();
|
||||
$self.offer.signing_pubkey = Some(keys.public_key());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -436,6 +436,12 @@ macro_rules! offer_builder_test_methods { (
|
|||
$return_value
|
||||
}
|
||||
|
||||
#[cfg_attr(c_bindings, allow(dead_code))]
|
||||
pub(crate) fn clear_signing_pubkey($($self_mut)* $self: $self_type) -> $return_type {
|
||||
$self.offer.signing_pubkey = None;
|
||||
$return_value
|
||||
}
|
||||
|
||||
#[cfg_attr(c_bindings, allow(dead_code))]
|
||||
pub(super) fn build_unchecked($self: $self_type) -> Offer {
|
||||
$self.build_without_checks()
|
||||
|
@ -542,7 +548,7 @@ pub(super) struct OfferContents {
|
|||
issuer: Option<String>,
|
||||
paths: Option<Vec<BlindedPath>>,
|
||||
supported_quantity: Quantity,
|
||||
signing_pubkey: PublicKey,
|
||||
signing_pubkey: Option<PublicKey>,
|
||||
}
|
||||
|
||||
macro_rules! offer_accessors { ($self: ident, $contents: expr) => {
|
||||
|
@ -604,7 +610,7 @@ macro_rules! offer_accessors { ($self: ident, $contents: expr) => {
|
|||
}
|
||||
|
||||
/// The public key used by the recipient to sign invoices.
|
||||
pub fn signing_pubkey(&$self) -> bitcoin::secp256k1::PublicKey {
|
||||
pub fn signing_pubkey(&$self) -> Option<bitcoin::secp256k1::PublicKey> {
|
||||
$contents.signing_pubkey()
|
||||
}
|
||||
} }
|
||||
|
@ -886,7 +892,7 @@ impl OfferContents {
|
|||
}
|
||||
}
|
||||
|
||||
pub(super) fn signing_pubkey(&self) -> PublicKey {
|
||||
pub(super) fn signing_pubkey(&self) -> Option<PublicKey> {
|
||||
self.signing_pubkey
|
||||
}
|
||||
|
||||
|
@ -905,8 +911,12 @@ impl OfferContents {
|
|||
_ => true,
|
||||
}
|
||||
});
|
||||
let signing_pubkey = match self.signing_pubkey() {
|
||||
Some(signing_pubkey) => signing_pubkey,
|
||||
None => return Err(()),
|
||||
};
|
||||
let keys = signer::verify_recipient_metadata(
|
||||
metadata, key, IV_BYTES, self.signing_pubkey(), tlv_stream, secp_ctx
|
||||
metadata, key, IV_BYTES, signing_pubkey, tlv_stream, secp_ctx
|
||||
)?;
|
||||
|
||||
let offer_id = OfferId::from_valid_invreq_tlv_stream(bytes);
|
||||
|
@ -941,7 +951,7 @@ impl OfferContents {
|
|||
paths: self.paths.as_ref(),
|
||||
issuer: self.issuer.as_ref(),
|
||||
quantity_max: self.supported_quantity.to_tlv_record(),
|
||||
node_id: Some(&self.signing_pubkey),
|
||||
node_id: self.signing_pubkey.as_ref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1089,9 +1099,10 @@ impl TryFrom<OfferTlvStream> for OfferContents {
|
|||
Some(n) => Quantity::Bounded(NonZeroU64::new(n).unwrap()),
|
||||
};
|
||||
|
||||
let signing_pubkey = match node_id {
|
||||
None => return Err(Bolt12SemanticError::MissingSigningPubkey),
|
||||
Some(node_id) => node_id,
|
||||
let (signing_pubkey, paths) = match (node_id, paths) {
|
||||
(None, None) => return Err(Bolt12SemanticError::MissingSigningPubkey),
|
||||
(_, Some(paths)) if paths.is_empty() => return Err(Bolt12SemanticError::MissingPaths),
|
||||
(node_id, paths) => (node_id, paths),
|
||||
};
|
||||
|
||||
Ok(OfferContents {
|
||||
|
@ -1154,7 +1165,7 @@ mod tests {
|
|||
assert_eq!(offer.paths(), &[]);
|
||||
assert_eq!(offer.issuer(), None);
|
||||
assert_eq!(offer.supported_quantity(), Quantity::One);
|
||||
assert_eq!(offer.signing_pubkey(), pubkey(42));
|
||||
assert_eq!(offer.signing_pubkey(), Some(pubkey(42)));
|
||||
|
||||
assert_eq!(
|
||||
offer.as_tlv_stream(),
|
||||
|
@ -1251,7 +1262,7 @@ mod tests {
|
|||
::deriving_signing_pubkey(desc, node_id, &expanded_key, &entropy, &secp_ctx)
|
||||
.amount_msats(1000)
|
||||
.build().unwrap();
|
||||
assert_eq!(offer.signing_pubkey(), node_id);
|
||||
assert_eq!(offer.signing_pubkey(), Some(node_id));
|
||||
|
||||
let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
|
||||
.build().unwrap()
|
||||
|
@ -1313,7 +1324,7 @@ mod tests {
|
|||
.amount_msats(1000)
|
||||
.path(blinded_path)
|
||||
.build().unwrap();
|
||||
assert_ne!(offer.signing_pubkey(), node_id);
|
||||
assert_ne!(offer.signing_pubkey(), Some(node_id));
|
||||
|
||||
let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
|
||||
.build().unwrap()
|
||||
|
@ -1471,7 +1482,7 @@ mod tests {
|
|||
.unwrap();
|
||||
let tlv_stream = offer.as_tlv_stream();
|
||||
assert_eq!(offer.paths(), paths.as_slice());
|
||||
assert_eq!(offer.signing_pubkey(), pubkey(42));
|
||||
assert_eq!(offer.signing_pubkey(), Some(pubkey(42)));
|
||||
assert_ne!(pubkey(42), pubkey(44));
|
||||
assert_eq!(tlv_stream.paths, Some(&paths));
|
||||
assert_eq!(tlv_stream.node_id, Some(&pubkey(42)));
|
||||
|
@ -1658,12 +1669,31 @@ mod tests {
|
|||
panic!("error parsing offer: {:?}", e);
|
||||
}
|
||||
|
||||
let offer = OfferBuilder::new("foo".into(), pubkey(42))
|
||||
.path(BlindedPath {
|
||||
introduction_node: IntroductionNode::NodeId(pubkey(40)),
|
||||
blinding_point: pubkey(41),
|
||||
blinded_hops: vec![
|
||||
BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] },
|
||||
BlindedHop { blinded_node_id: pubkey(44), encrypted_payload: vec![0; 44] },
|
||||
],
|
||||
})
|
||||
.clear_signing_pubkey()
|
||||
.build()
|
||||
.unwrap();
|
||||
if let Err(e) = offer.to_string().parse::<Offer>() {
|
||||
panic!("error parsing offer: {:?}", e);
|
||||
}
|
||||
|
||||
let mut builder = OfferBuilder::new("foo".into(), pubkey(42));
|
||||
builder.offer.paths = Some(vec![]);
|
||||
|
||||
let offer = builder.build().unwrap();
|
||||
if let Err(e) = offer.to_string().parse::<Offer>() {
|
||||
panic!("error parsing offer: {:?}", e);
|
||||
match offer.to_string().parse::<Offer>() {
|
||||
Ok(_) => panic!("expected error"),
|
||||
Err(e) => {
|
||||
assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingPaths));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -183,6 +183,8 @@ pub enum Bolt12SemanticError {
|
|||
DuplicatePaymentId,
|
||||
/// Blinded paths were expected but were missing.
|
||||
MissingPaths,
|
||||
/// Blinded paths were provided but were not expected.
|
||||
UnexpectedPaths,
|
||||
/// The blinded payinfo given does not match the number of blinded path hops.
|
||||
InvalidPayInfo,
|
||||
/// An invoice creation time was expected but was missing.
|
||||
|
|
|
@ -170,8 +170,8 @@ macro_rules! refund_explicit_metadata_builder_methods { () => {
|
|||
Ok(Self {
|
||||
refund: RefundContents {
|
||||
payer: PayerContents(metadata), description, absolute_expiry: None, issuer: None,
|
||||
paths: None, chain: None, amount_msats, features: InvoiceRequestFeatures::empty(),
|
||||
quantity: None, payer_id, payer_note: None,
|
||||
chain: None, amount_msats, features: InvoiceRequestFeatures::empty(),
|
||||
quantity: None, payer_id, payer_note: None, paths: None,
|
||||
},
|
||||
secp_ctx: None,
|
||||
})
|
||||
|
@ -209,8 +209,8 @@ macro_rules! refund_builder_methods { (
|
|||
Ok(Self {
|
||||
refund: RefundContents {
|
||||
payer: PayerContents(metadata), description, absolute_expiry: None, issuer: None,
|
||||
paths: None, chain: None, amount_msats, features: InvoiceRequestFeatures::empty(),
|
||||
quantity: None, payer_id: node_id, payer_note: None,
|
||||
chain: None, amount_msats, features: InvoiceRequestFeatures::empty(),
|
||||
quantity: None, payer_id: node_id, payer_note: None, paths: None,
|
||||
},
|
||||
secp_ctx: Some(secp_ctx),
|
||||
})
|
||||
|
@ -410,7 +410,6 @@ pub(super) struct RefundContents {
|
|||
description: String,
|
||||
absolute_expiry: Option<Duration>,
|
||||
issuer: Option<String>,
|
||||
paths: Option<Vec<BlindedPath>>,
|
||||
// invoice_request fields
|
||||
chain: Option<ChainHash>,
|
||||
amount_msats: u64,
|
||||
|
@ -418,6 +417,7 @@ pub(super) struct RefundContents {
|
|||
quantity: Option<u64>,
|
||||
payer_id: PublicKey,
|
||||
payer_note: Option<String>,
|
||||
paths: Option<Vec<BlindedPath>>,
|
||||
}
|
||||
|
||||
impl Refund {
|
||||
|
@ -734,7 +734,7 @@ impl RefundContents {
|
|||
description: Some(&self.description),
|
||||
features: None,
|
||||
absolute_expiry: self.absolute_expiry.map(|duration| duration.as_secs()),
|
||||
paths: self.paths.as_ref(),
|
||||
paths: None,
|
||||
issuer: self.issuer.as_ref(),
|
||||
quantity_max: None,
|
||||
node_id: None,
|
||||
|
@ -752,6 +752,7 @@ impl RefundContents {
|
|||
quantity: self.quantity,
|
||||
payer_id: Some(&self.payer_id),
|
||||
payer_note: self.payer_note.as_ref(),
|
||||
paths: self.paths.as_ref(),
|
||||
};
|
||||
|
||||
(payer, offer, invoice_request)
|
||||
|
@ -820,9 +821,12 @@ impl TryFrom<RefundTlvStream> for RefundContents {
|
|||
PayerTlvStream { metadata: payer_metadata },
|
||||
OfferTlvStream {
|
||||
chains, metadata, currency, amount: offer_amount, description,
|
||||
features: offer_features, absolute_expiry, paths, issuer, quantity_max, node_id,
|
||||
features: offer_features, absolute_expiry, paths: offer_paths, issuer, quantity_max,
|
||||
node_id,
|
||||
},
|
||||
InvoiceRequestTlvStream {
|
||||
chain, amount, features, quantity, payer_id, payer_note, paths
|
||||
},
|
||||
InvoiceRequestTlvStream { chain, amount, features, quantity, payer_id, payer_note },
|
||||
) = tlv_stream;
|
||||
|
||||
let payer = match payer_metadata {
|
||||
|
@ -853,6 +857,10 @@ impl TryFrom<RefundTlvStream> for RefundContents {
|
|||
|
||||
let absolute_expiry = absolute_expiry.map(Duration::from_secs);
|
||||
|
||||
if offer_paths.is_some() {
|
||||
return Err(Bolt12SemanticError::UnexpectedPaths);
|
||||
}
|
||||
|
||||
if quantity_max.is_some() {
|
||||
return Err(Bolt12SemanticError::UnexpectedQuantity);
|
||||
}
|
||||
|
@ -877,8 +885,8 @@ impl TryFrom<RefundTlvStream> for RefundContents {
|
|||
};
|
||||
|
||||
Ok(RefundContents {
|
||||
payer, description, absolute_expiry, issuer, paths, chain, amount_msats, features,
|
||||
quantity, payer_id, payer_note,
|
||||
payer, description, absolute_expiry, issuer, chain, amount_msats, features, quantity,
|
||||
payer_id, payer_note, paths,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -980,6 +988,7 @@ mod tests {
|
|||
quantity: None,
|
||||
payer_id: Some(&payer_pubkey()),
|
||||
payer_note: None,
|
||||
paths: None,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
@ -1173,12 +1182,12 @@ mod tests {
|
|||
.path(paths[1].clone())
|
||||
.build()
|
||||
.unwrap();
|
||||
let (_, offer_tlv_stream, invoice_request_tlv_stream) = refund.as_tlv_stream();
|
||||
assert_eq!(refund.paths(), paths.as_slice());
|
||||
let (_, _, invoice_request_tlv_stream) = refund.as_tlv_stream();
|
||||
assert_eq!(refund.payer_id(), pubkey(42));
|
||||
assert_eq!(refund.paths(), paths.as_slice());
|
||||
assert_ne!(pubkey(42), pubkey(44));
|
||||
assert_eq!(offer_tlv_stream.paths, Some(&paths));
|
||||
assert_eq!(invoice_request_tlv_stream.payer_id, Some(&pubkey(42)));
|
||||
assert_eq!(invoice_request_tlv_stream.paths, Some(&paths));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
Loading…
Add table
Reference in a new issue