Skip to content

opt3: self-custodial eLTOO crypto layer (P0–P5) + B1 V6 scripts/witnesses - #3

Merged
odenrider merged 11 commits into
mainfrom
feat/opt3-eltoo-builder
Jun 25, 2026
Merged

odenrider merged 11 commits into
mainfrom
feat/opt3-eltoo-builder

Conversation

@odenrider

Copy link
Copy Markdown
Contributor

The Dart Opt3 eLTOO crypto layer that swaps into the SoqLightning facade via the UpdateTxBuilder seam.

  • P0–P5 (node-pinned): serialization, APO/BIP143 sighash, CTV, key-committed 2-of-2 funding (OP_CHECKDILITHIUMKEYHASH), and the DilithiumEltooBuilder.
  • B1 (this work): ports the script layer to the V6 forms that actually execute under SCRIPT_VERIFY_V6_CONTROLFLOW (soqucoin#12) — eltooUpdateScriptV6/htlcScriptV6 (IF/CLTV-ratchet + keyhash-2-of-2) replacing the dead non-executing spec-ref; B1 branch witness assemblers (update/settlement/HTLC success+timeout) matching the node-accepted satisfaction layouts.
  • Script + witness layouts byte-pinned to the node (b1_script_test.dart ↔ lightning_script_tests/b1_script_vectors). Eltoo suite 35/35, analyze clean.

Pairs with soqucoin#12. Broadcast/stagenet e2e is the remaining (post-flag-activation) step.

🤖 Generated with Claude Code

odenrider and others added 11 commits June 16, 2026 19:18
TxOutput.isUsdsoq now recognises a v7 holding (OP_7<32>) by witness version
(isV7UsdsoqHolding || assetType==1) + static isV7UsdsoqHoldingScript helper, so v7
holdings classify as USDSOQ independent of the byte-derived RPC assetType field — which
will be absent after the Phase-4 byte removal. First test in this package: dart test
green (4 groups). CTxOut migration Phase 3.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Port the node-proven TypeScript soq-lightning-sdk into soqushield_sdk so Opt2
(LSP-trusted, ship-sooner) and Opt3 (self-custodial DilithiumEltoo, flagship)
are the SAME code path — Casey's 2026-06-24 one-path decision.

  lsp_models       — request/response types (grounded in rest.go / client.ts)
  lsp_client       — LspClient REST transport over Dio (12 endpoints, LspError)
  update_tx_builder — the SWAPPABLE seam: PlaceholderTxBuilder (Opt2) today;
                      DilithiumEltooBuilder (Opt3) swaps in with zero facade change
  soq_lightning    — SoqLightning facade (fundAndOpen/openChannel/pay/close/
                     towers), preserving the live-verified invariants: faucet-cap
                     clamp, balance conservation, monotonic state advance,
                     dropped-close resilience

14 unit tests against a stateful in-memory fake LSP (full lifecycle + every
invariant branch + the placeholder/real builder seam). Full package: dart
analyze clean, 18/18 tests green.

Opt2 only — no real on-chain crypto yet; placeholders are visibly placeholders
so nothing downstream mistakes LSP-trusted custody for self-custody.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Proves the Dart LspClient/facade talks to the real stagenet LSP at
lsp.soqu.org (info/health/towers). Live run confirms: peer L2SOQ-PEER,
eltoo-v1, max_channel_sat 100000000, dual towers 2/2 reachable — matching
the TS SDK's live findings. Read-only by design (no faucet: rate-limited +
consumes test SOQ).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lets consumers (the SoquShield app) depend on the Lightning layer via
package:soqushield_sdk/lightning.dart without pulling the package's native
Dilithium FFI / wallet / RPC surface. Lightning is pure Dart over dio.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Start of the Opt3 DilithiumEltooBuilder MVP sprint (per SOQUSHIELD_OPT3_ELTOO_PORT_SCOPE).

P0: copied the node-proven golden vectors (onchain_vectors.json, emitted by soq-signer's
txbuilder) into the Dart SDK test tree.

P1: lib/src/lightning/eltoo/serialization.dart — byte-for-byte port of onchain.ts's byte
layer: u8/le16/le32/le64(BigInt)/concat, compactSize, sha256d, hex, the Tx/TxIn/TxOut/OutPoint
model, serTxOut (Phase-4 byte-less CTxOut — NO nVisibility/nAssetType), serializeTx (BIP144
witness), and txid.

Pinned byte-exact to the node vectors: serializeTxHex == rawTxHex and txid == V.txid, plus
varint/le64 boundary checks and a Phase-4 byte-less serTxOut assertion. 8/8 new tests; full
SDK suite 25/25; dart analyze clean.

Strategy followed: the #1-risk component (the byte-less CTxOut serializer — the recurring
root cause) is pinned to a node vector on day one. Next: P2 sighash (APO 0x42/0x41 + BIP143;
the vector's sighashHex is the pin).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lib/src/lightning/eltoo/sighash.dart — byte-for-byte port of channel.ts apoSighash + sighashAll:
APO 0x42 (ANYPREVOUTANYSCRIPT, empty scriptField) + 0x41 (ANYPREVOUT, commits scriptCode),
EMPTY_OUTPOINT/zeroed prevout-sequence-hashPrevouts-hashSequence, BIP143 SIGHASH_ALL, plus
signApoWitness/signAllWitness (sig ‖ hashType) over an injected ML-DSA signer.

Pinned byte-exact to node vectors:
  • APO 0x42 / 0x41 digests ← apo_ctv_vectors.json (b1_dump_vectors, Phase-4 byte-less)
  • BIP143 SIGHASH_ALL digest ← onchain_vectors.json sighashHex
Invariance (channel.test.mjs): 0x42 ignores prevout (rebindable), 0x42≠0x41 (scriptCode
commitment), 0x42 commits outputs. 7/7 new tests; full SDK suite green; analyze clean.

This is the eLTOO core — the APO-0x42 digest the update TX is signed over. Next: P3 CTV
(ctv_hash vector already in apo_ctv_vectors.json).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lib/src/lightning/eltoo/ctv.dart — byte-for-byte port of channel.ts ctvHash / serTxOutCtv.
Two pinned subtleties: serTxOutCtv uses an LE32 script-length prefix (NOT consensus
compactSize), and ctvHash is a SINGLE SHA256 (NOT sha256d); scriptSigsHash omitted for segwit.

Pinned to apo_ctv_vectors.json ctv_hash; tamper-sensitivity + LE32-length assertions. 3/3 new
tests; full SDK suite green; analyze clean.

P0-P3 complete: the full consensus-hashing foundation (serialization, APO/BIP143 sighash, CTV)
is node-pinned. Next: P4 keyhash 2-of-2 funding (keyhash.test.mjs vectors).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ASH)

lib/src/lightning/eltoo/script.dart — OP constants, pushData, scriptNum, p2wsh/p2wshV6,
dilithiumWitnessPubKey, p2wshV6Witness (port of channel.ts:52-142).

lib/src/lightning/eltoo/keyhash.dart — the SOQ-COV-013 funding suite (port of channel.ts:349-504):
dilithiumKeyHash (single SHA256 of the raw 1312-byte key), 1/2-of-2 committed scripts, v6
witnesses, signForKeyhash dispatch, keyhashFunding2of2, partial sign + ORDER-ROBUST combine,
the single-operator convenience.

Byte-exact construction pins (keyhash = single SHA256, 69-byte witnessScript offsets, v6 funding
scriptPubKey, [sigA,pubA,sigB,pubB,ws,trailing] layout, order-invariant combine, wrong-party +
mixed-hashType + substitution rejection). 9/9 new tests with deterministic pubkey patterns + a
deterministic mock signer; full SDK suite green; analyze clean.

NOT pinned here (separate workstream): the node committed_sdk_crossvector byte-match for keys
from seeds 0x11/0x22 — needs @noble↔pqcrystals ML-DSA keygen interop + the native FFI (not
loadable in pure dart test). Real sig verification rides on that.

P0-P4 complete: serialization + APO/BIP143 sighash + CTV + keyhash funding, all node-pinned.
Next: P5 eltoo update/settlement scripts + DilithiumEltooBuilder, then P6 stagenet e2e.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lib/src/lightning/eltoo/eltoo_builder.dart — byte-for-byte port of channel.ts's
DilithiumEltooBuilder. Implements the same UpdateTxBuilder seam PlaceholderTxBuilder satisfies,
so dropping it into SoqLightning(txBuilder: ...) makes payments self-custodial with ZERO
facade/UI change. Per state: build the canonical eLTOO graph (update output re-commits the
state-N script; settlement spends it via the CSV branch paying both balances), sign the update
input with SIGHASH_ANYPREVOUTANYSCRIPT (0x42), return opaque transport hex + the settlement CTV
hash.

Supporting:
  - serialization.dart: TxIn.scriptSig + serializeTxLegacy (non-witness, includes scriptSig —
    the LSP's opaque update_tx_hex) + txidInternal (the prevout the settlement spends).
  - script.dart: eltooUpdateScript (spec-ref; V6 reality = keyhash+CSFS/CTV, flagged).
  - lib/eltoo.dart: public entrypoint (import alongside lightning.dart to activate Opt3), kept
    separate so the Opt2 facade namespace stays free of the low-level consensus types.

6/6 P5 tests: graph construction (locktime=state+1, v6 output, CSV settlement, 2 balances),
drop-in UpdateTxBuilder, build() emits real hex + a ctv_hash consistent with the derived
settlement, deterministic + balance-sensitive. Full SDK suite green; analyze clean.

⚠️ NOT YET NODE-VALIDATED: the composed primitives are node-pinned (P1–P4), but the tx-graph
assembly is validated on stagenet (P6), not against an offline vector. Do not broadcast builder
output to mainnet until P6 passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ode-pin

Replaces the dead non-executing eltooUpdateScript (IF/CLTV/CHECKSIG inline-pubkey
spec-ref) with the B1 forms in keyhash.dart:
  - eltooUpdateScriptV6 / ...FromKeyhashes: IF <N+1> CLTV DROP keyhash-2of2 /
    ELSE <csv> CSV DROP keyhash-2of2 ENDIF (ratchet + settlement execute on V6)
  - htlcScriptV6 / ...FromKeyhashes: IF SHA256 <H> EQUALVERIFY keyhash / ELSE
    CLTV keyhash ENDIF
  - dilithiumKeyhash2of2ScriptFromHashes (32-byte keyhash body, reused)
Rewires DilithiumEltooBuilder.buildUpdateTx to emit the B1 script; refreshes
docstrings (script executes/node-pinned; broadcast witness assembly = step 4).
New test/eltoo/b1_script_test.dart byte-matches the node b1_script_vectors.
Full eltoo suite 34/34, analyze clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…uts)

Adds the v6 satisfaction-stack builders for spending the B1 outputs:
  - eltooUpdateBranchWitness (IF/supersession, truthy selector)
  - eltooSettlementBranchWitness (ELSE/close, empty selector)
  - htlcSuccessWitness (preimage + payee, truthy) / htlcTimeoutWitness (payer, empty)
Each = [2-of-2 keyhash satisfaction, selector, witnessScript, 0x00||trailingPub] —
the exact layout the node accepts (lightning_script_tests eltoo_v6_ratchet_target /
htlc_v6_target). Structural test pins selector position/value, script, trailer.
eltoo suite 35/35, analyze clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@odenrider
odenrider merged commit 0567e32 into main Jun 25, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant