Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@ include doc/Makefile
include contrib/msggen/Makefile
include devtools/Makefile
include tools/Makefile
include contrib/libbolt12/Makefile
ifneq ($(RUST),0)
include cln-rpc/Makefile
include cln-grpc/Makefile
Expand Down
57 changes: 57 additions & 0 deletions contrib/libbolt12/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# libbolt12 - Makefile fragment included by CLN's top-level Makefile.
#
# Produces:
# libbolt12.a thin wrapper; consumers link libcommon.a +
# libccan.a + libsecp256k1 alongside it
# libbolt12-fat.a self-contained archive (only needs
# -lsecp256k1 at link time)
# contrib/libbolt12/run-bolt12 unit test binary

LIBBOLT12_SRC := contrib/libbolt12/bolt12_api.c
LIBBOLT12_OBJS := $(LIBBOLT12_SRC:.c=.o)
LIBBOLT12_HEADERS := \
contrib/libbolt12/bolt12.h \
contrib/libbolt12/bolt12_internal.h

LIBBOLT12_TEST_SRC := contrib/libbolt12/run-bolt12.c
LIBBOLT12_TEST_OBJS := $(LIBBOLT12_TEST_SRC:.c=.o)

# Thin wrapper library (just our wrapper code).
libbolt12.a: $(LIBBOLT12_OBJS)
@$(call VERBOSE, "ar libbolt12.a", $(AR) rcs $@ $(LIBBOLT12_OBJS))

# Fat archive: bundles libbolt12 + libcommon + libccan. External
# consumers still need -lsecp256k1 -lsodium -lwallycore -lm at link time
# because libcommon.a transitively references sodium/wally symbols from
# code paths libbolt12 itself doesn't use (psbt.c, script.c, randbytes.c
# etc.). A future refactor could extract a bolt12-only subset to drop
# those transitive deps.
libbolt12-fat.a: libbolt12.a libcommon.a libccan.a
@$(call VERBOSE, "ar libbolt12-fat.a", \
rm -rf .libbolt12-tmp && mkdir .libbolt12-tmp && \
cd .libbolt12-tmp && \
$(AR) x ../libbolt12.a && \
$(AR) x ../libcommon.a && \
$(AR) x ../libccan.a && \
$(AR) rcs ../libbolt12-fat.a *.o && \
cd .. && rm -rf .libbolt12-tmp)

# Test binary.
contrib/libbolt12/run-bolt12: $(LIBBOLT12_TEST_OBJS) libbolt12.a libcommon.a libccan.a
@$(call VERBOSE, "ld $@", \
$(CC) $(CFLAGS) -o $@ $(LIBBOLT12_TEST_OBJS) \
libbolt12.a libcommon.a libccan.a \
$(EXTERNAL_LDLIBS) $(LDLIBS))

check-libbolt12: contrib/libbolt12/run-bolt12
contrib/libbolt12/run-bolt12

ALL_C_SOURCES += $(LIBBOLT12_SRC) $(LIBBOLT12_TEST_SRC)
ALL_C_HEADERS += $(LIBBOLT12_HEADERS)

clean: libbolt12-clean
libbolt12-clean:
$(RM) libbolt12.a libbolt12-fat.a
$(RM) $(LIBBOLT12_OBJS) $(LIBBOLT12_TEST_OBJS)
$(RM) contrib/libbolt12/run-bolt12
$(RM) -rf .libbolt12-tmp
143 changes: 143 additions & 0 deletions contrib/libbolt12/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# libbolt12

Standalone C library for decoding and verifying BOLT12 offers and invoices,
built on top of Core Lightning's existing bolt12 implementation.

**Status**: Experimental (RFC). API may change before stabilization.

## Motivation

External C programs (e.g. mining pool payout verification tools, hardware
wallets, embedded devices) need to decode and verify BOLT12 offers/invoices
without depending on Rust, running a full CLN node, or shelling out to
`lightning-cli decode`. libbolt12 provides that.

## Features

- Decode `lno1...` offers and `lni1...` invoices from bech32m strings
- Access all offer/invoice properties via clean accessor functions
- Verify invoice BIP-340 Schnorr signatures (merkle-root over TLV)
- Verify proof of payment (SHA256 preimage matches payment hash)
- Compare offer_id and signing pubkeys between offer and invoice
- All-in-one `bolt12_verify_offer_payment()` convenience entry point

## Dependencies

Runtime (link-time):
- libsecp256k1 (Schnorr signature verification)
- libsodium (transitively pulled in by libcommon.a)
- libwallycore (transitively pulled in by libcommon.a)
- libm

libbolt12's own code does NOT use libsodium or libwally — it calls
`secp256k1_context_create()` directly instead of going through
`common_setup()`/wally, to avoid the global process-state mutations
(locale, env, progname) that `common_setup()` performs and to avoid
`common_setup()`'s `errx(1, ...)` failure mode that would kill the host
application.

However, `libcommon.a` as a whole still transitively references sodium and
wally symbols (from files like `psbt.c`, `script.c`, `randbytes.c` that
libbolt12 does not use). A future refactor could build a trimmed
`libcommon-bolt12.a` containing only the object files libbolt12 actually
needs, removing the transitive libsodium/libwally dependency. For now the
fat archive still requires them at link time.

## Building

From the CLN source root:

```sh
make libbolt12.a # thin archive (requires libcommon.a + libccan.a
# to be linked alongside it by consumers)
make libbolt12-fat.a # self-contained archive (only needs -lsecp256k1)
make check-libbolt12 # run the test suite
```

## Using the library

### Linking

With the fat archive (simpler for external consumers):

```sh
cc -o myprog main.c \
-I/path/to/cln/contrib/libbolt12 \
/path/to/cln/libbolt12-fat.a \
-lsecp256k1 -lsodium -lwallycore -lm
```

With the thin archive (if you already link the CLN archives):

```sh
cc -o myprog main.c \
-I/path/to/cln/contrib/libbolt12 \
/path/to/cln/libbolt12.a \
/path/to/cln/libcommon.a \
/path/to/cln/libccan.a \
-lsecp256k1 -lsodium -lwallycore -lm
```

### Example

```c
#include "bolt12.h"
#include <stdio.h>

int main(void) {
if (bolt12_init() != 0) {
fprintf(stderr, "Failed to initialize libbolt12\n");
return 1;
}

bolt12_error_t err;
int rc = bolt12_verify_offer_payment(
"lno1pg7y7s69...",
"lni1qqg9sr0tna...",
"a71dceaa4f2b86713834d6362035adf0eb7eab6c6c61ae3c8b68baffd9072cfc",
&err);

if (rc != 0)
fprintf(stderr, "Verification failed: %s\n", err.message);
else
printf("Payment verified successfully.\n");

bolt12_cleanup();
return rc;
}
```

For per-field access:

```c
bolt12_offer_t *offer = bolt12_offer_decode(offer_str, &err);
if (!offer) { /* handle err */ }

char description[256];
bolt12_offer_description(offer, description, sizeof(description));

uint64_t amount_msat;
if (bolt12_offer_amount(offer, &amount_msat) == 0) {
printf("Amount: %llu msat\n", (unsigned long long)amount_msat);
}

bolt12_offer_free(offer);
```

See `bolt12.h` for the full API.

## Thread safety

**Not thread-safe.** libbolt12 uses process-global state (the libsecp256k1
verification context and CLN's `tmpctx` tal root). Serialize all calls from
a single thread, or protect with an external mutex.

## Testing

```sh
make check-libbolt12
```

The test suite uses real offer/invoice/preimage triplets generated by
Core Lightning and Phoenix, exercising the happy path and all error
branches (mismatched signing keys, wrong preimage, invalid input format).
Loading
Loading