36 Commits
Author SHA1 Message Date
zeekay 970b3009de chore: sync working tree
Commits 1 outstanding change(s) that were sitting uncommitted.
No build artifacts and no secrets in the changeset (both checked).
2026-07-26 10:06:40 -07:00
zeekay ef0f405ec6 fix(deps): repair the module graph + patch the AWS EventStream DoS
`go build ./...` failed with "missing go.sum entry for module providing
package" (pkg/thresholdd) — the go.mod was out of sync with the imports, so
individual `go get`s just walk further down the chain; tidy is the fix.

Also patches the panic-driven denial of service in the AWS SDK for Go v2
EventStream decoder, at minimum patched versions: eventstream v1.7.8,
service/s3 v1.97.3.

Verified: go build ./... goes from exit 1 to exit 0.
2026-07-26 06:44:48 -07:00
zeekayandHanzo Dev 128d8e4840 quorum: make Policy self-describing on the wire (3-of-5)
A quorum written as `"threshold": 3` is ambiguous — it reads as either the
signer count or the polynomial degree, and the two differ by one. That
ambiguity is what produced degree-0 (1-of-n) custody keys from configs that
said 3-of-5.

Policy now implements TextMarshaler/TextUnmarshaler over the operator form, so
any config, genesis blob or API payload that carries a quorum carries it in the
one representation that cannot be misread. Decode of an absent or undeployable
policy is an error rather than a zero value: {0,0} would reach cmp.Keygen as
degree -1, and a wrong-degree key that already holds funds has no recovery path
short of a resharing ceremony.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-25 13:00:38 -07:00
zeekayandHanzo Dev 247e5e4740 threshold: make 3-of-5 unambiguous, and prove it drives a real Safe
The stack had two different numbers called "threshold". cmp.Keygen and
frost.Keygen take t, the polynomial degree, and need t+1 signers. Operators,
dashboards and runbooks say k-of-n. Provisioning a "3-of-5" wallet by passing
threshold=3 produces a 4-of-5, silently — nothing errors, and the mistake only
surfaces when three custodians hold a ceremony and cannot sign a key that
already holds value.

pkg/quorum is now the single place those two numbers meet. Policy{K,N} states
what operators mean; Degree() == K-1 is the only k->t conversion in the tree.
Callers pass policy.Degree() and never compute the off-by-one themselves.

Proofs, all against the real protocols rather than mocks:

  protocols/quorum_3of5_test.go — five-party CGGMP21 DKG at the policy degree,
  three parties sign, and the signature both verifies under the group key and
  ECRECOVERS to the group's EVM address. Recovery is the part that matters for
  custody: a key can verify correctly and still resolve to an address no one
  controls. Four different 3-subsets all sign for one address. Negatives: every
  2-of-5 subset is refused, and a forged third share cannot produce a signature
  that verifies. FROST covered at the same policy.

  protocols/safe_3of5_test.go — the custody claim end to end. Real SafeL2 v1.5.0
  bytecode behind the real SafeProxyFactory on the luxfi/geth EVM, owned solely
  by the 3-of-5 group address. The digest comes from the deployed Safe's own
  getTransactionHash, so the test cannot sign something the contract would not
  check. execTransaction succeeds, value moves, the nonce advances, a replay is
  rejected, and a valid signature from a different 3-of-5 key is rejected.

Fixes found along the way:

  protocols/lss/adapters/evm.go — GenerateEVMAddress hashed the COMPRESSED
  point, i.e. 32 bytes of X, where the EVM requires Keccak over the 64-byte
  uncompressed X||Y. Every address it produced was well-formed and belonged to
  no key; value sent to one is unspendable. It had no callers, so this was a
  loaded gun rather than a live wound. Now decompresses, and reports failure
  instead of returning a plausible wrong answer.

  internal/test/harness.go — WithTimeout moved the harness deadline but not the
  handler's own ProtocolTimeout, which was hard-coded to five minutes, so a
  raised timeout silently did nothing and a CGGMP21 DKG under -race died at
  five minutes blaming the context. The harness now owns one deadline. Also
  removes a duplicate RunProtocolWithTimeout.

  protocols/cmp/cmp_threshold_test.go — cases labelled "3-of-5" passed degree 3
  and signed with four parties. They were 4-of-5. Labels now derive from the
  policy, so the name and the behaviour are one fact.

  go.sum — was missing entries for luxfi/metric and mattn/go-isatty; the FROST
  packages did not compile from a clean checkout.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-25 11:40:29 -07:00
zeekayandHanzo Dev 74672ad61e test(ecdsa): live devnet proof that SigEthereum lands a real EVM tx
Env-gated integration test (LUX_DEVNET_RPC + LUX_DEVNET_PK): signs an EIP-155
legacy tx with the deployer key via SigEthereum, recovers the sender locally,
broadcasts it, and asserts the receipt lands with status=success. EIP-155
(chainID-protected) is used deliberately — Lux rejects unprotected pre-155 txs,
which is exactly why the KMS /sign format matters.

Verified on devnet chainID 31337: SigEthereum-signed tx mined, status=success.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-21 08:30:12 -07:00
zeekayandHanzo Dev ba60fc96c2 fix(ecdsa): make SigEthereum correct, non-mutating, and tested
SigEthereum had ZERO callers (test or otherwise) and two latent hazards that
made it unsafe to wire: it mutated its receiver's S (Negate) and R
(UnmarshalBinary) in place — curve.Scalar/Point are pointer-backed, so a
value receiver still aliases — and it did a pointless post-hoc R rewrite.

Rewrite: copy S before low-S normalization (no aliasing), validate the R/S
encodings, and derive V from the compressed-R Y-parity byte (flipped when S is
negated). Output is the canonical 65-byte R.x‖S‖V (V in {0,1}), EIP-2 low-S,
directly accepted by luxfi/evm ecrecover.

Tests (SDKROOT="$(xcrun --show-sdk-path)" go test ./pkg/ecdsa):
  - EVMRecoverable: 256 random signatures — each emits low-S, recovers to the
    signer's pubkey via decred secp256k1 RecoverCompact (the routine luxfi/mpc
    and the EVM's ecrecover use), and leaves the receiver unmutated.
  - NormalizesHighS: forces the high-S branch and proves the low-S flip still
    recovers to the signer.

This is the shared helper the mpcd signing sessions now wire for canonical
EVM-ready threshold-ECDSA signatures.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-20 15:31:49 -07:00
zeekay beb54a6373 chore(deps): bump geth v1.20.1 + luxfi deps — stack unification 2026-07-15 11:17:06 -07:00
zeekayandHanzo Dev e12eeeeae4 test(cmp): pin degree-2 => 3-of-5 threshold property (2 signers refused, 3 verify)
Regression guard for the KMS/MPC threshold fix (mpc 1e1d318): a CGGMP21 key at
polynomial degree t=2 (keyInfo.Threshold for a 3-of-5 wallet) MUST reject any
2-signer committee at cmp.Sign -> CanSign -> ValidThreshold(2,2)=false, before
any round, and MUST produce a verifying signature with any 3 signers. This is
the line between genuine threshold security and the degree-0 (1-of-n) bug.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-14 17:45:11 -07:00
zeekayandHanzo Dev 2f9bfb1b09 scheme/bls: home the BLS threshold Scheme impl here (was crypto/threshold/bls)
Consolidates all threshold-signature IMPLEMENTATIONS under luxfi/threshold
(alongside cmp/frost/lss/corona/pulsar). The Scheme/Signer/Aggregator/
Verifier INTERFACE stays in luxfi/crypto/threshold — it is the shared
low-level contract that crypto/signer + hsm depend on, so it cannot move
up without a module cycle. One home for impls, one home for the contract.

scheme/bls/ carries the native BLS Scheme + the dealerless Pedersen-VSS
DKG (RunDKG). It implements crypto/threshold.Scheme using crypto/bls
primitives. Consumers blank-import luxfi/threshold/scheme/bls to register.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-03 14:34:58 -07:00
zeekayandHanzo Dev b4b124eeff migrate thresholdd daemon to dealerless RSS keygen + no-reconstruct hyperball sign (pulsar v1.9.0 pkg/ layout)
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-06-30 16:14:25 -07:00
zeekay 3d5946a863 Merge gate-b/threshold-talus-wip: dealerless committee-DKG pulsar+corona dispatchers (pulsar v1.2.0, corona v0.8.0)
Port pkg/thresholdd pulsar dispatcher off the removed v0.3 trusted-dealer
API onto the dealerless GF(q) committee DKG (NewLargeDKGSession +
LargeThresholdSigner + LargeCombine). Corona dispatcher on dealerless
keyera.Bootstrap, full-committee signing. lss corona-Round1 drift fixed,
dead parity bench removed. Strict-PQ gate kept (reconstruct-at-combine
reasoning). go build ./... + pkg/thresholdd + protocols/{pulsar,corona,lss}
all green.
2026-06-27 19:26:50 -07:00
zeekay 0d5c5d13a3 gate-b: go mod tidy (prune 6 stale go.sum entries; pulsar v1.2.0 + corona v0.8.0 pins unchanged) 2026-06-27 19:26:34 -07:00
zeekay ef20043a76 gate-b: fix lss corona-Round1 drift, drop dead parity bench, correct v0.3 docs
- protocols/lss/lss_pulsar_test.go: corona v0.8.0 Signer.Round1 now
  returns (*Round1Data, error); capture the error (was assign-1-of-2).
- protocols/parity/parity_test.go: DELETED. It benchmarked the removed
  v0.1 pulsar.ThresholdSigner/Combine small-committee API (ripped in H-1,
  gone in pulsar v1.2.0). No backwards compat — dead bench for deleted
  code is removed, not resurrected.
- pkg/thresholdd/{doc,profile,thresholdd_test}.go: correct stale comments
  that named the removed v0.3 DealAlgebraicV03Shares/OrchestrateV03SignCtx
  symbols; describe the dealerless committee-DKG + reconstruct-at-combine
  reality and why the strict-PQ gate stays load-bearing.
2026-06-27 19:25:49 -07:00
zeekay e621ebaab0 fix(gate-b): corona dispatcher must sign with full committee (all n)
corona v0.8.0 bakes the FULL-COMMITTEE Lagrange coefficient into every
share (keyera.computeFullCommitteeLagrange over all n; kernel sets
party.Lambda = share.Lambda, no per-subset recompute). The Lagrange
identity Σλ_i·f(i)=f(0) holds only with all n contributing, so the
threshold sign requires the full committee — t is the Shamir RECOVERY
threshold, signing is n-of-n. Dispatcher signed with only t shares →
incomplete Lagrange basis → self-verify failed. Fix: signers = all n
(matches every corona kernel round-trip test). TestCoronaRoundTrip green.
2026-06-27 19:03:10 -07:00
zeekay e0d4bf8bef wip(gate-b): port thresholdd pulsar+corona dispatchers to DEALERLESS committee DKG
pulsar.go: rewrite onto pulsar v1.2.0 large_* GF(q) committee-DKG API
(NewLargeDKGSession 3-round dealerless keygen + NewLargeThresholdSigner
Round1/2 + LargeCombine). Removes the v0.3 trusted-dealer
DealAlgebraicV03Shares path entirely. Sign/Sign_Ctx collapse into one
ctx-aware largeSign helper. Strict-PQ gate KEPT with corrected reasoning
(LargeCombine is reconstruct-at-combine, weaker than no-reconstruct
TALUS; gate guards dev-tooling dispatcher from feeding strict-PQ chains).

corona.go: dealerless keyera.Bootstrap (Pedersen dkg2) rewire.

Package compiles clean (GOWORK=off go build ./pkg/thresholdd/).
2026-06-27 18:45:34 -07:00
zeekay cc3fb58d23 wip(gate-b): corona v0.8.0 + pulsar v1.2.0 adapters (GenerateKeysTrustedDealer rename)
Agent-started gate-B fix, preserved. protocols/corona + protocols/pulsar build
clean against pulsar v1.2.0 / corona v0.8.0. REMAINING: pkg/thresholdd/pulsar.go
still uses pulsar's removed v0.3 Algebraic API (AlgebraicSetup/DealAlgebraicV03Shares/
OrchestrateV03Sign) — needs the TALUS port.
2026-06-27 17:43:52 -07:00
zeekay 1cd3aa5ef9 corona: fix Pulsar→Corona naming relics in protocols/corona wrapper
This package wires the github.com/luxfi/corona Module-LWE kernel
(Ringtail/Raccoon, q=0x1000000004A01), but carried stale "Pulsar"
labels from when Corona was historically named Pulsar:

- package doc claimed it wired the Pulsar kernel (github.com/luxfi/pulsar)
  and was "the equivalent of protocols/corona" — self-contradictory; the
  imports are github.com/luxfi/corona/{keyera,threshold}. Rewrote the doc
  to state the scheme accurately and note the Corona-vs-Pulsar distinction
  (both Module-LWE; Pulsar is FIPS-204/ML-DSA q=8380417, Corona is
  Ringtail/Raccoon q=0x1000000004A01).
- error strings "pulsar:" → "corona:" (sentinel identities unchanged;
  substrings asserted by tests preserved).
- exported aliases PulsarKeyEraID/PulsarGroupID → CoronaKeyEraID/
  CoronaGroupID (the corona kernel already uses the Corona* names; these
  wrappers had zero external consumers across ~/work/lux).

No wire format, modulus, or test vector changed. Doc-, string-, and
identifier-only. corona pkg builds + tests green.
2026-06-27 16:29:18 -07:00
zeekay 7606b46ee9 security: fail-CLOSED strict-PQ gate — empty/unknown chainID DEFAULT-REFUSE
The one canonical RefuseUnderStrictPQ gate (decomplected — magnetar's
duplicate slhdsatee gate removed with the TEE extraction). Drop the
chainID=="" short-circuit so empty/unknown chains consult the resolver;
production wires def=ProfileStrictPQ → refuse. Proven by the policy matrix.
2026-06-27 11:38:14 -07:00
zeekay 856794662d security: delete fake TFHE (full-key-per-party); fail-CLOSED strict-PQ gate
- rm protocols/tfhe (ALLOW_FAKE_TFHE stub holding the full master key per
  party; real threshold-FHE is luxfi/fhe). Zero external importers.
- RefuseUnderStrictPQ: drop the chainID=="" short-circuit so empty/unknown
  chains consult the resolver. Production wires def=ProfileStrictPQ → empty/
  unknown DEFAULT-REFUSE. Proven by TestRefuseUnderStrictPQ_PolicyMatrix
  (empty/unknown REFUSED on a strict-default node).
2026-06-27 00:34:51 -07:00
zeekay a00f53240f security(threshold): drop v0.1 Combine/NewThresholdSigner re-exports from pulsar alias — H-1b
The threshold/protocols/pulsar alias is the surface consensus reaches
into the pulsar reference kernel. It re-exported the v0.1
reconstruct-then-sign API — ThresholdSigner, NewThresholdSigner,
Combine, and the Round1Message/Round2Message wire types. That path
reconstructs the full ML-DSA signing key in aggregator memory (H-1);
its only production consumer was consensus's quasar wave_signer, now
deleted.

Removed those five re-exports. The alias now exposes only identity,
DKG, single-party sign/verify, and parameter surfaces — no
reconstruct-then-sign forward. Live-path threshold signing runs through
the corona dealerless kernel (protocols/corona), not this alias.

Verified: GOWORK=off go build ./... exit 0 (alias + full module);
gofmt clean; no threshold-internal consumer of the removed symbols.
2026-06-26 19:57:09 -07:00
zeekay 25a1245b41 chore(threshold): refresh luxfi/mpc + luxfi/zap go.sum h1 hashes
Same upstream same-version tag re-cut churn picked up across the rip
branches (cf. mpc e3dcc3f). go mod verify clean; build green. Was the
'wip in-flight checkpoint' placeholder; this is the actual change.
2026-06-26 19:57:08 -07:00
zeekay d92cad5638 security(thresholdd): remove trusted-dealer BLS scheme — H-2
The thresholdd daemon registered a "bls" scheme whose keygen ran a
TrustedDealer in-process (one node mints the whole group secret key) —
the same single-point-of-compromise trusted-dealer BLS custody removed
from mpc. BLS is consensus-only (native per-validator keys), never a
custodied threshold key. Remove the daemon's bls scheme so the dispatcher
can no longer mint a trusted-dealer BLS key.

- Delete pkg/thresholdd/bls.go (blsScheme / TrustedDealer keygen).
- server.go: drop s.schemes["bls"] + the bls keygen/sign/verify procs.
- zap_schema.go: drop ProcBLS{Keygen,Sign,Verify} opcodes.
- Tests: delete the bls-specific round-trip + benchmarks; rewire the
  auth-gate tests (which used bls only as a fast keygen driver) to frost;
  drop bls from the sign_ctx unsupported-scheme loop.
- Scrub stale bls mentions from doc comments.

NOT touched: protocols/bls (the library primitive) stays — luxfi/chains
(a non-owned repo) consumes bls.TrustedDealer/AggregateSignatures
directly. Deleting protocols/bls must be a coordinated change with the
chains owner; flagged, not forced here.

Verified: GOWORK=off go build ./... green; affected thresholdd tests
(auth-gate, ProcOpcode, ZapShares, FrostRoundTrip, sign_ctx unsupported)
pass without -short. Zero bls refs remain in thresholdd.
2026-06-26 18:46:02 -07:00
zeekay bf0bda6934 corona→corona: academic Corona now only in lp-220-p3q-corona 2026-06-11 10:28:39 -07:00
Zach Kelling 4fefd9a8d9 chore: update go module dependencies 2026-02-13 15:11:03 -08:00
Zach Kelling f91008ca83 chore: sync dependencies and format code 2026-02-04 15:46:45 -08:00
Zach Kelling 427520b775 fix: upgrade log to v1.4.1 and fix nil logger check
- Update github.com/luxfi/log to v1.4.1 for correct Level type
- Remove broken replace directive from go.mod
- Fix nil pointer panic in NewHandler when logger is nil
- Adds explicit nil check before calling logger.IsZero()
2026-01-25 10:11:38 -08:00
Zach Kelling 18231f4440 Update fhe dependency to v1.7.6 with ThresholdRNG 2026-01-25 10:06:53 -08:00
Zach Kelling 28da6020f4 Fix TFHE tests - correct API usage and add mock provider
- Fix CalculateThreshold test to use single-parameter API (n -> floor(n/2)+1)
- Add mockThresholdProvider for local testing (fhe.NewLocalThresholdProvider doesn't exist)
- All TFHE tests now pass
2026-01-25 10:03:37 -08:00
Zach Kelling ecc7d09efb Add FROST Signature MarshalBinary/UnmarshalBinary methods
- Support x-only format (64 bytes) for BIP-340/Taproot compatibility
- Support compressed format (65 bytes) for general use
- Add NewSignature constructor
- Update dependencies (luxfi/log)
2026-01-25 03:29:39 -08:00
Zach Kelling 5f5ffc744f Clean up slop 2025-12-10 02:10:30 +00:00
Zach Kelling 126d49ea82 Add LLM.md 2025-12-10 02:10:06 +00:00
Zach Kelling 1f15881578 feat: merge upstream security fixes and OT improvements
Ported changes from taurusgroup/multi-party-sig upstream:

1. Security fix: Increased ZKModIterations from 12 to 128
   - Improves security of Paillier-Blum modulus validation
   - Aligns with theoretical statistical security parameter requirements

2. Extended OT monochrome check improvement (based on paper revision)
   - Added doubleFieldElement type for proper GF(2^k) multiplication
   - New helper functions: randFe, pluckColumnToFieldElement,
     pluckBitsToFieldElements, transposeToFieldSizeElements, adjustBatchSize
   - Improved constant-time equality check
   - Better batch size padding for security

3. Comment/typo fixes from upstream

Adapted to local naming conventions (Delta vs _Delta, _KDelta vs _K_Delta)
while preserving the security improvements from the upstream implementation.

Reference: https://eprint.iacr.org/2015/546
2025-12-10 02:06:27 +00:00
Zach Kelling 5e7261e4c9 chore: rename Avalanche references to Lux and remove backup files
- Rename Avalanche chain references to Lux in LSS adapters
- Update chain symbol from AVAX to LUX
- Remove obsolete .old and .bak test files
- Update README to reflect Lux branding
2025-12-10 02:01:01 +00:00
Zach Kelling 50b62443a9 Update go.mod dependencies 2025-09-24 02:39:33 +00:00
Zach Kelling 732120f849 Update README 2023-12-15 21:40:25 +01:00
Zach Kelling 4bfb692f8f Update README 2023-12-15 21:38:20 +01:00