Files
threshold/protocols/slhdsa-tee/profile.go
zeekay eeb9e3f27a protocols/slhdsa-tee + pkg/thresholdd: strict-PQ profile gate + t-of-n attested-combiner pool
Closes the magnetar v1.1 strict-atom transient-SHAKE-bytes residual for
the strict-PQ chain profile.

Why
---

magnetar v1.1's strict-atom Combine path (thbsse_assemble.go) reduces
the FIPS 205 master's residency window to a few microseconds inside
the public combiner's SHAKE-expansion buffers. That window is
acceptable for legacy-compat (public combiner, no host in TCB) but
not for strict-PQ deployments (regulated custody, sovereign
settlement, high-value bridge). Closing the residual fully requires
either MPC-over-SHAKE (open research) or a TEE-attested combiner.
This commit takes the second path: route every strict-PQ Combine
through luxfi/threshold/protocols/slhdsa-tee.

What
----

* `protocols/slhdsa-tee/profile.go` --- `ChainSecurityProfile` value
  type (LegacyCompat / StrictPQ) + canonical refusal sentinels
  (`ErrMagnetarNoTEEAttestation` = `ERR_MAGNETAR_NO_TEE_ATTESTATION`,
  `ErrMagnetarStaleAttestation`, `ErrMagnetarInsufficientQuorum`,
  `ErrMagnetarSignatureDivergence`).

* `protocols/slhdsa-tee/pool.go` --- `CombinerPool` t-of-n attested-
  combiner registry. Each member binds a `Signer` to its
  `LastVerifiedReport`; `Attest` refreshes freshness state via
  `env.VerifyEvidence` and pins vendor against `KnownIssuers`;
  `Combine` selects the first `t` fresh members, drives each signer's
  full slhdsa-tee.Sign machinery, and refuses byte-divergent output.
  Defaults: Threshold=2, RotationWindow=60s, KnownIssuers={amd.sev.snp}.

* `pkg/thresholdd/magnetar.go` --- scheme-bound profile gate. Single
  function `magnetarRefuseUnderStrictPQ` (one function, one place ---
  mirrors `precompile/contract.RefuseUnderStrictPQ`). Called at the
  top of `Sign` and `Sign_Ctx`. Under strict-PQ both refuse with the
  canonical sentinel; only `Sign_TEE` (single-host) and the new
  `Combine_TEE` (t-of-n pool) produce signatures. Three new accessors
  on the scheme: `SetChainSecurityProfile`, `SetCombinerPool`,
  `AttestCombinerMember`.

* `Combine_TEE` --- the canonical strict-PQ surface. Takes a slice of
  `PoolCombineMember` (per-member attestation payload + name) + shared
  jobID/msg/ctx, drives `CombinerPool.Combine`, returns the agreed
  wire bytes + per-member audit signatures.

Composition with the existing resolver gate
-------------------------------------------

The sister-agent resolver gate (`thresholdd.RefuseUnderStrictPQ` +
`ChainProfileResolver`) fires first in the JSON-RPC routing layer
when the per-request `X-Chain-ID` resolves to strict-PQ. The new
scheme-bound gate is the orthogonal second axis: it refuses whenever
the process-wide profile binding says strict-PQ, regardless of
chain-ID resolution state. Both pass = sign proceeds. Either refuses
= hard refusal.

Tests
-----

* `pkg/thresholdd/magnetar_tee_gate_test.go` (5 tests):
  - `TestMagnetarCombine_StrictPQProfile_RequiresTEE`
  - `TestMagnetarCombine_AttestationVerified_AllowsSign`
  - `TestMagnetarCombine_StaleAttestation_RejectsSign`
  - `TestMagnetarCombine_TwoOfThreeAttestedCombiners_MatchSig`
  - `TestMagnetarCombine_SignatureDivergence_HardRefusal`

* `protocols/slhdsa-tee/pool_test.go` (5 tests):
  - `TestCombinerPool_Constructor_Defaults`
  - `TestCombinerPool_AddMember`
  - `TestCombinerPool_Attest_VendorPin`
  - `TestCombinerPool_Combine_FreshnessGate`
  - `TestCombinerPool_Combine_ByteEqualityAcrossQuorum`
  - `TestCombinerPool_Combine_Divergence`

All 11 new tests green against the committed AMD Milan SEV-SNP
fixture. Full `./pkg/thresholdd/...` + `./protocols/slhdsa-tee/...`
suites pass.

Production attestation
----------------------

Strict-PQ deployments today MUST use SEV-SNP --- the only `cc/attest`
verifier currently production-implemented. TDX + NRAS verifiers
return `ErrNotImplemented` (tracked at lux/mpc#222 stages 2-3) and
the pool's `KnownIssuers` allowlist will admit them once shipped ---
no magnetar-side code change required.
2026-06-01 21:28:54 -07:00

76 lines
3.4 KiB
Go

// SPDX-License-Identifier: BSD-3-Clause
package slhdsatee
import "errors"
// ChainSecurityProfile is the value the consensus / chain layer
// publishes to express the residency posture it is willing to tolerate
// for SLH-DSA Combine.
//
// Decomplecting: profile is a value, not a place. The chain layer
// constructs it from its ChainConfig (e.g. the precompile
// StrictPQReporter the EVM precompiles use today) and hands it down
// at Sign call time. No global, no init() hook, no env var. The
// dispatcher reads it the same way the precompile contract.RefuseUnderStrictPQ
// helper reads its StrictPQReporter: ONE function, ONE place, ONE
// canonical refusal sentinel.
type ChainSecurityProfile int
const (
// ProfileLegacyCompat tolerates the strict-atom commodity-host
// Combine path. The FIPS 205 master bytes transiently exist in
// the public combiner's SHAKE-expansion buffers for a few
// microseconds. Acceptable for permissionless / community
// validation paths where no host is in the TCB; refused for
// strict-PQ deployments. This is the default.
ProfileLegacyCompat ChainSecurityProfile = 0
// ProfileStrictPQ requires every Combine to route through a
// TEE-attested combiner pool. The master bytes only ever exist
// inside a measured enclave (SEV-SNP / TDX / NRAS attested
// host) whose binary / firmware match the operator-asserted RIM
// and chain-validate to the vendor root. No commodity-host
// fallback; refusal is hard.
ProfileStrictPQ ChainSecurityProfile = 1
)
// String reports the canonical wire label for the profile.
func (p ChainSecurityProfile) String() string {
switch p {
case ProfileStrictPQ:
return "strict-PQ"
case ProfileLegacyCompat:
return "legacy-compat"
default:
return "unknown"
}
}
// ErrMagnetarNoTEEAttestation is the canonical refusal returned when
// a strict-PQ chain profile attempts to Combine without a valid TEE
// quote. The wire-stable identifier is "ERR_MAGNETAR_NO_TEE_ATTESTATION".
//
// Callers SHOULD switch on errors.Is(err, ErrMagnetarNoTEEAttestation)
// to distinguish profile-gate refusal from upstream attestation /
// release-gate errors.
var ErrMagnetarNoTEEAttestation = errors.New("ERR_MAGNETAR_NO_TEE_ATTESTATION: strict-PQ chain profile requires TEE-attested combiner")
// ErrMagnetarStaleAttestation is returned when an attested combiner's
// last successful re-attestation lies outside the configured rotation
// window. The bytes of the attestation chain-validate but the freshness
// guarantee has lapsed — operators MUST re-attest before the next sign.
var ErrMagnetarStaleAttestation = errors.New("ERR_MAGNETAR_STALE_ATTESTATION: combiner attestation outside rotation window")
// ErrMagnetarInsufficientQuorum is returned when fewer than the
// configured threshold-of-attested-combiners agreed on byte-identical
// output. The pool refuses to surface a signature whose origin is
// less than t attested combiners.
var ErrMagnetarInsufficientQuorum = errors.New("ERR_MAGNETAR_INSUFFICIENT_QUORUM: fewer than threshold attested combiners produced matching signature")
// ErrMagnetarSignatureDivergence is returned when the configured
// quorum DID produce signatures but they were not byte-identical.
// Hard refusal — divergence indicates a misconfigured or compromised
// combiner; the pool MUST NOT silently pick a winner.
var ErrMagnetarSignatureDivergence = errors.New("ERR_MAGNETAR_SIGNATURE_DIVERGENCE: attested combiners produced non-matching signature bytes")