Lux Proposals
← All proposals
LP-0020Final

LP-020: TEE-Only Extensions for Institutional MPC Custody

Abstract

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.

Motivation

Two threat models coexist in the Lux ecosystem:

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/.

Specification

Extension package layout


~/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.

Composition with ~/work/lux/mpc

The extension packages do NOT reimplement attestation, key

release, or HSM access. They compose four mature subsystems from

~/work/lux/mpc/:

1. Attestation verification — 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.

2. Attestation-gated key release — 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:

| Implementation | Use |
|---|---|
| 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.

3. HSM-resident wrap key — 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.

4. Human-in-the-loop approval — 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.

The sign chain

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.

When to use each path

| Use case | Default (permissionless) | TEE-only extension |
|---|---|---|
| Public-BFT consensus cert | yes — Pulsar / Corona / Magnetar | NO |
| M-Chain bridge custody | possible (Magnetar standalone) | YES (slhdsa-tee) — preferred |
| A-Chain confidential compute oracle | NO — needs attested binary | YES (mldsa-tee or rlwe-tee) |
| Regulated treasury | possible (multi-sig + Magnetar) | YES — operator-controlled |
| Permissionless DEX | YES | NO |
| Sovereign L1 finality | YES (PQ-heavy cert per LP-217; was "Polaris" in LP-017) | NO |

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.

Fireblocks parity

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.

Rationale

Why distinct extension packages

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.

Why wire byte-identity

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.

Why compose 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.

Why approval is mandatory

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.

Backwards Compatibility

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.

Test Cases

Each extension package ships its own test suite:

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:

Composition with mpc/pkg/kms:

Composition with mpc/pkg/hsm:

Reference Implementation

Security Considerations

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

Copyright and related rights waived via CC0.