Files
pq/profile.go
zeekay ac01b821e8 pq: per-primitive flags, unified Op enum, package-level Active
Aligns the standalone luxfi/pq library to the same per-primitive model
the geth core/vm gate now uses. One way only: the same eight Forbid*
flags, the same sixteen Op constants, the same family errors.

Profile now has 8 fields (one per cryptographic primitive family)
instead of the prior 7 categorical flags. Op renamed from Op uint16
with mixed-case names to Op uint8 with namespace-qualified names
(OpEcrecover, OpBn256Pairing, etc.). Canonical encoding bumped to
version 2; new pinned hashes:

  strict:     9efdbf424085b0557866c22b0a4e0c48e2ed90c8c9e3f699d17a3e0783cb2128
  permissive: 256f4daa37543549e06bd101aab7f21a9b9dff46d322a9392e6b035cf4f0f638

API surface (the smallest possible):

  type Profile struct { 8 Forbid* fields }
  func Strict() / Permissive() *Profile
  func (p *Profile) Forbids(op Op) bool
  func (p *Profile) Encode() [16]byte
  func (p *Profile) Hash() [32]byte
  func DecodeProfile([16]byte) (*Profile, bool)

  type Op uint8 — 16 stable identifiers
  var Err*Forbidden — 8 family errors

  func SetActive(*Profile)        — package-level state, atomic
  func Active() *Profile
  func Refuse(Op) error           — reads Active
  func RefuseUnder(*Profile, Op)  — context-free

  const EnvVar = "QUASAR_STRICT_PQ"
  func FromEnv() *Profile

Dropped concepts: context binding (WithProfile/FromContext), MustRefuse,
Violation type, categorical Forbid* flags, Op uint16 with mixed cases.

20 unit tests; vet clean.
2026-05-11 16:29:11 -07:00

162 lines
4.3 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package pq
import "golang.org/x/crypto/sha3"
// Profile selects which classical operations the strict-PQ boundary
// admits. A small immutable value; safe to copy and to publish in
// block headers via [Profile.Hash].
//
// Each Forbid* field names a cryptographic primitive family — not an
// abstract category. A chain operator typically sets every field at
// once via [Strict]; a nil profile means "admit every operation".
//
// Flag layout matches the EVM precompile addresses one-to-one so a
// node binary can install a profile, the EVM can refuse a precompile,
// and an observer can read [Profile.Hash] from a block header — all
// describing the same eight booleans.
type Profile struct {
// Asymmetric signatures.
ForbidEcrecover bool // ecrecover at 0x01 (secp256k1 recovery)
ForbidP256Verify bool // p256Verify at 0x100 (RIP-7212, secp256r1)
// Non-SHA3 hash / compression precompiles.
ForbidSHA256 bool // 0x02
ForbidRIPEMD160 bool // 0x03
ForbidBlake2F bool // 0x09 (Blake2b compression — not BLAKE3)
// Pairing-friendly curves, per family.
ForbidBn256 bool // alt_bn128 at 0x06–0x08 (BN254)
ForbidBLS12381 bool // EIP-2537 at 0x0b–0x11
// Polynomial commitment.
ForbidKZG bool // kzgPointEvaluation at 0x0a (Cancun)
}
// Strict returns the canonical strict-PQ profile: every Forbid* flag
// is true. Mainnet PQ chains install this.
func Strict() *Profile {
return &Profile{
ForbidEcrecover: true,
ForbidP256Verify: true,
ForbidSHA256: true,
ForbidRIPEMD160: true,
ForbidBlake2F: true,
ForbidBn256: true,
ForbidBLS12381: true,
ForbidKZG: true,
}
}
// Permissive returns a profile with every Forbid* flag false. The zero
// value Profile{} is observationally equivalent and a nil *Profile is
// the canonical "no profile installed" state — all three admit every
// operation.
func Permissive() *Profile { return &Profile{} }
// Forbids reports whether the profile refuses op. A nil profile
// returns false (classical semantics).
func (p *Profile) Forbids(op Op) bool {
if p == nil {
return false
}
switch op {
case OpEcrecover:
return p.ForbidEcrecover
case OpP256Verify:
return p.ForbidP256Verify
case OpSHA256:
return p.ForbidSHA256
case OpRIPEMD160:
return p.ForbidRIPEMD160
case OpBlake2F:
return p.ForbidBlake2F
case OpBn256Add, OpBn256ScalarMul, OpBn256Pairing:
return p.ForbidBn256
case OpBLS12381G1Add, OpBLS12381G1MSM,
OpBLS12381G2Add, OpBLS12381G2MSM,
OpBLS12381Pairing, OpBLS12381MapG1, OpBLS12381MapG2:
return p.ForbidBLS12381
case OpKZGPointEval:
return p.ForbidKZG
}
return false
}
// Encode returns the canonical 16-byte encoding of a profile.
//
// byte 0 version (currently 2)
// byte 1 flags: bit i = the i'th Forbid* field
// bytes 2..15 reserved, must be zero
//
// New primitive families extend the flags region; renaming or
// reordering fields requires a version bump.
func (p *Profile) Encode() [16]byte {
var b [16]byte
b[0] = profileVersion
if p == nil {
return b
}
var flags byte
if p.ForbidEcrecover {
flags |= 1 << 0
}
if p.ForbidP256Verify {
flags |= 1 << 1
}
if p.ForbidSHA256 {
flags |= 1 << 2
}
if p.ForbidRIPEMD160 {
flags |= 1 << 3
}
if p.ForbidBlake2F {
flags |= 1 << 4
}
if p.ForbidBn256 {
flags |= 1 << 5
}
if p.ForbidBLS12381 {
flags |= 1 << 6
}
if p.ForbidKZG {
flags |= 1 << 7
}
b[1] = flags
return b
}
// Hash returns the SHA3-256 of the canonical encoding. Block producers
// publish this hash so observers can confirm which profile a block
// was produced under.
func (p *Profile) Hash() [32]byte {
enc := p.Encode()
return sha3.Sum256(enc[:])
}
// DecodeProfile reconstructs a profile from its canonical encoding.
// Returns (nil, false) on unknown version or non-zero reserved bytes.
func DecodeProfile(b [16]byte) (*Profile, bool) {
if b[0] != profileVersion {
return nil, false
}
for i := 2; i < 16; i++ {
if b[i] != 0 {
return nil, false
}
}
flags := b[1]
p := &Profile{
ForbidEcrecover: flags&(1<<0) != 0,
ForbidP256Verify: flags&(1<<1) != 0,
ForbidSHA256: flags&(1<<2) != 0,
ForbidRIPEMD160: flags&(1<<3) != 0,
ForbidBlake2F: flags&(1<<4) != 0,
ForbidBn256: flags&(1<<5) != 0,
ForbidBLS12381: flags&(1<<6) != 0,
ForbidKZG: flags&(1<<7) != 0,
}
return p, true
}
const profileVersion = 2