The default v1 path for the PQ threshold stack is permissionless: any
participant can verify, signatures bind to public consensus state, and
no host sits in the trusted compute base. For institutional MPC
custody (M-Chain bridge custody operators, A-Chain confidential
compute oracles, regulated tenant treasury) v1 also ships an
operator-controlled extension surface that binds each sign call to
a verified hardware TEE attestation, a KMS-issued release gate, an
HSM-resident key, and a human (or programmatic) approval signature.
Wire output is byte-identical to the permissionless path; relying
parties verify with the same cloudflare/circl verifier and need
no awareness of which path produced the signature.
Two threat models coexist in the Lux ecosystem:
may control any individual participant. Security must hold under
this assumption. The default Pulsar threshold sign, Corona
Pedersen DKG, and Magnetar THBS-SE constructions are designed for
this model.
operator, regulated treasury): an operator is delegated custody
by a regulator or counterparty. The threat model permits trusted
custody with attested release — a specific, audited, attested
TEE binary is the only entity allowed to reconstruct a master
key, and even then only with HSM wrap-key cooperation and
out-of-band approval.
v1 must serve both without complecting them. The TEE-only extension
packages live in their own slot at
~/work/lux/threshold/protocols/{slhdsa-tee,mldsa-tee,rlwe-tee}/,
distinct from the permissionless protocols at
~/work/lux/threshold/protocols/{pulsar,corona}/ and from the
Magnetar standalone primitive at
~/work/lux/magnetar/ref/go/pkg/magnetar/.
~/work/lux/threshold/protocols/
slhdsa-tee/ SLH-DSA (FIPS 205) operator-controlled threshold
config.go
doc.go
envelope.go
sign.go
signer.go
signer_test.go
curve25519_test.go
testdata/
mldsa-tee/ ML-DSA (FIPS 204) operator-controlled threshold
config.go
doc.go
envelope.go
sign.go
signer.go
signer_test.go
curve25519_test.go
testdata/
rlwe-tee/ M-LWE (Corona-track) operator-controlled threshold
config.go
doc.go
envelope.go
sign.go
signer.go
signer_test.go
curve25519_test.go
testdata/
Each package is symmetric in shape. The wire output of each
package is byte-identical to the corresponding default primitive
(magnetar / pulsar / corona). The threshold-of-trust changes, the
wire does not.
~/work/lux/mpcThe extension packages do NOT reimplement attestation, key
release, or HSM access. They compose four mature subsystems from
~/work/lux/mpc/:
mpc/cc/attest
~/work/lux/mpc/cc/attest/
doc.go
verifier.go chain-validating dispatcher
verifier_test.go
tdx.go Intel TDX evidence parser + verifier
sev.go AMD SEV-SNP evidence parser + verifier
nras.go NVIDIA NRAS GPU evidence parser + verifier
testdata/
attest.Dispatch(evidence) → (report, error) is the single entry
point. The dispatcher routes to the per-vendor parser by evidence
magic, validates the certificate chain against the pinned vendor
root (Intel, AMD, NVIDIA), checks the RIM digest against policy,
and returns the parsed report. A v1 sign call MUST pass through
this dispatcher; bypassing it disables the extension's security
posture.
mpc/pkg/kms.ReleaseGate
~/work/lux/mpc/pkg/kms/release.go
ReleaseGate (declared at line 290) is the interface every key-
release primitive satisfies. Three implementations:
LocalReleaseGate (line 349) | single-process KMS root, dev or single-tenant prod |MPCReleaseGate | t-of-n KMS quorum; institutional production |HSMReleaseGate | HSM-backed root via mpc/pkg/hsm |ReleaseGate.Release(ctx, job, composite) (line 287) consumes a
parsed attestation report and emits the HPKE-sealed wrap key. AAD
binds (epoch, jobID, teePub, issuedNonce). Replay across epoch
or jobID is refused.
mpc/pkg/hsm.Provider
~/work/lux/mpc/pkg/hsm/
provider.go Provider interface
factory.go provider factory by URI scheme
aws.go AWS KMS
azure.go Azure Key Vault
gcp.go GCP Cloud KMS
file.go local file (dev only)
kms.go Lux KMS (MPC, in-cluster)
The wrap key never leaves the HSM. The release gate calls
Provider.Sign(ctx, keyURI, digest) to emit an HPKE-sealed
ciphertext; the recipient (the attested TEE) is the only entity
able to unwrap.
Zymbit / YubiHSM providers are accessible through Provider-
implementing wrappers; the factory at factory.go routes by URI
scheme.
mpc/pkg/approval
~/work/lux/mpc/pkg/approval/
types.go
factory.go
helpers.go
provider_safe.go Gnosis Safe multisig
provider_webauthn.go WebAuthn passkey
provider_ledger_enterprise.go Ledger Enterprise
provider_local_dev.go single-key local (dev)
ApprovalProvider.Authorize(ctx, jobID, msg) → ApprovalEvidence
gates each sign call on an explicit authorization. The WebAuthn
provider binds to a per-operator passkey and emits a signed
challenge that ReleaseGate verifies before releasing the wrap key.
YubiHSM-backed approval (via the Provider HSM path with a per-
slot policy) is accessible through provider_ledger_enterprise.go's
parallel surface.
A single sign call composes the four subsystems:
1. attest.Dispatch(evidence) → report
2. policy.Check(report) → policyOK
3. approval.Authorize(jobID, msg) → approvalEvidence
4. gate.Release(ctx, job, composite) → wrappedSeed
5. hsm.Sign(ctx, keyURI, digest)
OR
inProcessSign(seed, msg) → signature
6. return signature (byte-identical to FIPS-spec)
Each step's output is the next step's input. Failure at any step
aborts the call WITHOUT emitting a signature.
For SLH-DSA (slhdsa-tee), step 5 reconstructs the master seed
inside the attested TEE memory space, calls
circl/slhdsa.SignDeterministic, and zeroizes before return. The
seed is briefly present in TEE memory; this is the threshold-of-
trust difference vs the THBS-SE permissionless construction
(LP-021), where no party EVER holds the seed.
For ML-DSA (mldsa-tee) and Module-LWE/Corona (rlwe-tee), step 5 is
analogous: the master key lives sealed at rest, briefly resident
in attested TEE memory during the FIPS-spec Sign call, zeroized
before return.
The decision rule: if the threat model permits "trusted custody
with attested release", the TEE-only extension is preferable
because it gives the operator HSM-grade wrap-key isolation and an
audit trail per sign. If the threat model is permissionless
(adversary controls any participant), the default path is
required because TEE attestation is not a substitute for
mathematical-threshold protection against participant compromise.
The v1 institutional-custody surface is contract-compatible with
Fireblocks across the feature matrix at
~/work/lux/mpc/FIREBLOCKS_PARITY.md. The parity document is the
written contract for what the operator-side surface provides;
each row of the matrix points at a specific file in
~/work/lux/mpc/. v1 ships the rows tagged shipped; rows tagged
in-flight are tracked in their own LPs.
The parity statement is NOT "Lux replicates every Fireblocks
field"; it is "the threat model and operational surface required
to migrate an institutional Fireblocks deployment to Lux is met
by ~/work/lux/mpc/ plus these three extension packages". The
matrix is the verification artifact.
Mixing TEE-gated and permissionless code paths in the same
package conflates the trust model. A reviewer looking at
~/work/lux/threshold/protocols/pulsar/ should know without
reading further that the package targets the permissionless
model. Reviewing
~/work/lux/threshold/protocols/mldsa-tee/ should immediately
flag the institutional model.
The naming convention (<primitive>-tee) makes the trust model
visible in every import path. A go.mod that imports
mldsa-tee is making a specific deployment-model statement; a
go.mod that imports pulsar is making a different one.
Relying parties (verifier contracts, light clients, external
audit tooling) should not need to know which path produced a
signature to verify it. Byte-identity means a contract that
verifies slhdsa.Verify on a Magnetar group public key accepts
signatures from both the permissionless (THBS-SE) and the
institutional (slhdsa-tee) paths. The operational difference
lives entirely in the production-side trust model, not in the
output wire.
mpc/ rather than reimplement~/work/lux/mpc/ ships the attestation parsers, the release gate,
the HSM provider abstractions, and the approval providers as
audited, production-tested code (cross-tested against the
Fireblocks parity matrix). Reimplementing any of these in the
threshold packages would fork the audit surface.
The extension packages are SHALLOW: each is a thin envelope
around mpc/cc/attest + mpc/pkg/kms.ReleaseGate + mpc/pkg/hsm
+ mpc/pkg/approval, with primitive-specific glue (the FIPS-spec
Sign call) at the end.
A purely attestation-gated sign chain has no audit signal
distinguishing "the TEE chose to sign" from "the TEE was
instructed to sign". A WebAuthn or Ledger Enterprise approval
emits a signed authorization tied to a specific human (or
programmatic) identity. The release gate verifies this
authorization as part of the AAD; without it, no wrap key is
released.
The audit log path consumes the `(attestation_report,
approval_evidence, wrap_key_release, signature)` tuple and emits
an append-only audit record. This is the operator-side artifact
regulators consume.
None. v1 is the lock-in.
The pre-v1 path (TEE-only sign with no explicit release gate or
approval provider) is REMOVED. Operators on the pre-v1 path
migrate to the v1 extension API; the wire output is byte-identical
so downstream verifiers see no change.
Each extension package ships its own test suite:
~/work/lux/threshold/protocols/slhdsa-tee/signer_test.go —full sign-chain integration tests using testdata fixtures at
~/work/lux/threshold/protocols/slhdsa-tee/testdata/.
~/work/lux/threshold/protocols/mldsa-tee/signer_test.go —symmetric tests for ML-DSA.
~/work/lux/threshold/protocols/rlwe-tee/signer_test.go —symmetric tests for Module-LWE (Corona-track).
Each must include:
1. Happy-path test: valid attestation + valid approval + valid HSM
response → signature byte-identical to a single-party FIPS-spec
SignDeterministic on the reconstructed master key.
2. Attestation failure test: invalid evidence → sign refused.
3. Approval failure test: invalid approval signature → wrap key
not released → sign refused.
4. HSM failure test: HSM rejects unwrap → sign refused.
5. Replay test: same (epoch, jobID) → second release call refused.
Composition with mpc/cc/attest:
~/work/lux/mpc/cc/attest/verifier_test.go — chain validationfor TDX, SEV-SNP, NRAS evidence.
Composition with mpc/pkg/kms:
~/work/lux/mpc/pkg/kms/release_test.go — release gatesemantics.
~/work/lux/mpc/pkg/kms/release_nonce_test.go — single-usenonce binding.
Composition with mpc/pkg/hsm:
~/work/lux/mpc/pkg/hsm/hsm_test.go — provider factory andper-provider sign correctness.
~/work/lux/threshold/protocols/slhdsa-tee/doc.go:3-66 —SLH-DSA-TEE package contract.
~/work/lux/threshold/protocols/mldsa-tee/doc.go:3-25 —ML-DSA-TEE package contract.
~/work/lux/threshold/protocols/rlwe-tee/doc.go:3-22 —R-LWE-TEE package contract.
~/work/lux/mpc/cc/attest/verifier.go — attest.Dispatch andper-vendor parsers (tdx.go, sev.go, nras.go).
~/work/lux/mpc/pkg/kms/release.go:287 — ReleaseGate.Release.~/work/lux/mpc/pkg/kms/release.go:290 — ReleaseGate interfacedeclaration.
~/work/lux/mpc/pkg/kms/release.go:349 — LocalReleaseGate.~/work/lux/mpc/pkg/hsm/provider.go — Provider interface.~/work/lux/mpc/pkg/hsm/factory.go — provider factory.~/work/lux/mpc/pkg/approval/types.go — ApprovalProviderinterface.
~/work/lux/mpc/pkg/approval/provider_webauthn.go — WebAuthnpasskey provider.
~/work/lux/mpc/pkg/approval/provider_ledger_enterprise.go —Ledger Enterprise provider.
~/work/lux/mpc/FIREBLOCKS_PARITY.md — the institutionalparity contract.
The extension surface trades a fundamentally different threat
model than the permissionless path. The default Pulsar /
Magnetar / Corona constructions hold under "adversary controls any
participant"; the TEE extensions hold under "adversary controls
the host outside the TEE AND lacks valid attestation evidence".
A relying party CANNOT distinguish, from the signature wire bytes
alone, which threat model produced the signature. The relying
party trusts the GROUP PUBLIC KEY (the master public key the
extension binds to). The threat model decision is whose group
public key the relying party accepts.
For consensus certs (see LP-217 for cert mode selection — was LP-017
Polaris/Aurora/Pulsar profile, historical), the group public key MUST
be one produced under the permissionless threat model — a TEE-
extension-produced key would mean a single operator can produce
arbitrary signatures by attesting their own TEE binary, which
collapses consensus liveness to a single operator's availability.
For bridge custody and oracle attestation, the TEE-extension
group public key is the right choice — the operator's audit
trail, attestation chain, and approval log together form the
trust basis.
Mixing the two MUST be deliberate and documented. A relying party
that accepts both should distinguish them in its policy layer
(e.g. "this group public key signs bridge withdrawals only; that
group public key signs consensus certs only"). The wire codec
does not enforce the distinction; the policy does.
Replay protection is load-bearing: AAD on `(epoch, jobID, teePub,
issuedNonce)` means the same wrap key cannot be re-released for a
different sign even by the same TEE. A captured TEE attestation
cannot be reused at a later epoch.
The single-use nonce in kms/release_nonce_test.go is the
operational guard against TOCTOU between gate-release and sign.
Skipping or weakening the nonce check is a v1 regression.
Copyright and related rights waived via CC0.