Skip to content

bolt12: add Merkle tree and BIP-340 message signatures - #11061

Open
bitromortac wants to merge 9 commits into
lightningnetwork:masterfrom
bitromortac:2604-bolt12-1f
Open

bolt12: add Merkle tree and BIP-340 message signatures#11061
bitromortac wants to merge 9 commits into
lightningnetwork:masterfrom
bitromortac:2604-bolt12-1f

Conversation

@bitromortac

Copy link
Copy Markdown
Collaborator

Part of #10736. Based on #11001.

Adds Merkle tree construction over TLV records (MerkleRoot) and BIP-340 Schnorr message signatures (signMessage, verifySignature) for BOLT 12 invoice requests and invoices, and verifies the signature on read so a decoded message with an invalid signature is rejected.

Additionally, this PR vendors upstream lightning/bolts signature test vectors (signature-test.json) to pin Merkle construction and signature verification byte-for-byte against the spec.

The bolt12 package can already encode and decode the TLV layer but has
no way to carry an offer as a human-transportable string, which is the
form the spec specifies for QR codes, URLs and email signatures. BOLT
12's envelope is subtractive relative to BIP-173: there is no BCH
checksum, because the BIP-340 signature over the Merkle root already
secures the payload, and a '+' continuation marker may split the string
across lines. btcutil/bech32's public API always wraps the checksum, so
the alphabet layer is duplicated here rather than reused. Enforce a
whitelist of BOLT 12 prefixes (lno, lnr, lni) on both Encode and Decode.
Vendor the BOLT 12 offers-test.json fixtures so the offer decoder and
validator are checked against the specification's own strings rather
than hand-authored ones, which cannot drift from the spec without
someone noticing. Invalid vectors are tested to verify they are rejected
at some layer, and an aggregate stage census pins the distribution
across bech32 decode, TLV decode, and validation.
The invoice_request codec has round-trip coverage against locally-
constructed messages only, so a canonical-encoding bug would go
unnoticed until a real peer rejected a signature. Drive the decoder from
the spec's signature-test invoice_request and assert that re-encoding is
byte-identical to the wire bytes, because the signature commits to the
Merkle root of that exact encoding.
Add changes for the bech32 work in bolt12.
BOLT 12 signs the Merkle root of a tree built from the message's TLV
records. MerkleRoot constructs the tree from a []tlv.Record by hashing
each record into an LnLeaf and a uniqueness LnNonce, combining them
into LnBranch nodes, and recursing to a single root.

signableTLVs selects the records that enter the root, excluding the
240-1000 range the spec reserves for signature fields. The vendored
signature-test.json vectors pin the construction byte-for-byte against
the spec, down to every intermediate LnLeaf, LnNonce, and LnBranch
digest.
SignMessage and VerifySignature wrap btcec's Schnorr API around the
Merkle root with the spec's tagged-hash domain separator. Typed
helpers bind the construction to invoice requests and invoices, so
callers sign and verify messages rather than raw roots.

The signed vectors in signature-test.json verify the construction
against the spec, and a tamper matrix pins rejection of modified
roots, signatures, keys, and tags.
ValidateInvoiceRequestRead and ValidateInvoiceRead now run
VerifyInvoiceRequest and VerifyInvoice as their final step, so a
decoded message whose BIP-340 signature does not verify against
invreq_payer_id or invoice_node_id is rejected instead of only
checked for presence. This satisfies the reader-side MUSTs of the
BOLT 12 invoice_request and invoice requirements, and the docstrings
now state the contract instead of deferring it to callers.

The invoice_request wiring is pinned by the spec's signed vector; no
signed invoice vector exists, so the invoice side is pinned by
round-trip tests. Reader-validation fixtures now sign with Bob's
key, and the happy-path invoice_request test reuses the shared
validInvoiceRequest fixture.
Add changes for the Merkle tree and BIP-340 signature work in bolt12.
@saubyk saubyk added this to the v0.22.0 milestone Aug 11, 2026
@saubyk saubyk added this to lnd v0.22 Aug 11, 2026
@github-project-automation github-project-automation Bot moved this to Backlog in lnd v0.22 Aug 11, 2026
@saubyk saubyk moved this from Backlog to In progress in lnd v0.22 Aug 11, 2026
@saubyk saubyk added the bolt12 label Aug 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

2 participants