mirror of
https://github.com/luxfi/magnetar.git
synced 2026-07-27 02:53:47 +00:00
True no-reconstruction threshold SLH-DSA is impossible in the no-dealer /
no-preprocessing model a public, leaderless, permissionless chain needs
(Kondi-Kumar-Vanegas: extractable hash-based signatures cannot be thresholded
by black-box hash use). Stop pretending otherwise.
De-cheat:
- DELETE the circular/vacuous proofs that manufactured false assurance:
Magnetar_N1_Atom_Refinement.ec, Magnetar_N1_SHAKE_Expand.ec,
Magnetar_N4_KeyDeriveStable.ec, lemmas/{Magnetar_CT,SLHDSA_Functional}.ec.
The headline "strict-atom byte-equality" theorem was `apply <axiom that
restates the theorem>`; the Lean side was `sorry`/`:= True`.
- Re-open MAGNETAR-STRICT-ATOM (BLOCKERS): the public combiner DOES
reconstruct the full FIPS 205 master every signature; the v1.1 "closure"
only renamed identifiers. PROOF-CLAIMS / AXIOM-INVENTORY / TCB docs now say
what the code does.
- Re-label the name-grep "strict-atom" / "CT" checks as identifier-hygiene
lint, NOT security or constant-time properties.
- PVSS-DKG open-reveal (publishes the master to any observer) is gated as a
TEST-ONLY path with HONEST LIMITATIONS; production does not rely on it.
- Remove dead htRootCompute; staticcheck clean.
Honest three-leg posture (SPEC 1.0, BLOCKERS):
- Permissionless production = INDEPENDENT FIPS 205 sigs + the weighted quorum
certificate (luxfi/consensus), optionally STARK/FRI-compressed (luxfi/p3q).
No key sharing, no reconstruction.
- Trusted-hardware custody = TEE-attested combiner (trust-relocation, NOT MPC).
- THBS-SE = RESEARCH-ONLY (transient seed reconstruction at the combiner);
the T-SLH-DSA-MPC track (MPC over SHAKE) is the other research escape hatch.
Tests green (CGO=1, 71s). Net -923 lines.
167 lines
5.5 KiB
Go
167 lines
5.5 KiB
Go
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
//go:build ct
|
|
// +build ct
|
|
|
|
// Package dudect carries a Magnetar source-hygiene LINT. Run via:
|
|
//
|
|
// go test -tags ct -run TestStrictAtom_IdentHygiene ./ct/dudect/...
|
|
//
|
|
// or via scripts/checks/dudect-smoke.sh.
|
|
//
|
|
// =====================================================================
|
|
//
|
|
// THIS IS NOT A CONSTANT-TIME TEST. IT IS A NAME LINT.
|
|
//
|
|
// =====================================================================
|
|
//
|
|
// The check below parses the Combine emit-path sources and asserts
|
|
// that no identifier whose NAME is in a hardcoded list feeds an `if`,
|
|
// a `switch`, or an index expression. That is a property of variable
|
|
// NAMES, not of timing or memory-access behavior:
|
|
//
|
|
// - It does NOT measure execution time (no dudect, no statistical
|
|
// test, no cycle counting).
|
|
// - It does NOT track data flow: a secret that flows into a branch
|
|
// under a name NOT in the list is invisible to it.
|
|
// - It does NOT establish constant-timeness of any function.
|
|
//
|
|
// More fundamentally, constant-time is the WRONG property to assert
|
|
// for this path at all: `assembleSignatureBytes` reconstructs the
|
|
// full FIPS 205 master into `derivedMaterial`. A timing side-channel
|
|
// is moot when the secret is already sitting in plaintext in the
|
|
// combiner's buffer (see BLOCKERS.md::MAGNETAR-STRICT-ATOM-V11 and
|
|
// ASSEMBLE-INVARIANT.md). Treat this file as an identifier-style lint
|
|
// that keeps the named scratch buffers out of obvious branch/index
|
|
// positions, and nothing more.
|
|
package dudect
|
|
|
|
import (
|
|
"fmt"
|
|
"go/ast"
|
|
"go/parser"
|
|
"go/token"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"testing"
|
|
)
|
|
|
|
// strictAtomSources are the Go files the v1.1 CT track inspects. They
|
|
// are the entire trusted-computing-base of the strict-atom emit path.
|
|
var strictAtomSources = []string{
|
|
"../../ref/go/pkg/magnetar/thbsse_assemble.go",
|
|
"../../ref/go/pkg/magnetar/slhdsa_internal.go",
|
|
}
|
|
|
|
// secretTaggedIdent patterns. This is a NAME list, not a taint
|
|
// analysis. The lint flags an identifier ONLY when its spelling
|
|
// appears in this set AND it sits in a branch/index position. A
|
|
// secret carried under any other name is not detected. This catches
|
|
// nothing about timing; it is a style guard that keeps these
|
|
// specific scratch-buffer names out of obvious control-flow / index
|
|
// positions.
|
|
var secretTaggedIdents = map[string]struct{}{
|
|
"derivedMaterial": {},
|
|
"derivedExpandInput": {},
|
|
"derivedPkSeedSegment": {},
|
|
"secretSegment": {},
|
|
"prfAbsorb": {},
|
|
"prfMsgAbsorb": {},
|
|
}
|
|
|
|
// TestStrictAtom_IdentHygiene_NoNamedBufferInBranch is an
|
|
// identifier-hygiene LINT (NOT a constant-time property, NOT a
|
|
// security gate). It parses the Combine emit-path sources, walks the
|
|
// AST, and asserts no identifier whose NAME is in secretTaggedIdents
|
|
// feeds a control-flow branch or an index expression. See the package
|
|
// banner: this measures nothing about timing and proves nothing about
|
|
// confidentiality.
|
|
func TestStrictAtom_IdentHygiene_NoNamedBufferInBranch(t *testing.T) {
|
|
for _, rel := range strictAtomSources {
|
|
t.Run(filepath.Base(rel), func(t *testing.T) {
|
|
abs, err := filepath.Abs(rel)
|
|
if err != nil {
|
|
t.Fatalf("filepath.Abs(%s): %v", rel, err)
|
|
}
|
|
if _, err := os.Stat(abs); err != nil {
|
|
t.Fatalf("stat(%s): %v", abs, err)
|
|
}
|
|
fset := token.NewFileSet()
|
|
f, err := parser.ParseFile(fset, abs, nil, parser.ParseComments)
|
|
if err != nil {
|
|
t.Fatalf("parser.ParseFile(%s): %v", rel, err)
|
|
}
|
|
|
|
var violations []string
|
|
|
|
ast.Inspect(f, func(n ast.Node) bool {
|
|
switch v := n.(type) {
|
|
case *ast.IfStmt:
|
|
if name, ok := findSecretIdent(v.Cond); ok {
|
|
pos := fset.Position(v.Pos())
|
|
violations = append(violations, fmt.Sprintf(
|
|
"if-branch on secret-tagged %q at %s:%d",
|
|
name, filepath.Base(pos.Filename), pos.Line))
|
|
}
|
|
case *ast.SwitchStmt:
|
|
if name, ok := findSecretIdent(v.Tag); ok {
|
|
pos := fset.Position(v.Pos())
|
|
violations = append(violations, fmt.Sprintf(
|
|
"switch on secret-tagged %q at %s:%d",
|
|
name, filepath.Base(pos.Filename), pos.Line))
|
|
}
|
|
case *ast.IndexExpr:
|
|
// Only flag when the INDEX is a secret-tagged
|
|
// ident; indexing INTO a secret buffer (the LHS)
|
|
// is fine.
|
|
if name, ok := findSecretIdent(v.Index); ok {
|
|
pos := fset.Position(v.Pos())
|
|
violations = append(violations, fmt.Sprintf(
|
|
"index using secret-tagged %q at %s:%d",
|
|
name, filepath.Base(pos.Filename), pos.Line))
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
|
|
if len(violations) > 0 {
|
|
for _, v := range violations {
|
|
t.Errorf("strict-atom ident-hygiene lint: %s", v)
|
|
}
|
|
t.Fatalf("%d strict-atom ident-hygiene lint hit(s) in %s",
|
|
len(violations), rel)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// findSecretIdent returns the name of the first secret-tagged
|
|
// identifier appearing in the expression tree e, or "", false if none.
|
|
func findSecretIdent(e ast.Expr) (string, bool) {
|
|
if e == nil {
|
|
return "", false
|
|
}
|
|
var found string
|
|
ast.Inspect(e, func(n ast.Node) bool {
|
|
id, ok := n.(*ast.Ident)
|
|
if !ok {
|
|
return true
|
|
}
|
|
if _, secret := secretTaggedIdents[id.Name]; secret {
|
|
found = id.Name
|
|
return false
|
|
}
|
|
return true
|
|
})
|
|
if found != "" {
|
|
return found, true
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
// strictAtomCTMethodologyDoc is the prose statement of the CT obligation
|
|
// the harness enforces. Linked from README.md.
|
|
var strictAtomCTMethodologyDoc = regexp.MustCompile(`(?m)^## Methodology summary$`)
|