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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions example/live_probe.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Read-only live probe: proves the Dart LspClient talks to the real stagenet LSP.
// No faucet/open (rate-limited + consumes test SOQ) — just info/health/towers.
import 'package:soqushield_sdk/soqushield_sdk.dart';

Future<void> main() async {
final ln = SoqLightning(baseUrl: 'https://lsp.soqu.org');
final info = await ln.info();
print('info: peer=${info['peer_name']} net=${info['network']} '
'maxChannelSat=${info['max_channel_sat']} proto=${info['protocol_version']}');
try {
final h = await ln.health();
print('health: $h');
} catch (e) {
print('health: $e');
}
final t = await ln.towerStatus();
print('towers: ${t.reachable}/${t.towerCount} reachable, available=${t.available}');
for (final tw in t.towers) {
print(' - ${tw.name}: available=${tw.available} '
'watched=${tw.status?.watchedChannels}');
}
}
21 changes: 21 additions & 0 deletions lib/eltoo.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) 2026 Soqucoin Labs Inc.
// Distributed under the MIT software license.
//
// eltoo.dart — public entrypoint for the Opt3 self-custodial eLTOO crypto layer.
//
// Import alongside `package:soqushield_sdk/lightning.dart` to activate Opt3:
//
// import 'package:soqushield_sdk/lightning.dart';
// import 'package:soqushield_sdk/eltoo.dart';
//
// final ln = SoqLightning(
// baseUrl: 'https://lsp.soqu.org',
// txBuilder: DilithiumEltooBuilder(EltooBuilderOpts(...)), // self-custodial — same facade
// );
//
// Kept separate from lightning.dart so the Opt2 facade namespace stays free of the low-level
// consensus types (Tx/serializeTx/toHex/…). The builder implements the same UpdateTxBuilder
// seam, so the two entrypoints compose without change.
library;

export 'src/lightning/eltoo/eltoo.dart';
14 changes: 14 additions & 0 deletions lib/lightning.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) 2026 Soqucoin Labs Inc.
// Distributed under the MIT software license.
//
// lightning.dart — focused public entrypoint for the Soqucoin Lightning (eLTOO) layer.
//
// Import this (instead of the full `soqushield_sdk.dart` barrel) when you only need the
// Lightning SDK and want to avoid pulling the package's Dilithium FFI / wallet / RPC
// surface. The Lightning layer is pure Dart over `dio` — no native dependencies.
//
// import 'package:soqushield_sdk/lightning.dart';
// final ln = SoqLightning(baseUrl: 'https://lsp.soqu.org');
library;

export 'src/lightning/lightning.dart';
5 changes: 2 additions & 3 deletions lib/soqushield_sdk.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
/// - **Address** — Soqucoin Bech32m address encoding/decoding with PQ
/// witness program support.
/// - **RPC** — Typed client for the Soqucoin node JSON-RPC interface.
/// - **Vault** — pSOQ↔SOQ bridge vault operations with ML-DSA-44 signing.
///
/// ## Quick Start
///
Expand Down Expand Up @@ -63,8 +62,8 @@ export 'src/address/address_codec.dart';
export 'src/rpc/rpc_client.dart';
export 'src/rpc/rpc_models.dart' hide TxOutput;

// Vault bridge
export 'src/vault/vault_client.dart';
// Lightning (eLTOO, quantum-safe) — LSP client + SoqLightning facade + swappable TX builder
export 'src/lightning/lightning.dart';

// High-level wallet
export 'src/wallet.dart';
37 changes: 37 additions & 0 deletions lib/src/lightning/eltoo/ctv.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) 2026 Soqucoin Labs Inc.
// Distributed under the MIT software license.
//
// ctv.dart — Opt3 P3: BIP119 CheckTemplateVerify template hash.
//
// Byte-for-byte port of channel.ts ctvHash / serTxOutCtv. The eLTOO settlement TX is committed
// by this hash, so it must agree with the node's byte-less C++ CTV hashing exactly.
//
// Two easy-to-conflate subtleties, both pinned to node vectors (test/eltoo/ctv_test.dart):
// • serTxOutCtv uses an **LE32** script-length prefix — NOT the consensus compactSize.
// • ctvHash is a **single** SHA256 — NOT sha256d.
// • scriptSigsHash is included ONLY if some input has a non-empty scriptSig; segwit eLTOO
// spends have empty scriptSigs (our TxIn carries none), so it is omitted.

import 'dart:typed_data';

import 'serialization.dart';

/// CTV output serializer (Phase-4 byte-less): value(LE64) ‖ scriptlen(LE32) ‖ script.
/// Distinct from [serTxOut] (consensus), which uses compactSize for the length.
Uint8List serTxOutCtv(TxOut o) =>
concatBytes([le64(o.value), le32(o.scriptPubKey.length), o.scriptPubKey]);

/// BIP119 DefaultCheckTemplateVerifyHash (single SHA256). [nIn] is the input index this
/// template is bound to. scriptSigsHash is omitted (segwit spends have empty scriptSigs).
Uint8List ctvHash(Tx tx, int nIn) {
final parts = <Uint8List>[le32(tx.version), le32(tx.locktime)];

// scriptSig is always empty for segwit eLTOO spends → scriptSigsHash branch omitted.
parts.add(le32(tx.vin.length));
parts.add(sha256Once(concatBytes(tx.vin.map((i) => le32(i.sequence)).toList())));
parts.add(le32(tx.vout.length));
parts.add(sha256Once(concatBytes(tx.vout.map(serTxOutCtv).toList())));
parts.add(le32(nIn));

return sha256Once(concatBytes(parts)); // single SHA256, NOT double
}
17 changes: 17 additions & 0 deletions lib/src/lightning/eltoo/eltoo.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) 2026 Soqucoin Labs Inc.
// Distributed under the MIT software license.
//
// eltoo.dart — barrel for the Opt3 self-custodial eLTOO crypto layer.
//
// The real `DilithiumEltooBuilder` (and the primitives it composes) that swaps into the
// SoqLightning facade via the UpdateTxBuilder seam to make payments self-custodial. The crypto
// primitives (serialization, APO/BIP143 sighash, CTV, keyhash funding) are individually
// node-pinned (P1–P4); the builder's tx-graph assembly is pending stagenet validation (P6).
library;

export 'serialization.dart';
export 'script.dart';
export 'sighash.dart';
export 'ctv.dart';
export 'keyhash.dart';
export 'eltoo_builder.dart';
133 changes: 133 additions & 0 deletions lib/src/lightning/eltoo/eltoo_builder.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Copyright (c) 2026 Soqucoin Labs Inc.
// Distributed under the MIT software license.
//
// eltoo_builder.dart — Opt3 P5: the real DilithiumEltooBuilder.
//
// Byte-for-byte port of channel.ts:570-643. This is the piece that flips Opt2 → Opt3: it
// implements the same UpdateTxBuilder seam the PlaceholderTxBuilder satisfies, so dropping it
// into SoqLightning(txBuilder: ...) makes payments self-custodial with ZERO facade/UI change.
//
// For each state transition it builds the canonical eLTOO tx graph (update output re-commits the
// state-N B1 script; settlement spends it via the CSV branch paying both balances), signs the
// update input with SIGHASH_ANYPREVOUTANYSCRIPT (0x42, rebindable), and returns the opaque
// transport hex + the CTV hash committing the settlement payout.
//
// The update output now carries the B1 eLTOO script (eltooUpdateScriptV6) — IF/CLTV-ratchet +
// keyhash-2-of-2 — which EXECUTES on V6 under SCRIPT_VERIFY_V6_CONTROLFLOW and is node-pinned
// (b1_script_vectors). The keyhash sigs are produced via signApoWitness/0x42 (the opcode reads the
// trailing hashType and CheckSig's over the sighash — the correct primitive for keyhash, unlike
// CSFS). ⚠️ REMAINING (step 4): the self-custodial BROADCAST witness assembly (the IF/ELSE selector
// + keyhash 2-of-2 satisfaction + trailing v6 pubkey) and on-chain stagenet acceptance. build()
// still emits OPAQUE transport hex for the LSP co-sign flow; do not broadcast until step 4 lands.

import 'dart:typed_data';

import '../update_tx_builder.dart';
import 'ctv.dart';
import 'keyhash.dart';
import 'script.dart';
import 'serialization.dart';
import 'sighash.dart';

/// Inputs for the eLTOO builder. [funding] is the channel funding (or genesis) outpoint;
/// [fundingAmountSat] is the capacity (the input amount the update spends). [initiatorPub]/
/// [peerPub] are the two ML-DSA-44 pubkeys; [initiatorScriptPubKey]/[peerScriptPubKey] are the
/// settlement payout scripts. [secretKey] is OUR ML-DSA-44 key (signs the update with 0x42).
/// [sign] injects the ML-DSA signer (e.g. DilithiumNative.instance.sign).
class EltooBuilderOpts {
final OutPoint funding;
final BigInt fundingAmountSat;
final Uint8List initiatorPub;
final Uint8List peerPub;
final Uint8List initiatorScriptPubKey;
final Uint8List peerScriptPubKey;
final Uint8List secretKey;
final MlDsaSign sign;
final int settlementCsv;

const EltooBuilderOpts({
required this.funding,
required this.fundingAmountSat,
required this.initiatorPub,
required this.peerPub,
required this.initiatorScriptPubKey,
required this.peerScriptPubKey,
required this.secretKey,
required this.sign,
this.settlementCsv = 288,
});
}

/// Real self-custodial eLTOO TX builder (Opt3). Plugs into [SoqLightning] via [UpdateTxBuilder].
class DilithiumEltooBuilder implements UpdateTxBuilder {
final EltooBuilderOpts o;
const DilithiumEltooBuilder(this.o);

/// The update TX for state [stateNum]: spends the funding output into a v6 output that
/// re-commits the state-N eLTOO script. locktime = stateNum+1 satisfies the prior script's
/// IF-branch CLTV (newer state supersedes older — this ratchet EXECUTES on V6 under
/// SCRIPT_VERIFY_V6_CONTROLFLOW; see DL-V6-CONTROLFLOW-RESTORE).
Tx buildUpdateTx(int stateNum) {
final ws = eltooUpdateScriptV6(stateNum, o.initiatorPub, o.peerPub, settlementCsv: o.settlementCsv);
return Tx(
version: 2,
locktime: stateNum + 1,
vin: [TxIn(prevout: o.funding, sequence: 0, scriptPubKey: Uint8List(0))],
vout: [TxOut(o.fundingAmountSat, p2wshV6(ws))],
);
}

/// The settlement TX spending the update output (n=0) via the relative-CSV ELSE branch,
/// paying both balances out to their settlement scripts.
Tx buildSettlementTx(
Uint8List updateTxid, BigInt initiatorBalanceSat, BigInt peerBalanceSat) {
return Tx(
version: 2,
locktime: 0,
vin: [TxIn(prevout: OutPoint(updateTxid, 0), sequence: o.settlementCsv, scriptPubKey: Uint8List(0))],
vout: [
TxOut(initiatorBalanceSat, o.initiatorScriptPubKey),
TxOut(peerBalanceSat, o.peerScriptPubKey),
],
);
}

@override
Future<UpdateTx> build(UpdateContext ctx) async {
final stateNum = ctx.nextStateIndex;
final updateTx = buildUpdateTx(stateNum);

// Sign the update input with 0x42 (empty scriptCode — rebindable across states).
final witnessSig = signApoWitness(
Uint8List(0), updateTx, 0, sighashAnyprevoutAnyscript, o.fundingAmountSat, o.secretKey, o.sign);

// Attach the sig as the input's scriptSig surrogate for opaque transport (broadcast uses
// the witness). The txid commits the UNSIGNED form, so compute it before attaching.
final updateTxid = txidInternal(updateTx);
final signedUpdate = Tx(
version: updateTx.version,
locktime: updateTx.locktime,
vin: [
TxIn(
prevout: updateTx.vin[0].prevout,
sequence: updateTx.vin[0].sequence,
scriptPubKey: Uint8List(0),
scriptSig: witnessSig,
),
],
vout: updateTx.vout,
);

final settlementTx = buildSettlementTx(
updateTxid,
BigInt.from(ctx.nextInitiatorBalanceSat),
BigInt.from(ctx.nextPeerBalanceSat),
);

return UpdateTx(
updateTxHex: toHex(serializeTxLegacy(signedUpdate)),
settlementTxHex: toHex(serializeTxLegacy(settlementTx)),
ctvHash: toHex(ctvHash(settlementTx, 0)),
);
}
}
Loading
Loading