pub(super) const IV_BYTES: &[u8; IV_LEN] = b"LDK Offer ~~~~~~";
/// An identifier for an [`Offer`] built using [`DerivedMetadata`].
-#[derive(Clone, Copy, Debug, PartialEq)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct OfferId(pub [u8; 32]);
impl OfferId {
const ID_TAG: &'static str = "LDK Offer ID";
fn from_valid_offer_tlv_stream(bytes: &[u8]) -> Self {
- let tagged_hash = TaggedHash::new(Self::ID_TAG, &bytes);
+ let tagged_hash = TaggedHash::from_valid_tlv_stream_bytes(Self::ID_TAG, bytes);
+ Self(tagged_hash.to_bytes())
+ }
+
+ fn from_valid_invreq_tlv_stream(bytes: &[u8]) -> Self {
+ let tlv_stream = TlvStream::new(bytes).range(OFFER_TYPES);
+ let tagged_hash = TaggedHash::from_tlv_stream(Self::ID_TAG, tlv_stream);
Self(tagged_hash.to_bytes())
}
}
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,
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),
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());
}
}
$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()
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) => {
}
/// 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()
}
} }
}
}
- pub(super) fn signing_pubkey(&self) -> PublicKey {
+ pub(super) fn signing_pubkey(&self) -> Option<PublicKey> {
self.signing_pubkey
}
/// Verifies that the offer metadata was produced from the offer in the TLV stream.
pub(super) fn verify<T: secp256k1::Signing>(
&self, bytes: &[u8], key: &ExpandedKey, secp_ctx: &Secp256k1<T>
- ) -> Result<Option<KeyPair>, ()> {
+ ) -> Result<(OfferId, Option<KeyPair>), ()> {
match self.metadata() {
Some(metadata) => {
let tlv_stream = TlvStream::new(bytes).range(OFFER_TYPES).filter(|record| {
_ => true,
}
});
- signer::verify_recipient_metadata(
- metadata, key, IV_BYTES, self.signing_pubkey(), tlv_stream, secp_ctx
- )
+ 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, signing_pubkey, tlv_stream, secp_ctx
+ )?;
+
+ let offer_id = OfferId::from_valid_invreq_tlv_stream(bytes);
+
+ Ok((offer_id, keys))
},
None => Err(()),
}
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(),
}
}
}
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 {
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(),
::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()
.sign(payer_sign).unwrap();
- assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_ok());
+ match invoice_request.verify(&expanded_key, &secp_ctx) {
+ Ok(invoice_request) => assert_eq!(invoice_request.offer_id, offer.id()),
+ Err(_) => panic!("unexpected error"),
+ }
// Fails verification with altered offer field
let mut tlv_stream = offer.as_tlv_stream();
.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()
.sign(payer_sign).unwrap();
- assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_ok());
+ match invoice_request.verify(&expanded_key, &secp_ctx) {
+ Ok(invoice_request) => assert_eq!(invoice_request.offer_id, offer.id()),
+ Err(_) => panic!("unexpected error"),
+ }
// Fails verification with altered offer field
let mut tlv_stream = offer.as_tlv_stream();
.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)));
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));
+ },
}
}