diff --git a/amethyst/plans/2026-07-24-nwc-bolt12-pay.md b/amethyst/plans/2026-07-24-nwc-bolt12-pay.md index bb953291f4..37e0a30b8c 100644 --- a/amethyst/plans/2026-07-24-nwc-bolt12-pay.md +++ b/amethyst/plans/2026-07-24-nwc-bolt12-pay.md @@ -123,10 +123,12 @@ but is **not** wired into the pay path — we'd add the gate ourselves. A wallet may settle the offer and return no proof → payment succeeds but we can't publish a zap. Phase 1 is unaffected; Phase 2 must degrade gracefully ("paid, but no zap receipt available"). -3. **Our verifier can't check compressed proofs yet** (see - `quartz/plans/2026-07-23-bolt12-zap-interop-vectors.md`). Real wallet proofs are - compressed, so a zap we send may show locally as unverified. Publishing is fine; - local counting waits on the merkle-reconstruction work. +3. ~~**Our verifier can't check compressed proofs yet.**~~ **Resolved.** The + compressed-proof merkle reconstruction shipped and is validated byte-for-byte + against the lightning/bolts#1346 conformance vectors (see + `quartz/plans/2026-07-23-bolt12-zap-interop-vectors.md`). Real wallet + (selective-disclosure) proofs now reconstruct and verify, so a bound zap counts + locally as `cryptoVerified = true`. 4. **Maturity.** Both nwc#2 and NIP-2421 are unmerged; few/no wallets implement `pay` today. Gate hard on capability (Phase 3) and keep the intent fallback. @@ -191,7 +193,7 @@ BOLT12 rail even if it supports `pay`. Both fail safe toward lightning. Known limitations (follow-ons, not blockers): -- **Compressed proofs aren't locally counted.** A zap we send with a compressed - proof is published and valid, but our own `updateZapTotal` won't count it until - the merkle-reconstruction lands (see the interop-vectors plan). Other clients with - full BOLT12 support can count it. +- ~~**Compressed proofs aren't locally counted.**~~ **Resolved.** Compressed + (selective-disclosure) proofs now reconstruct and verify, so `updateZapTotal` + counts a bound zap locally. Validated against the lightning/bolts#1346 + conformance vectors — see `quartz/plans/2026-07-23-bolt12-zap-interop-vectors.md`. diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/actions/Bolt12ZapActions.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/actions/Bolt12ZapActions.kt index 9e82437c53..bcf6cdce3c 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/actions/Bolt12ZapActions.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/actions/Bolt12ZapActions.kt @@ -81,7 +81,7 @@ object Bolt12ZapActions { val proof = Bolt12PayerProof.parse(raw) ?: return null return runCatching { buildMap { - put("has_all_required_fields", proof.hasAllRequiredFields()) + put("has_all_required_fields", proof.hasAllCryptoFields()) put("compressed", proof.isCompressed()) proof.invreqPayerNote()?.let { put("invreq_payer_note", it) } proof.invreqPayerId()?.let { put("invreq_payer_id", Hex.encode(it)) } diff --git a/quartz/plans/2026-07-23-bolt12-zap-interop-vectors.md b/quartz/plans/2026-07-23-bolt12-zap-interop-vectors.md index 19374f4b8c..6f38edcf7c 100644 --- a/quartz/plans/2026-07-23-bolt12-zap-interop-vectors.md +++ b/quartz/plans/2026-07-23-bolt12-zap-interop-vectors.md @@ -1,67 +1,44 @@ # BOLT12 zap proof verification — interop test vectors (follow-up) -Status: **blocked on upstream.** The NIP-XX BOLT12-zap layer (`quartz/…/nipXXBolt12Zaps/`) -verifies fully-disclosed payer proofs and reports compressed ones as -`Bolt12ProofResult.Unsupported` (surfaced as `cryptoVerified = false`). Two pieces -of work are gated on the BOLT12 payer-proof spec ([lightning/bolts#1346]) merging -with published test vectors. +Status: **done** (both work items shipped), with one standing upstream caveat. +`Bolt12ProofVerifier` now reconstructs and fully verifies **compressed** BOLT12 +payer proofs — the selective-disclosure case real wallets emit — so they count as +`cryptoVerified = true` (subject to the usual offer-binding rule). Verified +byte-for-byte against the draft's own conformance vectors. -## Why it's gated +## What shipped -Today the crypto path (`Bolt12ProofVerifier` + `Bolt12Merkle`) is validated only by -**self-consistent round-trips** (our own encoder ↔ our own verifier, see -`Bolt12ProofFixture` + `Bolt12MerkleTest` + `Bolt12ZapValidatorTest`). That proves -internal correctness, not agreement with CLN/LDK. Several constants are our best -reading of the still-draft spec and MUST be reconciled against real vectors before -we trust wallet-produced proofs: +- **Vector-driven interop test.** `bolt12/payer-proof-test.json` from + [lightning/bolts#1346] is vendored into `quartz/src/commonTest/resources/bolt12/` + and driven by `Bolt12PayerProofVectorTest`: all 5 `valid_vectors` verify, all 23 + `invalid_vectors` are rejected, and the writer (`Bolt12ProofBuilder`) reproduces + each valid vector's `proof_omitted_tlvs` / `proof_missing_hashes` / + `proof_leaf_hashes` exactly. The vectors disproved two of our earlier guesses, + now fixed: + - the **nonce leaf** hashes the record's *type* bytes, not the full encoded TLV + (`Bolt12Merkle.nonceLeafHash`); + - the **proof signature** field name is `proof_signature`, not `signature` + (`Bolt12ProofVerifier.PROOF_SIG_FIELD`). -- TLV type numbers (`Bolt12PayerProof` companion): 240/241, 1001–1005, 22, 80–91, - 160–176. -- Signature digest tags (`Bolt12ProofVerifier`): `"lightning" + messagename + fieldname` - — `INVOICE_MESSAGE`/`PROOF_MESSAGE`/`SIGNATURE_FIELD`. The proof-signature field - name especially is a guess. -- Merkle leaf/branch tag strings + odd-node promotion (`Bolt12Merkle`) — believed to - match LDK, not checked byte-for-byte. -- 33-byte compressed `point` → BIP-340 x-only handling / even-y convention for - `invoice_node_id` and `invreq_payer_id`. +- **Compressed-proof merkle reconstruction.** `Bolt12Merkle.reconstructRoot` + rebuilds the invoice root from the disclosed `LnLeaf` hashes + `proof_leaf_hashes` + (nonce leaves) + `proof_omitted_tlvs` markers + `proof_missing_hashes` (consumed + post-order DFS smallest-to-largest). `invreq_metadata` (type 0) is always the + implied first omitted leaf. The `isCompressed()` short-circuit is gone; every + proof now goes through reconstruction. -## Work item 1 — vector-driven interop test +## Standing caveat (upstream) -When `bolt12/payer-proof-test.json` exists in #1346: +#1346 is still an unmerged draft. The TLV type numbers, signature digest tag +strings, leaf/branch tags, and the invoice/proof field ranges track the current +PR head (`vincenzopalazzo/bolts@1be97b2`) and MUST be re-checked if the spec +changes before it merges. The vector test is the tripwire: refresh the resource +from the merged BOLT and it will flag any drift. -1. Vendor the vectors into `quartz/src/commonTest/resources/` (or inline the hex). -2. Add `Bolt12PayerProofVectorTest`: for each `valid` proof assert - `Bolt12ProofVerifier.verify(...) is Valid`; for each `invalid` proof assert the - specific rejection reason. -3. Fix any constant above that the vectors disprove. If a fix is needed, the - round-trip tests will still pass (they move with our encoder) — the vector test - is the real gate. +## Not covered here -## Work item 2 — compressed-proof merkle reconstruction - -Real wallet proofs omit non-required invoice TLVs (blinded paths, etc.), which still -contributed to the invoice signature's merkle root — so `Bolt12ProofVerifier.verify` -currently returns `Unsupported` for them. Implement the reconstruction in -`Bolt12Merkle`, rebuilding the invoice root from: - -- disclosed invoice TLVs → compute their `LnLeaf` hashes locally; -- `proof_leaf_hashes` (1004) → the `LnNonce` leaves for disclosed fields (can't be - computed locally — the nonce tag embeds the possibly-omitted first TLV); -- `proof_omitted_tlvs` (1002) → markers for where omitted fields sit in - TLV-ascending order; -- `proof_missing_hashes` (1003) → sibling subtree hashes for omitted branches, - consumed post-order DFS smallest-to-largest. - -Then verify the invoice signature against the reconstructed root and drop the -`isCompressed()` short-circuit. Gate acceptance behind Work item 1's vectors — a -reconstruction that only round-trips against our own encoder proves nothing about -real-wallet interop. - -## Not gated on this - -Runtime validation is fully offline (no network) and everything else in the feature -— events, accounting, display, the fully-disclosed crypto path — is done. This -document only covers making compressed real-wallet proofs count as -`cryptoVerified = true`. +Offer↔recipient-identity binding is still out of scope — a verified proof only +proves payment to the *embedded* offer, not that the offer belongs to the +p-tagged recipient (see `Bolt12ZapValidator.isInvoiceBoundToOffer`). [lightning/bolts#1346]: https://github.com/lightning/bolts/pull/1346 diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/bolt12/Bolt12Merkle.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/bolt12/Bolt12Merkle.kt index 32550dec82..5432ca13cc 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/bolt12/Bolt12Merkle.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/bolt12/Bolt12Merkle.kt @@ -32,19 +32,22 @@ import com.vitorpamplona.quartz.utils.sha256.sha256 * - Tagged hash: `H(tag, msg) = SHA256(SHA256(tag) || SHA256(tag) || msg)`. * - For each signable TLV record (types outside the 240..1000 signature range), * two leaves are produced, in TLV-ascending order: - * 1. `H("LnLeaf", tlv)` - * 2. `H("LnNonce" || first-tlv, tlv)` where `first-tlv` is the encoded bytes - * of the numerically-first signable record. + * 1. `H("LnLeaf", tlv)` — over the record's full `type || length || value`. + * 2. `H("LnNonce" || first-tlv, type)` — over just the record's `type` field + * (its BigSize bytes), where `first-tlv` is the encoded bytes of the + * numerically-first signable record. (This is the subtle bit the BOLT + * spec's worked example pins down: the nonce leaf hashes the *type*, not + * the whole record.) * - Inner nodes: `H("LnBranch", lesser || greater)` (children sorted by their * 32-byte value). Odd nodes are promoted unchanged to the next level. * - The signature message digest is `H("lightning" || messagename || fieldname, * merkle_root)`, verified with BIP-340 against the signing key. * - * NOTE: this computes the root over a **fully-disclosed** record set. Compressed - * payer proofs (which omit some invoice TLVs and supply `proof_missing_hashes` / - * `proof_leaf_hashes` to reconstruct the tree) are not reconstructed here; the - * verifier reports those as unverifiable pending validation against the - * lightning/bolts#1346 test vectors. + * [rootHash] computes the root over a **fully-disclosed** record set. + * [reconstructRoot] rebuilds it from a selectively-disclosed payer proof + * (lightning/bolts#1346), pulling omitted subtrees from `proof_missing_hashes`. + * Both are exercised byte-for-byte against the spec's `payer-proof-test.json` + * vectors. */ object Bolt12Merkle { private val LN_NONCE = "LnNonce".encodeToByteArray() @@ -82,7 +85,7 @@ object Bolt12Merkle { var nodes = ArrayList(signableRecords.size * 2) for (record in signableRecords) { nodes.add(taggedHashPrecomputed(LN_LEAF_TAG_HASH, record.encoded)) - nodes.add(taggedHashPrecomputed(nonceTagHash, record.encoded)) + nodes.add(taggedHashPrecomputed(nonceTagHash, BigSize.encode(record.type))) } while (nodes.size > 1) { @@ -102,6 +105,155 @@ object Bolt12Merkle { return nodes[0] } + /** + * The `H("LnLeaf", tlv)` leaf hash of a single record (over its full encoded + * `type || length || value`). + */ + fun leafHash(encodedRecord: ByteArray): ByteArray = taggedHashPrecomputed(LN_LEAF_TAG_HASH, encodedRecord) + + /** + * The `H("LnNonce" || first-tlv, type)` nonce leaf hash for a record of the + * given [type], where [firstTlvEncoded] is the encoded numerically-first + * signable record of the message. + */ + fun nonceLeafHash( + firstTlvEncoded: ByteArray, + type: Long, + ): ByteArray = taggedHash(LN_NONCE + firstTlvEncoded, BigSize.encode(type)) + + /** + * The per-field merkle node — `branch(H("LnLeaf", tlv), nonceLeafHash)` — the + * hash that sits directly above a single TLV's leaf pair. A compressed proof + * supplies [nonceLeafHash] (from `proof_leaf_hashes`) because it depends on + * `first-tlv`, which the proof may have omitted. + */ + fun fieldNode( + encodedRecord: ByteArray, + nonceLeafHash: ByteArray, + ): ByteArray = branch(leafHash(encodedRecord), nonceLeafHash) + + /** + * A node of the reconstruction/emission tree. A leaf carries a per-field node + * hash ([leafHash], null only for a reader's omitted position) and whether it + * was omitted; an inner node carries [left]/[right]. + */ + private class TreeNode private constructor( + val left: TreeNode?, + val right: TreeNode?, + val leafHash: ByteArray?, + val omitted: Boolean, + ) { + val isLeaf: Boolean get() = left == null + + companion object { + fun leaf( + hash: ByteArray?, + omitted: Boolean, + ) = TreeNode(null, null, hash, omitted) + + fun fork( + left: TreeNode, + right: TreeNode, + ) = TreeNode(left, right, null, false) + } + } + + /** Rebuilds the pair-adjacent / promote-odd tree shape [rootHash] flattens. */ + private fun buildTree(leaves: List): TreeNode { + var level = leaves + while (level.size > 1) { + val next = ArrayList((level.size + 1) / 2) + var i = 0 + while (i < level.size) { + if (i + 1 < level.size) { + next.add(TreeNode.fork(level[i], level[i + 1])) + i += 2 + } else { + next.add(level[i]) + i += 1 + } + } + level = next + } + return level[0] + } + + /** + * Reconstructs the merkle root of a selectively-disclosed BOLT12 message + * (lightning/bolts#1346). [leafNodeHashes] holds one entry per non-signature + * leaf of the **original** message in ascending-type order: the per-field node + * hash ([fieldNode]) for a disclosed field, or `null` for an omitted one. When + * exactly one child of an inner node is entirely omitted, its hash is pulled + * from [missingHashes] in the post-order depth-first (smallest-to-largest) + * order the writer emitted them. + * + * Returns `null` if the tree cannot be closed with exactly the supplied + * missing hashes (too few, too many, or an entirely-omitted root) — i.e. an + * unverifiable proof. + */ + fun reconstructRoot( + leafNodeHashes: List, + missingHashes: List, + ): ByteArray? { + if (leafNodeHashes.isEmpty()) return null + val tree = buildTree(leafNodeHashes.map { TreeNode.leaf(it, omitted = it == null) }) + var idx = 0 + var failed = false + + fun eval(node: TreeNode): ByteArray? { + if (failed) return null + if (node.isLeaf) return node.leafHash + val a = eval(node.left!!) + val b = eval(node.right!!) + return when { + failed -> null + a == null && b == null -> null + a != null && b != null -> branch(a, b) + else -> { + if (idx >= missingHashes.size) { + failed = true + null + } else { + branch(a ?: b!!, missingHashes[idx++]) + } + } + } + } + + val root = eval(tree) + return if (failed || root == null || idx != missingHashes.size) null else root + } + + /** + * The writer dual of [reconstructRoot]: given every non-signature leaf's + * per-field node hash ([leafNodeHashes]) and whether each was omitted + * ([leafOmitted]), emits the `proof_missing_hashes` — the hash of each subtree + * that is the lone entirely-omitted child of an inner node — in post-order + * depth-first (smallest-to-largest) order. Used to mint payer proofs (tests + + * interop harness), never on the hot verification path. + */ + fun emitMissingHashes( + leafNodeHashes: List, + leafOmitted: List, + ): List { + require(leafNodeHashes.size == leafOmitted.size) { "leaf hash / omitted-flag size mismatch" } + if (leafNodeHashes.isEmpty()) return emptyList() + val tree = buildTree(leafNodeHashes.indices.map { TreeNode.leaf(leafNodeHashes[it], leafOmitted[it]) }) + val missing = ArrayList() + + // Returns the subtree hash and whether it is entirely omitted. + fun emit(node: TreeNode): Pair { + if (node.isLeaf) return node.leafHash!! to node.omitted + val (a, aOmitted) = emit(node.left!!) + val (b, bOmitted) = emit(node.right!!) + if (aOmitted != bOmitted) missing.add(if (aOmitted) a else b) + return branch(a, b) to (aOmitted && bOmitted) + } + + emit(tree) + return missing + } + private fun branch( a: ByteArray, b: ByteArray, diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/bolt12/Bolt12PayerProof.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/bolt12/Bolt12PayerProof.kt index 479c12cd5e..bbcbadca72 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/bolt12/Bolt12PayerProof.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/bolt12/Bolt12PayerProof.kt @@ -24,9 +24,11 @@ package com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12 * A parsed BOLT12 payer proof (`lnp1...`), per lightning/bolts#1346. * * A payer proof copies the relevant offer / invoice-request / invoice TLV fields, - * plus the invoice's `signature`, and adds the payer's own `proof_signature`, - * the `proof_preimage`, and (for compressed proofs) the merkle-reconstruction - * fields `proof_missing_hashes` / `proof_leaf_hashes` / `proof_omitted_tlvs`. + * plus the invoice's `signature`, and adds the payer's own `proof_signature`, the + * `proof_preimage`, and the merkle-reconstruction fields `proof_missing_hashes` / + * `proof_leaf_hashes` / `proof_omitted_tlvs` that let [Bolt12ProofVerifier] rebuild + * the invoice root even though `invreq_metadata` (and optionally other fields) are + * withheld for privacy. * * The type numbers below are the ones proposed in lightning/bolts#1346 and MUST * be reconciled against the final merged BOLT if they change. @@ -61,32 +63,59 @@ class Bolt12PayerProof( fun proofLeafHashes(): ByteArray? = tlv.value(TYPE_PROOF_LEAF_HASHES) /** - * True when the proof omits some of the original invoice's TLV fields and - * relies on `proof_missing_hashes` to reconstruct the merkle tree. Such - * proofs need the compressed-tree reconstruction to verify the invoice - * signature (not yet implemented — see [Bolt12ProofVerifier]). + * The `proof_omitted_tlvs` marker numbers (BigSize-decoded), empty when the + * field is absent. Returns null if the bytes don't decode as a BigSize list — + * a malformed proof the verifier must reject. */ - fun isCompressed(): Boolean { - if (tlv.has(TYPE_PROOF_OMITTED_TLVS)) return true - val missing = proofMissingHashes() - return missing != null && missing.isNotEmpty() + fun omittedTlvMarkers(): List? { + val bytes = proofOmittedTlvs() ?: return emptyList() + return try { + val reader = TlvReader(bytes) + buildList { while (reader.remaining() > 0) add(reader.readBigSize()) } + } catch (_: Exception) { + null + } } - /** The signable invoice records (types < 240) — used to recompute the invoice merkle root when fully disclosed. */ - fun invoiceSignableRecords(): List = tlv.records.filter { it.type < TlvRecord.SIGNATURE_TYPE_MIN } + /** `proof_leaf_hashes` split into 32-byte hashes, or null if not a whole multiple of 32. */ + fun leafHashList(): List? = split32(proofLeafHashes()) + + /** `proof_missing_hashes` split into 32-byte hashes, or null if not a whole multiple of 32. */ + fun missingHashList(): List? = split32(proofMissingHashes()) + + /** + * The disclosed invoice (offer / invoice-request / invoice) records — the + * fields whose types fall in the invoice ranges 1..239 and + * 1_000_000_000..3_999_999_999 (lightning/bolts#1346), excluding the signature + * elements and the proof-specific fields (240..999_999_999). + */ + fun invoiceIncludedRecords(): List = tlv.records.filter { isInvoiceField(it.type) } + + /** + * True when the proof omits some of the original invoice's TLV fields (i.e. + * carries `proof_omitted_tlvs` markers). Every proof reconstructs the invoice + * root through the merkle machinery — `invreq_metadata` (type 0) is always + * omitted — but this flags the extra selective disclosure for display. + */ + fun isCompressed(): Boolean = omittedTlvMarkers()?.isNotEmpty() == true /** The signable proof records (everything but the 240..1000 signature elements) — used for the payer proof signature. */ fun proofSignableRecords(): List = tlv.records.filter { !it.isSignatureElement() } - /** True when every field NIP-XX validation requires is present. */ - fun hasAllRequiredFields(): Boolean = + /** + * True when every field the BOLT12 crypto verification requires is present and + * well-sized (lightning/bolts#1346 reader rules). `invreq_payer_note` is *not* + * required here — the NIP-XX zap binding checks it separately in the validator. + */ + fun hasAllCryptoFields(): Boolean = invreqPayerId() != null && - invreqPayerNote() != null && - invoicePaymentHash() != null && + invoicePaymentHash()?.size == 32 && invoiceNodeId() != null && invoiceSignature()?.size == 64 && proofSignature()?.size == 64 && - proofPreimage()?.size == 32 + proofPreimage()?.size == 32 && + tlv.has(TYPE_PROOF_MISSING_HASHES) && + tlv.has(TYPE_PROOF_LEAF_HASHES) companion object { // Offer / invoice-request fields copied into the proof. @@ -122,6 +151,20 @@ class Bolt12PayerProof( const val TYPE_PROOF_LEAF_HASHES = 1004L const val TYPE_PROOF_NOTE = 1005L + /** + * The invoice TLV type ranges that participate in the invoice merkle tree, + * per lightning/bolts#1346: 1..239 (offer/invreq/invoice) and + * 1_000_000_000..3_999_999_999 (high/unknown invoice fields). Excludes the + * signature range 240..1000 and the proof-specific fields 1001..999_999_999. + */ + fun isInvoiceField(type: Long): Boolean = type in 1L..239L || type in 1_000_000_000L..3_999_999_999L + + private fun split32(bytes: ByteArray?): List? { + if (bytes == null) return null + if (bytes.size % 32 != 0) return null + return (0 until bytes.size / 32).map { bytes.copyOfRange(it * 32, it * 32 + 32) } + } + fun parse(canonicalProof: String): Bolt12PayerProof? { val bytes = Bolt12Bech32.decodeToBytesOrNull(canonicalProof, Bolt12Bech32.PAYER_PROOF_HRP) ?: return null val tlv = TlvStream.readOrNull(bytes) ?: return null diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/bolt12/Bolt12ProofBuilder.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/bolt12/Bolt12ProofBuilder.kt new file mode 100644 index 0000000000..e4b5548703 --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/bolt12/Bolt12ProofBuilder.kt @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12 + +/** + * Mints a canonical BOLT12 `lnp1...` payer proof from a full invoice, per + * lightning/bolts#1346 — the writer dual of [Bolt12ProofVerifier]. Production never + * *creates* payer proofs (a paying wallet does); this exists for the self-consistent + * test fixtures and the interop harness, and to prove the reader against + * writer-produced streams. It reproduces the spec `payer-proof-test.json` vectors' + * `proof_omitted_tlvs` / `proof_missing_hashes` / `proof_leaf_hashes` byte-for-byte. + */ +object Bolt12ProofBuilder { + /** + * One non-signature invoice TLV. [include] flags whether it is disclosed in the + * proof; `invreq_metadata` (type 0) is always omitted regardless (it is the + * hashing nonce and must never be revealed). + */ + class InvoiceField( + val type: Long, + val value: ByteArray, + val include: Boolean, + ) + + /** + * @param invoiceFields ALL non-signature invoice TLVs in ascending type order, + * including `invreq_metadata` (type 0). + * @param preimage the `proof_preimage`; the caller ensures the invoice's + * `invoice_payment_hash` (type 168) is its SHA-256 (or deliberately corrupt). + * @param signInvoiceDigest signs the 32-byte invoice merkle digest (node key). + * @param signProofDigest signs the 32-byte proof merkle digest (payer key). + */ + fun build( + invoiceFields: List, + preimage: ByteArray, + proofNote: String? = null, + signInvoiceDigest: (ByteArray) -> ByteArray, + signProofDigest: (ByteArray) -> ByteArray, + ): String { + val firstTlv = TlvRecord(invoiceFields.first().type, invoiceFields.first().value).encoded + + // Invoice signature (240) over the full invoice merkle root. + val invoiceRecords = invoiceFields.map { TlvRecord(it.type, it.value) } + val invoiceRoot = Bolt12Merkle.rootHash(invoiceRecords) + val invoiceSig = signInvoiceDigest(Bolt12Merkle.signatureDigest("invoice", INVOICE_SIG_FIELD, invoiceRoot)) + + // Disclosed invoice fields (type 0 is always withheld). + val disclosed = invoiceFields.filter { it.include && it.type != 0L } + val leafHashes = disclosed.map { Bolt12Merkle.nonceLeafHash(firstTlv, it.type) } + val markers = renumberOmitted(invoiceFields) + + val leafNodeHashes = + invoiceFields.map { Bolt12Merkle.fieldNode(TlvRecord(it.type, it.value).encoded, Bolt12Merkle.nonceLeafHash(firstTlv, it.type)) } + val leafOmitted = invoiceFields.map { !(it.include && it.type != 0L) } + val missingHashes = Bolt12Merkle.emitMissingHashes(leafNodeHashes, leafOmitted) + + // Records the proof signature (241) commits to: everything but the 240..1000 + // signature elements — disclosed invoice fields plus the proof-specific fields. + val proofSignable = + buildList { + disclosed.forEach { add(TlvRecord(it.type, it.value)) } + add(TlvRecord(Bolt12PayerProof.TYPE_PROOF_PREIMAGE, preimage)) + if (markers.isNotEmpty()) add(TlvRecord(Bolt12PayerProof.TYPE_PROOF_OMITTED_TLVS, encodeMarkers(markers))) + add(TlvRecord(Bolt12PayerProof.TYPE_PROOF_MISSING_HASHES, concat(missingHashes))) + add(TlvRecord(Bolt12PayerProof.TYPE_PROOF_LEAF_HASHES, concat(leafHashes))) + if (proofNote != null) add(TlvRecord(Bolt12PayerProof.TYPE_PROOF_NOTE, proofNote.encodeToByteArray())) + }.sortedBy { it.type } + val proofRoot = Bolt12Merkle.rootHash(proofSignable) + val proofSig = signProofDigest(Bolt12Merkle.signatureDigest("payer_proof", PROOF_SIG_FIELD, proofRoot)) + + val all = + ( + proofSignable + + TlvRecord(Bolt12PayerProof.TYPE_SIGNATURE, invoiceSig) + + TlvRecord(Bolt12PayerProof.TYPE_PROOF_SIGNATURE, proofSig) + ).sortedBy { it.type } + return Bolt12Bech32.encode(Bolt12Bech32.PAYER_PROOF_HRP, TlvStream(all).encode()) + } + + /** + * Minimal renumbering of omitted fields into `proof_omitted_tlvs` markers + * (lightning/bolts#1346): `invreq_metadata` (type 0) is implied and never + * emitted; every other omitted field takes the previous included type + 1, or + * the next value after the previous marker (starting at 1), with the + * 1_000_000_000 jump once the low range (≤239) is exhausted. + */ + private fun renumberOmitted(invoiceFields: List): List { + val markers = ArrayList() + var prevIncludedType: Long? = null + for (f in invoiceFields) { + if (f.include && f.type != 0L) { + prevIncludedType = f.type + continue + } + if (f.type == 0L) { + prevIncludedType = null + continue + } + markers.add( + when { + prevIncludedType != null -> prevIncludedType + 1 + markers.isEmpty() -> 1L + markers.last() == 239L -> 1_000_000_000L + else -> markers.last() + 1 + }, + ) + prevIncludedType = null + } + return markers + } + + private fun encodeMarkers(markers: List): ByteArray { + var size = 0 + for (m in markers) size += BigSize.encodedSize(m) + val out = ByteArray(size) + var offset = 0 + for (m in markers) { + val enc = BigSize.encode(m) + enc.copyInto(out, offset) + offset += enc.size + } + return out + } + + private fun concat(hashes: List): ByteArray { + val out = ByteArray(hashes.size * 32) + var offset = 0 + for (h in hashes) { + h.copyInto(out, offset) + offset += 32 + } + return out + } + + private const val INVOICE_SIG_FIELD = "signature" + private const val PROOF_SIG_FIELD = "proof_signature" +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/verify/Bolt12ProofResult.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/verify/Bolt12ProofResult.kt index 8002ce5bc5..73cbaa06fe 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/verify/Bolt12ProofResult.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/verify/Bolt12ProofResult.kt @@ -52,22 +52,18 @@ sealed interface Bolt12ProofResult { val reason: Reason, ) : Bolt12ProofResult - /** - * The proof could not be verified with the currently-implemented checks (e.g. - * a compressed proof needing the not-yet-validated merkle reconstruction). - * Whether to surface it as unverified or drop it is the caller's policy. - */ - @Immutable - data class Unsupported( - val reason: Reason, - ) : Bolt12ProofResult - enum class Reason { MISSING_REQUIRED_FIELDS, PREIMAGE_MISMATCH, + + /** + * The selective-disclosure fields (`proof_omitted_tlvs` / + * `proof_missing_hashes` / `proof_leaf_hashes`) are malformed or don't + * describe a closable merkle tree, so the invoice root can't be rebuilt. + */ + RECONSTRUCTION_FAILED, INVOICE_SIGNATURE_INVALID, PROOF_SIGNATURE_INVALID, MALFORMED_KEY, - COMPRESSED_PROOF_UNSUPPORTED, } } diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/verify/Bolt12ProofVerifier.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/verify/Bolt12ProofVerifier.kt index d73c35ebea..b06b8ac7d1 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/verify/Bolt12ProofVerifier.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/verify/Bolt12ProofVerifier.kt @@ -23,59 +23,56 @@ package com.vitorpamplona.quartz.nipXXBolt12Zaps.verify import com.vitorpamplona.quartz.nip01Core.crypto.Nip01Crypto import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.Bolt12Merkle import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.Bolt12PayerProof +import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.TlvRecord import com.vitorpamplona.quartz.utils.sha256.sha256 /** * Cryptographic verification of a BOLT12 `lnp` payer proof, per lightning/bolts#1346: * * 1. `SHA256(proof_preimage) == invoice_payment_hash` — proves the payment settled. - * 2. The invoice `signature` (240) is valid over the invoice merkle root, signed - * by `invoice_node_id`. - * 3. The `proof_signature` (241) is valid over the proof merkle root, signed by - * `invreq_payer_id`. + * 2. The invoice `signature` (240) is valid over the **reconstructed** invoice + * merkle root, signed by `invoice_node_id`. The proof discloses only some of + * the invoice's TLV fields (`invreq_metadata` is always withheld); the omitted + * branches are rebuilt from `proof_missing_hashes` / `proof_leaf_hashes` / + * `proof_omitted_tlvs` via [Bolt12Merkle.reconstructRoot]. + * 3. The `proof_signature` (241) is valid over the proof's own (fully-disclosed) + * merkle root, signed by `invreq_payer_id`. * - * The merkle machinery ([Bolt12Merkle]) and the BIP-340 checks ([Nip01Crypto.verify]) - * are exercised end-to-end by the round-trip tests. **Interop caveat:** the exact - * signature field names and, especially, the compressed-proof merkle - * reconstruction (`proof_missing_hashes` / `proof_leaf_hashes` / `proof_omitted_tlvs`) - * have not been checked against lightning/bolts#1346's `payer-proof-test.json` - * vectors — that spec is still an unmerged draft. Until then, this verifier only - * fully validates signatures for **fully-disclosed** proofs and reports - * compressed proofs as [Bolt12ProofResult.Unsupported]. Callers decide whether an - * unsupported crypto check may still be surfaced (labeled unverified) or dropped. + * The full reader path — reconstruction and both BIP-340 checks — is exercised + * byte-for-byte against the spec's `payer-proof-test.json` vectors + * ([Bolt12PayerProofVectorTest]). The BOLT signature message/field names below + * still track the unmerged draft and must be reconciled if it changes on merge. */ class Bolt12ProofVerifier { fun verify(proof: Bolt12PayerProof): Bolt12ProofResult { - if (!proof.hasAllRequiredFields()) { + if (!proof.hasAllCryptoFields()) { return Bolt12ProofResult.Invalid(Bolt12ProofResult.Reason.MISSING_REQUIRED_FIELDS) } - val preimage = proof.proofPreimage() ?: return Bolt12ProofResult.Invalid(Bolt12ProofResult.Reason.MISSING_REQUIRED_FIELDS) - val paymentHash = proof.invoicePaymentHash() ?: return Bolt12ProofResult.Invalid(Bolt12ProofResult.Reason.MISSING_REQUIRED_FIELDS) + val preimage = proof.proofPreimage()!! + val paymentHash = proof.invoicePaymentHash()!! // 1. Settlement proof: the preimage must hash to the invoice payment hash. if (!sha256(preimage).contentEquals(paymentHash)) { return Bolt12ProofResult.Invalid(Bolt12ProofResult.Reason.PREIMAGE_MISMATCH) } - // 2/3. Signature checks require reconstructing the invoice merkle root; for a - // compressed proof that needs the (unverified) missing-hash reconstruction. - if (proof.isCompressed()) { - return Bolt12ProofResult.Unsupported(Bolt12ProofResult.Reason.COMPRESSED_PROOF_UNSUPPORTED) - } - + // 2. Invoice signature over the reconstructed invoice merkle root. + val invoiceRoot = + reconstructInvoiceRoot(proof) + ?: return Bolt12ProofResult.Invalid(Bolt12ProofResult.Reason.RECONSTRUCTION_FAILED) val invoiceSig = proof.invoiceSignature()!! val nodeId = xOnly(proof.invoiceNodeId()!!) ?: return Bolt12ProofResult.Invalid(Bolt12ProofResult.Reason.MALFORMED_KEY) - val invoiceRoot = Bolt12Merkle.rootHash(proof.invoiceSignableRecords()) - val invoiceDigest = Bolt12Merkle.signatureDigest(INVOICE_MESSAGE, SIGNATURE_FIELD, invoiceRoot) + val invoiceDigest = Bolt12Merkle.signatureDigest(INVOICE_MESSAGE, INVOICE_SIG_FIELD, invoiceRoot) if (!Nip01Crypto.verify(invoiceSig, invoiceDigest, nodeId)) { return Bolt12ProofResult.Invalid(Bolt12ProofResult.Reason.INVOICE_SIGNATURE_INVALID) } + // 3. Payer proof signature over the proof's own (disclosed) records. val proofSig = proof.proofSignature()!! val payerId = xOnly(proof.invreqPayerId()!!) ?: return Bolt12ProofResult.Invalid(Bolt12ProofResult.Reason.MALFORMED_KEY) val proofRoot = Bolt12Merkle.rootHash(proof.proofSignableRecords()) - val proofDigest = Bolt12Merkle.signatureDigest(PROOF_MESSAGE, SIGNATURE_FIELD, proofRoot) + val proofDigest = Bolt12Merkle.signatureDigest(PROOF_MESSAGE, PROOF_SIG_FIELD, proofRoot) if (!Nip01Crypto.verify(proofSig, proofDigest, payerId)) { return Bolt12ProofResult.Invalid(Bolt12ProofResult.Reason.PROOF_SIGNATURE_INVALID) } @@ -83,6 +80,71 @@ class Bolt12ProofVerifier { return Bolt12ProofResult.Valid(paymentHash = paymentHash, invoiceAmountMillisats = proof.invoiceAmount()) } + /** + * Rebuilds the invoice merkle root from a selectively-disclosed proof. Returns + * null when the proof's compression fields are malformed or don't describe a + * closable tree — an unverifiable proof the caller must not count. + */ + private fun reconstructInvoiceRoot(proof: Bolt12PayerProof): ByteArray? { + val markers = proof.omittedTlvMarkers() ?: return null + val leafHashes = proof.leafHashList() ?: return null + val missingHashes = proof.missingHashList() ?: return null + + val included = proof.invoiceIncludedRecords().sortedBy { it.type } + if (leafHashes.size != included.size) return null + if (!markersValid(markers, included)) return null + + // Ordered non-signature leaves of the original invoice: the implied + // `invreq_metadata` (type 0) plus every omitted marker plus every disclosed + // field, merged in ascending numeric order. Disclosed fields carry their + // per-field node hash; omitted positions are null (filled from missing hashes). + val positions = ArrayList>(included.size + markers.size + 1) + positions.add(0L to null) // type 0 (invreq_metadata) is always omitted + for (m in markers) positions.add(m to null) + for (r in included) positions.add(r.type to r) + positions.sortBy { it.first } + + var leafIdx = 0 + val leafNodeHashes = + positions.map { (_, record) -> + if (record == null) { + null + } else { + Bolt12Merkle.fieldNode(record.encoded, leafHashes[leafIdx++]) + } + } + + return Bolt12Merkle.reconstructRoot(leafNodeHashes, missingHashes) + } + + /** + * The lightning/bolts#1346 reader rules for `proof_omitted_tlvs`: strictly + * ascending, non-zero, within the invoice ranges, never the number of a + * disclosed field, and each a valid *minimal renumbering* successor — one more + * than a disclosed field, one more than the previous marker (or 0 for the + * first), or the 1_000_000_000 jump after 239. + */ + private fun markersValid( + markers: List, + included: List, + ): Boolean { + val includedTypes = included.mapTo(HashSet()) { it.type } + var prev = 0L + for ((i, m) in markers.withIndex()) { + if (i > 0 && m <= markers[i - 1]) return false // strict ascending, no duplicates + if (m == 0L) return false + if (!Bolt12PayerProof.isInvoiceField(m)) return false + if (m in includedTypes) return false + val validSuccessor = + (m - 1) in includedTypes || + m == prev + 1 || + (m == 1_000_000_000L && prev == 239L) + if (!validSuccessor) return false + prev = m + } + return true + } + /** * A BOLT12 `point` is a 33-byte compressed secp256k1 key; BIP-340 uses the * 32-byte x-only form. Drop the parity prefix. (Already-x-only 32-byte input @@ -100,6 +162,11 @@ class Bolt12ProofVerifier { // These strings track lightning/bolts#1346 and must be reconciled on merge. const val INVOICE_MESSAGE = "invoice" const val PROOF_MESSAGE = "payer_proof" - const val SIGNATURE_FIELD = "signature" + + /** The invoice's own signature field is named `signature`. */ + const val INVOICE_SIG_FIELD = "signature" + + /** The payer proof's signature field is named `proof_signature`. */ + const val PROOF_SIG_FIELD = "proof_signature" } } diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/verify/Bolt12ZapValidation.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/verify/Bolt12ZapValidation.kt index 757a65a03e..c0809e0d10 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/verify/Bolt12ZapValidation.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/verify/Bolt12ZapValidation.kt @@ -36,10 +36,12 @@ sealed interface Bolt12ZapValidation { * @property paymentHashHex the proof's `invoice_payment_hash`, hex-encoded — * the key clients MUST deduplicate on before summing. * @property proofCryptoVerified true when the BOLT12 payer-proof signatures - * were fully verified; false when the proof is structurally valid and bound - * but its signatures could not yet be checked (a compressed proof — see - * [Bolt12ProofVerifier]). Callers decide whether to count or merely display - * the latter, and MUST label it as unverified. + * verified **and** the settled invoice is provably the embedded offer's (the + * offer publishes an `offer_issuer_id`, uses no blinded paths, and the + * invoice node key equals it). False when the signatures verify but the offer + * hides its destination (blinded paths / no issuer id), so paying *this* offer + * isn't proven — see [Bolt12ProofVerifier]. Callers decide whether to count or + * merely display the latter, and MUST label it as unverified. */ @Immutable data class Valid( @@ -94,6 +96,7 @@ sealed interface Bolt12ZapValidation { OFFER_PROOF_MISMATCH, PROOF_MISSING_REQUIRED_FIELDS, PROOF_PREIMAGE_MISMATCH, + PROOF_RECONSTRUCTION_FAILED, PROOF_INVOICE_SIGNATURE_INVALID, PROOF_SIGNATURE_INVALID, PROOF_MALFORMED_KEY, diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/verify/Bolt12ZapValidator.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/verify/Bolt12ZapValidator.kt index e05acb5d62..8a5164efec 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/verify/Bolt12ZapValidator.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/verify/Bolt12ZapValidator.kt @@ -138,7 +138,6 @@ class Bolt12ZapValidator( val cryptoOk = when (cryptoResult) { is Bolt12ProofResult.Valid -> true - is Bolt12ProofResult.Unsupported -> false is Bolt12ProofResult.Invalid -> return invalid(mapProofReason(cryptoResult.reason)) } @@ -225,11 +224,10 @@ class Bolt12ZapValidator( when (reason) { Bolt12ProofResult.Reason.MISSING_REQUIRED_FIELDS -> Reason.PROOF_MISSING_REQUIRED_FIELDS Bolt12ProofResult.Reason.PREIMAGE_MISMATCH -> Reason.PROOF_PREIMAGE_MISMATCH + Bolt12ProofResult.Reason.RECONSTRUCTION_FAILED -> Reason.PROOF_RECONSTRUCTION_FAILED Bolt12ProofResult.Reason.INVOICE_SIGNATURE_INVALID -> Reason.PROOF_INVOICE_SIGNATURE_INVALID Bolt12ProofResult.Reason.PROOF_SIGNATURE_INVALID -> Reason.PROOF_SIGNATURE_INVALID Bolt12ProofResult.Reason.MALFORMED_KEY -> Reason.PROOF_MALFORMED_KEY - // A compressed proof never reaches here (it returns Unsupported, not Invalid). - Bolt12ProofResult.Reason.COMPRESSED_PROOF_UNSUPPORTED -> Reason.PROOF_MISSING_REQUIRED_FIELDS } private fun invalid(reason: Reason) = Bolt12ZapValidation.Invalid(reason) diff --git a/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/builder/Bolt12ZapBuilderTest.kt b/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/builder/Bolt12ZapBuilderTest.kt index 84ce92ef67..1aa3d9862a 100644 --- a/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/builder/Bolt12ZapBuilderTest.kt +++ b/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/builder/Bolt12ZapBuilderTest.kt @@ -36,8 +36,8 @@ import kotlin.test.assertTrue * Proves the send-side assembly ([Bolt12ZapBuilder.buildProfileIntent] → * [Bolt12ZapBuilder.payerNote] → [Bolt12ZapBuilder.buildZap]) — the exact path * `Account.sendBolt12Zap` drives — produces a kind:9736 the validator accepts. Uses - * a self-consistent fixture proof bound to the built intent (not a wallet interop - * vector), so it exercises structure + binding, not the compressed-merkle gap. + * a self-consistent fixture proof bound to the built intent; byte-exact wallet + * interop is covered separately by [Bolt12PayerProofVectorTest]. */ class Bolt12ZapBuilderTest { private val validator = Bolt12ZapValidator() diff --git a/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/verify/Bolt12PayerProofVectorTest.kt b/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/verify/Bolt12PayerProofVectorTest.kt new file mode 100644 index 0000000000..396ca38ef6 --- /dev/null +++ b/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/verify/Bolt12PayerProofVectorTest.kt @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.quartz.nipXXBolt12Zaps.verify + +import com.vitorpamplona.quartz.TestResourceLoader +import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.Bolt12PayerProof +import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.Bolt12ProofBuilder +import com.vitorpamplona.quartz.utils.Hex +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertIs +import kotlin.test.assertTrue +import kotlin.test.fail + +/** + * Byte-exact interop against the lightning/bolts#1346 conformance suite + * (`bolt12/payer-proof-test.json`), the draft's own CLN/LDK-generated vectors. + * + * - Every `valid_vectors` entry must fully verify ([Bolt12ProofVerifier] returns + * [Bolt12ProofResult.Valid]) — this exercises the compressed-proof merkle + * reconstruction and both BIP-340 signature checks against real proofs. + * - Every `invalid_vectors` entry must be rejected (never [Bolt12ProofResult.Valid]). + * - The writer ([Bolt12ProofBuilder]) reproduces each valid vector's + * `proof_omitted_tlvs` / `proof_missing_hashes` / `proof_leaf_hashes` exactly. + */ +class Bolt12PayerProofVectorTest { + private val vectors by lazy { + Json.parseToJsonElement(TestResourceLoader().loadString("bolt12/payer-proof-test.json")).jsonObject + } + + private val verifier = Bolt12ProofVerifier() + + @Test + fun everyValidVectorVerifies() { + val valid = vectors["valid_vectors"]!!.jsonArray + assertTrue(valid.size >= 5, "expected the full valid vector set") + for (vector in valid) { + val obj = vector.jsonObject + val name = obj["name"]!!.jsonPrimitive.content + val bech32 = obj["result"]!!.jsonObject["bech32"]!!.jsonPrimitive.content + val proof = Bolt12PayerProof.parse(bech32) ?: fail("valid vector '$name' failed to parse") + val result = verifier.verify(proof) + assertIs(result, "valid vector '$name' did not verify: $result") + } + } + + @Test + fun everyInvalidVectorIsRejected() { + val invalid = vectors["invalid_vectors"]!!.jsonArray + assertTrue(invalid.size >= 20, "expected the full invalid vector set") + for (vector in invalid) { + val obj = vector.jsonObject + val reason = obj["reason"]?.jsonPrimitive?.content ?: "?" + val bech32 = obj["bech32"]!!.jsonPrimitive.content + val proof = Bolt12PayerProof.parse(bech32) + // Rejection is either an unparseable stream or any non-Valid crypto result. + val verified = proof?.let { verifier.verify(it) } + assertTrue( + verified !is Bolt12ProofResult.Valid, + "invalid vector '$reason' was accepted", + ) + } + } + + @Test + fun writerReproducesEveryValidVectorCompressionFields() { + val valid = vectors["valid_vectors"]!!.jsonArray + for (vector in valid) { + val obj = vector.jsonObject + val name = obj["name"]!!.jsonPrimitive.content + val working = obj["working"]!!.jsonObject + + val invoiceFields = + obj["input"]!! + .jsonObject["invoice_fields"]!! + .jsonArray + .map { it.jsonObject } + .filter { it["type"]!!.jsonPrimitive.content.toLong() !in 240L..1000L } + .map { + Bolt12ProofBuilder.InvoiceField( + type = it["type"]!!.jsonPrimitive.content.toLong(), + value = Hex.decode(it["hex"]!!.jsonPrimitive.content), + include = it["included"]!!.jsonPrimitive.content.toBoolean(), + ) + } + + // Deterministic compression fields don't depend on the signatures, so + // dummy signers suffice; we read the minted proof back and compare. + val minted = + Bolt12ProofBuilder.build( + invoiceFields = invoiceFields, + preimage = Hex.decode(obj["input"]!!.jsonObject["preimage"]!!.jsonPrimitive.content), + signInvoiceDigest = { ByteArray(64) }, + signProofDigest = { ByteArray(64) }, + ) + val proof = Bolt12PayerProof.parse(minted) ?: fail("writer output for '$name' failed to parse") + + val expectedMarkers = working["proof_omitted_tlvs"]!!.jsonArray.map { it.jsonPrimitive.content.toLong() } + assertEquals(expectedMarkers, proof.omittedTlvMarkers(), "proof_omitted_tlvs mismatch for '$name'") + + val expectedMissing = working["proof_missing_hashes"]!!.jsonArray.map { it.jsonPrimitive.content } + assertEquals(expectedMissing, proof.missingHashList()!!.map { Hex.encode(it) }, "proof_missing_hashes mismatch for '$name'") + + val expectedLeaves = working["proof_leaf_hashes"]!!.jsonArray.map { it.jsonPrimitive.content } + assertEquals(expectedLeaves, proof.leafHashList()!!.map { Hex.encode(it) }, "proof_leaf_hashes mismatch for '$name'") + } + } +} diff --git a/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/verify/Bolt12ProofFixture.kt b/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/verify/Bolt12ProofFixture.kt index ba41644604..e397c01877 100644 --- a/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/verify/Bolt12ProofFixture.kt +++ b/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/verify/Bolt12ProofFixture.kt @@ -23,19 +23,20 @@ package com.vitorpamplona.quartz.nipXXBolt12Zaps.verify import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair import com.vitorpamplona.quartz.nip01Core.crypto.Nip01Crypto import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.Bolt12Bech32 -import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.Bolt12Merkle import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.Bolt12Offer import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.Bolt12PayerProof +import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.Bolt12ProofBuilder import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.Bolt12Values import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.TlvRecord import com.vitorpamplona.quartz.nipXXBolt12Zaps.bolt12.TlvStream import com.vitorpamplona.quartz.utils.sha256.sha256 /** - * Builds matched BOLT12 offers and payer proofs for tests, self-signing them with - * Quartz's own secp256k1 so the whole merkle + BIP-340 path is exercised. This is - * a self-consistent construction, not a CLN/LDK interop vector — see - * [Bolt12ProofVerifier]. + * Builds matched BOLT12 offers and payer proofs for tests, minting spec-compliant + * (lightning/bolts#1346) proofs through [Bolt12ProofBuilder] and self-signing them + * with Quartz's own secp256k1 so the whole reconstruction + merkle + BIP-340 path + * is exercised. This is a self-consistent construction; byte-exact CLN/LDK interop + * is covered separately by [Bolt12PayerProofVectorTest]. */ object Bolt12ProofFixture { /** A 33-byte compressed point (even parity) wrapping an x-only key. */ @@ -73,48 +74,31 @@ object Bolt12ProofFixture { // the preimage check (SHA256(preimage) != invoice_payment_hash) is what rejects it. val paymentHash = sha256(preimage).also { if (corruptPaymentHash) it[0] = (it[0] + 1).toByte() } - // The invoice's signed records (types < 240), in ascending order. - val invoiceRecords = + // The full invoice's non-signature TLVs, ascending. invreq_metadata (type 0) + // is always present and always withheld; invreq_amount (82) is the field the + // `compressed` flag selectively omits. + val invoiceFields = listOf( - TlvRecord(Bolt12PayerProof.TYPE_OFFER_ISSUER_ID, nodePoint), - TlvRecord(Bolt12PayerProof.TYPE_INVREQ_AMOUNT, Bolt12Values.tu64ToBytes(amountMillisats)), - TlvRecord(Bolt12PayerProof.TYPE_INVREQ_PAYER_ID, payerPoint), - TlvRecord(Bolt12PayerProof.TYPE_INVREQ_PAYER_NOTE, payerNote.encodeToByteArray()), - TlvRecord(Bolt12PayerProof.TYPE_INVOICE_PAYMENT_HASH, paymentHash), - TlvRecord(Bolt12PayerProof.TYPE_INVOICE_AMOUNT, Bolt12Values.tu64ToBytes(amountMillisats)), - TlvRecord(Bolt12PayerProof.TYPE_INVOICE_NODE_ID, nodePoint), + Bolt12ProofBuilder.InvoiceField(TYPE_INVREQ_METADATA, ByteArray(16), include = false), + Bolt12ProofBuilder.InvoiceField(Bolt12PayerProof.TYPE_OFFER_ISSUER_ID, nodePoint, include = true), + Bolt12ProofBuilder.InvoiceField(Bolt12PayerProof.TYPE_INVREQ_AMOUNT, Bolt12Values.tu64ToBytes(amountMillisats), include = !compressed), + Bolt12ProofBuilder.InvoiceField(Bolt12PayerProof.TYPE_INVREQ_PAYER_ID, payerPoint, include = true), + Bolt12ProofBuilder.InvoiceField(Bolt12PayerProof.TYPE_INVREQ_PAYER_NOTE, payerNote.encodeToByteArray(), include = true), + Bolt12ProofBuilder.InvoiceField(Bolt12PayerProof.TYPE_INVOICE_PAYMENT_HASH, paymentHash, include = true), + Bolt12ProofBuilder.InvoiceField(Bolt12PayerProof.TYPE_INVOICE_AMOUNT, Bolt12Values.tu64ToBytes(amountMillisats), include = true), + Bolt12ProofBuilder.InvoiceField(Bolt12PayerProof.TYPE_INVOICE_NODE_ID, nodePoint, include = true), ) - val invoiceRoot = Bolt12Merkle.rootHash(invoiceRecords) + val invoiceSigningKey = if (breakInvoiceSignature) payerLightningKey else nodeKey - val invoiceSig = - Nip01Crypto.sign( - Bolt12Merkle.signatureDigest(Bolt12ProofVerifier.INVOICE_MESSAGE, Bolt12ProofVerifier.SIGNATURE_FIELD, invoiceRoot), - invoiceSigningKey.privKey!!, - ) - - val preimageRecord = TlvRecord(Bolt12PayerProof.TYPE_PROOF_PREIMAGE, preimage) - - // The payer proof signs everything but the 240..1000 signature elements. - val proofSignable = invoiceRecords + preimageRecord - val proofRoot = Bolt12Merkle.rootHash(proofSignable) val proofSigningKey = if (breakProofSignature) nodeKey else payerLightningKey - val proofSig = - Nip01Crypto.sign( - Bolt12Merkle.signatureDigest(Bolt12ProofVerifier.PROOF_MESSAGE, Bolt12ProofVerifier.SIGNATURE_FIELD, proofRoot), - proofSigningKey.privKey!!, - ) - val records = - buildList { - addAll(invoiceRecords) - add(TlvRecord(Bolt12PayerProof.TYPE_SIGNATURE, invoiceSig)) - add(TlvRecord(Bolt12PayerProof.TYPE_PROOF_SIGNATURE, proofSig)) - add(preimageRecord) - if (compressed) { - // A non-empty proof_missing_hashes marks the proof as compressed. - add(TlvRecord(Bolt12PayerProof.TYPE_PROOF_MISSING_HASHES, ByteArray(32) { 9 })) - } - } - return Bolt12Bech32.encode(Bolt12Bech32.PAYER_PROOF_HRP, TlvStream(records).encode()) + return Bolt12ProofBuilder.build( + invoiceFields = invoiceFields, + preimage = preimage, + signInvoiceDigest = { digest -> Nip01Crypto.sign(digest, invoiceSigningKey.privKey!!) }, + signProofDigest = { digest -> Nip01Crypto.sign(digest, proofSigningKey.privKey!!) }, + ) } + + private const val TYPE_INVREQ_METADATA = 0L } diff --git a/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/verify/Bolt12ZapValidatorTest.kt b/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/verify/Bolt12ZapValidatorTest.kt index e7e11a8663..0d1d20ab50 100644 --- a/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/verify/Bolt12ZapValidatorTest.kt +++ b/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nipXXBolt12Zaps/verify/Bolt12ZapValidatorTest.kt @@ -76,8 +76,11 @@ class Bolt12ZapValidatorTest { } @Test - fun acceptsButFlagsACompressedProofAsUnverified() = + fun verifiesACompressedProofThroughMerkleReconstruction() = runTest { + // The proof withholds `invreq_amount`, forcing the verifier to rebuild the + // invoice merkle root from `proof_missing_hashes` before checking the + // invoice signature. Bound to a directly-addressed offer, it is fully verified. val signer = NostrSignerInternal(KeyPair()) val nodeKey = KeyPair() val preimage = ByteArray(32) { (it + 1).toByte() } @@ -88,7 +91,7 @@ class Bolt12ZapValidatorTest { val result = validator.validate(signedZap(signer, intent, proof)) assertIs(result) - assertTrue(!result.proofCryptoVerified, "a compressed proof is bound but not yet crypto-verified") + assertTrue(result.proofCryptoVerified, "a compressed proof bound to the offer must verify") } @Test diff --git a/quartz/src/commonTest/resources/bolt12/payer-proof-test.json b/quartz/src/commonTest/resources/bolt12/payer-proof-test.json new file mode 100644 index 0000000000..6a7c89e4ef --- /dev/null +++ b/quartz/src/commonTest/resources/bolt12/payer-proof-test.json @@ -0,0 +1,969 @@ +{ + "payer_secret": "4242424242424242424242424242424242424242424242424242424242424242", + "keys": { + "offer_issuer_id": { + "secret": "4646464646464646464646464646464646464646464646464646464646464646", + "pubkey": "024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382" + }, + "invreq_payer_id": { + "secret": "4242424242424242424242424242424242424242424242424242424242424242", + "pubkey": "0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c" + }, + "first_node_id": { + "secret": "4343434343434343434343434343434343434343434343434343434343434343", + "pubkey": "027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007" + }, + "first_path_key": { + "secret": "4444444444444444444444444444444444444444444444444444444444444444", + "pubkey": "032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991" + }, + "blinded_node_id": { + "secret": "4545454545454545454545454545454545454545454545454545454545454545", + "pubkey": "02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145" + }, + "invoice_node_id": { + "secret": "4646464646464646464646464646464646464646464646464646464646464646", + "pubkey": "024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382" + } + }, + "valid_vectors": [ + { + "name": "full_disclosure", + "input": { + "invoice": "lni1qqgqqqqqqqqqqqqqqqqqqqqqqqqqq93pqf9u9gcjv52n7pl8pc96kzrjfe4ctcshlrxk9r8tv2t5y3amfyecy5szq059sggry3jnatzrgjyqqtxqdwlm0ug0uxyerc6lnljrqtd75mfr20wq4vw2qasz0uc7h32x9s0aecdhxlk075kn046aafpuuyw8f5j652t3vha2yqrsxtqt0nu4xf9q05znnzeyq96dcrptu3zdj6c4n2nv0aa3ue5xszv3qypwm2aaz66peqm3hyh09uzvzxzmfupmdhx49w5m0rva0jyu3u3pz3gqzqqqqqqqqqqqqqqqqqqqqqqqqqq2y8qqqqqqzqqqqqpqqqcqqqqqqqqqqqzqqqqqqqqqqqq9qqq2gpr82fuc32pqwtxkappzcsrlkmgfs6g0zyct0hkhashh7hsaxz7e65slq9fkx7f65qsrazhq6zqqqqqqqqqqqqqqqqqqqzczzqjtc233yeg48ur7wrst4vy8ynntsh3p07xdv2xwkc5hgfrmkjfnstcyp7aextn2n4d5mzx2phwfe70cejyqaaq78my4wndgna3ymwyc4vlf60kke258g33nhp23vldqpygemxp54ecl0vr0qfejm3xpm6atq4mlavkstcqszss", + "invoice_hex": "0010000000000000000000000000000000001621024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382520203e858210324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1ca076027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e6686809910102edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145001000000000000000000000000000000000a21c00000001000000020003000000000000000400000000000000050000a40467527988a82072cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793aa0203e8ae0d08000000000000000000000000b021024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382f040fbb932e6a9d5b4d88ca0ddc9cf9f8cc880ef41e3ec9574da89f624db898ab3e9d3ed6caa8744633b855167da009119d9834ae71f7b06f02732dc4c1debab0577feb2d05e010142", + "preimage": "0101010101010101010101010101010101010101010101010101010101010101", + "invoice_fields": [ + { + "type": 0, + "len": 16, + "hex": "00000000000000000000000000000000", + "included": false + }, + { + "type": 22, + "len": 33, + "hex": "024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382", + "included": true + }, + { + "type": 82, + "len": 2, + "hex": "03e8", + "included": true + }, + { + "type": 88, + "len": 33, + "hex": "0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c", + "included": true + }, + { + "type": 160, + "len": 118, + "hex": "027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e6686809910102edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145001000000000000000000000000000000000", + "included": true + }, + { + "type": 162, + "len": 28, + "hex": "00000001000000020003000000000000000400000000000000050000", + "included": true + }, + { + "type": 164, + "len": 4, + "hex": "67527988", + "included": true + }, + { + "type": 168, + "len": 32, + "hex": "72cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793", + "included": true + }, + { + "type": 170, + "len": 2, + "hex": "03e8", + "included": true + }, + { + "type": 174, + "len": 13, + "hex": "08000000000000000000000000", + "included": true + }, + { + "type": 176, + "len": 33, + "hex": "024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382", + "included": true + }, + { + "type": 240, + "len": 64, + "hex": "fbb932e6a9d5b4d88ca0ddc9cf9f8cc880ef41e3ec9574da89f624db898ab3e9d3ed6caa8744633b855167da009119d9834ae71f7b06f02732dc4c1debab0577", + "included": false + }, + { + "type": 3000000001, + "len": 1, + "hex": "42", + "included": true + } + ] + }, + "working": { + "invoice_merkle_root": "cb9e0c81bb39fc244f9f523c748ab4de0e09f1a5fef74359c2e1f7cc7cdc7447", + "invoice_sighash": "538aab18f82285032db132cecf7275ba2cbb462ef1f4d151588f836d0ce47aae", + "invoice_signature": "fbb932e6a9d5b4d88ca0ddc9cf9f8cc880ef41e3ec9574da89f624db898ab3e9d3ed6caa8744633b855167da009119d9834ae71f7b06f02732dc4c1debab0577", + "proof_merkle_root": "d461a2dd3099e2c43ccef4c3c8d36f720948fe21712ea94dceccb4bddb0c9e5e", + "proof_leaf_hashes": [ + "8c9057ed88f3c5a6b6441dcac3b5e4cefb3615904d7362b86e78427fb695f461", + "8dc54a97453dee6f207fa5216a30f1567442712ca98852bc789b73885029283c", + "f2deaf5f30be3ced89fc7c24d422819bf06af0e48a31423bbd0e2634f3c3de67", + "f54f80c94a87383f2a8ef7c3e461c62b67a51da5bccf6cd96a7dbab29bea51fa", + "7849b8b856e1d2a63d9ce7dc1a78e05cbb2def1f5d7709c48e8707e0a59fe51e", + "19e7e4eee6bf56c6c589fe50035490c1a7c91b753cb8007c4b52838a6772f997", + "f0191c35000247554b8d0a196898a794bf3de89982571178d931affb654f0c1a", + "dc0b8de03f1a0b0531bff146982d7d613ef6e1ef8d3bdd9590971fc18d835ffb", + "c14cfffaa314261bcbb2ed4ca24d5717bb608d8a6cc9910790bc1d49af7858ab", + "7e92b77b9e3843650f6cd7ee94b6753ea9df3533710b04dee686ad376515a5cb", + "abaab91b367e30fea7026daf9f2590bb7e9cc31db8221f4013c67289e38f22c8" + ], + "proof_omitted_tlvs": [], + "proof_missing_hashes": [ + "0b510ba4c6884d603159ced2f0ca21e772424b59e52a2191bbfbcf07377805a1" + ] + }, + "result": { + "payer_sig": "4961333409f453b5518fcbc662936fcb46e6c1db8d963c44b67d5677f0ffce3ac5c42293d1bc1298b0d67320a772e20a06c069dfa7079df9207b675357735dd7", + "proof_fields": [ + { + "type": 22, + "len": 33, + "hex": "024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382" + }, + { + "type": 82, + "len": 2, + "hex": "03e8" + }, + { + "type": 88, + "len": 33, + "hex": "0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c" + }, + { + "type": 160, + "len": 118, + "hex": "027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e6686809910102edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145001000000000000000000000000000000000" + }, + { + "type": 162, + "len": 28, + "hex": "00000001000000020003000000000000000400000000000000050000" + }, + { + "type": 164, + "len": 4, + "hex": "67527988" + }, + { + "type": 168, + "len": 32, + "hex": "72cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793" + }, + { + "type": 170, + "len": 2, + "hex": "03e8" + }, + { + "type": 174, + "len": 13, + "hex": "08000000000000000000000000" + }, + { + "type": 176, + "len": 33, + "hex": "024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382" + }, + { + "type": 240, + "len": 64, + "hex": "fbb932e6a9d5b4d88ca0ddc9cf9f8cc880ef41e3ec9574da89f624db898ab3e9d3ed6caa8744633b855167da009119d9834ae71f7b06f02732dc4c1debab0577" + }, + { + "type": 241, + "len": 64, + "hex": "4961333409f453b5518fcbc662936fcb46e6c1db8d963c44b67d5677f0ffce3ac5c42293d1bc1298b0d67320a772e20a06c069dfa7079df9207b675357735dd7" + }, + { + "type": 1001, + "len": 32, + "hex": "0101010101010101010101010101010101010101010101010101010101010101" + }, + { + "type": 1003, + "len": 32, + "hex": "0b510ba4c6884d603159ced2f0ca21e772424b59e52a2191bbfbcf07377805a1" + }, + { + "type": 1004, + "len": 352, + "hex": "8c9057ed88f3c5a6b6441dcac3b5e4cefb3615904d7362b86e78427fb695f4618dc54a97453dee6f207fa5216a30f1567442712ca98852bc789b73885029283cf2deaf5f30be3ced89fc7c24d422819bf06af0e48a31423bbd0e2634f3c3de67f54f80c94a87383f2a8ef7c3e461c62b67a51da5bccf6cd96a7dbab29bea51fa7849b8b856e1d2a63d9ce7dc1a78e05cbb2def1f5d7709c48e8707e0a59fe51e19e7e4eee6bf56c6c589fe50035490c1a7c91b753cb8007c4b52838a6772f997f0191c35000247554b8d0a196898a794bf3de89982571178d931affb654f0c1adc0b8de03f1a0b0531bff146982d7d613ef6e1ef8d3bdd9590971fc18d835ffbc14cfffaa314261bcbb2ed4ca24d5717bb608d8a6cc9910790bc1d49af7858ab7e92b77b9e3843650f6cd7ee94b6753ea9df3533710b04dee686ad376515a5cbabaab91b367e30fea7026daf9f2590bb7e9cc31db8221f4013c67289e38f22c8" + }, + { + "type": 3000000001, + "len": 1, + "hex": "42" + } + ], + "bech32": "lnp1zcssyj7z5vfx29flqlnsuzatppeyu6u9ugtl3ntz3n4k996zg7a5jvuz2gpq86zcyypjgef743p5fzqq9nqxh0ah7y87rzv3ud0eleps9kl2d5348hq2k89qwcp87v0tc4rzc87uuxmn0m8l2tfh6aw75s7wz8r56fd299ckt74zqpcr9s9he72nyjs86pfe3vjqzaxups47g3xedv2e4fk877c7v6rgpxgszqhd4w73ddqusdcmjthj7pxprpd57qakmn2jh2dh3kwhezwg7gs3g5qpqqqqqqqqqqqqqqqqqqqqqqqqqq9zrsqqqqqpqqqqqqsqqvqqqqqqqqqqqpqqqqqqqqqqqqzsqq9yq3n4y7vg4qs89ntwss3vgplmd5ycdy83zv9hmmt7ctmltcwnp0va2g0sz5mr0ya2qgp73tsdpqqqqqqqqqqqqqqqqqqqpvppqf9u9gcjv52n7pl8pc96kzrjfe4ctcshlrxk9r8tv2t5y3amfyec9uzqlwun9e4f6k6d3r9qmhyul8uvezqw7s0raj2hfk5f7cjdhzv2k05a8mtv42r5gcems4gk0ksqjyvanq62uu0hkphsyuedcnqaaw4s2al3gpykzve5p8698d233l9uvc5ndl95dekpmwxev0zyke74valsll8r43wyy2far0qjnzcdvueq5aewyzsxcp5alfc8nhujq7m82dthxhwhl5p7jgqpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpq87s86eqpdgshfxx3pxkqv2eemf0pj3puaeyyj6eu54zrydml08swdmcqksl6qlvl5qkprys2lkc3u7956myg8w2cw67fnhmxc2eqntnv2uxu7zz07mftarp3hz549698hhx7grl55sk5v832e6yyufv4xy990rcndecs5pf9q709h40tuctu08d3878cfx5y2qehur27rjg5v2z8w7suf3570pauel4f7qvjj588qlj4rhhc0jxr33tv7j3mfdueakdj6nah2efh6j3lfuynw9c2msa9f3annnacxncupwtkt00rawhwzwy36rs0c99nlj3ux08unhwd06kcmzcnljsqd2fpsd8eydh209cqp7yk55r3fnh97vh7qv3cdgqqfr42judpgvk3x98jjlnm6yesft3z7xexxhlke20psddczuduql35zc9xxllz35c947kz0hku8hc6w7ajkgfw87p3kp4l77pfnll4gc5ycduhvhdfj3y64chhdsgmznvexgs0y9ur4y677zc4dlf9dmmncuyxeg0dnt7a99kw5l2nhe4xdcskpx7u6r26dm9zkjuh2a2hydnvl3sl6nsymd0nujepwm7nnp3mwpzraqp83nj383c7gkgl6edqhspq9pq" + } + }, + { + "name": "minimal_disclosure", + "input": { + "invoice": "lni1qqgqqqqqqqqqqqqqqqqqqqqqqqqqq93pqf9u9gcjv52n7pl8pc96kzrjfe4ctcshlrxk9r8tv2t5y3amfyecy5szq059sggry3jnatzrgjyqqtxqdwlm0ug0uxyerc6lnljrqtd75mfr20wq4vw2qasz0uc7h32x9s0aecdhxlk075kn046aafpuuyw8f5j652t3vha2yqrsxtqt0nu4xf9q05znnzeyq96dcrptu3zdj6c4n2nv0aa3ue5xszv3qypwm2aaz66peqm3hyh09uzvzxzmfupmdhx49w5m0rva0jyu3u3pz3gqzqqqqqqqqqqqqqqqqqqqqqqqqqq2y8qqqqqqzqqqqqpqqqcqqqqqqqqqqqzqqqqqqqqqqqq9qqq2gpr82fuc32pqwtxkappzcsrlkmgfs6g0zyct0hkhashh7hsaxz7e65slq9fkx7f65qsrazczzqjtc233yeg48ur7wrst4vy8ynntsh3p07xdv2xwkc5hgfrmkjfnstcyqz3nyfzk3d42umkj2gqjh4l7zpevq04aefl6gnu4kql3e5ymu29s4q7fcvsst9uvmqx6q6yhje3vsraqple9pnxuf5vtwz0l68r6uvvs", + "invoice_hex": "0010000000000000000000000000000000001621024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382520203e858210324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1ca076027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e6686809910102edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145001000000000000000000000000000000000a21c00000001000000020003000000000000000400000000000000050000a40467527988a82072cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793aa0203e8b021024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382f0400a33224568b6aae6ed252012bd7fe1072c03ebdca7fa44f95b03f1cd09be28b0a83c9c32105978cd80da068979662c80fa00ff250ccdc4d18b709ffd1c7ae319", + "preimage": "0101010101010101010101010101010101010101010101010101010101010101", + "invoice_fields": [ + { + "type": 0, + "len": 16, + "hex": "00000000000000000000000000000000", + "included": false + }, + { + "type": 22, + "len": 33, + "hex": "024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382", + "included": false + }, + { + "type": 82, + "len": 2, + "hex": "03e8", + "included": false + }, + { + "type": 88, + "len": 33, + "hex": "0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c", + "included": true + }, + { + "type": 160, + "len": 118, + "hex": "027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e6686809910102edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145001000000000000000000000000000000000", + "included": false + }, + { + "type": 162, + "len": 28, + "hex": "00000001000000020003000000000000000400000000000000050000", + "included": false + }, + { + "type": 164, + "len": 4, + "hex": "67527988", + "included": false + }, + { + "type": 168, + "len": 32, + "hex": "72cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793", + "included": true + }, + { + "type": 170, + "len": 2, + "hex": "03e8", + "included": false + }, + { + "type": 176, + "len": 33, + "hex": "024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382", + "included": true + }, + { + "type": 240, + "len": 64, + "hex": "0a33224568b6aae6ed252012bd7fe1072c03ebdca7fa44f95b03f1cd09be28b0a83c9c32105978cd80da068979662c80fa00ff250ccdc4d18b709ffd1c7ae319", + "included": false + } + ] + }, + "working": { + "invoice_merkle_root": "0501ea6d4ad9fe7fce7edd5e3795987bd409d66c5709c2a17f9c0dfb839e3d8e", + "invoice_sighash": "41ce7b274b0e73e60dd6abf4fa51ccae892b161adc24d4099f620b12c59a03e5", + "invoice_signature": "0a33224568b6aae6ed252012bd7fe1072c03ebdca7fa44f95b03f1cd09be28b0a83c9c32105978cd80da068979662c80fa00ff250ccdc4d18b709ffd1c7ae319", + "proof_merkle_root": "ccc704b73e8190c71bf2ac3661d631c8c5ac502b78641708c8e61c0a1c8ebb72", + "proof_leaf_hashes": [ + "f2deaf5f30be3ced89fc7c24d422819bf06af0e48a31423bbd0e2634f3c3de67", + "f0191c35000247554b8d0a196898a794bf3de89982571178d931affb654f0c1a", + "7e92b77b9e3843650f6cd7ee94b6753ea9df3533710b04dee686ad376515a5cb" + ], + "proof_omitted_tlvs": [ + 1, + 2, + 89, + 90, + 91, + 169 + ], + "proof_missing_hashes": [ + "bf8cb2b1d6fa9bcdcab501b59f82c65c506b7f43514737f7197f1fcfeaebad41", + "b9406f4ce526a6a0d4e0b3a63ed89a832e31cb9939dfe1a7b5dd7232d32c02ab", + "cd9c44b53b31700c9ed0e3330ce425f7f18fac2fc1d566a34468439274f0e316", + "9f9830f2c3070cfbad13fde30ee36cd7143591164ed12040a9cd595c96840ac9", + "998ab7fa9c743fb9dbdb0d8d46fbe3ad333400bd07f328dcdb6008790bc9d2db" + ] + }, + "result": { + "payer_sig": "cebc25d40a2d927b5ebcab8400f542fbb7a8f462e8dd802bb13e050b9bf293b7457c19b46a476740f97c9d6ec141f23434c5d4fa253a1d2eb8896ebad99455cc", + "proof_fields": [ + { + "type": 88, + "len": 33, + "hex": "0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c" + }, + { + "type": 168, + "len": 32, + "hex": "72cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793" + }, + { + "type": 176, + "len": 33, + "hex": "024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382" + }, + { + "type": 240, + "len": 64, + "hex": "0a33224568b6aae6ed252012bd7fe1072c03ebdca7fa44f95b03f1cd09be28b0a83c9c32105978cd80da068979662c80fa00ff250ccdc4d18b709ffd1c7ae319" + }, + { + "type": 241, + "len": 64, + "hex": "cebc25d40a2d927b5ebcab8400f542fbb7a8f462e8dd802bb13e050b9bf293b7457c19b46a476740f97c9d6ec141f23434c5d4fa253a1d2eb8896ebad99455cc" + }, + { + "type": 1001, + "len": 32, + "hex": "0101010101010101010101010101010101010101010101010101010101010101" + }, + { + "type": 1002, + "len": 6, + "hex": "0102595a5ba9" + }, + { + "type": 1003, + "len": 160, + "hex": "bf8cb2b1d6fa9bcdcab501b59f82c65c506b7f43514737f7197f1fcfeaebad41b9406f4ce526a6a0d4e0b3a63ed89a832e31cb9939dfe1a7b5dd7232d32c02abcd9c44b53b31700c9ed0e3330ce425f7f18fac2fc1d566a34468439274f0e3169f9830f2c3070cfbad13fde30ee36cd7143591164ed12040a9cd595c96840ac9998ab7fa9c743fb9dbdb0d8d46fbe3ad333400bd07f328dcdb6008790bc9d2db" + }, + { + "type": 1004, + "len": 96, + "hex": "f2deaf5f30be3ced89fc7c24d422819bf06af0e48a31423bbd0e2634f3c3de67f0191c35000247554b8d0a196898a794bf3de89982571178d931affb654f0c1a7e92b77b9e3843650f6cd7ee94b6753ea9df3533710b04dee686ad376515a5cb" + } + ], + "bech32": "lnp1tqssxfr986kyx3ygqqkvq6alklcslcvfj834l8lyxqkmafkjx57up2cu4qs89ntwss3vgplmd5ycdy83zv9hmmt7ctmltcwnp0va2g0sz5mr0yasyypyhs4rzfj320c8uu8qh2cgwf8xhp0zzluv6c5vad3fwsj8hdyn8qhsgq9rxgj9dzm24ehdy5sp90tluyrjcqltmjnl538etvplrngfhc5tp2punsepqktcekqd5p5f09nzeq86qrlj2rxdcngckuyll5w84cce79qva0p96s9zmynmt672hpqq74p0hdag733w3hvq9wcnupgtn0ef8d690svmg6j8vaq0jlyadmq5ru35xnzaf7398gwjawyfd6adn9z4en7s86fqqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyql6ql2qcqsyk26tw5l6qlt5zlcev436mafhnw2k5qmt8uzcew9q6mlgdg5wdlhr9l3lnl2awk5rw2qdaxw2f4x5r2wpvax8mvf4qewx89ejwwluxnmthtjxtfjcq4tekwyfdfmx9cqe8ksuveseep97lccltp0c82kdg6ydppeya8suvtflxps7tpswr8m45flmccwudkdw9p4jytya5fqgz5u6k2uj6zq4jve32ml48r587uahkcd34r0hcadxv6qp0g87v5dekmqppushjwjm07s8mrq7t027heshc7wmz0u0sjdgg5pn0cx4u8y3gc5ywaapcnrfu7rmenlqxgux5qqy364fwxs5xtgnznef0eaazvcy4c30rvnrtlmv48scxn7j2mhh83cgdjs7mxha62tvaf7480n2vm3pvzdae5x45mk29d9ev" + } + }, + { + "name": "with_note", + "input": { + "invoice": "lni1qqgqqqqqqqqqqqqqqqqqqqqqqqqqq93pqf9u9gcjv52n7pl8pc96kzrjfe4ctcshlrxk9r8tv2t5y3amfyecy5szq059sggry3jnatzrgjyqqtxqdwlm0ug0uxyerc6lnljrqtd75mfr20wq4vw2qasz0uc7h32x9s0aecdhxlk075kn046aafpuuyw8f5j652t3vha2yqrsxtqt0nu4xf9q05znnzeyq96dcrptu3zdj6c4n2nv0aa3ue5xszv3qypwm2aaz66peqm3hyh09uzvzxzmfupmdhx49w5m0rva0jyu3u3pz3gqzqqqqqqqqqqqqqqqqqqqqqqqqqq2y8qqqqqqzqqqqqpqqqcqqqqqqqqqqqzqqqqqqqqqqqq9qqq2gpr82fuc32pqwtxkappzcsrlkmgfs6g0zyct0hkhashh7hsaxz7e65slq9fkx7f65qsrazczzqjtc233yeg48ur7wrst4vy8ynntsh3p07xdv2xwkc5hgfrmkjfnstcyqz3nyfzk3d42umkj2gqjh4l7zpevq04aefl6gnu4kql3e5ymu29s4q7fcvsst9uvmqx6q6yhje3vsraqple9pnxuf5vtwz0l68r6uvvs", + "invoice_hex": "0010000000000000000000000000000000001621024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382520203e858210324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1ca076027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e6686809910102edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145001000000000000000000000000000000000a21c00000001000000020003000000000000000400000000000000050000a40467527988a82072cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793aa0203e8b021024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382f0400a33224568b6aae6ed252012bd7fe1072c03ebdca7fa44f95b03f1cd09be28b0a83c9c32105978cd80da068979662c80fa00ff250ccdc4d18b709ffd1c7ae319", + "preimage": "0101010101010101010101010101010101010101010101010101010101010101", + "note": "test note", + "invoice_fields": [ + { + "type": 0, + "len": 16, + "hex": "00000000000000000000000000000000", + "included": false + }, + { + "type": 22, + "len": 33, + "hex": "024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382", + "included": false + }, + { + "type": 82, + "len": 2, + "hex": "03e8", + "included": false + }, + { + "type": 88, + "len": 33, + "hex": "0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c", + "included": true + }, + { + "type": 160, + "len": 118, + "hex": "027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e6686809910102edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145001000000000000000000000000000000000", + "included": false + }, + { + "type": 162, + "len": 28, + "hex": "00000001000000020003000000000000000400000000000000050000", + "included": false + }, + { + "type": 164, + "len": 4, + "hex": "67527988", + "included": false + }, + { + "type": 168, + "len": 32, + "hex": "72cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793", + "included": true + }, + { + "type": 170, + "len": 2, + "hex": "03e8", + "included": false + }, + { + "type": 176, + "len": 33, + "hex": "024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382", + "included": true + }, + { + "type": 240, + "len": 64, + "hex": "0a33224568b6aae6ed252012bd7fe1072c03ebdca7fa44f95b03f1cd09be28b0a83c9c32105978cd80da068979662c80fa00ff250ccdc4d18b709ffd1c7ae319", + "included": false + } + ] + }, + "working": { + "invoice_merkle_root": "0501ea6d4ad9fe7fce7edd5e3795987bd409d66c5709c2a17f9c0dfb839e3d8e", + "invoice_sighash": "41ce7b274b0e73e60dd6abf4fa51ccae892b161adc24d4099f620b12c59a03e5", + "invoice_signature": "0a33224568b6aae6ed252012bd7fe1072c03ebdca7fa44f95b03f1cd09be28b0a83c9c32105978cd80da068979662c80fa00ff250ccdc4d18b709ffd1c7ae319", + "proof_merkle_root": "327c38426d1d557f3669f19cdb440187e312679eadcfb61a9a8dcb0098639467", + "proof_leaf_hashes": [ + "f2deaf5f30be3ced89fc7c24d422819bf06af0e48a31423bbd0e2634f3c3de67", + "f0191c35000247554b8d0a196898a794bf3de89982571178d931affb654f0c1a", + "7e92b77b9e3843650f6cd7ee94b6753ea9df3533710b04dee686ad376515a5cb" + ], + "proof_omitted_tlvs": [ + 1, + 2, + 89, + 90, + 91, + 169 + ], + "proof_missing_hashes": [ + "bf8cb2b1d6fa9bcdcab501b59f82c65c506b7f43514737f7197f1fcfeaebad41", + "b9406f4ce526a6a0d4e0b3a63ed89a832e31cb9939dfe1a7b5dd7232d32c02ab", + "cd9c44b53b31700c9ed0e3330ce425f7f18fac2fc1d566a34468439274f0e316", + "9f9830f2c3070cfbad13fde30ee36cd7143591164ed12040a9cd595c96840ac9", + "998ab7fa9c743fb9dbdb0d8d46fbe3ad333400bd07f328dcdb6008790bc9d2db" + ] + }, + "result": { + "payer_sig": "2a47f98770ae814119d682a7b19f7ce9e050a3bb49d9b0a031c46d9cb57a3075ddc23a742f6d33bc4ec8e1778327494bea76d2ee564625e99bfb95cc209a7722", + "proof_fields": [ + { + "type": 88, + "len": 33, + "hex": "0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c" + }, + { + "type": 168, + "len": 32, + "hex": "72cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793" + }, + { + "type": 176, + "len": 33, + "hex": "024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382" + }, + { + "type": 240, + "len": 64, + "hex": "0a33224568b6aae6ed252012bd7fe1072c03ebdca7fa44f95b03f1cd09be28b0a83c9c32105978cd80da068979662c80fa00ff250ccdc4d18b709ffd1c7ae319" + }, + { + "type": 241, + "len": 64, + "hex": "2a47f98770ae814119d682a7b19f7ce9e050a3bb49d9b0a031c46d9cb57a3075ddc23a742f6d33bc4ec8e1778327494bea76d2ee564625e99bfb95cc209a7722" + }, + { + "type": 1001, + "len": 32, + "hex": "0101010101010101010101010101010101010101010101010101010101010101" + }, + { + "type": 1002, + "len": 6, + "hex": "0102595a5ba9" + }, + { + "type": 1003, + "len": 160, + "hex": "bf8cb2b1d6fa9bcdcab501b59f82c65c506b7f43514737f7197f1fcfeaebad41b9406f4ce526a6a0d4e0b3a63ed89a832e31cb9939dfe1a7b5dd7232d32c02abcd9c44b53b31700c9ed0e3330ce425f7f18fac2fc1d566a34468439274f0e3169f9830f2c3070cfbad13fde30ee36cd7143591164ed12040a9cd595c96840ac9998ab7fa9c743fb9dbdb0d8d46fbe3ad333400bd07f328dcdb6008790bc9d2db" + }, + { + "type": 1004, + "len": 96, + "hex": "f2deaf5f30be3ced89fc7c24d422819bf06af0e48a31423bbd0e2634f3c3de67f0191c35000247554b8d0a196898a794bf3de89982571178d931affb654f0c1a7e92b77b9e3843650f6cd7ee94b6753ea9df3533710b04dee686ad376515a5cb" + }, + { + "type": 1005, + "len": 9, + "hex": "74657374206e6f7465" + } + ], + "bech32": "lnp1tqssxfr986kyx3ygqqkvq6alklcslcvfj834l8lyxqkmafkjx57up2cu4qs89ntwss3vgplmd5ycdy83zv9hmmt7ctmltcwnp0va2g0sz5mr0yasyypyhs4rzfj320c8uu8qh2cgwf8xhp0zzluv6c5vad3fwsj8hdyn8qhsgq9rxgj9dzm24ehdy5sp90tluyrjcqltmjnl538etvplrngfhc5tp2punsepqktcekqd5p5f09nzeq86qrlj2rxdcngckuyll5w84cce79qz53lesac2aq2pr8tg9fa3na7wnczs5wa5nkds5qcugmvuk4arqawacga8gtmdxw7yaj8pw7pjwj2tafmd9mjkgcj7nxlmjhxzpxnhyt7s86fqqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyql6ql2qcqsyk26tw5l6qlt5zlcev436mafhnw2k5qmt8uzcew9q6mlgdg5wdlhr9l3lnl2awk5rw2qdaxw2f4x5r2wpvax8mvf4qewx89ejwwluxnmthtjxtfjcq4tekwyfdfmx9cqe8ksuveseep97lccltp0c82kdg6ydppeya8suvtflxps7tpswr8m45flmccwudkdw9p4jytya5fqgz5u6k2uj6zq4jve32ml48r587uahkcd34r0hcadxv6qp0g87v5dekmqppushjwjm07s8mrq7t027heshc7wmz0u0sjdgg5pn0cx4u8y3gc5ywaapcnrfu7rmenlqxgux5qqy364fwxs5xtgnznef0eaazvcy4c30rvnrtlmv48scxn7j2mhh83cgdjs7mxha62tvaf7480n2vm3pvzdae5x45mk29d9e07s8mgfw3jhxapqdehhgeg" + } + }, + { + "name": "left_subtree_omitted", + "input": { + "invoice": "lni1qqgqqqqqqqqqqqqqqqqqqqqqqqqqq93pqf9u9gcjv52n7pl8pc96kzrjfe4ctcshlrxk9r8tv2t5y3amfyecy5szq059sggry3jnatzrgjyqqtxqdwlm0ug0uxyerc6lnljrqtd75mfr20wq4vw2qasz0uc7h32x9s0aecdhxlk075kn046aafpuuyw8f5j652t3vha2yqrsxtqt0nu4xf9q05znnzeyq96dcrptu3zdj6c4n2nv0aa3ue5xszv3qypwm2aaz66peqm3hyh09uzvzxzmfupmdhx49w5m0rva0jyu3u3pz3gqzqqqqqqqqqqqqqqqqqqqqqqqqqq2y8qqqqqqzqqqqqpqqqcqqqqqqqqqqqzqqqqqqqqqqqq9qqq2gpr82fuc32pqwtxkappzcsrlkmgfs6g0zyct0hkhashh7hsaxz7e65slq9fkx7f65qsrazczzqjtc233yeg48ur7wrst4vy8ynntsh3p07xdv2xwkc5hgfrmkjfnstcyqz3nyfzk3d42umkj2gqjh4l7zpevq04aefl6gnu4kql3e5ymu29s4q7fcvsst9uvmqx6q6yhje3vsraqple9pnxuf5vtwz0l68r6uvvs", + "invoice_hex": "0010000000000000000000000000000000001621024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382520203e858210324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1ca076027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e6686809910102edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145001000000000000000000000000000000000a21c00000001000000020003000000000000000400000000000000050000a40467527988a82072cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793aa0203e8b021024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382f0400a33224568b6aae6ed252012bd7fe1072c03ebdca7fa44f95b03f1cd09be28b0a83c9c32105978cd80da068979662c80fa00ff250ccdc4d18b709ffd1c7ae319", + "preimage": "0101010101010101010101010101010101010101010101010101010101010101", + "invoice_fields": [ + { + "type": 0, + "len": 16, + "hex": "00000000000000000000000000000000", + "included": false + }, + { + "type": 22, + "len": 33, + "hex": "024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382", + "included": false + }, + { + "type": 82, + "len": 2, + "hex": "03e8", + "included": false + }, + { + "type": 88, + "len": 33, + "hex": "0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c", + "included": true + }, + { + "type": 160, + "len": 118, + "hex": "027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e6686809910102edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145001000000000000000000000000000000000", + "included": false + }, + { + "type": 162, + "len": 28, + "hex": "00000001000000020003000000000000000400000000000000050000", + "included": false + }, + { + "type": 164, + "len": 4, + "hex": "67527988", + "included": false + }, + { + "type": 168, + "len": 32, + "hex": "72cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793", + "included": true + }, + { + "type": 170, + "len": 2, + "hex": "03e8", + "included": true + }, + { + "type": 176, + "len": 33, + "hex": "024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382", + "included": true + }, + { + "type": 240, + "len": 64, + "hex": "0a33224568b6aae6ed252012bd7fe1072c03ebdca7fa44f95b03f1cd09be28b0a83c9c32105978cd80da068979662c80fa00ff250ccdc4d18b709ffd1c7ae319", + "included": false + } + ] + }, + "working": { + "invoice_merkle_root": "0501ea6d4ad9fe7fce7edd5e3795987bd409d66c5709c2a17f9c0dfb839e3d8e", + "invoice_sighash": "41ce7b274b0e73e60dd6abf4fa51ccae892b161adc24d4099f620b12c59a03e5", + "invoice_signature": "0a33224568b6aae6ed252012bd7fe1072c03ebdca7fa44f95b03f1cd09be28b0a83c9c32105978cd80da068979662c80fa00ff250ccdc4d18b709ffd1c7ae319", + "proof_merkle_root": "1b0bab09a5fcccec19eb3aaafbbfa4075944fd1f65691ed715fbbc17a59b3651", + "proof_leaf_hashes": [ + "f2deaf5f30be3ced89fc7c24d422819bf06af0e48a31423bbd0e2634f3c3de67", + "f0191c35000247554b8d0a196898a794bf3de89982571178d931affb654f0c1a", + "dc0b8de03f1a0b0531bff146982d7d613ef6e1ef8d3bdd9590971fc18d835ffb", + "7e92b77b9e3843650f6cd7ee94b6753ea9df3533710b04dee686ad376515a5cb" + ], + "proof_omitted_tlvs": [ + 1, + 2, + 89, + 90, + 91 + ], + "proof_missing_hashes": [ + "bf8cb2b1d6fa9bcdcab501b59f82c65c506b7f43514737f7197f1fcfeaebad41", + "b9406f4ce526a6a0d4e0b3a63ed89a832e31cb9939dfe1a7b5dd7232d32c02ab", + "cd9c44b53b31700c9ed0e3330ce425f7f18fac2fc1d566a34468439274f0e316", + "9f9830f2c3070cfbad13fde30ee36cd7143591164ed12040a9cd595c96840ac9" + ] + }, + "result": { + "payer_sig": "ed03ec58d5ac676539486bb6e8d6f389b6375b6693ffdf30a93919590d744fa393f945a5de3bb57d65c7f61def1cefa8aa038725b15bf0fa797dfd08ca97538a", + "proof_fields": [ + { + "type": 88, + "len": 33, + "hex": "0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c" + }, + { + "type": 168, + "len": 32, + "hex": "72cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793" + }, + { + "type": 170, + "len": 2, + "hex": "03e8" + }, + { + "type": 176, + "len": 33, + "hex": "024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382" + }, + { + "type": 240, + "len": 64, + "hex": "0a33224568b6aae6ed252012bd7fe1072c03ebdca7fa44f95b03f1cd09be28b0a83c9c32105978cd80da068979662c80fa00ff250ccdc4d18b709ffd1c7ae319" + }, + { + "type": 241, + "len": 64, + "hex": "ed03ec58d5ac676539486bb6e8d6f389b6375b6693ffdf30a93919590d744fa393f945a5de3bb57d65c7f61def1cefa8aa038725b15bf0fa797dfd08ca97538a" + }, + { + "type": 1001, + "len": 32, + "hex": "0101010101010101010101010101010101010101010101010101010101010101" + }, + { + "type": 1002, + "len": 5, + "hex": "0102595a5b" + }, + { + "type": 1003, + "len": 128, + "hex": "bf8cb2b1d6fa9bcdcab501b59f82c65c506b7f43514737f7197f1fcfeaebad41b9406f4ce526a6a0d4e0b3a63ed89a832e31cb9939dfe1a7b5dd7232d32c02abcd9c44b53b31700c9ed0e3330ce425f7f18fac2fc1d566a34468439274f0e3169f9830f2c3070cfbad13fde30ee36cd7143591164ed12040a9cd595c96840ac9" + }, + { + "type": 1004, + "len": 128, + "hex": "f2deaf5f30be3ced89fc7c24d422819bf06af0e48a31423bbd0e2634f3c3de67f0191c35000247554b8d0a196898a794bf3de89982571178d931affb654f0c1adc0b8de03f1a0b0531bff146982d7d613ef6e1ef8d3bdd9590971fc18d835ffb7e92b77b9e3843650f6cd7ee94b6753ea9df3533710b04dee686ad376515a5cb" + } + ], + "bech32": "lnp1tqssxfr986kyx3ygqqkvq6alklcslcvfj834l8lyxqkmafkjx57up2cu4qs89ntwss3vgplmd5ycdy83zv9hmmt7ctmltcwnp0va2g0sz5mr0ya2qgp73vppqf9u9gcjv52n7pl8pc96kzrjfe4ctcshlrxk9r8tv2t5y3amfyec9uzqpgejy3tgk64wdmf9yqft6llpqukq867u5layf72mq0cu6zd79zc2s0yuxgg9j7xdsrdqdztevckgp7sqlujsenwy6x9hp8lar3awxx03grks8mzc6kkxwefefp4md6xk7wymvd6mv6fllhes4yu3jkgdw3868ylegkjauwa404ju0asaauwwl292qwrjtv2m7ra8jl0apr9fw5u2l5p7jgqpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpq87s86s9qyp9jkjml5p7hq9l3jetr4h6n0xu4dgpkk0c93ju2p4h7s63gumlwxtlrl8746adgxu5qm6vu5n2dgx5uze6v0kcn2pjuvwtnyualcd8khwhyvkn9sp2hnvugj6nkvtspj0dpcenpnjztal337kzlsw4v635g6zrjf60pcckn7vrpukrqux0htgnlh3sacmv6u2rtygkfmgjqs9fe4v4e95yptyl6qlvsredat6lxzlremvfl37zf4pzsxdlq6hsuj9rzs3mh58zvd8nc00x0uqers6sqqj8249c6zsedzv2099l8h5fnqjhz9udjvd0ldj57rq6ms9cmcplrg9s2vdl79rfsttavyl0dc0035aam9vsju0urrvrtlahay4h0w0rssm9pakd0m55ke6na2wlx5ehzzcymmngdtfhv526tjc" + } + }, + { + "name": "empty_proof_omitted_tlvs_explicit", + "input": { + "invoice": "lni1qqgqqqqqqqqqqqqqqqqqqqqqqqqqq93pqf9u9gcjv52n7pl8pc96kzrjfe4ctcshlrxk9r8tv2t5y3amfyecy5szq059sggry3jnatzrgjyqqtxqdwlm0ug0uxyerc6lnljrqtd75mfr20wq4vw2qasz0uc7h32x9s0aecdhxlk075kn046aafpuuyw8f5j652t3vha2yqrsxtqt0nu4xf9q05znnzeyq96dcrptu3zdj6c4n2nv0aa3ue5xszv3qypwm2aaz66peqm3hyh09uzvzxzmfupmdhx49w5m0rva0jyu3u3pz3gqzqqqqqqqqqqqqqqqqqqqqqqqqqq2y8qqqqqqzqqqqqpqqqcqqqqqqqqqqqzqqqqqqqqqqqq9qqq2gpr82fuc32pqwtxkappzcsrlkmgfs6g0zyct0hkhashh7hsaxz7e65slq9fkx7f65qsrazczzqjtc233yeg48ur7wrst4vy8ynntsh3p07xdv2xwkc5hgfrmkjfnstcyqz3nyfzk3d42umkj2gqjh4l7zpevq04aefl6gnu4kql3e5ymu29s4q7fcvsst9uvmqx6q6yhje3vsraqple9pnxuf5vtwz0l68r6uvvs", + "invoice_hex": "0010000000000000000000000000000000001621024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382520203e858210324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1ca076027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e6686809910102edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145001000000000000000000000000000000000a21c00000001000000020003000000000000000400000000000000050000a40467527988a82072cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793aa0203e8b021024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382f0400a33224568b6aae6ed252012bd7fe1072c03ebdca7fa44f95b03f1cd09be28b0a83c9c32105978cd80da068979662c80fa00ff250ccdc4d18b709ffd1c7ae319", + "preimage": "0101010101010101010101010101010101010101010101010101010101010101", + "invoice_fields": [ + { + "type": 0, + "len": 16, + "hex": "00000000000000000000000000000000", + "included": false + }, + { + "type": 22, + "len": 33, + "hex": "024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382", + "included": true + }, + { + "type": 82, + "len": 2, + "hex": "03e8", + "included": true + }, + { + "type": 88, + "len": 33, + "hex": "0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c", + "included": true + }, + { + "type": 160, + "len": 118, + "hex": "027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e6686809910102edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145001000000000000000000000000000000000", + "included": true + }, + { + "type": 162, + "len": 28, + "hex": "00000001000000020003000000000000000400000000000000050000", + "included": true + }, + { + "type": 164, + "len": 4, + "hex": "67527988", + "included": true + }, + { + "type": 168, + "len": 32, + "hex": "72cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793", + "included": true + }, + { + "type": 170, + "len": 2, + "hex": "03e8", + "included": true + }, + { + "type": 176, + "len": 33, + "hex": "024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382", + "included": true + }, + { + "type": 240, + "len": 64, + "hex": "0a33224568b6aae6ed252012bd7fe1072c03ebdca7fa44f95b03f1cd09be28b0a83c9c32105978cd80da068979662c80fa00ff250ccdc4d18b709ffd1c7ae319", + "included": false + } + ] + }, + "working": { + "invoice_merkle_root": "0501ea6d4ad9fe7fce7edd5e3795987bd409d66c5709c2a17f9c0dfb839e3d8e", + "invoice_sighash": "41ce7b274b0e73e60dd6abf4fa51ccae892b161adc24d4099f620b12c59a03e5", + "invoice_signature": "0a33224568b6aae6ed252012bd7fe1072c03ebdca7fa44f95b03f1cd09be28b0a83c9c32105978cd80da068979662c80fa00ff250ccdc4d18b709ffd1c7ae319", + "proof_merkle_root": "a98cbee700b100ede32c19f9525264d51a105751fb27e790dad011bb38415b89", + "proof_leaf_hashes": [ + "8c9057ed88f3c5a6b6441dcac3b5e4cefb3615904d7362b86e78427fb695f461", + "8dc54a97453dee6f207fa5216a30f1567442712ca98852bc789b73885029283c", + "f2deaf5f30be3ced89fc7c24d422819bf06af0e48a31423bbd0e2634f3c3de67", + "f54f80c94a87383f2a8ef7c3e461c62b67a51da5bccf6cd96a7dbab29bea51fa", + "7849b8b856e1d2a63d9ce7dc1a78e05cbb2def1f5d7709c48e8707e0a59fe51e", + "19e7e4eee6bf56c6c589fe50035490c1a7c91b753cb8007c4b52838a6772f997", + "f0191c35000247554b8d0a196898a794bf3de89982571178d931affb654f0c1a", + "dc0b8de03f1a0b0531bff146982d7d613ef6e1ef8d3bdd9590971fc18d835ffb", + "7e92b77b9e3843650f6cd7ee94b6753ea9df3533710b04dee686ad376515a5cb" + ], + "proof_omitted_tlvs": [], + "proof_missing_hashes": [ + "0b510ba4c6884d603159ced2f0ca21e772424b59e52a2191bbfbcf07377805a1" + ] + }, + "result": { + "payer_sig": "dffe62d6446c182f2503e780fce09fc5cf310ef14a54f65dd1df124039b7a897f2940470ac0f9857f826b232289c0ac4e6927ca67d805167fb0add352f88add1", + "proof_fields": [ + { + "type": 22, + "len": 33, + "hex": "024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382" + }, + { + "type": 82, + "len": 2, + "hex": "03e8" + }, + { + "type": 88, + "len": 33, + "hex": "0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c" + }, + { + "type": 160, + "len": 118, + "hex": "027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e6686809910102edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145001000000000000000000000000000000000" + }, + { + "type": 162, + "len": 28, + "hex": "00000001000000020003000000000000000400000000000000050000" + }, + { + "type": 164, + "len": 4, + "hex": "67527988" + }, + { + "type": 168, + "len": 32, + "hex": "72cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793" + }, + { + "type": 170, + "len": 2, + "hex": "03e8" + }, + { + "type": 176, + "len": 33, + "hex": "024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382" + }, + { + "type": 240, + "len": 64, + "hex": "0a33224568b6aae6ed252012bd7fe1072c03ebdca7fa44f95b03f1cd09be28b0a83c9c32105978cd80da068979662c80fa00ff250ccdc4d18b709ffd1c7ae319" + }, + { + "type": 241, + "len": 64, + "hex": "dffe62d6446c182f2503e780fce09fc5cf310ef14a54f65dd1df124039b7a897f2940470ac0f9857f826b232289c0ac4e6927ca67d805167fb0add352f88add1" + }, + { + "type": 1001, + "len": 32, + "hex": "0101010101010101010101010101010101010101010101010101010101010101" + }, + { + "type": 1002, + "len": 0, + "hex": "" + }, + { + "type": 1003, + "len": 32, + "hex": "0b510ba4c6884d603159ced2f0ca21e772424b59e52a2191bbfbcf07377805a1" + }, + { + "type": 1004, + "len": 288, + "hex": "8c9057ed88f3c5a6b6441dcac3b5e4cefb3615904d7362b86e78427fb695f4618dc54a97453dee6f207fa5216a30f1567442712ca98852bc789b73885029283cf2deaf5f30be3ced89fc7c24d422819bf06af0e48a31423bbd0e2634f3c3de67f54f80c94a87383f2a8ef7c3e461c62b67a51da5bccf6cd96a7dbab29bea51fa7849b8b856e1d2a63d9ce7dc1a78e05cbb2def1f5d7709c48e8707e0a59fe51e19e7e4eee6bf56c6c589fe50035490c1a7c91b753cb8007c4b52838a6772f997f0191c35000247554b8d0a196898a794bf3de89982571178d931affb654f0c1adc0b8de03f1a0b0531bff146982d7d613ef6e1ef8d3bdd9590971fc18d835ffb7e92b77b9e3843650f6cd7ee94b6753ea9df3533710b04dee686ad376515a5cb" + } + ], + "bech32": "lnp1zcssyj7z5vfx29flqlnsuzatppeyu6u9ugtl3ntz3n4k996zg7a5jvuz2gpq86zcyypjgef743p5fzqq9nqxh0ah7y87rzv3ud0eleps9kl2d5348hq2k89qwcp87v0tc4rzc87uuxmn0m8l2tfh6aw75s7wz8r56fd299ckt74zqpcr9s9he72nyjs86pfe3vjqzaxups47g3xedv2e4fk877c7v6rgpxgszqhd4w73ddqusdcmjthj7pxprpd57qakmn2jh2dh3kwhezwg7gs3g5qpqqqqqqqqqqqqqqqqqqqqqqqqqq9zrsqqqqqpqqqqqqsqqvqqqqqqqqqqqpqqqqqqqqqqqqzsqq9yq3n4y7vg4qs89ntwss3vgplmd5ycdy83zv9hmmt7ctmltcwnp0va2g0sz5mr0ya2qgp73vppqf9u9gcjv52n7pl8pc96kzrjfe4ctcshlrxk9r8tv2t5y3amfyec9uzqpgejy3tgk64wdmf9yqft6llpqukq867u5layf72mq0cu6zd79zc2s0yuxgg9j7xdsrdqdztevckgp7sqlujsenwy6x9hp8lar3awxx03gr0luckkg3kpste9q0ncpl8qnlzu7vgw7999faja6803yspek75f0u55q3c2cruc2luzdv3j9zwq438xjf72vlvq29nlkzkax5hc3tw3l5p7jgqpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpq87s86sql5p7kgqt2y96f35gf4srzkww6tcv5g08wfpykk099gserwlmeurnw7q9587s8m8aqysgeyzhaky083dxkezpmjkrkhjva7ekzkgy6umzhph8ssnlk62lgcvdc49fw3faaehjqla9y94rpu2kw3p8zt9f3pftc7ymwwy9q2fg8nedat6lxzlremvfl37zf4pzsxdlq6hsuj9rzs3mh58zvd8nc00x0a20sry54pec8u4gaa7ru3suv2m855w6t0x0dnvk5ld6k2d755060pym3wzku8f2v0vuulwp578qtjajmmclt4msn3ywsur7pfvlu50pnelyamnt74kxckylu5qr2jgvrf7frd6newqq03949qu2vae0n9lsrywr2qqzga25hrg2r95f3fu5hu773xvz2ugh3kf34lak2ncvrtwqhr0q8udqkpf3hlc5dxpd04snaahpa7xnhhv4jzt3lsvdsd0lkl5jkaaeuwzrv58ke4lwjjm8204fmu6nxugtqn0wdp4dxaj3tfwt" + } + } + ], + "invalid_vectors": [ + { + "reason": "missing_invreq_payer_id", + "bech32": "lnp14qs89ntwss3vgplmd5ycdy83zv9hmmt7ctmltcwnp0va2g0sz5mr0yasyypyhs4rzfj320c8uu8qh2cgwf8xhp0zzluv6c5vad3fwsj8hdyn8qhsgq9rxgj9dzm24ehdy5sp90tluyrjcqltmjnl538etvplrngfhc5tp2punsepqktcekqd5p5f09nzeq86qrlj2rxdcngckuyll5w84cce79qva0p96s9zmynmt672hpqq74p0hdag733w3hvq9wcnupgtn0ef8d690svmg6j8vaq0jlyadmq5ru35xnzaf7398gwjawyfd6adn9z4en7s86fqqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyql6ql2qcqsyk26tw5l6qlt5zlcev436mafhnw2k5qmt8uzcew9q6mlgdg5wdlhr9l3lnl2awk5rw2qdaxw2f4x5r2wpvax8mvf4qewx89ejwwluxnmthtjxtfjcq4tekwyfdfmx9cqe8ksuveseep97lccltp0c82kdg6ydppeya8suvtflxps7tpswr8m45flmccwudkdw9p4jytya5fqgz5u6k2uj6zq4jve32ml48r587uahkcd34r0hcadxv6qp0g87v5dekmqppushjwjm07s8mrq7t027heshc7wmz0u0sjdgg5pn0cx4u8y3gc5ywaapcnrfu7rmenlqxgux5qqy364fwxs5xtgnznef0eaazvcy4c30rvnrtlmv48scxn7j2mhh83cgdjs7mxha62tvaf7480n2vm3pvzdae5x45mk29d9ev" + }, + { + "reason": "missing_invoice_payment_hash", + "bech32": "lnp1tqssxfr986kyx3ygqqkvq6alklcslcvfj834l8lyxqkmafkjx57up2cukqssyj7z5vfx29flqlnsuzatppeyu6u9ugtl3ntz3n4k996zg7a5jvuz7pqq5vezg45td2hxa5jjqy4a0lsswtqra0w207jyl9ds8uwdpxlz3v9g8jwryyze0rxcpksx39ukvtyqlgq07fgvehzdrzmsnl73c7hrr8c5pn4uyh2q5tvj0d0te2uyqr6597ah4r6x96xasq4mz0s9pwdl9yahg47pndr2gan5p7tun4hvzs0jxs6vt486y5ap6t4c39ht4kv52hx06qlfyqqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsrlgragrqzqjetfd6nlgrawstlr9jk8t04x7de26srdvlstr9c5rt0ap4z3eh7uvh7870at466sdegph5eefx56sdfc9n5cld3x5r9ccuhxfemls60dwawgedxtqz40xec3948vchqry76r3nxr8yyhmlrrav9lqa2e4rg35y8yn57r33d8ucxrevxpcvlwk38l0rpm3ke4c5xkg3vnk3ypq2nn2etjtggzkfnx9t075uwslmnk7mpkx5d7lr45engq9aqlej3hxmvqy8jz7f6tdl6qlvvredat6lxzlremvfl37zf4pzsxdlq6hsuj9rzs3mh58zvd8nc00x0uqers6sqqj8249c6zsedzv2099l8h5fnqjhz9udjvd0ldj57rq606ftw7u78ppk2rmv6lhffdn4865a7dfnwy9sfhhxs6knweg45h9s" + }, + { + "reason": "missing_invoice_node_id", + "bech32": "lnp1tqssxfr986kyx3ygqqkvq6alklcslcvfj834l8lyxqkmafkjx57up2cu4qs89ntwss3vgplmd5ycdy83zv9hmmt7ctmltcwnp0va2g0sz5mr0ylsgq9rxgj9dzm24ehdy5sp90tluyrjcqltmjnl538etvplrngfhc5tp2punsepqktcekqd5p5f09nzeq86qrlj2rxdcngckuyll5w84cce79qva0p96s9zmynmt672hpqq74p0hdag733w3hvq9wcnupgtn0ef8d690svmg6j8vaq0jlyadmq5ru35xnzaf7398gwjawyfd6adn9z4en7s86fqqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyql6ql2qcqsyk26tw5l6qlt5zlcev436mafhnw2k5qmt8uzcew9q6mlgdg5wdlhr9l3lnl2awk5rw2qdaxw2f4x5r2wpvax8mvf4qewx89ejwwluxnmthtjxtfjcq4tekwyfdfmx9cqe8ksuveseep97lccltp0c82kdg6ydppeya8suvtflxps7tpswr8m45flmccwudkdw9p4jytya5fqgz5u6k2uj6zq4jve32ml48r587uahkcd34r0hcadxv6qp0g87v5dekmqppushjwjm07s8mrq7t027heshc7wmz0u0sjdgg5pn0cx4u8y3gc5ywaapcnrfu7rmenlqxgux5qqy364fwxs5xtgnznef0eaazvcy4c30rvnrtlmv48scxn7j2mhh83cgdjs7mxha62tvaf7480n2vm3pvzdae5x45mk29d9ev" + }, + { + "reason": "missing_signature", + "bech32": "lnp1tqssxfr986kyx3ygqqkvq6alklcslcvfj834l8lyxqkmafkjx57up2cu4qs89ntwss3vgplmd5ycdy83zv9hmmt7ctmltcwnp0va2g0sz5mr0yasyypyhs4rzfj320c8uu8qh2cgwf8xhp0zzluv6c5vad3fwsj8hdyn8qh3gr8tcfw5pgkey767hj4cgq84gtam0285vt5dmqptkylq2zum72fmw3turx6x53m8gruhe8twc9qlydp5ch205ff6r5ht3ztwhtveg4wvl5p7jgqpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpq87s86sxqyp9jkjm487s86aqh7xt9vwkl2dumj44qx6elqkxt3gxkl6r29rn0ace0u0ul6ht44qmjsr0fnjjdf4q6nst8f37mzdgxt33ewvnnhlp576a6u3j6vkq927dn3zt2we3wqxfa58rxvxwgf0h7x86ct7p64n2x3rggwf8fu8rz60esv8jcvrse7adz077xrhrdnt3gdv3ze8dzgzq48x4jhykss9vnxv2klafcaplh8dakrvdgma78tfnxsqt6pln9rwdkcqg0y9un5kml5p7cc8jm6h47v978nkcnlruyn2z9qvm7p40pey2x9prh0gwyc608s77vlcpj8p4qqpyw42t359pj6yc572t700gnxp9wytcmyc6l7m9fuxp5l5jkaaeuwzrv58ke4lwjjm8204fmu6nxugtqn0wdp4dxaj3tfwt" + }, + { + "reason": "missing_proof_preimage", + "bech32": "lnp1tqssxfr986kyx3ygqqkvq6alklcslcvfj834l8lyxqkmafkjx57up2cu4qs89ntwss3vgplmd5ycdy83zv9hmmt7ctmltcwnp0va2g0sz5mr0yasyypyhs4rzfj320c8uu8qh2cgwf8xhp0zzluv6c5vad3fwsj8hdyn8qhsgq9rxgj9dzm24ehdy5sp90tluyrjcqltmjnl538etvplrngfhc5tp2punsepqktcekqd5p5f09nzeq86qrlj2rxdcngckuyll5w84cce79qva0p96s9zmynmt672hpqq74p0hdag733w3hvq9wcnupgtn0ef8d690svmg6j8vaq0jlyadmq5ru35xnzaf7398gwjawyfd6adn9z4en7s86sxqyp9jkjm487s86aqh7xt9vwkl2dumj44qx6elqkxt3gxkl6r29rn0ace0u0ul6ht44qmjsr0fnjjdf4q6nst8f37mzdgxt33ewvnnhlp576a6u3j6vkq927dn3zt2we3wqxfa58rxvxwgf0h7x86ct7p64n2x3rggwf8fu8rz60esv8jcvrse7adz077xrhrdnt3gdv3ze8dzgzq48x4jhykss9vnxv2klafcaplh8dakrvdgma78tfnxsqt6pln9rwdkcqg0y9un5kml5p7cc8jm6h47v978nkcnlruyn2z9qvm7p40pey2x9prh0gwyc608s77vlcpj8p4qqpyw42t359pj6yc572t700gnxp9wytcmyc6l7m9fuxp5l5jkaaeuwzrv58ke4lwjjm8204fmu6nxugtqn0wdp4dxaj3tfwt" + }, + { + "reason": "missing_proof_missing_hashes", + "bech32": "lnp1tqssxfr986kyx3ygqqkvq6alklcslcvfj834l8lyxqkmafkjx57up2cu4qs89ntwss3vgplmd5ycdy83zv9hmmt7ctmltcwnp0va2g0sz5mr0yasyypyhs4rzfj320c8uu8qh2cgwf8xhp0zzluv6c5vad3fwsj8hdyn8qhsgq9rxgj9dzm24ehdy5sp90tluyrjcqltmjnl538etvplrngfhc5tp2punsepqktcekqd5p5f09nzeq86qrlj2rxdcngckuyll5w84cce79qva0p96s9zmynmt672hpqq74p0hdag733w3hvq9wcnupgtn0ef8d690svmg6j8vaq0jlyadmq5ru35xnzaf7398gwjawyfd6adn9z4en7s86fqqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyql6ql2qcqsyk26tw5l6qlvvredat6lxzlremvfl37zf4pzsxdlq6hsuj9rzs3mh58zvd8nc00x0uqers6sqqj8249c6zsedzv2099l8h5fnqjhz9udjvd0ldj57rq606ftw7u78ppk2rmv6lhffdn4865a7dfnwy9sfhhxs6knweg45h9s" + }, + { + "reason": "missing_proof_leaf_hashes", + "bech32": "lnp1tqssxfr986kyx3ygqqkvq6alklcslcvfj834l8lyxqkmafkjx57up2cu4qs89ntwss3vgplmd5ycdy83zv9hmmt7ctmltcwnp0va2g0sz5mr0yasyypyhs4rzfj320c8uu8qh2cgwf8xhp0zzluv6c5vad3fwsj8hdyn8qhsgq9rxgj9dzm24ehdy5sp90tluyrjcqltmjnl538etvplrngfhc5tp2punsepqktcekqd5p5f09nzeq86qrlj2rxdcngckuyll5w84cce79qva0p96s9zmynmt672hpqq74p0hdag733w3hvq9wcnupgtn0ef8d690svmg6j8vaq0jlyadmq5ru35xnzaf7398gwjawyfd6adn9z4en7s86fqqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyql6ql2qcqsyk26tw5l6qlt5zlcev436mafhnw2k5qmt8uzcew9q6mlgdg5wdlhr9l3lnl2awk5rw2qdaxw2f4x5r2wpvax8mvf4qewx89ejwwluxnmthtjxtfjcq4tekwyfdfmx9cqe8ksuveseep97lccltp0c82kdg6ydppeya8suvtflxps7tpswr8m45flmccwudkdw9p4jytya5fqgz5u6k2uj6zq4jve32ml48r587uahkcd34r0hcadxv6qp0g87v5dekmqppushjwjmv" + }, + { + "reason": "missing_proof_signature", + "bech32": "lnp1tqssxfr986kyx3ygqqkvq6alklcslcvfj834l8lyxqkmafkjx57up2cu4qs89ntwss3vgplmd5ycdy83zv9hmmt7ctmltcwnp0va2g0sz5mr0yasyypyhs4rzfj320c8uu8qh2cgwf8xhp0zzluv6c5vad3fwsj8hdyn8qhsgq9rxgj9dzm24ehdy5sp90tluyrjcqltmjnl538etvplrngfhc5tp2punsepqktcekqd5p5f09nzeq86qrlj2rxdcngckuyll5w84ccel5p7jgqpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpq87s86sxqyp9jkjm487s86aqh7xt9vwkl2dumj44qx6elqkxt3gxkl6r29rn0ace0u0ul6ht44qmjsr0fnjjdf4q6nst8f37mzdgxt33ewvnnhlp576a6u3j6vkq927dn3zt2we3wqxfa58rxvxwgf0h7x86ct7p64n2x3rggwf8fu8rz60esv8jcvrse7adz077xrhrdnt3gdv3ze8dzgzq48x4jhykss9vnxv2klafcaplh8dakrvdgma78tfnxsqt6pln9rwdkcqg0y9un5kml5p7cc8jm6h47v978nkcnlruyn2z9qvm7p40pey2x9prh0gwyc608s77vlcpj8p4qqpyw42t359pj6yc572t700gnxp9wytcmyc6l7m9fuxp5l5jkaaeuwzrv58ke4lwjjm8204fmu6nxugtqn0wdp4dxaj3tfwt" + }, + { + "reason": "wrong_proof_preimage", + "bech32": "lnp1tqssxfr986kyx3ygqqkvq6alklcslcvfj834l8lyxqkmafkjx57up2cu4qs89ntwss3vgplmd5ycdy83zv9hmmt7ctmltcwnp0va2g0sz5mr0yasyypyhs4rzfj320c8uu8qh2cgwf8xhp0zzluv6c5vad3fwsj8hdyn8qhsgq9rxgj9dzm24ehdy5sp90tluyrjcqltmjnl538etvplrngfhc5tp2punsepqktcekqd5p5f09nzeq86qrlj2rxdcngckuyll5w84cce79qva0p96s9zmynmt672hpqq74p0hdag733w3hvq9wcnupgtn0ef8d690svmg6j8vaq0jlyadmq5ru35xnzaf7398gwjawyfd6adn9z4en7s86fqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq06ql2qcqsyk26tw5l6qlt5zlcev436mafhnw2k5qmt8uzcew9q6mlgdg5wdlhr9l3lnl2awk5rw2qdaxw2f4x5r2wpvax8mvf4qewx89ejwwluxnmthtjxtfjcq4tekwyfdfmx9cqe8ksuveseep97lccltp0c82kdg6ydppeya8suvtflxps7tpswr8m45flmccwudkdw9p4jytya5fqgz5u6k2uj6zq4jve32ml48r587uahkcd34r0hcadxv6qp0g87v5dekmqppushjwjm07s8mrq7t027heshc7wmz0u0sjdgg5pn0cx4u8y3gc5ywaapcnrfu7rmenlqxgux5qqy364fwxs5xtgnznef0eaazvcy4c30rvnrtlmv48scxn7j2mhh83cgdjs7mxha62tvaf7480n2vm3pvzdae5x45mk29d9ev" + }, + { + "reason": "proof_omitted_tlvs_not_ascending", + "bech32": "lnp1tqssxfr986kyx3ygqqkvq6alklcslcvfj834l8lyxqkmafkjx57up2cu4qs89ntwss3vgplmd5ycdy83zv9hmmt7ctmltcwnp0va2g0sz5mr0yasyypyhs4rzfj320c8uu8qh2cgwf8xhp0zzluv6c5vad3fwsj8hdyn8qhsgq9rxgj9dzm24ehdy5sp90tluyrjcqltmjnl538etvplrngfhc5tp2punsepqktcekqd5p5f09nzeq86qrlj2rxdcngckuyll5w84cce79qva0p96s9zmynmt672hpqq74p0hdag733w3hvq9wcnupgtn0ef8d690svmg6j8vaq0jlyadmq5ru35xnzaf7398gwjawyfd6adn9z4en7s86fqqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyql6ql2qcqsyk2649dl6qlt5zlcev436mafhnw2k5qmt8uzcew9q6mlgdg5wdlhr9l3lnl2awk5rw2qdaxw2f4x5r2wpvax8mvf4qewx89ejwwluxnmthtjxtfjcq4tekwyfdfmx9cqe8ksuveseep97lccltp0c82kdg6ydppeya8suvtflxps7tpswr8m45flmccwudkdw9p4jytya5fqgz5u6k2uj6zq4jve32ml48r587uahkcd34r0hcadxv6qp0g87v5dekmqppushjwjm07s8mrq7t027heshc7wmz0u0sjdgg5pn0cx4u8y3gc5ywaapcnrfu7rmenlqxgux5qqy364fwxs5xtgnznef0eaazvcy4c30rvnrtlmv48scxn7j2mhh83cgdjs7mxha62tvaf7480n2vm3pvzdae5x45mk29d9ev" + }, + { + "reason": "proof_omitted_tlvs_contains_zero", + "bech32": "lnp1tqssxfr986kyx3ygqqkvq6alklcslcvfj834l8lyxqkmafkjx57up2cu4qs89ntwss3vgplmd5ycdy83zv9hmmt7ctmltcwnp0va2g0sz5mr0yasyypyhs4rzfj320c8uu8qh2cgwf8xhp0zzluv6c5vad3fwsj8hdyn8qhsgq9rxgj9dzm24ehdy5sp90tluyrjcqltmjnl538etvplrngfhc5tp2punsepqktcekqd5p5f09nzeq86qrlj2rxdcngckuyll5w84cce79qva0p96s9zmynmt672hpqq74p0hdag733w3hvq9wcnupgtn0ef8d690svmg6j8vaq0jlyadmq5ru35xnzaf7398gwjawyfd6adn9z4en7s86fqqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyql6ql2quqqzqjetfd6nlgrawstlr9jk8t04x7de26srdvlstr9c5rt0ap4z3eh7uvh7870at466sdegph5eefx56sdfc9n5cld3x5r9ccuhxfemls60dwawgedxtqz40xec3948vchqry76r3nxr8yyhmlrrav9lqa2e4rg35y8yn57r33d8ucxrevxpcvlwk38l0rpm3ke4c5xkg3vnk3ypq2nn2etjtggzkfnx9t075uwslmnk7mpkx5d7lr45engq9aqlej3hxmvqy8jz7f6tdl6qlvvredat6lxzlremvfl37zf4pzsxdlq6hsuj9rzs3mh58zvd8nc00x0uqers6sqqj8249c6zsedzv2099l8h5fnqjhz9udjvd0ldj57rq606ftw7u78ppk2rmv6lhffdn4865a7dfnwy9sfhhxs6knweg45h9s" + }, + { + "reason": "proof_omitted_tlvs_contains_signature_field", + "bech32": "lnp1tqssxfr986kyx3ygqqkvq6alklcslcvfj834l8lyxqkmafkjx57up2cu4qs89ntwss3vgplmd5ycdy83zv9hmmt7ctmltcwnp0va2g0sz5mr0yasyypyhs4rzfj320c8uu8qh2cgwf8xhp0zzluv6c5vad3fwsj8hdyn8qhsgq9rxgj9dzm24ehdy5sp90tluyrjcqltmjnl538etvplrngfhc5tp2punsepqktcekqd5p5f09nzeq86qrlj2rxdcngckuyll5w84cce79qva0p96s9zmynmt672hpqq74p0hdag733w3hvq9wcnupgtn0ef8d690svmg6j8vaq0jlyadmq5ru35xnzaf7398gwjawyfd6adn9z4en7s86fqqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyql6ql2quqsyk26tw5lrlgrawstlr9jk8t04x7de26srdvlstr9c5rt0ap4z3eh7uvh7870at466sdegph5eefx56sdfc9n5cld3x5r9ccuhxfemls60dwawgedxtqz40xec3948vchqry76r3nxr8yyhmlrrav9lqa2e4rg35y8yn57r33d8ucxrevxpcvlwk38l0rpm3ke4c5xkg3vnk3ypq2nn2etjtggzkfnx9t075uwslmnk7mpkx5d7lr45engq9aqlej3hxmvqy8jz7f6tdl6qlvvredat6lxzlremvfl37zf4pzsxdlq6hsuj9rzs3mh58zvd8nc00x0uqers6sqqj8249c6zsedzv2099l8h5fnqjhz9udjvd0ldj57rq606ftw7u78ppk2rmv6lhffdn4865a7dfnwy9sfhhxs6knweg45h9s" + }, + { + "reason": "proof_omitted_tlvs_contains_proof_field", + "bech32": "lnp1tqssxfr986kyx3ygqqkvq6alklcslcvfj834l8lyxqkmafkjx57up2cu4qs89ntwss3vgplmd5ycdy83zv9hmmt7ctmltcwnp0va2g0sz5mr0yasyypyhs4rzfj320c8uu8qh2cgwf8xhp0zzluv6c5vad3fwsj8hdyn8qhsgq9rxgj9dzm24ehdy5sp90tluyrjcqltmjnl538etvplrngfhc5tp2punsepqktcekqd5p5f09nzeq86qrlj2rxdcngckuyll5w84cce79qva0p96s9zmynmt672hpqq74p0hdag733w3hvq9wcnupgtn0ef8d690svmg6j8vaq0jlyadmq5ru35xnzaf7398gwjawyfd6adn9z4en7s86fqqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyql6ql2pyqsyk26tw5l6qlfl5p7hg9l3jetr4h6n0xu4dgpkk0c93ju2p4h7s63gumlwxtlrl8746adgxu5qm6vu5n2dgx5uze6v0kcn2pjuvwtnyualcd8khwhyvkn9sp2hnvugj6nkvtspj0dpcenpnjztal337kzlsw4v635g6zrjf60pcckn7vrpukrqux0htgnlh3sacmv6u2rtygkfmgjqs9fe4v4e95yptyenz4hl2w8g0aem0dsmr2xl0366ve5qz7s0uegmndkqzrep0ya9klaq0kxpuk74a0np03uakylclpy6s3grxlsdtcwfz33ggam6r3xxneu8hn87qv3cdgqqfr42judpgvk3x98jjlnm6yesft3z7xexxhlke20psd8ay4h0w0rssm9pakd0m55ke6na2wlx5ehzzcymmngdtfhv526tjc" + }, + { + "reason": "proof_omitted_tlvs_contains_high_field", + "bech32": "lnp1tqssxfr986kyx3ygqqkvq6alklcslcvfj834l8lyxqkmafkjx57up2cu4qs89ntwss3vgplmd5ycdy83zv9hmmt7ctmltcwnp0va2g0sz5mr0yasyypyhs4rzfj320c8uu8qh2cgwf8xhp0zzluv6c5vad3fwsj8hdyn8qhsgq9rxgj9dzm24ehdy5sp90tluyrjcqltmjnl538etvplrngfhc5tp2punsepqktcekqd5p5f09nzeq86qrlj2rxdcngckuyll5w84cce79qva0p96s9zmynmt672hpqq74p0hdag733w3hvq9wcnupgtn0ef8d690svmg6j8vaq0jlyadmq5ru35xnzaf7398gwjawyfd6adn9z4en7s86fqqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyql6ql2pvqsyk26tw5lamnt9qq06qlt5zlcev436mafhnw2k5qmt8uzcew9q6mlgdg5wdlhr9l3lnl2awk5rw2qdaxw2f4x5r2wpvax8mvf4qewx89ejwwluxnmthtjxtfjcq4tekwyfdfmx9cqe8ksuveseep97lccltp0c82kdg6ydppeya8suvtflxps7tpswr8m45flmccwudkdw9p4jytya5fqgz5u6k2uj6zq4jve32ml48r587uahkcd34r0hcadxv6qp0g87v5dekmqppushjwjm07s8mrq7t027heshc7wmz0u0sjdgg5pn0cx4u8y3gc5ywaapcnrfu7rmenlqxgux5qqy364fwxs5xtgnznef0eaazvcy4c30rvnrtlmv48scxn7j2mhh83cgdjs7mxha62tvaf7480n2vm3pvzdae5x45mk29d9ev" + }, + { + "reason": "proof_omitted_tlvs_contains_included_tlv_field", + "bech32": "lnp1tqssxfr986kyx3ygqqkvq6alklcslcvfj834l8lyxqkmafkjx57up2cu4qs89ntwss3vgplmd5ycdy83zv9hmmt7ctmltcwnp0va2g0sz5mr0yasyypyhs4rzfj320c8uu8qh2cgwf8xhp0zzluv6c5vad3fwsj8hdyn8qhsgq9rxgj9dzm24ehdy5sp90tluyrjcqltmjnl538etvplrngfhc5tp2punsepqktcekqd5p5f09nzeq86qrlj2rxdcngckuyll5w84cce79qva0p96s9zmynmt672hpqq74p0hdag733w3hvq9wcnupgtn0ef8d690svmg6j8vaq0jlyadmq5ru35xnzaf7398gwjawyfd6adn9z4en7s86fqqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyql6ql2quqsykzetfd6nlgrawstlr9jk8t04x7de26srdvlstr9c5rt0ap4z3eh7uvh7870at466sdegph5eefx56sdfc9n5cld3x5r9ccuhxfemls60dwawgedxtqz40xec3948vchqry76r3nxr8yyhmlrrav9lqa2e4rg35y8yn57r33d8ucxrevxpcvlwk38l0rpm3ke4c5xkg3vnk3ypq2nn2etjtggzkfnx9t075uwslmnk7mpkx5d7lr45engq9aqlej3hxmvqy8jz7f6tdl6qlvvredat6lxzlremvfl37zf4pzsxdlq6hsuj9rzs3mh58zvd8nc00x0uqers6sqqj8249c6zsedzv2099l8h5fnqjhz9udjvd0ldj57rq606ftw7u78ppk2rmv6lhffdn4865a7dfnwy9sfhhxs6knweg45h9s" + }, + { + "reason": "proof_omitted_tlvs_not_sequential", + "bech32": "lnp1tqssxfr986kyx3ygqqkvq6alklcslcvfj834l8lyxqkmafkjx57up2cu4qs89ntwss3vgplmd5ycdy83zv9hmmt7ctmltcwnp0va2g0sz5mr0yasyypyhs4rzfj320c8uu8qh2cgwf8xhp0zzluv6c5vad3fwsj8hdyn8qhsgq9rxgj9dzm24ehdy5sp90tluyrjcqltmjnl538etvplrngfhc5tp2punsepqktcekqd5p5f09nzeq86qrlj2rxdcngckuyll5w84cce79qva0p96s9zmynmt672hpqq74p0hdag733w3hvq9wcnupgtn0ef8d690svmg6j8vaq0jlyadmq5ru35xnzaf7398gwjawyfd6adn9z4en7s86fqqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyql6ql2qcqsyktyv4n06qlt5zlcev436mafhnw2k5qmt8uzcew9q6mlgdg5wdlhr9l3lnl2awk5rw2qdaxw2f4x5r2wpvax8mvf4qewx89ejwwluxnmthtjxtfjcq4tekwyfdfmx9cqe8ksuveseep97lccltp0c82kdg6ydppeya8suvtflxps7tpswr8m45flmccwudkdw9p4jytya5fqgz5u6k2uj6zq4jve32ml48r587uahkcd34r0hcadxv6qp0g87v5dekmqppushjwjm07s8mrq7t027heshc7wmz0u0sjdgg5pn0cx4u8y3gc5ywaapcnrfu7rmenlqxgux5qqy364fwxs5xtgnznef0eaazvcy4c30rvnrtlmv48scxn7j2mhh83cgdjs7mxha62tvaf7480n2vm3pvzdae5x45mk29d9ev" + }, + { + "reason": "proof_leaf_hashes_too_few", + "bech32": "lnp1tqssxfr986kyx3ygqqkvq6alklcslcvfj834l8lyxqkmafkjx57up2cu4qs89ntwss3vgplmd5ycdy83zv9hmmt7ctmltcwnp0va2g0sz5mr0yasyypyhs4rzfj320c8uu8qh2cgwf8xhp0zzluv6c5vad3fwsj8hdyn8qhsgq9rxgj9dzm24ehdy5sp90tluyrjcqltmjnl538etvplrngfhc5tp2punsepqktcekqd5p5f09nzeq86qrlj2rxdcngckuyll5w84cce79qva0p96s9zmynmt672hpqq74p0hdag733w3hvq9wcnupgtn0ef8d690svmg6j8vaq0jlyadmq5ru35xnzaf7398gwjawyfd6adn9z4en7s86fqqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyql6ql2qcqsyk26tw5l6qlt5zlcev436mafhnw2k5qmt8uzcew9q6mlgdg5wdlhr9l3lnl2awk5rw2qdaxw2f4x5r2wpvax8mvf4qewx89ejwwluxnmthtjxtfjcq4tekwyfdfmx9cqe8ksuveseep97lccltp0c82kdg6ydppeya8suvtflxps7tpswr8m45flmccwudkdw9p4jytya5fqgz5u6k2uj6zq4jve32ml48r587uahkcd34r0hcadxv6qp0g87v5dekmqppushjwjm07s8mzq7t027heshc7wmz0u0sjdgg5pn0cx4u8y3gc5ywaapcnrfu7rmenlqxgux5qqy364fwxs5xtgnznef0eaazvcy4c30rvnrtlmv48scxs" + }, + { + "reason": "proof_leaf_hashes_too_many", + "bech32": "lnp1tqssxfr986kyx3ygqqkvq6alklcslcvfj834l8lyxqkmafkjx57up2cu4qs89ntwss3vgplmd5ycdy83zv9hmmt7ctmltcwnp0va2g0sz5mr0yasyypyhs4rzfj320c8uu8qh2cgwf8xhp0zzluv6c5vad3fwsj8hdyn8qhsgq9rxgj9dzm24ehdy5sp90tluyrjcqltmjnl538etvplrngfhc5tp2punsepqktcekqd5p5f09nzeq86qrlj2rxdcngckuyll5w84cce79qva0p96s9zmynmt672hpqq74p0hdag733w3hvq9wcnupgtn0ef8d690svmg6j8vaq0jlyadmq5ru35xnzaf7398gwjawyfd6adn9z4en7s86fqqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyql6ql2qcqsyk26tw5l6qlt5zlcev436mafhnw2k5qmt8uzcew9q6mlgdg5wdlhr9l3lnl2awk5rw2qdaxw2f4x5r2wpvax8mvf4qewx89ejwwluxnmthtjxtfjcq4tekwyfdfmx9cqe8ksuveseep97lccltp0c82kdg6ydppeya8suvtflxps7tpswr8m45flmccwudkdw9p4jytya5fqgz5u6k2uj6zq4jve32ml48r587uahkcd34r0hcadxv6qp0g87v5dekmqppushjwjm07s8myq7t027heshc7wmz0u0sjdgg5pn0cx4u8y3gc5ywaapcnrfu7rmenlqxgux5qqy364fwxs5xtgnznef0eaazvcy4c30rvnrtlmv48scxn7j2mhh83cgdjs7mxha62tvaf7480n2vm3pvzdae5x45mk29d9evqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq" + }, + { + "reason": "proof_missing_hashes_too_few", + "bech32": "lnp1tqssxfr986kyx3ygqqkvq6alklcslcvfj834l8lyxqkmafkjx57up2cu4qs89ntwss3vgplmd5ycdy83zv9hmmt7ctmltcwnp0va2g0sz5mr0yasyypyhs4rzfj320c8uu8qh2cgwf8xhp0zzluv6c5vad3fwsj8hdyn8qhsgq9rxgj9dzm24ehdy5sp90tluyrjcqltmjnl538etvplrngfhc5tp2punsepqktcekqd5p5f09nzeq86qrlj2rxdcngckuyll5w84cce79qva0p96s9zmynmt672hpqq74p0hdag733w3hvq9wcnupgtn0ef8d690svmg6j8vaq0jlyadmq5ru35xnzaf7398gwjawyfd6adn9z4en7s86fqqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyql6ql2qcqsyk26tw5l6qltszlcev436mafhnw2k5qmt8uzcew9q6mlgdg5wdlhr9l3lnl2awk5rw2qdaxw2f4x5r2wpvax8mvf4qewx89ejwwluxnmthtjxtfjcq4tekwyfdfmx9cqe8ksuveseep97lccltp0c82kdg6ydppeya8suvtflxps7tpswr8m45flmccwudkdw9p4jytya5fqgz5u6k2uj6zq4j0aq0kxpuk74a0np03uakylclpy6s3grxlsdtcwfz33ggam6r3xxneu8hn87qv3cdgqqfr42judpgvk3x98jjlnm6yesft3z7xexxhlke20psd8ay4h0w0rssm9pakd0m55ke6na2wlx5ehzzcymmngdtfhv526tjc" + }, + { + "reason": "proof_missing_hashes_too_many", + "bech32": "lnp1tqssxfr986kyx3ygqqkvq6alklcslcvfj834l8lyxqkmafkjx57up2cu4qs89ntwss3vgplmd5ycdy83zv9hmmt7ctmltcwnp0va2g0sz5mr0yasyypyhs4rzfj320c8uu8qh2cgwf8xhp0zzluv6c5vad3fwsj8hdyn8qhsgq9rxgj9dzm24ehdy5sp90tluyrjcqltmjnl538etvplrngfhc5tp2punsepqktcekqd5p5f09nzeq86qrlj2rxdcngckuyll5w84cce79qva0p96s9zmynmt672hpqq74p0hdag733w3hvq9wcnupgtn0ef8d690svmg6j8vaq0jlyadmq5ru35xnzaf7398gwjawyfd6adn9z4en7s86fqqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyql6ql2qcqsyk26tw5l6qltczlcev436mafhnw2k5qmt8uzcew9q6mlgdg5wdlhr9l3lnl2awk5rw2qdaxw2f4x5r2wpvax8mvf4qewx89ejwwluxnmthtjxtfjcq4tekwyfdfmx9cqe8ksuveseep97lccltp0c82kdg6ydppeya8suvtflxps7tpswr8m45flmccwudkdw9p4jytya5fqgz5u6k2uj6zq4jve32ml48r587uahkcd34r0hcadxv6qp0g87v5dekmqppushjwjmvqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplgra3s09h40tuctu08d3878cfx5y2qehur27rjg5v2z8w7suf3570pauelsrywr2qqzga25hrg2r95f3fu5hu773xvz2ugh3kf34lak2ncvrflf9dmmncuyxeg0dnt7a99kw5l2nhe4xdcskpx7u6r26dm9zkjuk" + }, + { + "reason": "wrong_invoice_signature", + "bech32": "lnp1tqssxfr986kyx3ygqqkvq6alklcslcvfj834l8lyxqkmafkjx57up2cu4qs89ntwss3vgplmd5ycdy83zv9hmmt7ctmltcwnp0va2g0sz5mr0yasyypyhs4rzfj320c8uu8qh2cgwf8xhp0zzluv6c5vad3fwsj8hdyn8qhsgq9nxgj9dzm24ehdy5sp90tluyrjcqltmjnl538etvplrngfhc5tp2punsepqktcekqd5p5f09nzeq86qrlj2rxdcngckuyll5w84cce79qva0p96s9zmynmt672hpqq74p0hdag733w3hvq9wcnupgtn0ef8d690svmg6j8vaq0jlyadmq5ru35xnzaf7398gwjawyfd6adn9z4en7s86fqqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyql6ql2qcqsyk26tw5l6qlt5zlcev436mafhnw2k5qmt8uzcew9q6mlgdg5wdlhr9l3lnl2awk5rw2qdaxw2f4x5r2wpvax8mvf4qewx89ejwwluxnmthtjxtfjcq4tekwyfdfmx9cqe8ksuveseep97lccltp0c82kdg6ydppeya8suvtflxps7tpswr8m45flmccwudkdw9p4jytya5fqgz5u6k2uj6zq4jve32ml48r587uahkcd34r0hcadxv6qp0g87v5dekmqppushjwjm07s8mrq7t027heshc7wmz0u0sjdgg5pn0cx4u8y3gc5ywaapcnrfu7rmenlqxgux5qqy364fwxs5xtgnznef0eaazvcy4c30rvnrtlmv48scxn7j2mhh83cgdjs7mxha62tvaf7480n2vm3pvzdae5x45mk29d9ev" + }, + { + "reason": "wrong_proof_signature", + "bech32": "lnp1tqssxfr986kyx3ygqqkvq6alklcslcvfj834l8lyxqkmafkjx57up2cu4qs89ntwss3vgplmd5ycdy83zv9hmmt7ctmltcwnp0va2g0sz5mr0yasyypyhs4rzfj320c8uu8qh2cgwf8xhp0zzluv6c5vad3fwsj8hdyn8qhsgq9rxgj9dzm24ehdy5sp90tluyrjcqltmjnl538etvplrngfhc5tp2punsepqktcekqd5p5f09nzeq86qrlj2rxdcngckuyll5w84cce79qvl0p96s9zmynmt672hpqq74p0hdag733w3hvq9wcnupgtn0ef8d690svmg6j8vaq0jlyadmq5ru35xnzaf7398gwjawyfd6adn9z4en7s86fqqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyql6ql2qcqsyk26tw5l6qlt5zlcev436mafhnw2k5qmt8uzcew9q6mlgdg5wdlhr9l3lnl2awk5rw2qdaxw2f4x5r2wpvax8mvf4qewx89ejwwluxnmthtjxtfjcq4tekwyfdfmx9cqe8ksuveseep97lccltp0c82kdg6ydppeya8suvtflxps7tpswr8m45flmccwudkdw9p4jytya5fqgz5u6k2uj6zq4jve32ml48r587uahkcd34r0hcadxv6qp0g87v5dekmqppushjwjm07s8mrq7t027heshc7wmz0u0sjdgg5pn0cx4u8y3gc5ywaapcnrfu7rmenlqxgux5qqy364fwxs5xtgnznef0eaazvcy4c30rvnrtlmv48scxn7j2mhh83cgdjs7mxha62tvaf7480n2vm3pvzdae5x45mk29d9ev" + }, + { + "reason": "contains_invreq_metadata", + "bech32": "lnp1qqq5ykppqvjx204vgdzgsqpvcp4mldl3plscny0rt707gvpdh6ndydfacz43e2pqwtxkappzcsrlkmgfs6g0zyct0hkhashh7hsaxz7e65slq9fkx7fmqggzf0p2xyn9z5ls0ecwpw4ssujwdwz7y9lce43ge6mzjapy0w6fxwp0qsq2xv3y269k4tnw6ffqz27hlcg89sp7hh98lfz0jkcr78xsn03gkz5re8pjzpvh3nvqmgrgj7tx9jq05q8ly5xvm3x33dcfllgu0t33nu2qe67zt4q29kf8kh4u4wzqpa2zlwm63arzarwcq2a38czshxljjwm52lqek34ywe6ql97f6mkpg8ergdx96naz2wsa96ugjm46mx29tn8aq05jqqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpl5p75pspqfv45kafl5p7hg9l3jetr4h6n0xu4dgpkk0c93ju2p4h7s63gumlwxtlrl8746adgxu5qm6vu5n2dgx5uze6v0kcn2pjuvwtnyualcd8khwhyvkn9sp2hnvugj6nkvtspj0dpcenpnjztal337kzlsw4v635g6zrjf60pcckn7vrpukrqux0htgnlh3sacmv6u2rtygkfmgjqs9fe4v4e95yptyenz4hl2w8g0aem0dsmr2xl0366ve5qz7s0uegmndkqzrep0ya9klaq0kxpuk74a0np03uakylclpy6s3grxlsdtcwfz33ggam6r3xxneu8hn87qv3cdgqqfr42judpgvk3x98jjlnm6yesft3z7xexxhlke20psd8ay4h0w0rssm9pakd0m55ke6na2wlx5ehzzcymmngdtfhv526tjc" + } + ] +}