-
Notifications
You must be signed in to change notification settings - Fork 116
[RFC] event: expose BOLT12 invoice in PaymentSuccessful for proof of payment #733
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
[RFC] event: expose BOLT12 invoice in PaymentSuccessful for proof of payment #733
Conversation
This patch adds the `bolt12_invoice` field to the `PaymentSuccessful` event, enabling users to obtain proof of payment for BOLT12 transactions. Problem: Previously, after a successful BOLT12 payment, users had no way to access the paid invoice data. This made it impossible to provide proof of payment to third parties, who need both the payment preimage and the original invoice to verify that sha256(preimage) matches the invoice's payment_hash. Solution: Add a `bolt12_invoice: Option<Vec<u8>>` field to `PaymentSuccessful` that contains the serialized BOLT12 invoice bytes. The invoice is serialized using LDK's standard encoding, which can be parsed back using `Bolt12Invoice::try_from(bytes)` in native Rust, or by hex-encoding the bytes and using `Bolt12Invoice.from_str()` in FFI bindings. Design decisions: - Store as `Vec<u8>` rather than the complex `PaidBolt12Invoice` type to avoid UniFFI limitations with objects in enum variants - Return `None` for `StaticInvoice` (async payments) since proof of payment is not possible for those payment types anyway - Use TLV tag 7 for serialization, maintaining backward compatibility with existing persisted events This implementation follows the maintainer guidance from PR lightningdevkit#563 to expose the invoice via the event rather than storing it in the payment store. Signed-off-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com>
|
I've assigned @tnull as a reviewer! |
|
@tnull IDK if this is a good design to have with the ffi, but I had to work around some unify ffi limitation with the enum type that is used inside the |
|
🔔 1st Reminder Hey @tnull! This PR has been waiting for your review. |
|
🔔 2nd Reminder Hey @tnull! This PR has been waiting for your review. |
|
🔔 3rd Reminder Hey @tnull! This PR has been waiting for your review. |
tnull
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exposing the BOLT12 invoice makes sense to me, though we should do it properly instead of just exposing the bytes.
src/event.rs
Outdated
| /// | ||
| /// To parse the invoice in native Rust, use `Bolt12Invoice::try_from(bytes)`. | ||
| /// In FFI bindings, hex-encode the bytes and use `Bolt12Invoice.from_str(hex_string)`. | ||
| bolt12_invoice: Option<Vec<u8>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we should expose the raw bytes here, although I have to say that I also find the PaidBolt12Invoice type pretty awkward tbh. But I guess we'll have to bite that bullet now.
Note that we'll probably need to add our own version of PaidBolt12Invoice though as Uniffi doesn't support tuple enum variants, so we'll need to add a local version with named enum variants, and then add an impl From<LdkPaidBolt12Invoice> for PaidBolt12Invoice, as well as expose the StaticInvoice APIs in the ffi/types.rs/UDL.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about the commit 4c6b18e
This patch refactors the BOLT12 invoice exposure in PaymentSuccessful to use properly typed wrappers instead of raw Vec<u8> bytes. Problem: The previous implementation exposed the BOLT12 invoice as Option<Vec<u8>> which required users to manually deserialize the bytes and didn't provide a good API experience. Additionally, UniFFI bindings need proper type wrappers to generate idiomatic foreign language bindings. Solution: - Add StaticInvoice wrapper type with accessor methods (offer_id, is_offer_expired, signing_pubkey, etc.) - Create PaidBolt12Invoice as a struct with a discriminant enum (PaidBolt12InvoiceKind) and optional fields for bolt12_invoice and static_invoice - Implement From<LdkPaidBolt12Invoice> for automatic conversion - Add Writeable/Readable implementations for serialization - Update UDL bindings with StaticInvoice interface and PaidBolt12Invoice dictionary Design decisions: - PaidBolt12Invoice is a struct (dictionary) rather than an enum because UniFFI does not support objects in enum variant data. The workaround uses a discriminant enum (PaidBolt12InvoiceKind) with optional object fields, which is the recommended pattern per UniFFI documentation. - Both uniffi and non-uniffi builds share the same API through conditional compilation in types.rs, ensuring consistent behavior across all build configurations. Signed-off-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com>
4c6b18e to
a3a60b3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The changes are getting closer, but please make sure to avoid unnecessary boilerplate and stick to the approach we took for the other types (like Offer, Refund, etc).
This also needs a rebase by now, sorry!
Btw, please re-request review when you made updates, otherwise I might not always see it immediately.
| "StaticInvoice", | ||
| }; | ||
|
|
||
| dictionary PaidBolt12Invoice { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we make this an
[Enum]
interface PaidBolt12Invoice {
instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mh I was hacking round this this afternoon and this is what I got
error: failed to run custom build command for `ldk-node v0.8.0+git`
Caused by:
process didn't exit successfully (exit status: 101)
--- stdout
cargo:rerun-if-changed=bindings/ldk_node.udl
cargo:rerun-if-env-changed=UNIFFI_TESTS_DISABLE_EXTENSIONS
--- stderr
thread 'main' panicked at build.rs:10:59:
called `Result::unwrap()` on an `Err` value: Objects cannot currently be used in enum variant data
The key part is:
Objects cannot currently be used in enum variant data
This occurs when trying to use:
[Enum]
interface PaidBolt12Invoice {
Bolt12Invoice(Bolt12Invoice invoice);
StaticInvoice(StaticInvoice invoice);
};It looks like that it is happening because Bolt12Invoice and StaticInvoice are defined as interface (Objects) in UniFFI, they cannot be used as parameters in enum variants.
Am I missing something?
| /// Unlike [`Bolt12Invoice`], a `StaticInvoice` does not support proof of payment | ||
| /// because the payment hash is not derived from a preimage known only to the recipient. | ||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||
| pub struct StaticInvoice { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to wrap StaticInvoice in the non-uniffi case. Please follow the same approach as we did for Offer, Refund, etc. and reuse the existing maybe_wrap macros etc.
| } | ||
| } | ||
|
|
||
| impl Writeable for PaidBolt12Invoice { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We def. shouldn't need to reimplement anything related to serialization.
This patch adds the
bolt12_invoicefield to thePaymentSuccessfulevent, enabling users to obtain proof of payment for BOLT12 transactions.Problem:
Previously, after a successful BOLT12 payment, users had no way to access the paid invoice data. This made it impossible to provide proof of payment to third parties, who need both the payment preimage and the original invoice to verify that sha256(preimage) matches the invoice's payment_hash.
Solution:
Add a
bolt12_invoice: Option<Vec<u8>>field toPaymentSuccessfulthat contains the serialized BOLT12 invoice bytes. The invoice is serialized using LDK's standard encoding, which can be parsed back usingBolt12Invoice::try_from(bytes)in native Rust, or by hex-encoding the bytes and usingBolt12Invoice.from_str()in FFI bindings.Design decisions:
Vec<u8>rather than the complexPaidBolt12Invoicetype to avoid UniFFI limitations with objects in enum variantsNoneforStaticInvoice(async payments) since proof of payment is not possible for those payment types anywayThis implementation follows the maintainer guidance from PR #563 to expose the invoice via the event rather than storing it in the payment store.