mirror of
https://github.com/luxfi/magnetar.git
synced 2026-07-27 02:53:47 +00:00
316 lines
13 KiB
Markdown
316 lines
13 KiB
Markdown
# Magnetar --- Security Posture by Chain Profile
|
|||
|
|
|
||
|
|
This document is the honest threat-model statement for Magnetar's two
|
||
|
|
canonical Combine paths, parameterised by the chain-security profile
|
||
|
|
the embedding consensus layer publishes.
|
||
|
|
|
||
|
|
It is the load-bearing prose for the strict-PQ profile gate landed at
|
||
|
|
2026-06-01 in `luxfi/threshold/pkg/thresholdd/magnetar.go` (Combine
|
||
|
|
gate + t-of-n attested-combiner pool).
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 1. Two profiles, two threat models
|
||
|
|
|
||
|
|
Magnetar exposes ONE wire format (MAGS / MAGG, byte-identical to
|
||
|
|
single-party FIPS 205) and exactly TWO Combine paths whose acceptance
|
||
|
|
is gated by the chain-security profile in effect:
|
||
|
|
|
||
|
|
| Profile | Combine path | TCB |
|
||
|
|
|---------------------|---------------------------------------|------------------------------------|
|
||
|
|
| `legacy-compat` | strict-atom commodity-host (v1.1) | public combiner host (microseconds)|
|
||
|
|
| `strict-PQ` | TEE-attested t-of-n pool | measured enclave (only) |
|
||
|
|
|
||
|
|
Wire bytes are identical across both paths. The distinction is
|
||
|
|
entirely in **where the FIPS 205 master bytes are allowed to
|
||
|
|
transiently exist** during the SHAKE absorb step that emits the
|
||
|
|
signature.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 2. Residual gap closed by `strict-PQ`
|
||
|
|
|
||
|
|
### 2.1 Commodity-host Combine (legacy-compat)
|
||
|
|
|
||
|
|
Magnetar v1.1's strict-atom Combine path is implemented at
|
||
|
|
`ref/go/pkg/magnetar/thbsse_assemble.go::assembleSignatureBytes`
|
||
|
|
backed by `slhdsa_internal.go::slhSignAtom`. The discipline pins:
|
||
|
|
|
||
|
|
```
|
||
|
|
grep -rE "SK\.seed|SK\.prf|sk_seed|sk_prf" thbsse_assemble.go
|
||
|
|
```
|
||
|
|
|
||
|
|
returns zero matches. There is no named transient seed binder; the
|
||
|
|
master is decomposed into per-atom positional slices of a SHAKE
|
||
|
|
expansion buffer.
|
||
|
|
|
||
|
|
**Residual gap.** The bytes of the FIPS 205 master DO exist
|
||
|
|
transiently inside `derivedMaterial` (the SHAKE-expansion output
|
||
|
|
buffer) and `derivedExpandInput` (the Lagrange-reconstructed input to
|
||
|
|
SHAKE) for the duration of the SHAKE absorb --- approximately a few
|
||
|
|
microseconds on a modern host. A coredump or `/proc/self/mem` dump at
|
||
|
|
exactly the right wall-clock instant would observe them. This is the
|
||
|
|
strictest discipline available **without crossing into either**
|
||
|
|
(a) MPC-over-SHAKE (open research; multi-second per signature) or
|
||
|
|
(b) a TEE-attested host inside the TCB.
|
||
|
|
|
||
|
|
For the **legacy-compat profile**, this residency window is acceptable.
|
||
|
|
The combiner role is PUBLIC --- any peer can run the strict-atom
|
||
|
|
Combine on its own substrate. No host is privileged. The strict-atom
|
||
|
|
discipline is materially stronger than the v1.0
|
||
|
|
reveal-and-aggregate model and is the recommended commodity-host
|
||
|
|
posture.
|
||
|
|
|
||
|
|
### 2.2 TEE-attested Combine (strict-PQ)
|
||
|
|
|
||
|
|
For deployments that cannot tolerate the microsecond residency
|
||
|
|
window --- regulated custody, sovereign settlement, high-value
|
||
|
|
bridge --- the `strict-PQ` profile requires that every Combine route
|
||
|
|
through the `luxfi/threshold/protocols/slhdsa-tee` package. In this
|
||
|
|
path:
|
||
|
|
|
||
|
|
1. The wrapped master seed lives at-rest in an HSM
|
||
|
|
(`luxfi/mpc/pkg/hsm.Provider` --- file, AWS KMS, Azure Key Vault,
|
||
|
|
GCP Cloud KMS).
|
||
|
|
2. The seed is **only** unwrapped inside a hardware-attested TEE
|
||
|
|
(SEV-SNP / TDX / NRAS) whose binary + firmware chain-validate to
|
||
|
|
the vendor root (AMD KDS / Intel PCS / NVIDIA NRAS JWKS) and whose
|
||
|
|
measurement matches the operator-asserted RIM digest.
|
||
|
|
3. Each sign call is gated by `luxfi/mpc/pkg/kms.ReleaseGate.Issue`
|
||
|
|
(fresh single-use nonce + epoch) and `Release` (RIM allowlist +
|
||
|
|
hardware allowlist + Require* issuers).
|
||
|
|
4. The FIPS 205 master bytes only ever exist inside the measured
|
||
|
|
enclave. The host process never sees plaintext --- it sees only
|
||
|
|
the AEAD-sealed session key, the wrapped seed ciphertext, and the
|
||
|
|
resulting wire signature.
|
||
|
|
|
||
|
|
Output bytes are byte-identical to single-party FIPS 205
|
||
|
|
`SignDeterministic` on the same `(seed, msg, ctx)` tuple. Any
|
||
|
|
verifier holding the canonical MAGG group public key validates
|
||
|
|
without any awareness of the TEE substrate.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 3. Profile gate (one function, one place)
|
||
|
|
|
||
|
|
The dispatcher-side gate is the single function
|
||
|
|
`magnetarRefuseUnderStrictPQ` at
|
||
|
|
`luxfi/threshold/pkg/thresholdd/magnetar.go`. It mirrors the
|
||
|
|
precompile-side gate `contract.RefuseUnderStrictPQ` in
|
||
|
|
`luxfi/precompile/contract/strict_pq.go` and follows the same
|
||
|
|
discipline:
|
||
|
|
|
||
|
|
- ONE function (no duplication across schemes).
|
||
|
|
- ONE place (called at the top of `Sign` and `Sign_Ctx`).
|
||
|
|
- ONE canonical refusal sentinel
|
||
|
|
(`slhdsatee.ErrMagnetarNoTEEAttestation`, wire-stable identifier
|
||
|
|
`ERR_MAGNETAR_NO_TEE_ATTESTATION`).
|
||
|
|
|
||
|
|
Under `ProfileStrictPQ`:
|
||
|
|
|
||
|
|
- `Sign` → refuses with `ErrMagnetarNoTEEAttestation`.
|
||
|
|
- `Sign_Ctx` → refuses with `ErrMagnetarNoTEEAttestation`.
|
||
|
|
- `Sign_TEE` → allowed (single-host attested-release sign).
|
||
|
|
- `Combine_TEE` → allowed (t-of-n attested-combiner pool sign).
|
||
|
|
|
||
|
|
Under `ProfileLegacyCompat` (default):
|
||
|
|
|
||
|
|
- All four paths allowed.
|
||
|
|
- `Combine_TEE` still works when a pool is wired --- the strict-PQ
|
||
|
|
posture is opt-in even on legacy-compat chains.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 4. T-of-n attested-combiner pool
|
||
|
|
|
||
|
|
The strict-PQ Combine path is **not single-host**. The pool at
|
||
|
|
`luxfi/threshold/protocols/slhdsa-tee/pool.go` registers `n` attested
|
||
|
|
combiner endpoints; each holds an independently provisioned
|
||
|
|
`slhdsa-tee.Signer` (independent wrapped seed). Combine selects the
|
||
|
|
first `t` members whose attestation freshness is inside the rotation
|
||
|
|
window, drives each member's full Sign machinery, and **refuses if
|
||
|
|
the produced bytes are not byte-identical**.
|
||
|
|
|
||
|
|
SLH-DSA `SignDeterministic` is byte-stable: same `(seed, msg, ctx)`
|
||
|
|
→ same bytes. Two attested combiners holding the same wrapped seed
|
||
|
|
under the same RIM MUST produce identical output. Divergence indicates
|
||
|
|
a misconfigured or compromised combiner; the pool refuses with
|
||
|
|
`ErrMagnetarSignatureDivergence` --- no silent winner-picking.
|
||
|
|
|
||
|
|
### 4.1 Threshold-of-attested-combiners policy
|
||
|
|
|
||
|
|
The default deployment posture is **t = 2, n = 3**. Operator policy
|
||
|
|
may pin higher `t` for higher-value deployments. The constructor
|
||
|
|
refuses `t < 2` (no single-attested-host quorum):
|
||
|
|
|
||
|
|
```go
|
||
|
|
slhdsatee.NewCombinerPool(slhdsatee.CombinerPoolConfig{
|
||
|
|
Threshold: 2, // t-of-n, t >= 2
|
||
|
|
RotationWindow: 60 * time.Second, // per-member attestation TTL
|
||
|
|
KnownIssuers: map[string]struct{}{ // vendor root pin
|
||
|
|
"amd.sev.snp": {},
|
||
|
|
},
|
||
|
|
})
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4.2 Rotation window
|
||
|
|
|
||
|
|
Each pool member's attestation freshness is bounded by
|
||
|
|
`RotationWindow`. Outside the window, the member is treated as
|
||
|
|
**stale** and not eligible for the quorum. Combine refuses with
|
||
|
|
`ErrMagnetarStaleAttestation` when insufficient fresh members exist.
|
||
|
|
|
||
|
|
Re-attestation is the operator control-plane's responsibility: a
|
||
|
|
periodic tick captures fresh `SNP_GUEST_REQUEST` / `TDREPORT` /
|
||
|
|
NRAS-JWT bytes from each combiner and submits via
|
||
|
|
`CombinerPool.Attest`. The Combine path is then a freshness **read**
|
||
|
|
--- it never blocks on KDS / PCS / NRAS network roundtrips.
|
||
|
|
|
||
|
|
**Recommended cadence**: every Lux block (`~1s`), with a 60s
|
||
|
|
`RotationWindow`. This absorbs ~60 blocks of control-plane latency
|
||
|
|
between attestation capture and the next sign request. Latency-
|
||
|
|
sensitive deployments may shrink to 5s rotation; long-lived custody
|
||
|
|
deployments may extend to 5min with periodic compensating audits.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 5. Attestation kinds in production
|
||
|
|
|
||
|
|
The strict-PQ profile accepts evidence whose `Vendor` lies in the
|
||
|
|
operator-configured `KnownIssuers` set. Today the three supported
|
||
|
|
kinds are:
|
||
|
|
|
||
|
|
| Kind | Vendor string | Verifier status |
|
||
|
|
|-------------|---------------------|----------------------------------------------------|
|
||
|
|
| `sev_snp` | `amd.sev.snp` | PRODUCTION (`luxfi/mpc/cc/attest/sev.go` + go-sev-guest) |
|
||
|
|
| `tdx` | `intel.tdx` | STUB (`luxfi/mpc/cc/attest/tdx.go`, ErrNotImplemented; tracked #222 stage 2) |
|
||
|
|
| `nras` | `nvidia.nras.v1` | STUB (`luxfi/mpc/cc/attest/nras.go`, ErrNotImplemented; tracked #222 stage 3) |
|
||
|
|
|
||
|
|
**Production today:** strict-PQ deployments MUST use SEV-SNP. TDX and
|
||
|
|
NRAS chain-validators are stubs that return `ErrNotImplemented` ---
|
||
|
|
the pool refuses any Attest call routing through them. When the
|
||
|
|
upstream verifiers ship, no code changes are required at the magnetar
|
||
|
|
dispatcher --- the pool's `KnownIssuers` set is the only knob.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 6. Defence in depth: each layer is independently complete
|
||
|
|
|
||
|
|
Composition: each step in the strict-PQ Combine path is independently
|
||
|
|
complete and refuses on its own without relying on later steps:
|
||
|
|
|
||
|
|
| Step | Responsibility | Refusal sentinel |
|
||
|
|
|------|----------------|------------------|
|
||
|
|
| Profile gate | `Sign` / `Sign_Ctx` on strict-PQ | `ErrMagnetarNoTEEAttestation` |
|
||
|
|
| Pool wired | `Combine_TEE` requires pool | `errMagnetarPoolUnwired` |
|
||
|
|
| Freshness | Attestation < rotation window | `ErrMagnetarStaleAttestation` |
|
||
|
|
| Quorum | At least `t` fresh members | `ErrMagnetarInsufficientQuorum` |
|
||
|
|
| Vendor pin | KnownIssuers ∋ report.Vendor | `slhdsa-tee: vendor not in KnownIssuers` |
|
||
|
|
| Approval | (if ApprovalRequired) signed intent | `slhdsatee.ErrApprovalDenied` |
|
||
|
|
| Chain | `attest.Dispatch` validates evidence | `attest.ErrChainInvalid` / `ErrSignatureInvalid` |
|
||
|
|
| RIM/HW | gate.Release checks allowlists | `slhdsatee.ErrPolicyRefused` |
|
||
|
|
| HSM | wrapped seed unsealable inside TEE | `slhdsatee.ErrCorruptWrappedSeed` |
|
||
|
|
| Divergence | byte-equality across quorum | `ErrMagnetarSignatureDivergence` |
|
||
|
|
|
||
|
|
**No silent fallback.** A failure at any step is hard refusal at the
|
||
|
|
pool level --- the strict-PQ profile cannot silently drop a member
|
||
|
|
mid-flight.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 7. Threat model summary
|
||
|
|
|
||
|
|
### 7.1 Adversary capabilities (strict-PQ)
|
||
|
|
|
||
|
|
We model an adversary that controls:
|
||
|
|
|
||
|
|
- the operator's host process outside the TEE (compromised binary,
|
||
|
|
malicious operator);
|
||
|
|
- the operator's at-rest HSM ciphertext (compromised HSM admin,
|
||
|
|
stolen volume snapshot);
|
||
|
|
- the network between the operator and the chain (replay,
|
||
|
|
reordering, drop).
|
||
|
|
|
||
|
|
We do NOT model an adversary that:
|
||
|
|
|
||
|
|
- has compromised AMD / Intel / NVIDIA's vendor signing keys
|
||
|
|
(assumed-out, defence-in-depth via vendor key rotation);
|
||
|
|
- has physical access to the TEE die during sign (assumed-out for
|
||
|
|
CSP-hosted deployments; mitigated by chassis-attest for on-prem);
|
||
|
|
- has compromised the wall-clock or RotationWindow policy (assumed
|
||
|
|
honest; the rotation is operator policy, not a cryptographic
|
||
|
|
guarantee --- monitor for skew).
|
||
|
|
|
||
|
|
### 7.2 Adversary capabilities (legacy-compat)
|
||
|
|
|
||
|
|
Same as strict-PQ, MINUS the explicit acceptance that the master
|
||
|
|
bytes transiently exist in the public combiner's SHAKE-expansion
|
||
|
|
buffers for ~microseconds. This window is exposed to:
|
||
|
|
|
||
|
|
- a coredump / `/proc/self/mem` snapshot fired during the Combine
|
||
|
|
window;
|
||
|
|
- a kernel exploit that reads user-space process memory while the
|
||
|
|
Combine goroutine is suspended.
|
||
|
|
|
||
|
|
Neither is exploitable without root-equivalent access to the host
|
||
|
|
running the public combiner. The combiner is public --- any peer can
|
||
|
|
run it on its own substrate. The window IS finite microseconds. We
|
||
|
|
ship this with full disclosure rather than hide it.
|
||
|
|
|
||
|
|
### 7.3 Why strict-PQ closes residual #1 fully
|
||
|
|
|
||
|
|
In strict-PQ mode the host running the Sign / Combine routine is a
|
||
|
|
measured enclave. The kernel running on the enclave is the vendor-
|
||
|
|
signed enclave runtime. There is no `/proc/self/mem` from the host
|
||
|
|
side that observes enclave RAM --- the hardware encrypts pages
|
||
|
|
in-place against a chip-rooted key the host CPU cannot read. The
|
||
|
|
microsecond residency window still exists, but it exists inside the
|
||
|
|
enclave's address space, which is cryptographically opaque to every
|
||
|
|
party except the enclave itself.
|
||
|
|
|
||
|
|
This is the standard guarantee SEV-SNP / TDX provide and is the
|
||
|
|
basis on which institutional-custody platforms (AWS Nitro,
|
||
|
|
Google Cloud Confidential Computing, Azure Confidential VMs) build.
|
||
|
|
Strict-PQ Magnetar is no more, and no less, secure than the
|
||
|
|
underlying CSP enclave guarantee.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 8. Verification
|
||
|
|
|
||
|
|
The gate is exercised end-to-end at
|
||
|
|
`luxfi/threshold/pkg/thresholdd/magnetar_tee_gate_test.go`:
|
||
|
|
|
||
|
|
- `TestMagnetarCombine_StrictPQProfile_RequiresTEE` --- strict-PQ
|
||
|
|
refuses `Sign` / `Sign_Ctx`; permits `Combine_TEE` only when pool
|
||
|
|
is wired.
|
||
|
|
- `TestMagnetarCombine_AttestationVerified_AllowsSign` --- end-to-end
|
||
|
|
pool drive against committed AMD Milan SEV-SNP fixture; output
|
||
|
|
verifies under `magnetar.VerifyBytes`.
|
||
|
|
- `TestMagnetarCombine_StaleAttestation_RejectsSign` --- members
|
||
|
|
outside rotation window refused; partial re-attestation recovers.
|
||
|
|
- `TestMagnetarCombine_TwoOfThreeAttestedCombiners_MatchSig` ---
|
||
|
|
2-of-3 quorum surfaces signature; 1-of-3 refuses.
|
||
|
|
- `TestMagnetarCombine_SignatureDivergence_HardRefusal` ---
|
||
|
|
pool refuses byte-divergent quorum output.
|
||
|
|
|
||
|
|
All five tests pass under `-race` against the committed Milan
|
||
|
|
SEV-SNP fixture (`pkg/thresholdd/testdata/sev_snp_attestation_milan.bin`,
|
||
|
|
`sev_snp_vcek_milan.cer`) replayed via `dispatchKDSReplay()`.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 9. Cross-references
|
||
|
|
|
||
|
|
- Strict-atom Combine load-bearing prose: `ASSEMBLE-INVARIANT.md`.
|
||
|
|
- Strict-atom KAT: `TestSlhdsaInternal_ByteEqualToCirclSign`.
|
||
|
|
- Profile sentinel value: `slhdsatee.ProfileStrictPQ`
|
||
|
|
(`luxfi/threshold/protocols/slhdsa-tee/profile.go`).
|
||
|
|
- Precompile-side strict-PQ gate (analogous):
|
||
|
|
`luxfi/precompile/contract/strict_pq.go`.
|
||
|
|
- Pool implementation:
|
||
|
|
`luxfi/threshold/protocols/slhdsa-tee/pool.go`.
|
||
|
|
- Chain attestation verifiers:
|
||
|
|
`luxfi/mpc/cc/attest/{sev,tdx,nras}.go`.
|
||
|
|
- Magnetar v1.0 → v1.1 strict-atom closure: `CHANGELOG.md` v1.1.0.
|