mirror of
https://github.com/luxfi/pq.git
synced 2026-10-30 15:22:02 +00:00
Decomplects the strict-PQ posture from the per-package
SecurityProfile types that previously lived in luxfi/warp,
luxfi/zap, lux/dex, luxfi/fhe. Each of those redeclared the same
three constants + helpers + ErrClassicalAuthForbidden sentinel,
which forced cross-package callers to convert between equivalent
types and prevented errors.Is from working across boundaries.
The decomplected design:
VALUE — lives in lux/pq:
Mode (Classical / Hybrid / StrictPQ)
ModeFromPQFlag(bool) Mode
ModeFromString(string) (Mode, error)
(Mode).IsPostQuantum() bool
(Mode).IsPQAware() bool
(Mode).Profile() *Profile ← bridge to precompile forbid-bits
ErrClassicalAuthForbidden ← umbrella sentinel, errors.Is works everywhere
PLACES — stay in each consumer:
warp.RequireMLDSACertSetForMode(mode, envelope) error
zap.RequireAttestationForMode(mode, attestation, ctx, verify) error
dex.VerifyOrderForMode(mode, order) error
fhe.DefaultParamsForMode(mode) ParametersLiteral
evm.txpool admits MLDSATxType only when chainConfig.PQ != nil
(which lux/pq.Mode.Profile() populates at vm.Initialize)
Each consumer keeps its own RequireFooForMode because the INPUTS
differ (envelope / order / precompile-op / FHE-params), but they
all dispatch on the SAME pq.Mode type and return the SAME
pq.ErrClassicalAuthForbidden sentinel. Audit pipelines grep one
identifier across every refusal site.
Mode.Profile() is the bridge to the existing pq.Profile
(precompile forbid-bits, from profile.go): ModeStrictPQ →
Strict(), ModeClassical + ModeHybrid → Permissive(). Hybrid
validates PQ authentication at higher layers but doesn't refuse
classical primitives inside contracts.
Tests pin the predicate matrix (4 modes × 3 predicates), the
Mode → Profile bridge, ModeFromString case-sensitivity, and
errors.Is wrapping semantics.
Next: migrate warp, zap, dex, fhe, cli to import pq.Mode in
place of their local SecurityProfile types.
105 lines
3.2 KiB
Go
105 lines
3.2 KiB
Go
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package pq
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
)
|
|
|
|
func TestMode_StringAndPredicates(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
mode Mode
|
|
wantString string
|
|
wantPostQuantum bool
|
|
wantPQAware bool
|
|
}{
|
|
{ModeClassical, "classical", false, false},
|
|
{ModeHybrid, "hybrid", false, true},
|
|
{ModeStrictPQ, "strict-pq", true, true},
|
|
{Mode(99), "unknown", false, false},
|
|
} {
|
|
if tc.mode.String() != tc.wantString {
|
|
t.Errorf("Mode(%d).String() = %q, want %q",
|
|
tc.mode, tc.mode.String(), tc.wantString)
|
|
}
|
|
if tc.mode.IsPostQuantum() != tc.wantPostQuantum {
|
|
t.Errorf("%s.IsPostQuantum() = %t, want %t",
|
|
tc.mode, tc.mode.IsPostQuantum(), tc.wantPostQuantum)
|
|
}
|
|
if tc.mode.IsPQAware() != tc.wantPQAware {
|
|
t.Errorf("%s.IsPQAware() = %t, want %t",
|
|
tc.mode, tc.mode.IsPQAware(), tc.wantPQAware)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestMode_ProfileLink pins the bridge between the higher-level
|
|
// named mode and the lower-level precompile forbid-bits. The
|
|
// invariant: strict-PQ mode installs Strict() at the EVM
|
|
// precompile layer; classical and hybrid install Permissive().
|
|
func TestMode_ProfileLink(t *testing.T) {
|
|
if p := ModeStrictPQ.Profile(); !p.ForbidEcrecover || !p.ForbidBLS12381 {
|
|
t.Error("ModeStrictPQ.Profile() did not return Strict()")
|
|
}
|
|
for _, m := range []Mode{ModeClassical, ModeHybrid} {
|
|
p := m.Profile()
|
|
if p.ForbidEcrecover || p.ForbidBLS12381 {
|
|
t.Errorf("%s.Profile() returned Strict() (want Permissive())", m)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestModeFromPQFlag(t *testing.T) {
|
|
if got := ModeFromPQFlag(true); got != ModeStrictPQ {
|
|
t.Errorf("ModeFromPQFlag(true) = %s, want strict-pq", got)
|
|
}
|
|
if got := ModeFromPQFlag(false); got != ModeClassical {
|
|
t.Errorf("ModeFromPQFlag(false) = %s, want classical", got)
|
|
}
|
|
}
|
|
|
|
func TestModeFromString(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
input string
|
|
want Mode
|
|
wantErr bool
|
|
}{
|
|
{"classical", ModeClassical, false},
|
|
{"hybrid", ModeHybrid, false},
|
|
{"strict-pq", ModeStrictPQ, false},
|
|
{"", 0, true},
|
|
{"PQ", 0, true},
|
|
{"strict_pq", 0, true},
|
|
{"Strict-PQ", 0, true}, // case sensitive
|
|
} {
|
|
got, err := ModeFromString(tc.input)
|
|
if (err != nil) != tc.wantErr {
|
|
t.Errorf("ModeFromString(%q) err=%v, wantErr=%t",
|
|
tc.input, err, tc.wantErr)
|
|
continue
|
|
}
|
|
if !tc.wantErr && got != tc.want {
|
|
t.Errorf("ModeFromString(%q) = %s, want %s",
|
|
tc.input, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestErrClassicalAuthForbidden_IsCheckable(t *testing.T) {
|
|
wrapped := errors.New("envelope rejected: " + ErrClassicalAuthForbidden.Error())
|
|
// Direct sentinel comparison via errors.Is works because the
|
|
// sentinel is a package-level var. Downstream packages wrap
|
|
// it with fmt.Errorf("...: %w", pq.ErrClassicalAuthForbidden);
|
|
// errors.Is unwraps and matches.
|
|
if errors.Is(wrapped, ErrClassicalAuthForbidden) {
|
|
t.Error("errors.Is matched on string-wrapped (not %w-wrapped) error — that's wrong, this test pins the negative case")
|
|
}
|
|
// Positive: %w-wrapped errors should unwrap correctly.
|
|
formatted := errors.Join(ErrClassicalAuthForbidden, errors.New("at gate X"))
|
|
if !errors.Is(formatted, ErrClassicalAuthForbidden) {
|
|
t.Error("errors.Is failed to match a Join-wrapped sentinel")
|
|
}
|
|
}
|