Macro-ize InvoiceBuilder

InvoiceBuilder is not exported to bindings because it has methods that
take `self` by value and are only implemented for certain type
parameterizations. Define these methods using macros such that different
builders and related methods can be defined for c_bindings.
This commit is contained in:
Jeffrey Czyz 2024-02-25 14:58:07 -06:00
parent 3a92c7b08a
commit 07d628e0fa
No known key found for this signature in database
GPG key ID: 912EF12EA67705F5
3 changed files with 130 additions and 111 deletions

View file

@ -169,7 +169,7 @@ pub struct DerivedSigningPubkey(KeyPair);
impl SigningPubkeyStrategy for ExplicitSigningPubkey {}
impl SigningPubkeyStrategy for DerivedSigningPubkey {}
impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> {
macro_rules! invoice_explicit_signing_pubkey_builder_methods { ($self: ident, $self_type: ty) => {
pub(super) fn for_offer(
invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>,
created_at: Duration, payment_hash: PaymentHash
@ -203,25 +203,25 @@ impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> {
/// Builds an unsigned [`Bolt12Invoice`] after checking for valid semantics. It can be signed by
/// [`UnsignedBolt12Invoice::sign`].
pub fn build(self) -> Result<UnsignedBolt12Invoice, Bolt12SemanticError> {
pub fn build($self: $self_type) -> Result<UnsignedBolt12Invoice, Bolt12SemanticError> {
#[cfg(feature = "std")] {
if self.invoice.is_offer_or_refund_expired() {
if $self.invoice.is_offer_or_refund_expired() {
return Err(Bolt12SemanticError::AlreadyExpired);
}
}
#[cfg(not(feature = "std"))] {
if self.invoice.is_offer_or_refund_expired_no_std(self.invoice.created_at()) {
if $self.invoice.is_offer_or_refund_expired_no_std($self.invoice.created_at()) {
return Err(Bolt12SemanticError::AlreadyExpired);
}
}
let InvoiceBuilder { invreq_bytes, invoice, .. } = self;
let InvoiceBuilder { invreq_bytes, invoice, .. } = $self;
Ok(UnsignedBolt12Invoice::new(invreq_bytes, invoice))
}
}
} }
impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> {
macro_rules! invoice_derived_signing_pubkey_builder_methods { ($self: ident, $self_type: ty) => {
pub(super) fn for_offer_using_keys(
invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>,
created_at: Duration, payment_hash: PaymentHash, keys: KeyPair
@ -256,23 +256,23 @@ impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> {
/// Builds a signed [`Bolt12Invoice`] after checking for valid semantics.
pub fn build_and_sign<T: secp256k1::Signing>(
self, secp_ctx: &Secp256k1<T>
$self: $self_type, secp_ctx: &Secp256k1<T>
) -> Result<Bolt12Invoice, Bolt12SemanticError> {
#[cfg(feature = "std")] {
if self.invoice.is_offer_or_refund_expired() {
if $self.invoice.is_offer_or_refund_expired() {
return Err(Bolt12SemanticError::AlreadyExpired);
}
}
#[cfg(not(feature = "std"))] {
if self.invoice.is_offer_or_refund_expired_no_std(self.invoice.created_at()) {
if $self.invoice.is_offer_or_refund_expired_no_std($self.invoice.created_at()) {
return Err(Bolt12SemanticError::AlreadyExpired);
}
}
let InvoiceBuilder {
invreq_bytes, invoice, signing_pubkey_strategy: DerivedSigningPubkey(keys)
} = self;
} = $self;
let unsigned_invoice = UnsignedBolt12Invoice::new(invreq_bytes, invoice);
let invoice = unsigned_invoice
@ -282,9 +282,11 @@ impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> {
.unwrap();
Ok(invoice)
}
}
} }
impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> {
macro_rules! invoice_builder_methods { (
$self: ident, $self_type: ty, $return_type: ty, $return_value: expr
) => {
pub(crate) fn amount_msats(
invoice_request: &InvoiceRequest
) -> Result<u64, Bolt12SemanticError> {
@ -326,57 +328,69 @@ impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> {
/// [`Bolt12Invoice::is_expired`].
///
/// Successive calls to this method will override the previous setting.
pub fn relative_expiry(mut self, relative_expiry_secs: u32) -> Self {
pub fn relative_expiry(mut $self: $self_type, relative_expiry_secs: u32) -> $return_type {
let relative_expiry = Duration::from_secs(relative_expiry_secs as u64);
self.invoice.fields_mut().relative_expiry = Some(relative_expiry);
self
$self.invoice.fields_mut().relative_expiry = Some(relative_expiry);
$return_value
}
/// Adds a P2WSH address to [`Bolt12Invoice::fallbacks`].
///
/// Successive calls to this method will add another address. Caller is responsible for not
/// adding duplicate addresses and only calling if capable of receiving to P2WSH addresses.
pub fn fallback_v0_p2wsh(mut self, script_hash: &WScriptHash) -> Self {
pub fn fallback_v0_p2wsh(mut $self: $self_type, script_hash: &WScriptHash) -> $return_type {
let address = FallbackAddress {
version: WitnessVersion::V0.to_num(),
program: Vec::from(script_hash.to_byte_array()),
};
self.invoice.fields_mut().fallbacks.get_or_insert_with(Vec::new).push(address);
self
$self.invoice.fields_mut().fallbacks.get_or_insert_with(Vec::new).push(address);
$return_value
}
/// Adds a P2WPKH address to [`Bolt12Invoice::fallbacks`].
///
/// Successive calls to this method will add another address. Caller is responsible for not
/// adding duplicate addresses and only calling if capable of receiving to P2WPKH addresses.
pub fn fallback_v0_p2wpkh(mut self, pubkey_hash: &WPubkeyHash) -> Self {
pub fn fallback_v0_p2wpkh(mut $self: $self_type, pubkey_hash: &WPubkeyHash) -> $return_type {
let address = FallbackAddress {
version: WitnessVersion::V0.to_num(),
program: Vec::from(pubkey_hash.to_byte_array()),
};
self.invoice.fields_mut().fallbacks.get_or_insert_with(Vec::new).push(address);
self
$self.invoice.fields_mut().fallbacks.get_or_insert_with(Vec::new).push(address);
$return_value
}
/// Adds a P2TR address to [`Bolt12Invoice::fallbacks`].
///
/// Successive calls to this method will add another address. Caller is responsible for not
/// adding duplicate addresses and only calling if capable of receiving to P2TR addresses.
pub fn fallback_v1_p2tr_tweaked(mut self, output_key: &TweakedPublicKey) -> Self {
pub fn fallback_v1_p2tr_tweaked(mut $self: $self_type, output_key: &TweakedPublicKey) -> $return_type {
let address = FallbackAddress {
version: WitnessVersion::V1.to_num(),
program: Vec::from(&output_key.serialize()[..]),
};
self.invoice.fields_mut().fallbacks.get_or_insert_with(Vec::new).push(address);
self
$self.invoice.fields_mut().fallbacks.get_or_insert_with(Vec::new).push(address);
$return_value
}
/// Sets [`Bolt12Invoice::invoice_features`] to indicate MPP may be used. Otherwise, MPP is
/// disallowed.
pub fn allow_mpp(mut self) -> Self {
self.invoice.fields_mut().features.set_basic_mpp_optional();
self
pub fn allow_mpp(mut $self: $self_type) -> $return_type {
$self.invoice.fields_mut().features.set_basic_mpp_optional();
$return_value
}
} }
impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> {
invoice_explicit_signing_pubkey_builder_methods!(self, Self);
}
impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> {
invoice_derived_signing_pubkey_builder_methods!(self, Self);
}
impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> {
invoice_builder_methods!(self, Self, Self, self);
}
/// A semantically valid [`Bolt12Invoice`] that hasn't been signed.
@ -412,32 +426,38 @@ impl UnsignedBolt12Invoice {
pub fn tagged_hash(&self) -> &TaggedHash {
&self.tagged_hash
}
}
macro_rules! unsigned_invoice_sign_method { ($self: ident, $self_type: ty) => {
/// Signs the [`TaggedHash`] of the invoice using the given function.
///
/// Note: The hash computation may have included unknown, odd TLV records.
///
/// This is not exported to bindings users as functions aren't currently mapped.
pub fn sign<F, E>(mut self, sign: F) -> Result<Bolt12Invoice, SignError<E>>
pub fn sign<F, E>(mut $self: $self_type, sign: F) -> Result<Bolt12Invoice, SignError<E>>
where
F: FnOnce(&Self) -> Result<Signature, E>
{
let pubkey = self.contents.fields().signing_pubkey;
let signature = merkle::sign_message(sign, &self, pubkey)?;
let pubkey = $self.contents.fields().signing_pubkey;
let signature = merkle::sign_message(sign, &$self, pubkey)?;
// Append the signature TLV record to the bytes.
let signature_tlv_stream = SignatureTlvStreamRef {
signature: Some(&signature),
};
signature_tlv_stream.write(&mut self.bytes).unwrap();
signature_tlv_stream.write(&mut $self.bytes).unwrap();
Ok(Bolt12Invoice {
bytes: self.bytes,
contents: self.contents,
bytes: $self.bytes,
contents: $self.contents,
signature,
tagged_hash: self.tagged_hash,
tagged_hash: $self.tagged_hash,
})
}
} }
impl UnsignedBolt12Invoice {
unsigned_invoice_sign_method!(self, Self);
}
impl AsRef<TaggedHash> for UnsignedBolt12Invoice {

View file

@ -662,17 +662,9 @@ impl UnsignedInvoiceRequest {
invoice_request_accessors!(self, self.contents);
}
impl InvoiceRequest {
offer_accessors!(self, self.contents.inner.offer);
invoice_request_accessors!(self, self.contents);
/// Signature of the invoice request using [`payer_id`].
///
/// [`payer_id`]: Self::payer_id
pub fn signature(&self) -> Signature {
self.signature
}
macro_rules! invoice_request_respond_with_explicit_signing_pubkey_methods { (
$self: ident, $contents: expr, $builder: ty
) => {
/// Creates an [`InvoiceBuilder`] for the request with the given required fields and using the
/// [`Duration`] since [`std::time::SystemTime::UNIX_EPOCH`] as the creation time.
///
@ -684,13 +676,13 @@ impl InvoiceRequest {
/// [`Duration`]: core::time::Duration
#[cfg(feature = "std")]
pub fn respond_with(
&self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash
) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, Bolt12SemanticError> {
&$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash
) -> Result<$builder, Bolt12SemanticError> {
let created_at = std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
self.respond_with_no_std(payment_paths, payment_hash, created_at)
$contents.respond_with_no_std(payment_paths, payment_hash, created_at)
}
/// Creates an [`InvoiceBuilder`] for the request with the given required fields.
@ -719,31 +711,48 @@ impl InvoiceRequest {
/// [`Bolt12Invoice::created_at`]: crate::offers::invoice::Bolt12Invoice::created_at
/// [`OfferBuilder::deriving_signing_pubkey`]: crate::offers::offer::OfferBuilder::deriving_signing_pubkey
pub fn respond_with_no_std(
&self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
&$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
created_at: core::time::Duration
) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, Bolt12SemanticError> {
if self.invoice_request_features().requires_unknown_bits() {
) -> Result<$builder, Bolt12SemanticError> {
if $contents.invoice_request_features().requires_unknown_bits() {
return Err(Bolt12SemanticError::UnknownRequiredFeatures);
}
InvoiceBuilder::for_offer(self, payment_paths, created_at, payment_hash)
<$builder>::for_offer(&$contents, payment_paths, created_at, payment_hash)
}
} }
macro_rules! invoice_request_verify_method { ($self: ident, $self_type: ty) => {
/// Verifies that the request was for an offer created using the given key. Returns the verified
/// request which contains the derived keys needed to sign a [`Bolt12Invoice`] for the request
/// if they could be extracted from the metadata.
///
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
pub fn verify<T: secp256k1::Signing>(
self, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
$self: $self_type, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
) -> Result<VerifiedInvoiceRequest, ()> {
let keys = self.contents.inner.offer.verify(&self.bytes, key, secp_ctx)?;
let keys = $self.contents.inner.offer.verify(&$self.bytes, key, secp_ctx)?;
Ok(VerifiedInvoiceRequest {
inner: self,
inner: $self,
keys,
})
}
} }
impl InvoiceRequest {
offer_accessors!(self, self.contents.inner.offer);
invoice_request_accessors!(self, self.contents);
invoice_request_respond_with_explicit_signing_pubkey_methods!(self, self, InvoiceBuilder<ExplicitSigningPubkey>);
invoice_request_verify_method!(self, Self);
/// Signature of the invoice request using [`payer_id`].
///
/// [`payer_id`]: Self::payer_id
pub fn signature(&self) -> Signature {
self.signature
}
pub(crate) fn as_tlv_stream(&self) -> FullInvoiceRequestTlvStreamRef {
let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) =
self.contents.as_tlv_stream();
@ -754,37 +763,9 @@ impl InvoiceRequest {
}
}
impl VerifiedInvoiceRequest {
offer_accessors!(self, self.inner.contents.inner.offer);
invoice_request_accessors!(self, self.inner.contents);
/// Creates an [`InvoiceBuilder`] for the request with the given required fields and using the
/// [`Duration`] since [`std::time::SystemTime::UNIX_EPOCH`] as the creation time.
///
/// See [`InvoiceRequest::respond_with_no_std`] for further details.
///
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
///
/// [`Duration`]: core::time::Duration
#[cfg(feature = "std")]
pub fn respond_with(
&self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash
) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, Bolt12SemanticError> {
self.inner.respond_with(payment_paths, payment_hash)
}
/// Creates an [`InvoiceBuilder`] for the request with the given required fields.
///
/// See [`InvoiceRequest::respond_with_no_std`] for further details.
///
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
pub fn respond_with_no_std(
&self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
created_at: core::time::Duration
) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, Bolt12SemanticError> {
self.inner.respond_with_no_std(payment_paths, payment_hash, created_at)
}
macro_rules! invoice_request_respond_with_derived_signing_pubkey_methods { (
$self: ident, $contents: expr, $builder: ty
) => {
/// Creates an [`InvoiceBuilder`] for the request using the given required fields and that uses
/// derived signing keys from the originating [`Offer`] to sign the [`Bolt12Invoice`]. Must use
/// the same [`ExpandedKey`] as the one used to create the offer.
@ -796,13 +777,13 @@ impl VerifiedInvoiceRequest {
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
#[cfg(feature = "std")]
pub fn respond_using_derived_keys(
&self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash
) -> Result<InvoiceBuilder<DerivedSigningPubkey>, Bolt12SemanticError> {
&$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash
) -> Result<$builder, Bolt12SemanticError> {
let created_at = std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
self.respond_using_derived_keys_no_std(payment_paths, payment_hash, created_at)
$self.respond_using_derived_keys_no_std(payment_paths, payment_hash, created_at)
}
/// Creates an [`InvoiceBuilder`] for the request using the given required fields and that uses
@ -815,22 +796,29 @@ impl VerifiedInvoiceRequest {
///
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
pub fn respond_using_derived_keys_no_std(
&self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
&$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
created_at: core::time::Duration
) -> Result<InvoiceBuilder<DerivedSigningPubkey>, Bolt12SemanticError> {
if self.inner.invoice_request_features().requires_unknown_bits() {
) -> Result<$builder, Bolt12SemanticError> {
if $self.inner.invoice_request_features().requires_unknown_bits() {
return Err(Bolt12SemanticError::UnknownRequiredFeatures);
}
let keys = match self.keys {
let keys = match $self.keys {
None => return Err(Bolt12SemanticError::InvalidMetadata),
Some(keys) => keys,
};
InvoiceBuilder::for_offer_using_keys(
&self.inner, payment_paths, created_at, payment_hash, keys
<$builder>::for_offer_using_keys(
&$self.inner, payment_paths, created_at, payment_hash, keys
)
}
} }
impl VerifiedInvoiceRequest {
offer_accessors!(self, self.inner.contents.inner.offer);
invoice_request_accessors!(self, self.inner.contents);
invoice_request_respond_with_explicit_signing_pubkey_methods!(self, self.inner, InvoiceBuilder<ExplicitSigningPubkey>);
invoice_request_respond_with_derived_signing_pubkey_methods!(self, self.inner, InvoiceBuilder<DerivedSigningPubkey>);
}
impl InvoiceRequestContents {

View file

@ -480,7 +480,9 @@ impl Refund {
pub fn payer_note(&self) -> Option<PrintableString> {
self.contents.payer_note()
}
}
macro_rules! respond_with_explicit_signing_pubkey_methods { ($self: ident, $builder: ty) => {
/// Creates an [`InvoiceBuilder`] for the refund with the given required fields and using the
/// [`Duration`] since [`std::time::SystemTime::UNIX_EPOCH`] as the creation time.
///
@ -492,14 +494,14 @@ impl Refund {
/// [`Duration`]: core::time::Duration
#[cfg(feature = "std")]
pub fn respond_with(
&self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
&$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
signing_pubkey: PublicKey,
) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, Bolt12SemanticError> {
) -> Result<$builder, Bolt12SemanticError> {
let created_at = std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
self.respond_with_no_std(payment_paths, payment_hash, signing_pubkey, created_at)
$self.respond_with_no_std(payment_paths, payment_hash, signing_pubkey, created_at)
}
/// Creates an [`InvoiceBuilder`] for the refund with the given required fields.
@ -525,16 +527,18 @@ impl Refund {
///
/// [`Bolt12Invoice::created_at`]: crate::offers::invoice::Bolt12Invoice::created_at
pub fn respond_with_no_std(
&self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
&$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
signing_pubkey: PublicKey, created_at: Duration
) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, Bolt12SemanticError> {
if self.features().requires_unknown_bits() {
) -> Result<$builder, Bolt12SemanticError> {
if $self.features().requires_unknown_bits() {
return Err(Bolt12SemanticError::UnknownRequiredFeatures);
}
InvoiceBuilder::for_refund(self, payment_paths, created_at, payment_hash, signing_pubkey)
<$builder>::for_refund($self, payment_paths, created_at, payment_hash, signing_pubkey)
}
} }
macro_rules! respond_with_derived_signing_pubkey_methods { ($self: ident, $builder: ty) => {
/// Creates an [`InvoiceBuilder`] for the refund using the given required fields and that uses
/// derived signing keys to sign the [`Bolt12Invoice`].
///
@ -545,9 +549,9 @@ impl Refund {
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
#[cfg(feature = "std")]
pub fn respond_using_derived_keys<ES: Deref>(
&self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
&$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
expanded_key: &ExpandedKey, entropy_source: ES
) -> Result<InvoiceBuilder<DerivedSigningPubkey>, Bolt12SemanticError>
) -> Result<$builder, Bolt12SemanticError>
where
ES::Target: EntropySource,
{
@ -555,7 +559,7 @@ impl Refund {
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
self.respond_using_derived_keys_no_std(
$self.respond_using_derived_keys_no_std(
payment_paths, payment_hash, created_at, expanded_key, entropy_source
)
}
@ -569,22 +573,29 @@ impl Refund {
///
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
pub fn respond_using_derived_keys_no_std<ES: Deref>(
&self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
&$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
created_at: core::time::Duration, expanded_key: &ExpandedKey, entropy_source: ES
) -> Result<InvoiceBuilder<DerivedSigningPubkey>, Bolt12SemanticError>
) -> Result<$builder, Bolt12SemanticError>
where
ES::Target: EntropySource,
{
if self.features().requires_unknown_bits() {
if $self.features().requires_unknown_bits() {
return Err(Bolt12SemanticError::UnknownRequiredFeatures);
}
let nonce = Nonce::from_entropy_source(entropy_source);
let keys = signer::derive_keys(nonce, expanded_key);
InvoiceBuilder::for_refund_using_keys(self, payment_paths, created_at, payment_hash, keys)
<$builder>::for_refund_using_keys($self, payment_paths, created_at, payment_hash, keys)
}
} }
impl Refund {
respond_with_explicit_signing_pubkey_methods!(self, InvoiceBuilder<ExplicitSigningPubkey>);
respond_with_derived_signing_pubkey_methods!(self, InvoiceBuilder<DerivedSigningPubkey>);
}
#[cfg(test)]
impl Refund {
fn as_tlv_stream(&self) -> RefundTlvStreamRef {
self.contents.as_tlv_stream()
}