Commit Graph
168 Commits
Author SHA1 Message Date
zeekay 5d9ae0169e mpc: drop Ringtail identifiers — wire API renamed to Corona
The two MPC files that still carried Ringtail names:
  cmd/mpcd/main.go        — comment ref to luxfi/ringtail
  pkg/api/handlers_kms.go — ValidatorKeySet wire schema

handlers_kms.go renames:
  ValidatorKeySet.RingtailWalletID   → CoronaWalletID
  ValidatorKeySet.RingtailPublicKey  → CoronaPublicKey
  JSON tags ringtailWalletId/PubKey  → coronaWalletId/PubKey
  local `ringtailWalletID` and the per-validator suffix
  "validator-<id>-ringtail"          → "validator-<id>-corona"
  router switch `case "ringtail":`   → `case "corona":`

cmd/mpcd/main.go: comment "luxfi/corona and luxfi/ringtail" was
collapsed to just "luxfi/corona" — the dispatcher's not-yet-impl
stub now references only the live R-LWE library.

Wire break, no compat layer (forwards-perfection policy):
KMS callers must re-issue requests with the new field names.

Build: go build ./... PASS.
Tests: pkg/api PASS 1.374s.
2026-05-14 09:34:28 -07:00
zeekay b3b083c6d5 mpcd: gate embedded threshold dispatcher with bearer-token + loopback bind
Red round 5 finding B1. The embedded dispatcher on --threshold-listen
was an anonymous local signing oracle:

  1. Any process on the host could POST `{"method":"frost.sign",...}`
     and obtain signatures with no auth gate (only the API on :9800
     enforced MPC_INTERNAL_API_KEY).
  2. An operator typo `MPC_THRESHOLD_LISTEN=0.0.0.0:7300` exposed it
     cluster-wide.
  3. Any SSRF inside mpcd pivoted into the dispatcher.

Fix mirrors the existing internal-API auth pattern (cmd/mpcd/main.go
line ~456):

  - Auth token: read MPC_THRESHOLD_AUTH_TOKEN; if unset, derive a
    deterministic per-cluster token from the node Ed25519 seed
    (SHA-256(seed || "mpc-threshold-rpc") hex). All nodes share the
    same key without extra config; log warns when derivation is used.
    Server.SetAuthToken installs the gate.
  - Loopback bind: refuse non-loopback addr unless
    MPC_THRESHOLD_ALLOW_REMOTE=1 — operators must explicitly
    acknowledge the exposure. Bare `:port` (= 0.0.0.0) is NOT loopback.

Also picks up luxfi/threshold v1.6.10 which removes the pulsar/corona
in-memory-token surface (Red HIGH B2) — pulsar.*/corona.* now return a
typed "not yet implemented" error.

Tests:
  - TestIsThresholdLoopback locks the loopback predicate (8 cases:
    127.0.0.1, [::1], localhost OK; 0.0.0.0, :port, RFC1918 refused).
  - TestEmbeddedDispatcher_AuthGate proves 401 on missing/wrong token,
    200 on valid token through the dispatcher mpcd installs.

4 tests pass in cmd/mpcd (was 2, +2).
2026-05-13 16:46:22 -07:00
zeekay 0ea64a63e7 mpcd: embed luxfi/threshold/pkg/thresholdd dispatcher (six schemes)
mpcd is the production threshold daemon. With this commit it embeds
the JSON-RPC dispatcher from luxfi/threshold/pkg/thresholdd on a new
--threshold-listen flag (default 127.0.0.1:7300, MPC_THRESHOLD_LISTEN
env var). The dispatcher exposes every supported scheme:

  cggmp21 (secp256k1 ECDSA, CGGMP21 fork)
  frost   (secp256k1 Schnorr, RFC 9591)
  bls     (BLS12-381 Shamir/Lagrange)
  sr25519 (Ristretto255, via mpc node's own session — separate path)
  pulsar  (M-LWE / FIPS 204 ML-DSA-65 threshold, via luxfi/corona/threshold)
  corona  (R-LWE / Ringtail, via luxfi/ringtail/threshold)
  doerner (reserved; upstream protocol non-functional, returns explicit error)

Wire format matches teleport/mpc/src/signers/rpc.ts byte-for-byte:

  POST /  {"jsonrpc":"2.0","id":N,"method":"<scheme>.<op>","params":{...}}

Methods per scheme: keygen / sign / verify. Public-key handles are
returned by keygen and bound on sign/verify.

Decomplecting note: the dispatcher carries zero policy. Profile
gating, auth, audit, and rate-limiting live on mpcd's existing API
surface (and on the cluster ingress in front of it). The threshold
listener is loopback by default; production exposure happens via the
KMS-facing ZAP server or the gateway.

There is no separate thresholdd daemon to deploy. The luxfi/threshold
repo's cmd/thresholdd remains as a developer / CI binary that imports
the same package — one wire, one implementation, two startup paths.

Tests: TestEmbeddedDispatcher_BLSRoundTrip (full keygen → sign →
verify → forgery rejection) and TestEmbeddedDispatcher_UnknownScheme
(-32601 method-not-found contract) both pass. README updated with the
new protocol table; full surface documented in
luxfi/threshold/LLM.md "thresholdd — unified JSON-RPC dispatcher".
2026-05-13 15:56:56 -07:00
zeekay 08c09c0349 feat(zapauth): JWKS-bearer gate on KMS-facing ZAP server (LP-103)
New pkg/zapauth verifies a JWT presented in OpAuthHello (0x00EF)
BEFORE the X25519+ML-KEM-768 hybrid handshake. Per-peer claims are
attached by ZAP peer ID and consulted on every opcode dispatch.

Backwards compatible: defaults to auth=off so v1.13.x clients keep
working. ZAP_AUTH_REQUIRED=true rejects unauthenticated peers; the
flip happens after every KMS deploys with bearer minting (luxfi/kms
v1.9.0 pkg/iamclient).

- pkg/zapauth/{verifier,verifier_test}.go — Verifier, JWKS cache,
  AuthHello frame, stdlib-only signature verification (RS/ES/EdDSA).
- pkg/api/zap_kms_server.go — KMSZapConfig, StartKMSZAPWith, per-op
  checkAuth gate, OpAuthHello handler.
- pkg/api/zap_kms_server_auth_test.go — required+advisory+missing+bad
  token + nil-verifier integration tests against a self-contained
  ES256 JWKS endpoint.
- cmd/mpcd/main.go — wires ZAP_JWKS_URL / ZAP_EXPECTED_ISS /
  ZAP_EXPECTED_AUDIENCES / ZAP_AUTH_REQUIRED env vars.
- docs/zap-auth.md, LLM.md — protocol reference + LLM map entry.

Tests: pkg/zapauth (13/13), pkg/api KMSZAP (10/10 — 5 original +
5 new auth integration), pkg/zap (existing untouched).
2026-05-02 09:30:48 -07:00
zeekay 4b30830afc fix(mpcd): default --kms-zap-listen to :9653 to match operator zapPort
The Liquid operator already provisions a Service exposing port 9653
on every MPC StatefulSet (spec.zapPort=9653). Match that default so
no operator/CR change is required for v1.13.0 rollout.
2026-05-01 01:36:46 -07:00
zeekay 3cf1004d92 feat(mpcd): expose ZAP server on :9970 for KMS handshake
Adds threshold-signing transport so KMS can use mpcd as MPC backend.
Reuses ML-KEM-768 hybrid handshake from luxfi/kms/pkg/zap (#55) by
vendoring the same construction into mpc/pkg/zap — wire constants
(opcodes 0x00F0/0x00F1, HKDF info "kms/zap/v1/session", direction
tags) stay in lockstep with KMS.

New surface:
- pkg/zap: vendored handshake + session (forward-only convergent)
- pkg/api/zap_kms_server.go: KMS-facing ZAP node (opcodes
  0x0001/0x0010/0x0011/0x0012/0x0020) wired onto MPCBackend
- cmd/mpcd: --kms-zap-listen flag (default :9970), enabled out of the
  box; default-disable by passing empty string

Existing pkg/api/zap_server.go (dashboard event bus, opcodes 0x0060+)
is unchanged — different consumer, different port.

Closes the secrets-only mode gap on mainnet (#70).
2026-05-01 01:35:13 -07:00
zeekay 6495b89e56 test(mpcd): standalone 1-of-1 integration smoke + cmd/mpcd/README
Document mpcd's two topology modes explicitly in cmd/mpcd/README.md:
standalone (1-of-1 self-loop) for smoke tests / dev sandboxes, and
networked (t-of-n) for production. The same binary handles both — only
flags differ.

Add cmd/mpcd/standalone_test.go behind the integration build tag. It
builds mpcd, runs it with --threshold=1 and no peers, polls /healthz
until signing_quorum=true, and asserts the response shape (mode,
threshold, version). Keygen is intentionally NOT exercised — CGGMP21
and FROST cannot produce shares with a single party.

Run: go test -tags=integration ./cmd/mpcd/... -count=1 -timeout 90s

Honest scope-limit: mpc trunk has a pre-existing build break in
pkg/policy (undefined fhethr.* symbols) and pkg/api (handlers_approval
references s.approval that's not on the Server struct). The smoke test
itself is correct and passes type-check against cmd/mpcd; it will go
green once the upstream policy/api regression is resolved.
2026-04-28 13:42:36 -07:00
zeekay 39fcdca875 mpcd: author missing buildHSMSignerConfig helper (refs #227)
Commit af4139d ("deployment-mode flag + audit dispatcher") referenced
buildHSMSignerConfig at the HSM signer wiring site but the helper file
was never staged — same pattern as the missing airgap files repaired
under #208. The current main.go works around the gap by passing nil to
hsm.NewSigner, which loses the operator-supplied --hsm-signer-key-id and
prevents per-provider config promotion.

cmd/mpcd/hsm_signer_config.go:
  - validates provider name against the luxfi/hsm v1.1.3 factory list
    (aws/gcp/azure, zymbit, yubihsm, nitrokey, pkcs11, kmip, coldcard,
    foundation, keystone, ngrave, gridplus/lattice, ledger, trezor,
    tr31, mldsa, local) and the documented aliases.
  - promotes --hsm-signer-key-id into the canonical config key for each
    cloud KMS provider (aws.key_id, gcp.crypto_key, azure.key_name).
  - leaves vendor-specific surface (slot/pin/library, vault URL,
    transport) to the factory's MPC_HSM_<P>_* env-var fallback so the
    CLI flag surface stays minimal.

cmd/mpcd/main.go: route --hsm-signer through the helper and surface
config errors before reaching hsm.NewSigner.

cmd/mpcd/hsm_signer_config_test.go: covers cloud-KMS key-id promotion,
hardware-wallet empty-map case, alias acceptance, case-insensitive
provider names, and rejection of empty + unknown providers. Helper
+ tests pass under `go test -race -count=1` in isolation.

go.sum: refresh github.com/luxfi/edwards25519 v0.1.0 hashes to match
proxy.golang.org. Pre-existing checksum drift blocked all `go build`
attempts on this branch and on main.

Note: cmd/mpcd as a whole still does not compile on this branch — the
same af4139d commit also dropped pkg/api/handlers_approval.go without
adding the Server.approval field, and pkg/policy/fhe_threshold_decryptor.go
references symbols that never landed. Both are out of scope for #227 and
will be repaired under follow-up tickets in the same #208-style cleanup.
2026-04-28 01:14:50 -07:00
zeekay 0a8fa2e0cc Merge kms-nonce-bind-2026-04-28
# Conflicts:
#	CHANGELOG.md
2026-04-28 00:58:33 -07:00
zeekay dae0f9258d neutral: drop 'Lux'/'Hanzo' brand prefixes in user-facing strings
MPC and KMS are protocols, not products. The Go module path
github.com/luxfi/mpc remains canonical (location), but user-facing
strings (--help, README, comments, banners) are brand-neutral.
'Lux MPC' → 'MPC', 'Lux KMS' → 'KMS', 'Hanzo MPC' → 'MPC'.
2026-04-25 21:46:16 -07:00
zeekay 0b8b6fdc0c feat(zap): adopt canonical port 9999 across MPC + transport
ZAP is the binary protocol — like HTTP=80 and HTTPS=443, ZAP=9999
across the Lux ecosystem. The DNS name (e.g. zap.kms.svc, zap.mpc.svc,
mpc-node-headless.lux-mpc.svc) disambiguates the service; the port is
always the same.

Changes:
- mpcd --listen default :9651 → :9999
- pkg/transport/{transport,factory}.go: comments and examples
- k8s/mpc-statefulset.yaml: ports + peer template
- k8s/network-policy.yaml: ingress/egress allow rules
- cloudbuild.yaml: EXPOSE
- deployments/customer-3of5/{compose.yml,k8s/*,topology.yaml}: full
  template sweep — every peer hostname is distinct, port is uniform 9999
- README.md / LLM.md: example commands

luxd validator P2P (9631 mainnet, 9641 testnet, 9651 devnet) is a
DIFFERENT protocol on a DIFFERENT pod and is unchanged.
2026-04-25 21:18:39 -07:00
zeekay 0a20ec7bf6 fix(mpc): HasSigningQuorum uses operator-facing threshold semantics
Follow-up to 8156f2e. The CLI/CRD `--threshold` flag surfaces the
OPERATOR-facing "minimum signers required" count (e.g. threshold=2 for
a 2-of-3 ensemble, threshold=3 for 3-of-5). The CGGMP21 signing API
internally stores the polynomial degree (one below signers-required)
in keyInfo.Threshold — so the sign code continues to compare
`len(signerPeerIDs) >= keyInfo.Threshold + 1`. Those two surfaces are
off-by-one by protocol library convention.

HasSigningQuorum is operator-facing (used by /healthz) so it uses the
CRD/CLI semantics directly: quorum holds when `readyCount >= threshold`.

Scientist's repro: 3-node 2-of-3 ensemble (CRD threshold=2) with one
peer down — readyCount=2, threshold=2 → quorum holds, /healthz returns
200. With the previous commit the arithmetic was readyCount>=threshold+1
which would have required all 3 nodes up.

Tests updated accordingly (Test_HasSigningQuorum_3Node_2of3 now uses
threshold=2 matching the deployed CRD, Test_HasSigningQuorum_5Node_3of5
uses threshold=3, boundary test covers the new arithmetic).
2026-04-20 18:52:49 -07:00
zeekay 8156f2ed4e fix(mpc): /healthz reports quorum — not all-peers-ready (P0-1)
Survivor nodes in a t-of-n ensemble now report healthy so long as they
still have enough ready peers (including self) to form a signing quorum
for their configured threshold. Previously, ArePeersReady() was the gate
and any single peer loss collapsed availability on the remaining nodes.

Repro (Scientist, 2026-04-20): scale liquid-mpc-mpc-node StatefulSet
from 3 → 2 replicas in the liquidity devnet; surviving pods returned HTTP
503 with log "Peers not ready expected=3 ready=2" even though 2 nodes
are sufficient for the 2-of-3 (threshold=1) signing quorum.

Fix:

- pkg/transport/registry.go: add HasSigningQuorum(threshold) which returns
  true when readyCount >= threshold+1 (the CGGMP21/FROST signers-required
  count used everywhere in pkg/mpc/node.go).
- pkg/mpc/registry.go: matching method on the legacy Consul-backed
  registry plus interface addition to PeerRegistry.
- cmd/mpcd/main.go: /healthz handler switched to HasSigningQuorum(threshold)
  for the 200/503 gate. ArePeersReady() is still reported in the JSON
  body (as `ready`) for observability. `status` becomes
  "healthy" / "healthy-reduced" / "degraded" so operators can see the
  in-between state.
- cmd/mpcd/main.go: ConsensusPeerRegistry wrapper delegates the new
  method to the underlying transport registry.

Keygen sessions deliberately continue to require ArePeersReady() — a
fresh wallet still needs every participant present. Only the /healthz
gate is relaxed, which matches the availability story for signing.

Tests: pkg/transport/registry_test.go covers
- Test_HasSigningQuorum_3Node_2of3 (the Scientist repro — survivors
  still quorum-ok after one peer loss)
- Test_HasSigningQuorum_5Node_3of5 (larger ensemble, f=2 tolerance)
- Test_HasSigningQuorum_ArePeersReady_Decoupled (the two APIs do not
  gate each other)
- Test_HasSigningQuorum_BoundaryCheck (pins arithmetic: required =
  threshold+1, not threshold)

All 11 test cases pass; transport + mpc package suites clean.
2026-04-20 18:48:31 -07:00
zeekay 850bb4a80a feat(ui): embedded MPC admin dashboard — @hanzo/gui + go:embed
Vite + React SPA with 9 pages (Dashboard, Wallets, Vaults, Policies,
Settlements, Bridge, Payments, Audit, Settings) served at /_/mpc/ via
go:embed. Replaces the separate Next.js dashboard pod — the UI now ships
inside the mpcd binary. Uses @tanstack/react-query, wouter hash router,
dark theme, inline styles. Build output: 272KB.
2026-03-16 10:00:00 -07:00
zeekay a54e54de9f cmd: rename mpc-cli → mpc (pairs with mpcd daemon)
Matches the sshd/ssh, httpd/http unix pattern. Single Docker image
now ships both binaries: ENTRYPOINT is mpcd (the daemon); override
with 'mpc <subcommand>' for CLI ops (peers, identity, recover, etc).

Also aligns with lux/kms (single binary with subcommands) — operators
get the same shape across all Base-family daemons.
2026-04-13 16:22:11 -07:00
zeekay f657f8221a fix: remove postgres/valkey/legacy mode — consensus+ZapDB only
- Removed --mode, --api-db, --api-kv, --db flags
- Removed legacy NATS/Consul transport (runNode, ~200 lines)
- Removed standalone API server (runAPIOnly, ~200 lines)
- Removed Consul helpers, NATS connection, interactive prompts
- Dashboard now uses ZapDB (SQLite) instead of postgres
- pkg/db: removed postgres backend, removed valkey client
- Consensus mode is the only mode. Zero external deps.
2026-04-13 04:14:40 -07:00
zeekay 9b5d931b71 fix: /healthz only — removed legacy health endpoints 2026-04-09 18:31:27 -07:00
zeekay a2d35948b4 feat: add /healthz endpoint (platform health check standard)
Add /healthz alias on the internal MPC API (port 9800) alongside
the existing /health endpoint. Both return the same response.
2026-04-09 18:04:32 -07:00
zeekay a1e0f8fd6b Merge hanzo/mpc additions: ConsensusKV, HSM providers, policy engine, rename CLI to mpc-cli
- pkg/infra: Add abstract KV interface, ConsensusKV (dual-cert BFT with
  Ed25519 + ML-DSA-65), NatsKV, ConsulKVAdapter, HybridKV, SQLiteMeta
  with full test coverage
- pkg/hsm: Add Provider interface and implementations for AWS KMS,
  GCP Cloud KMS, Azure Key Vault, Zymbit HSM, Hanzo KMS, and file-based
  providers with registry and tests. Rename existing FileProvider to
  FilePasswordProvider to avoid collision with new Provider interface
- pkg/policy: Add transaction governance engine with signers, roles,
  spending limits, whitelist/blacklist, time windows, rate limiting,
  approval workflow, audit log, and FHE private policy evaluation
- contracts: Add ThresholdPolicy.sol for on-chain policy enforcement
- cmd: Rename lux-mpc-cli to mpc-cli
- Makefile: Update build targets for mpc-cli
2026-04-08 13:16:06 -07:00
zeekay de1d872851 fix: address red team findings — auth, TLS, KMS fallback, AAD
M-1:  compose.yml commented section now enforces JWT_SECRET required syntax
M-3:  TLS client config uses TOFU certificate pinning instead of blind
      InsecureSkipVerify — SHA-256 fingerprint pinned on first connect,
      mismatches rejected on subsequent connections
M-4:  TLSOnlyListener plaintext escape hatch gated behind both
      ALLOW_PLAINTEXT=true AND ENVIRONMENT=development
M-5:  KMS fallback to unencrypted storage is now log.Fatal in production
      (ENVIRONMENT != "development"), including Put-time failures
M-6:  isKeyShare() now checks org: prefix, key-type prefixes (frost:,
      bls:, sr25519:, tfhe:), and hex wallet IDs instead of returning true
M-9:  AES-GCM Encrypt/Decrypt accept optional AAD variadic parameter
      for context-binding; existing callers remain backward compatible
M-12: Internal /keygen endpoint requires org_id in request body (400 if
      missing); orgID propagated to GenerateKeyMessage and audit log
2026-04-07 14:06:10 -07:00
zeekay 21da3d64e0 security: harden internal MPC API, enforce TLS-only transport, restrict egress
- Add bearer token auth to internal MPC API (port 9800): /keygen, /keys,
  /backup now require Authorization header. Token from MPC_INTERNAL_API_KEY
  env (KMS-synced) or derived from Ed25519 node identity.
- Replace DualModeListener with TLSOnlyListener: plaintext peer connections
  are rejected. TLS 1.3 with PQ key exchange is mandatory.
- Add dedicated 20 RPM rate limiter on bridge signing and intent signing
  endpoints (separate from global 100 RPM).
- Add body size limit (1MB), read/write timeouts, and 10 RPM rate limiter
  to internal MPC API server.
- Restrict K8s NetworkPolicy egress for MPC nodes to peers, postgres,
  valkey, DNS, and HTTPS only (was unrestricted).
- Wire cluster-api-key in api-deployment for authenticated proxy to
  internal MPC API. Forward as Authorization: Bearer header.
- Remove hardcoded dev JWT_SECRET default from compose.yml (now required).
- Add internal-api-key to KMSSecret CRD and StatefulSet env.
2026-04-07 14:00:47 -07:00
zeekay 4decb5c091 feat: Base framework + consensus default + minimal ops
- Mount chi handlers on Hanzo Base (lifecycle, health, auth framework)
- Default mode: consensus (ZAP p2p) — NATS/Consul deprecated
- compose.yml: 2 services (api + dashboard), SQLite, no external DB
- config.yaml: consensus mode, SQLite-first
- Makefile: simplified build targets, docker compose format
- K8s: kustomization.yaml for idempotent deploys
- Removed listenAddr from NewServer (Base handles listening)
- Legacy NATS/Consul preserved but not default
2026-04-07 01:18:53 -07:00
zeekay 60fc83b28d fix: remove /api/ prefix from all routes — use /v1/ only
Subdomain routing (mpc.lux.network/v1/) not path prefix (/api/v1/).
Updated: server routes, KMS routes, main.go forwarding, compose,
K8s dashboard config, ingressroute, landing page HTML.
2026-04-07 00:01:47 -07:00
zeekay 04f303dcb2 security: fix 15 custody audit findings, add regression + fuzz tests
Fixes all findings from the Blue Team defensive security review of the
Satschel → Liquidity.io custody architecture transition.

HIGH:
- S-1: Implement P-256 ECDH + AES-256-GCM encryption of passkey backup shards
- W-2: Reject unparseable WebAuthn public keys (was silently skipping verification)
- O-1: Add Ed25519 binary signature verification package (pkg/integrity)

MEDIUM:
- W-1: Validate attestation is a real P-256 curve point, not raw attestation object
- W-3: Add origin validation to all WebAuthn flows (register, verify, trade approve)
- N-2: Remove plaintext TCP fallback from MPC node transport (TLS mandatory)
- K-1: Fail-closed on empty/whitespace orgID (prevents cross-tenant key leakage)
- A-1: Make AuditEntry immutable (Update/Delete return error)
- A-2: Add MPC-level signing audit log (wallet, message hash, intent, trigger)
- O-3: Invalidate backup shards after key reshare (stale shares are useless)
- O-2: Reject env/file password providers in production (require cloud HSM)

LOW:
- T-1: Add error handling + MarkSignFailed to signing goroutine
- T-2: Verify caller authorization in handleTradeSubmit
- K-2: Secret erasure stub now calls runtime.GC() instead of no-op
- N-1: Add VerifyConnection callback to reject peers without TLS certs

Tests: 69 unit + 7 fuzz targets, ~900K fuzz executions, 19/19 packages green.
Makefile: add -tags json1 for SQLite JSON function support in ORM tests.
2026-04-01 09:00:06 -07:00
zeekay 0e90ba3d15 fix: per-org MPC isolation — orgID end-to-end threading + security hardening
- M-1 CRITICAL: Remove legacy key share fallback (cross-tenant leak)
- M-2 HIGH: orgID now required string on all Node methods (not variadic)
- M-3 HIGH: Key material zeroing after signing sessions
- Thread orgID through MPCBackend interface, all API handlers
- Reshare + TFHE sessions use OrgScopedKey for reads/writes
- Remove hardcoded JWT default (fail-hard if not set)
- Add warning log for empty orgID legacy path
- Regression tests: TestGetKeyShareWithFallback_M1_CrossTenantLeak
2026-03-24 16:55:54 -07:00
zeekay 913dfd3778 fix: goimports formatting with local-prefixes (matches golangci-lint) 2026-03-22 13:17:50 -07:00
zeekay cb1bc5ecbc security: add orgID multi-tenancy isolation, rename badger to zapdb
Multi-tenancy isolation (addresses Dragonfire review MPC audit):
- Add OrgID field to all event messages (keygen, sign, reshare)
- Scope session dedup keys as orgID-walletID-txID
- Namespace KVStore keys with org prefix (org:<orgID>:<key>)
- Backward-compatible fallback to legacy unscoped keys
- New org_kvstore.go with OrgScopedKey/GetKeyShareWithFallback

Naming cleanup:
- Rename all badger references to zapdb in code, config, tests
- config.yaml: badger_password → zapdb_password
- AppConfig: BadgerPassword → ZapDBPassword
- Fix MPCiumVersion typo → MPCVersion
2026-03-22 12:02:30 -07:00
zeekay c7f126adbf feat: BLS threshold + security hardening + ZapDB KMS-backing
Security (from red team review):
- Replay protection: request_id + timestamp freshness on sign API
- Ed25519 message auth re-enabled on all protocol messages
- DKG timeout (5min) + signing timeout (30s) with watchdog
- Dedup map TTL cleanup (10min expiry, 2min sweep)

BLS threshold signing:
- KeyTypeBLS + keygen/signing sessions
- TrustedDealer pattern for BLS DKG
- CBOR serialization for BLS config
- 8 tests

ZapDB KMS-backing:
- HSM PasswordProvider wired to ZapDB init
- Supports: AWS KMS, GCP KMS, Azure KV, env, file
- Default env for backward compat
- 19 tests

Also: sr25519 sessions, fixed SignWireMessage build break
2026-03-19 01:15:33 -07:00
zeekay bdd4d05ab0 Wire HSM threshold attestation into MPC daemon
- Add --hsm-attest / MPC_HSM_ATTEST flag for share attestation
- Add --hsm-signer / --hsm-signer-key-id flags for co-signing
- KeyShareVault initialized when attestation enabled
- Bump github.com/luxfi/hsm v1.0.0 → v1.1.0
2026-03-02 23:52:08 -08:00
zeekay 45721d9485 feat: multi-chain address derivation (BTC, SOL), fix policy engine
- Derive Bitcoin P2PKH address from secp256k1 ECDSA pubkey
- Derive Solana address from Ed25519 pubkey (base58)
- Fix loadPolicies: gracefully return empty list instead of error
  when no policies exist (was causing "failed to load policies" error)
- Filter enabled policies in-memory (JSONB boolean filter unreliable)
- Add BTC/SOL address fields to KeygenResult and wallet creation
- Update /info endpoint with full chain support matrix
- Map EVM-compatible chains (Lux, XRP) and Ed25519 chains (TON)
2026-02-24 23:19:15 -08:00
zeekay 45b89f9f86 fix: derive eth_address from 32-byte ECDSA keys, return int for connected_peers
- /health returns connected_peers as int (count) not string array
- /keygen derives eth_address from 32-byte keys (was >= 33, now >= 32)
- Skip initiator verification when no explicit key configured (consensus mode)
2026-02-24 23:02:25 -08:00
zeekay f8a232a907 fix: sign keygen/sign messages in consensus mode, fix API proxy paths
- ConsensusIdentityStore now uses node's own key as initiator key when
  no explicit event_initiator_pubkey is configured (consensus mode)
- /keygen HTTP endpoint signs GenerateKeyMessage with node's Ed25519 key
- ConsensusMPCBackend.TriggerKeygen/TriggerSign sign messages before publish
- apiOnlyMPCBackend forwards to /keygen and /health (internal API paths)
  instead of /api/v1/keygen and /api/v1/status (dashboard API paths)
- mpc-api deployment points to port 9800 (internal API, no auth required)
  instead of port 8081 (dashboard API, auth required)
- /health response uses snake_case keys matching ClusterStatus JSON tags
2026-02-23 22:44:17 -08:00
zeekay a7152aaa0b fix(ci): goimports formatting, no-panic logger test, remove unconvert
- goimports -w on all pkg/api, pkg/db, cmd/mpcd files
- TestError_PanicsOnOddKeyValues → TestError_OddKeyValuesLogsWarning
  (logger was intentionally changed to warn+sentinel instead of panic)
- int(threshold) → threshold (threshold is already int from c.Int())
2026-03-01 18:40:56 -08:00
zeekay fa1466abd9 refactor(kvstore): rename Badger→ZapDB types, clean up package API
- git mv badger.go→zapdb.go, badger_backup.go→zap_backup.go
- BadgerKVStore→Store, BadgerConfig→Config, NewBadgerKVStore→New
- BadgerBackupExecutor→Backup, NewBadgerBackupExecutor→NewBackup
- BadgerBackupMeta→BackupMeta, BadgerBackupVersionInfo→BackupVersion
- Config fields: DBPath→Path, EncryptionKey→Key, BackupEncryptionKey→BackupKey, BackupDir→Dir
- Backup fields: BackupEncryptionKey→Key, BackupDir→Dir
- Store fields: BackupExecutor→Exec, db→DB (exported, fixes e2e compile)
- Updated all callers: transport/factory, transport/state, backup/s3,
  cmd/mpcd/main, cmd/lux-mpc-cli/recovery, scripts/migration/add-key-type,
  e2e/base_test, pkg/kvstore/backup_test
2026-03-01 18:33:01 -08:00
zeekay 0c2a896d67 feat(mpc): key listing, KMS backup/restore, API-only backend, CGGMP21 config serialization 2026-03-01 18:12:18 -08:00
zeekay e0f42e0542 fix(security): Ed25519 initiator verification, proper share verification, no panics
- ConsensusIdentityStore.VerifyInitiatorMessage: implement real Ed25519
  signature verification using the configured event_initiator_pubkey
  instead of blindly accepting all messages
- cggmp21Aggregator.VerifyShare: implement ECDSA partial signature
  verification using secp256k1 curve arithmetic (R' = u1*G + u2*Y_i,
  check R'.x == r mod n) against the party's public key share
- frostAggregator.VerifyShare: implement Schnorr/BIP-340 partial
  signature verification (compute R_i = s_i*G - c*Y_i, verify on curve)
- logger.Error: replace panic on odd keyValues with warning log and
  sentinel value to avoid crashing production
- session.ProcessInboundMessage/ProcessOutboundMessage: replace panics
  with error log and graceful no-op for unoverridden base methods
- eddsaReshareSession: implement ProcessInboundMessage (unmarshal wire
  format, dedup, route to FROST party) and ProcessOutboundMessage (read
  from party Messages channel, wrap in wire format, send)
- cggmp21ReshareSession: implement matching message handlers
- eddsaReshareSession.Reshare: extract actual Ed25519 public key from
  FROST result via GetPublicKeyBytes() instead of empty placeholder
2026-03-01 18:08:14 -08:00
zeekay a509333af0 feat(hsm): AWS/GCP/Azure KMS password providers, CREATE2 address prediction
- Implement CREATE2 address prediction in PredictAccountAddress() using
  keccak256(0xff ++ factory ++ salt ++ keccak256(initCode))[12:] formula.
  Also add general-purpose PredictCreate2Address() with raw [20]byte params.

- Create pkg/hsm package with PasswordProvider interface and 5 implementations:
  - AWSKMSProvider: decrypts ZAPDB_ENCRYPTED_PASSWORD via AWS KMS Decrypt API
    with full SigV4 signing and EC2 instance metadata credential fallback
  - GCPKMSProvider: decrypts via GCP Cloud KMS REST API with GCE metadata OAuth2
  - AzureKVProvider: unwraps via Azure Key Vault REST API with MSI token auth
  - EnvProvider: reads from LUX_MPC_PASSWORD/ZAPDB_PASSWORD env vars (default)
  - FileProvider: reads from file (K8s mounted secrets)

- Add NewPasswordProvider factory (pkg/hsm/factory.go) driven by MPC_HSM_PROVIDER
  env var (values: aws, gcp, azure, env, file)

- Wire HSM provider into mpcd consensus startup, replacing direct env var read
  with graceful fallback chain: HSM provider -> viper config -> dev default

- Add --hsm-provider and --hsm-key-id CLI flags with MPC_HSM_PROVIDER/MPC_HSM_KEY_ID
  env var sources

- 25 tests passing (9 smart + 16 HSM)
2026-03-01 18:05:47 -08:00
zeekay 2e92b3ffa3 refactor: use luxfi/database abstraction for KV storage, rename BadgerDB→ZapDB
- Replace direct github.com/luxfi/zapdb/v4 imports with github.com/luxfi/database
  abstraction in pkg/kvstore/ (zapdb.New + encdb.New for encryption)
- BadgerKVStore now uses database.Database interface internally — backend is
  pluggable (zapdb/leveldb/memdb) without changing MPC code
- Remove custom badger_logger.go (database module handles its own logging)
- Update BadgerBackupExecutor to use database.Database instead of *badger.DB;
  RestoreAllBackupsEncrypted loads into raw zapdb (values are already
  individually encrypted by encdb)
- Rewrite backup_test.go to use KVStore interface instead of raw badger API
- Update migration script to use KVStore Put/Get/Delete/Keys instead of txn API
- Rename all user-visible "BadgerDB"/"Badger" strings to "ZapDB" in logs,
  prompts, and comments across cmd/mpcd/main.go, pkg/kvstore/, pkg/transport/
- Fix backup_test.go type reference: badgerBackupExecutor→BadgerBackupExecutor
  (resolves CI test and lint failures)
2026-03-01 16:00:07 -08:00
zeekay df84005b46 feat: replace BadgerDB with ZapDB, remove local replace directives, vendor mode CI/CD
- Replace github.com/dgraph-io/badger/v4 with github.com/luxfi/zapdb/v4 v4.9.2
  throughout pkg/kvstore/, pkg/transport/, cmd/mpcd/, e2e/, scripts/
- Rename BadgerPath/BadgerPassword → ZapDBPath/ZapDBPassword in FactoryConfig
- Remove local replace directives from go.mod:
  - hanzoai/orm: now uses published v0.3.2 (tagged on GitHub)
  - hanzoai/kv-go/v9: now uses published v9.17.2-hanzo.1
  - Only remaining replace: agl/ed25519 → luxfi/edwards25519
- Add vendor/ directory (GOWORK=off go mod vendor) for reproducible builds
- Update Dockerfile to golang:1.26-alpine + -mod=vendor build flag
- Update CI/CD to use self-hosted linux/amd64 runners (no cross-compilation)
- Add k8s/valkey.yaml for session KV store
- Update k8s manifests: api-kv support, ZapDB password env, fresh secrets
- Dashboard API and MPC nodes all tested running on cluster (3/3 nodes healthy)
2026-03-01 15:27:32 -08:00
zeekay 1d81ef8910 fix: subscribe to correct signing result topic and parse byte fields
TriggerSign was subscribing to mpc_signing_result.{txID} but the
signing session publishes to mpc_signing_result.{walletID}. Also
fixed result parsing: R/S are []byte (base64 in JSON), not hex strings.
2026-03-01 05:52:45 -08:00
zeekay 0db75edc2b fix: populate ParticipantPeerIDs in ConsensusKeyInfoStore for signing
ConsensusKeyInfoStore.Get() was returning empty ParticipantPeerIDs,
causing "Not enough participants to sign" errors. Now populates from
peer registry's ready peers list.
2026-03-01 05:34:02 -08:00
zeekay da6187fe72 fix: normalize ecdsa->secp256k1 key type in signing requests 2026-03-01 05:24:49 -08:00
zeekay 061fa5356c fix: register key type as secp256k1 (not ecdsa) for CGGMP21 signing 2026-03-01 05:17:41 -08:00
zeekay 864b6c217d fix: pass key type in signing requests, increase API timeout to 120s
- TriggerSign now looks up key type from keyInfoStore (was empty, causing
  "CGGMP21 only supports Secp256k1" errors)
- Wire keyInfoStore into ConsensusMPCBackend
- Increase HTTP middleware timeout from 30s to 120s for MPC signing
2026-03-01 05:10:49 -08:00
zeekay c4a3a7b679 fix: derive ETH addresses from 32-byte ECDSA x-coordinates
CGGMP21 keygen returns 32-byte x-coordinate, not 33-byte compressed key.
Added secp256k1 point decompression (y² = x³ + 7 mod p) to recover the
full uncompressed public key for keccak256 address derivation.
2026-02-28 14:15:03 -08:00
zeekay 704959e846 feat: add standalone api subcommand for dashboard-only deployment
mpcd api --db $DATABASE_URL --jwt-secret $JWT_SECRET --listen :8081

Runs the Dashboard API server without MPC transport, suitable for
cloud dashboard deployments where MPC operations are handled by
separate MPC nodes.
2026-02-28 13:41:12 -08:00
zeekay f110e06800 feat: MPC wallet dashboard - full-stack implementation
Complete MPC wallet management platform rivaling Fireblocks/Utila/Fordefi:

Backend (Go):
- REST API with 60+ endpoints on chi router (pkg/api/)
- PostgreSQL schema with 13 tables (pkg/db/)
- JWT + MFA auth, bcrypt passwords, API key management
- Policy engine (deny/approve/require_approval)
- Transaction signing flow with approval workflow
- Subscription scheduler for recurring payments
- Payment requests with public pay pages
- Safe + ERC-4337 smart wallet integration (pkg/smart/)
- Rate limiting, webhook delivery (HMAC-SHA256), audit logging
- 27 passing tests (auth, policy engine, middleware)

Frontend (Next.js 14):
- 27 routes with monochrome dark theme (Tailwind + Geist fonts)
- Full API client with JWT auto-refresh
- React Query hooks for all resources
- Pages: dashboard, vaults, wallets, transactions, policies,
  whitelist, team, settings, API keys, webhooks, audit,
  subscriptions, payments, smart wallets

Infrastructure:
- Dockerfiles for API (Go) and dashboard (Next.js standalone)
- compose.yml for local development (PostgreSQL + API + dashboard)
- K8s manifests for mpc.lux.network + cloud.lux.network
- GitHub Actions deploy workflow (build → push GHCR → kubectl apply)
- TLS via cert-manager, Traefik IngressRoute for both domains
2026-02-28 13:27:34 -08:00
zeekay 113615960a feat: add /keygen HTTP endpoint, PubSub self-delivery, increase keygen timeout
- Add POST /keygen endpoint for triggering MPC keygen via HTTP API
- Fix PubSub self-delivery: Publish now delivers to local subscribers
  before broadcasting to peers (required for consensus mode keygen)
- Increase KeyGenTimeOut from 30s to 120s (Paillier prime generation
  takes 6-15s per node, multi-round protocol needs more time)
- Add pubKeyToEthAddress helper for deriving ETH addresses from ECDSA keys
- Bump version to 0.3.3
2026-02-27 22:25:09 -08:00
zeekay db4c7ea771 feat: add S3 backup integration, keepalive pings, and connection stability
- Add S3 backup manager (pkg/backup/s3.go) for periodic encrypted BadgerDB
  backups with upload to S3-compatible storage (MinIO)
- Add 30s keepalive pings in transport layer to prevent idle read timeouts
- Increase read timeout from 30s to 5 minutes
- Add connection tie-breaking for simultaneous peer connections (lower node
  ID keeps outbound) to prevent connection storms
- Add pointer-specific peer removal (removePeerConn) to prevent orphaned
  goroutines from removing replacement connections
- Export BadgerBackupExecutor type for use by backup package
- Add /backup HTTP endpoint for on-demand backup triggers
- Bump version to 0.3.2
2026-02-27 21:59:49 -08:00
zeekay 9abd19cf43 fix: lint issues - goimports formatting and unnecessary conversion
- Update .golangci.yml to v2 format with formatters section
- Fix goimports for local package grouping (tfhe_session.go, crypto_adapter.go, manager_test.go)
- Remove unnecessary int() conversion in mpcd main.go
2026-02-05 16:56:51 -08:00