Files
zeekay b33640f6b2 pq: promote RefuseUnder to per-Profile method, deprecate package-globals
Closes CR-1: per-chain PQ profile gating in geth core/vm requires a
method receiver so the call site reads `evm.chainConfig.PQ.RefuseUnder(op)`
without picking up the process-global state.

Changes:
  - (*Profile).RefuseUnder(op) Op) error  — canonical per-profile gate
  - Refuse / Active / SetActive marked Deprecated; one-shot stderr warning
    emitted on first use of any package-level shim.
  - Tests rewritten to exercise the method form on Strict / Permissive /
    nil receivers, plus OpUnknown / unknown-Op fallbacks.

Backwards-compatible at the wire/contract level: Profile.Hash() and the
admission set are unchanged. Only the gating API moved from function to
method; the deprecated function call sites still resolve, but each is
now annotated and emits a deprecation line.
2026-05-11 23:47:47 -07:00

116 lines
4.0 KiB
Go

package pq
import (
"errors"
"os"
"sync"
"sync/atomic"
)
// stderr is the destination for the deprecation warning emitted by the
// package-level shims. It is unexported; tests that call the shims
// will receive one stderr line per process.
var stderr = os.Stderr
// Family errors. Each names the specific primitive family it refuses;
// dashboards and replay tools attribute violations to the right family.
var (
ErrEcrecoverForbidden = errors.New("ecrecover forbidden by chain security profile (PQ)")
ErrP256VerifyForbidden = errors.New("p256Verify forbidden by chain security profile (PQ)")
ErrSHA256Forbidden = errors.New("sha256 forbidden by chain security profile (PQ)")
ErrRIPEMD160Forbidden = errors.New("ripemd160 forbidden by chain security profile (PQ)")
ErrBlake2FForbidden = errors.New("blake2F forbidden by chain security profile (PQ)")
ErrBn256Forbidden = errors.New("alt_bn128 (BN254) family forbidden by chain security profile (PQ)")
ErrBLS12381Forbidden = errors.New("BLS12-381 family forbidden by chain security profile (PQ)")
ErrKZGForbidden = errors.New("KZG point evaluation forbidden by chain security profile (PQ)")
)
// errorFor returns the family-specific error for op, or nil if op is
// admissible or unknown.
func errorFor(op Op) error {
switch op {
case OpEcrecover:
return ErrEcrecoverForbidden
case OpP256Verify:
return ErrP256VerifyForbidden
case OpSHA256:
return ErrSHA256Forbidden
case OpRIPEMD160:
return ErrRIPEMD160Forbidden
case OpBlake2F:
return ErrBlake2FForbidden
case OpBn256Add, OpBn256ScalarMul, OpBn256Pairing:
return ErrBn256Forbidden
case OpBLS12381G1Add, OpBLS12381G1MSM,
OpBLS12381G2Add, OpBLS12381G2MSM,
OpBLS12381Pairing, OpBLS12381MapG1, OpBLS12381MapG2:
return ErrBLS12381Forbidden
case OpKZGPointEval:
return ErrKZGForbidden
}
return nil
}
// RefuseUnder reports whether p admits op. Returns nil when admissible
// (including when p is nil — classical semantics) or family-specific
// error otherwise. RefuseUnder(OpUnknown) always returns nil.
//
// This is the canonical, per-profile gate. Callers should hold a
// *Profile (typically from chain config) and call this method directly.
func (p *Profile) RefuseUnder(op Op) error {
if p == nil || !p.Forbids(op) {
return nil
}
return errorFor(op)
}
// active holds a deprecated, package-level profile.
//
// Deprecated: a process-global profile is wrong for multi-chain hosts
// (last-writer-wins). Hold a *Profile per chain (e.g. on ChainConfig)
// and call (*Profile).RefuseUnder directly. The package-level state is
// retained as a compatibility shim only.
var active atomic.Pointer[Profile]
// warnOnce guards the one-shot deprecation warning emitted from each
// shim. We do not import a logger here (pq has no log dep); the message
// is written to standard error so misuse is loud but harmless.
var warnOnce sync.Once
func warnDeprecated() {
warnOnce.Do(func() {
// stderr write avoids a logger dependency; one line per process.
const msg = "pq: package-global active profile is deprecated; use per-chain ChainConfig.PQ and (*Profile).RefuseUnder instead\n"
// stderr write is best-effort.
_, _ = stderr.Write([]byte(msg))
})
}
// SetActive installs the package-level profile.
//
// Deprecated: store the profile per chain (e.g. ChainConfig.PQ) and use
// (*Profile).RefuseUnder. The package-global path has last-writer-wins
// semantics across chains hosted in one process.
func SetActive(p *Profile) {
warnDeprecated()
active.Store(p)
}
// Active returns the package-level profile, or nil if none has been
// installed (classical semantics).
//
// Deprecated: see [SetActive].
func Active() *Profile {
warnDeprecated()
return active.Load()
}
// Refuse reports whether the package-level profile admits op.
//
// Deprecated: call (*Profile).RefuseUnder on the per-chain profile.
// Retained as a shim that reads the deprecated package-level state.
func Refuse(op Op) error {
warnDeprecated()
return active.Load().RefuseUnder(op)
}