Skip to content

[RFC] contrib: add libbolt12 standalone decode/verify library#9056

Draft
vincenzopalazzo wants to merge 1 commit intoElementsProject:masterfrom
vincenzopalazzo:contrib/libbolt12-rfc
Draft

[RFC] contrib: add libbolt12 standalone decode/verify library#9056
vincenzopalazzo wants to merge 1 commit intoElementsProject:masterfrom
vincenzopalazzo:contrib/libbolt12-rfc

Conversation

@vincenzopalazzo
Copy link
Copy Markdown
Collaborator

Summary

RFC: Add a standalone C library (libbolt12) that wraps CLN's existing bolt12 implementation behind a clean public API — no tal, no CLN internal types exposed.

Motivation

External C programs (e.g. mining pool payout verification tools) need to decode and verify BOLT12 offers/invoices without depending on Rust or running a full CLN node. Currently the only options are:

  1. Use rust-lightning's offers module (requires Rust toolchain)
  2. Call lightning-cli decode as a subprocess (requires running CLN)
  3. Reimplement from scratch

This library provides a fourth option: link against libbolt12.a and call clean C functions.

What it provides

#include "bolt12.h"

bolt12_init();

// All-in-one verification
bolt12_error_t err;
int rc = bolt12_verify_offer_payment("lno1...", "lni1...", preimage_hex, &err);

// Or step by step with property access
bolt12_offer_t *offer = bolt12_offer_decode("lno1...", &err);
char desc[256];
bolt12_offer_description(offer, desc, sizeof(desc));
bolt12_pubkey_t pk;
bolt12_offer_effective_signing_pubkey(offer, &pk);

bolt12_invoice_t *inv = bolt12_invoice_decode("lni1...", &err);
uint64_t amount;
bolt12_invoice_amount(inv, &amount);

bolt12_verify_invoice_signature(inv, &err);
bolt12_verify_proof_of_payment(inv, preimage_hex, &err);
bolt12_compare_signing_pubkeys(offer, inv, &err);

bolt12_invoice_free(inv);
bolt12_offer_free(offer);
bolt12_cleanup();

API surface

Decode/Free:

  • bolt12_offer_decode() / bolt12_offer_free()
  • bolt12_invoice_decode() / bolt12_invoice_free()

Offer accessors:

  • bolt12_offer_id(), bolt12_offer_description(), bolt12_offer_issuer()
  • bolt12_offer_amount(), bolt12_offer_currency()
  • bolt12_offer_effective_signing_pubkey(), bolt12_offer_num_paths()
  • bolt12_offer_absolute_expiry(), bolt12_offer_quantity_max(), bolt12_offer_chains()

Invoice accessors:

  • bolt12_invoice_payment_hash(), bolt12_invoice_amount(), bolt12_invoice_description()
  • bolt12_invoice_signing_pubkey(), bolt12_invoice_offer_id()
  • bolt12_invoice_created_at(), bolt12_invoice_expiry(), bolt12_invoice_signature()
  • bolt12_invoice_payer_note(), bolt12_invoice_quantity(), bolt12_invoice_num_paths()

Verification:

  • bolt12_verify_invoice_signature() — BIP-340 schnorr over merkle root
  • bolt12_verify_proof_of_payment() — SHA256(preimage) == payment_hash
  • bolt12_compare_offer_id() / bolt12_compare_signing_pubkeys()
  • bolt12_verify_offer_payment() — all-in-one convenience

Implementation

~1400 lines total. The implementation is a thin wrapper (~660 lines in bolt12_api.c) around CLN's existing offer_decode(), invoice_decode(), bolt12_check_signature(), merkle_tlv() etc. No CLN code was modified — only a one-line include added to the top-level Makefile.

Opaque types (bolt12_offer_t, bolt12_invoice_t) hide the internal tal tree. Public API uses only standard C types (uint8_t[32], uint64_t, char *).

Build

make libbolt12.a          # thin archive (needs libcommon.a + libccan.a)
make libbolt12-fat.a      # self-contained (only needs -lsecp256k1 -lwallycore -lsodium)
make check-libbolt12      # run tests (19/19 passing)

Test results

Results: 19/19 passed
- Decode offer/invoice with property extraction ✓
- Full verification pipeline ✓
- Mismatched offer/invoice detection ✓
- Bad preimage detection ✓
- Invalid input handling ✓

Questions for reviewers

  • Is contrib/ the right home for this, or should it live elsewhere?
  • Should the library also expose encode functions (bolt12_offer_encode() etc.)?
  • Any interest in a pkg-config file / install target for external consumers?

Test plan

  • make check-libbolt12 passes (19/19 tests)
  • CI integration (needs review of Makefile include approach)
  • Test with external consumer program

🤖 Generated with Claude Code

@vincenzopalazzo vincenzopalazzo changed the title contrib: add libbolt12 standalone decode/verify library [RFC] contrib: add libbolt12 standalone decode/verify library Apr 13, 2026
@vincenzopalazzo vincenzopalazzo force-pushed the contrib/libbolt12-rfc branch 4 times, most recently from 51c340d to 63bf809 Compare April 14, 2026 08:41
Add a standalone C library (libbolt12) that wraps CLN's existing bolt12
implementation behind a clean public API with no CLN-internal types
exposed (no tal, no TLV structs).

The library provides:
- Decode BOLT12 offers (lno1...) and invoices (lni1...)
- Access all offer/invoice properties via accessor functions
- Verify invoice BIP-340 schnorr signatures
- Verify proof of payment (preimage vs payment_hash)
- Compare offer_id and signing pubkeys between offer and invoice
- All-in-one bolt12_verify_offer_payment() convenience function

The motivation is enabling external C programs (e.g. mining pool payout
verification tools) to decode and verify BOLT12 offers/invoices without
depending on Rust or running a full CLN node. The library links against
libcommon.a and libccan.a, with a fat archive option (libbolt12-fat.a)
for external consumers.

libbolt12 deliberately avoids common_setup() because common_setup()
mutates process-global state (locale, env, progname) and calls errx(1)
on init failures -- both unacceptable for a reusable library. Instead,
bolt12_init() does the minimum: secp256k1_context_create() plus a
tmpctx. bolt12_cleanup() reverses this.

Includes a test suite (contrib/libbolt12/run-bolt12.c) using real-world
test vectors from Phoenix and CLN-generated offers/invoices, exercising
the happy path and all documented error branches.

Changelog-Added: contrib: new libbolt12 library exposing BOLT12 decode and verification as a standalone C API.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant