Files
zeekayandGitHub 96da029e9a Thinking Chains: chains (#2)
* hanzo-evm: aiquorum + aimarket precompiles, evmllm (geth AI precompile + e2e), operator/canonical (commit/reveal, int8-quant), aiffi bench. Thinking-chain substrate.

* hanzo-evm/evm: wire AI precompiles (inference 0x0300..03 + modelregistry)

Registers deterministic int8 inference (Tier-1 in-consensus AI) + ModelSpec
registry into the Hanzo EVM. threshold dep aligned to v1.9.4 (go.mod match).
Builds green (55M binary). aivmbridge follows luxfi/precompile release.
2026-06-21 20:53:42 -07:00

441 lines
15 KiB
Go

// Copyright (C) 2026, Hanzo AI Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package canonical
import (
"bytes"
"encoding/binary"
"encoding/json"
"math"
"strings"
"testing"
"github.com/luxfi/crypto"
"github.com/luxfi/geth/common"
)
func h(b byte) common.Hash {
var x common.Hash
for i := range x {
x[i] = b
}
return x
}
// --- ModelSpecHash: exact preimage reconstruction -------------------------
func TestModelSpecHash_ExactPreimage(t *testing.T) {
s := ModelSpec{
ModelID: "zen-nano",
ModelHash: h(0x11),
TokenizerHash: h(0x22),
RuntimeVersion: "hanzo-engine/1.2.3",
SamplingHash: h(0x33),
PromptTemplateHash: h(0x44),
EmbeddingModelHash: h(0x55),
}
// Reconstruct the preimage independently and compare the hash.
var lp [4]byte
var buf []byte
binary.BigEndian.PutUint32(lp[:], uint32(len("zen-nano")))
buf = append(buf, lp[:]...)
buf = append(buf, "zen-nano"...)
buf = append(buf, h(0x11).Bytes()...)
buf = append(buf, h(0x22).Bytes()...)
binary.BigEndian.PutUint32(lp[:], uint32(len("hanzo-engine/1.2.3")))
buf = append(buf, lp[:]...)
buf = append(buf, "hanzo-engine/1.2.3"...)
buf = append(buf, h(0x33).Bytes()...)
buf = append(buf, h(0x44).Bytes()...)
buf = append(buf, h(0x55).Bytes()...)
want := common.BytesToHash(crypto.Keccak256(buf))
if got := s.ModelSpecHash(); got != want {
t.Fatalf("ModelSpecHash mismatch\n got %s\nwant %s", got.Hex(), want.Hex())
}
}
// Field ordering must be load-bearing: swapping two values changes the hash.
func TestModelSpecHash_OrderingSensitive(t *testing.T) {
a := ModelSpec{ModelID: "m", ModelHash: h(0x01), TokenizerHash: h(0x02), RuntimeVersion: "r", SamplingHash: h(0x03), PromptTemplateHash: h(0x04), EmbeddingModelHash: h(0x05)}
b := a
b.SamplingHash, b.PromptTemplateHash = a.PromptTemplateHash, a.SamplingHash // swap two 32B fields
if a.ModelSpecHash() == b.ModelSpecHash() {
t.Fatal("swapping sampling/prompt-template hashes did not change the digest — ordering not enforced")
}
}
// model_id length-prefix must prevent "ab"|"c" colliding with "a"|"bc".
func TestModelSpecHash_LengthPrefixNoConcatCollision(t *testing.T) {
base := ModelSpec{ModelHash: h(1), TokenizerHash: h(2), RuntimeVersion: "x", SamplingHash: h(3), PromptTemplateHash: h(4), EmbeddingModelHash: h(5)}
a := base
a.ModelID = "ab"
b := base
b.ModelID = "a" // shorter id, different bytes — must differ
if a.ModelSpecHash() == b.ModelSpecHash() {
t.Fatal("length-prefix failed: distinct model ids collided")
}
}
// --- Commit: operator binding --------------------------------------------
func TestCommit_OperatorBound(t *testing.T) {
job, spec, prompt, out, emb := h(1), h(2), h(3), h(4), h(5)
nonce := h(9)
opA := common.HexToAddress("0x00000000000000000000000000000000000000aa")
opB := common.HexToAddress("0x00000000000000000000000000000000000000bb")
cA := Commit(job, spec, prompt, out, emb, opA, nonce)
cB := Commit(job, spec, prompt, out, emb, opB, nonce)
if cA == cB {
t.Fatal("commit is NOT operator-bound: two operators with identical reveal produced the same commit (replay/copy attack possible)")
}
}
func TestCommit_NonceBound(t *testing.T) {
job, spec, prompt, out, emb := h(1), h(2), h(3), h(4), h(5)
op := common.HexToAddress("0x00000000000000000000000000000000000000aa")
if Commit(job, spec, prompt, out, emb, op, h(7)) == Commit(job, spec, prompt, out, emb, op, h(8)) {
t.Fatal("commit not bound to nonce")
}
}
// --- output_hash modes ----------------------------------------------------
func TestOutputHashRaw(t *testing.T) {
out := []byte("I am Zen, a language model.")
want := common.BytesToHash(crypto.Keccak256(out))
if OutputHashRaw(out) != want {
t.Fatal("raw output hash mismatch")
}
if OutputHashRaw([]byte("x")) == OutputHashRaw([]byte("y")) {
t.Fatal("distinct outputs collided")
}
}
// --- governance: bucketing (integer round-half-to-even) -------------------
func TestBucketBps_RoundHalfToEven(t *testing.T) {
cases := []struct {
in uint16
want uint16
}{
{0, 0},
{1, 0},
{499, 0},
{500, 0}, // .5 at index 0 -> even (0)
{501, 1000}, // just over midpoint rounds up
{999, 1000},
{1000, 1000},
{1499, 1000},
{1500, 2000}, // .5 at index 1 (odd) -> round to even (2)
{1501, 2000},
{2000, 2000},
{2499, 2000},
{2500, 2000}, // .5 at index 2 (even) -> stay (2)
{2501, 3000},
{3500, 4000}, // .5 at index 3 (odd) -> 4
{9499, 9000},
{9500, 10000}, // .5 at index 9 (odd) -> 10
{9999, 10000},
{10000, 10000},
{12345, 10000}, // clamp
}
for _, c := range cases {
d := Decision{ConfidenceBps: c.in}
if got := d.BucketBps(); got != c.want {
t.Errorf("BucketBps(%d) = %d, want %d", c.in, got, c.want)
}
}
}
// The governance consensus hash must be INDEPENDENT of rationale/citations and
// DEPENDENT on {spec, vote, bucket}. This is the rationale-exclusion proof.
func TestOutputHashGovernance_RationaleExcluded(t *testing.T) {
spec := h(0xAB)
base := Decision{ModelSpecHash: spec, Vote: VoteYes, ConfidenceBps: 8200}
d1 := base
d1.Rationale = "Approve: the proposal funds core infra and the budget is sound."
d1.Citations = []string{"ipfs://Qm111", "https://forum/post/1"}
d2 := base
d2.Rationale = "YES — infra funding justified; numbers check out. Different words entirely."
d2.Citations = nil
if OutputHashGovernance(d1) != OutputHashGovernance(d2) {
t.Fatal("rationale/citations leaked into the consensus hash: identical {vote,bucket} must hash identically")
}
// Same raw confidence inside the SAME bucket must also match.
d3 := base
d3.ConfidenceBps = 8499 // snaps to 8000, same as 8200
if OutputHashGovernance(base) != OutputHashGovernance(d3) {
t.Fatalf("same-bucket confidences (8200 vs 8499 -> 8000) did not match")
}
// Different vote must NOT match.
dNo := base
dNo.Vote = VoteNo
if OutputHashGovernance(base) == OutputHashGovernance(dNo) {
t.Fatal("different votes collided into the same consensus hash")
}
// Different bucket must NOT match.
dLow := base
dLow.ConfidenceBps = 2000
if OutputHashGovernance(base) == OutputHashGovernance(dLow) {
t.Fatal("different buckets collided")
}
// Different spec must NOT match (spec is bound in).
dSpec := base
dSpec.ModelSpecHash = h(0xCD)
if OutputHashGovernance(base) == OutputHashGovernance(dSpec) {
t.Fatal("different spec collided — spec not bound into the decision")
}
}
func TestConsensusPreimage_Layout(t *testing.T) {
spec := h(0x7E)
d := Decision{ModelSpecHash: spec, Vote: VoteNo, ConfidenceBps: 3000}
p := d.consensusPreimage()
if len(p) != 35 {
t.Fatalf("preimage len = %d, want 35", len(p))
}
if !bytes.Equal(p[:32], spec.Bytes()) {
t.Fatal("preimage[0:32] != spec")
}
if p[32] != byte(VoteNo) {
t.Fatalf("vote byte = %d, want %d", p[32], VoteNo)
}
if binary.BigEndian.Uint16(p[33:35]) != 3000 {
t.Fatalf("bucket = %d, want 3000", binary.BigEndian.Uint16(p[33:35]))
}
}
// --- governance: strict parse + fail-secure -------------------------------
func TestParseDecision_Valid(t *testing.T) {
spec := h(0x42)
raw := []byte(`{"vote":"yes","confidence_bps":7500,"rationale":"sound","citations":["a","b"],"model_spec":"` + spec.Hex() + `"}`)
d, err := ParseDecision(raw, spec)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if d.Vote != VoteYes || d.ConfidenceBps != 7500 || d.Rationale != "sound" || len(d.Citations) != 2 {
t.Fatalf("parsed wrong: %+v", d)
}
if d.ModelSpecHash != spec {
t.Fatal("spec not bound on parse")
}
}
func TestParseDecision_Rejections(t *testing.T) {
spec := h(0x42)
ok := spec.Hex()
cases := map[string]string{
"unknown field": `{"vote":"yes","confidence_bps":1,"rationale":"r","model_spec":"` + ok + `","evil":1}`,
"bad vote": `{"vote":"maybe","confidence_bps":1,"rationale":"r","model_spec":"` + ok + `"}`,
"missing vote": `{"confidence_bps":1,"rationale":"r","model_spec":"` + ok + `"}`,
"confidence too high": `{"vote":"yes","confidence_bps":10001,"rationale":"r","model_spec":"` + ok + `"}`,
"confidence negative": `{"vote":"yes","confidence_bps":-1,"rationale":"r","model_spec":"` + ok + `"}`,
"missing confidence": `{"vote":"yes","rationale":"r","model_spec":"` + ok + `"}`,
"missing rationale": `{"vote":"yes","confidence_bps":1,"model_spec":"` + ok + `"}`,
"missing model_spec": `{"vote":"yes","confidence_bps":1,"rationale":"r"}`,
"wrong model_spec": `{"vote":"yes","confidence_bps":1,"rationale":"r","model_spec":"` + h(0x99).Hex() + `"}`,
"trailing data": `{"vote":"yes","confidence_bps":1,"rationale":"r","model_spec":"` + ok + `"} garbage`,
"trailing json": `{"vote":"yes","confidence_bps":1,"rationale":"r","model_spec":"` + ok + `"}{}`,
"not an object": `"yes"`,
"empty": ``,
"prose before": `Sure! {"vote":"yes","confidence_bps":1,"rationale":"r","model_spec":"` + ok + `"}`,
}
for name, raw := range cases {
if _, err := ParseDecision([]byte(raw), spec); err == nil {
t.Errorf("%s: expected rejection, got nil error for %q", name, raw)
}
}
}
func TestAbstain(t *testing.T) {
spec := h(7)
d := Abstain(spec, "parse failed twice")
if d.Vote != VoteAbstain || d.ConfidenceBps != 0 || d.ModelSpecHash != spec {
t.Fatalf("abstain wrong: %+v", d)
}
// Two operators that both abstain (different reasons) still agree on the
// consensus hash — a quorum of abstentions is a legitimate outcome.
d2 := Abstain(spec, "engine error")
if OutputHashGovernance(d) != OutputHashGovernance(d2) {
t.Fatal("two abstentions under the same spec must produce the same consensus hash")
}
}
// --- embedding quantization ----------------------------------------------
func TestQuantizeEmbedding_Layout(t *testing.T) {
v := []float32{0, 0.5, -0.5, 1.0, -1.0}
ser, scale, err := QuantizeEmbedding(v)
if err != nil {
t.Fatalf("QuantizeEmbedding: %v", err)
}
// scale = max|v| / 127 = 1/127
if got := binary.BigEndian.Uint32(ser[:4]); got != uint32(len(v)) {
t.Fatalf("dim header = %d, want %d", got, len(v))
}
// layout total = 4 + dim + 4
if len(ser) != 4+len(v)+4 {
t.Fatalf("serialized len = %d, want %d", len(ser), 4+len(v)+4)
}
// scale is f32be at the tail.
if tail := binary.BigEndian.Uint32(ser[len(ser)-4:]); tail != math.Float32bits(scale) {
t.Fatalf("scale tail mismatch: tail=%08x want=%08x", tail, math.Float32bits(scale))
}
// 1.0 / (1/127) = 127 -> int8 127; -1.0 -> -127; 0 -> 0.
q := ser[4 : 4+len(v)]
if int8(q[3]) != 127 || int8(q[4]) != -127 || int8(q[0]) != 0 {
t.Fatalf("quantization wrong: q=%v", []int8{int8(q[0]), int8(q[1]), int8(q[2]), int8(q[3]), int8(q[4])})
}
}
func TestQuantizeEmbedding_AllZeroScaleOne(t *testing.T) {
ser, scale, err := QuantizeEmbedding([]float32{0, 0, 0})
if err != nil {
t.Fatalf("QuantizeEmbedding: %v", err)
}
if scale != 1 {
t.Fatalf("all-zero scale = %v, want 1", scale)
}
for _, b := range ser[4 : 4+3] {
if int8(b) != 0 {
t.Fatal("all-zero vector must quantize to all-zero int8")
}
}
}
func TestQuantizeEmbedding_RoundHalfToEven(t *testing.T) {
// Construct a vector where v_i/scale lands exactly on .5 boundaries.
// scale = max|v|/127. Pick max = 127 so scale = 1, then values 0.5, 1.5, 2.5.
v := []float32{127, 0.5, 1.5, 2.5, 3.5}
ser, scale, err := QuantizeEmbedding(v)
if err != nil {
t.Fatalf("QuantizeEmbedding: %v", err)
}
if scale != 1 {
t.Fatalf("scale = %v, want 1", scale)
}
q := ser[4 : 4+len(v)]
// round-half-to-even: 0.5->0, 1.5->2, 2.5->2, 3.5->4
want := []int8{127, 0, 2, 2, 4}
for i := range want {
if int8(q[i]) != want[i] {
t.Errorf("q[%d] = %d, want %d (round-half-to-even)", i, int8(q[i]), want[i])
}
}
}
func TestEmbeddingHash_Deterministic(t *testing.T) {
mustEmb := func(v []float32) common.Hash {
t.Helper()
h, err := EmbeddingHash(v)
if err != nil {
t.Fatalf("EmbeddingHash: %v", err)
}
return h
}
v := []float32{0.1, -0.2, 0.3, 0.9, -0.7}
if mustEmb(v) != mustEmb(append([]float32(nil), v...)) {
t.Fatal("embedding hash not deterministic for identical input")
}
w := append([]float32(nil), v...)
w[0] = 0.1000001 // perturb beyond a quantization step at this scale
// Not guaranteed to differ for tiny perturbations within a bucket; use a clear change.
w[0] = 0.5
if mustEmb(v) == mustEmb(w) {
t.Fatal("clearly different embeddings collided")
}
}
// --- RFC 8785 canonical JSON ---------------------------------------------
func TestCanonicalJSON_KeySortingAndStability(t *testing.T) {
r := AuditRecord{
JobID: "0x01",
ModelSpecHash: "0x02",
Mode: "governance",
Operator: "0x03",
Vote: "yes",
ConfidenceBps: 8200,
BucketBps: 8000,
Rationale: "ok",
Citations: []string{"a", "b"},
OutputHash: "0x04",
EmbeddingHash: "0x05",
Nonce: "0x06",
Commit: "0x07",
}
out := string(r.CanonicalJSON())
// Keys must appear in lexicographic order. Spot-check a few orderings.
idx := func(k string) int { return strings.Index(out, `"`+k+`":`) }
keysInOrder := []string{"bucket_bps", "citations", "commit", "confidence_bps", "embedding_hash", "job_id", "mode", "model_spec_hash", "nonce", "operator", "output_hash", "rationale", "vote"}
prev := -1
for _, k := range keysInOrder {
at := idx(k)
if at < 0 {
t.Fatalf("key %q missing from canonical output", k)
}
if at < prev {
t.Fatalf("key %q out of sorted order in: %s", k, out)
}
prev = at
}
// Deterministic: re-marshal is byte-identical.
if string(r.CanonicalJSON()) != out {
t.Fatal("CanonicalJSON not deterministic")
}
// Must be valid JSON.
var sink map[string]any
if err := json.Unmarshal([]byte(out), &sink); err != nil {
t.Fatalf("canonical output is not valid JSON: %v\n%s", err, out)
}
}
func TestCanonicalJSON_InjectionSafe(t *testing.T) {
// A malicious rationale tries to break out of its string and inject members.
evil := "\",\"vote\":\"no\",\"x\":\"" + "\x00\x07\b\t\n\f\r\"\\</>&" + "\u4e16\u754c"
r := AuditRecord{Mode: "governance", Vote: "yes", Rationale: evil}
out := r.CanonicalJSON()
// Output must still be valid JSON and the top-level vote must remain "yes"
// (the injected "vote":"no" must be trapped inside the rationale string).
var m map[string]any
if err := json.Unmarshal(out, &m); err != nil {
t.Fatalf("injection broke JSON validity: %v\n%s", err, out)
}
if m["vote"] != "yes" {
t.Fatalf("injection escaped the rationale string: vote=%v", m["vote"])
}
if m["rationale"] != evil {
t.Fatalf("rationale round-trip mismatch:\n got %q\nwant %q", m["rationale"], evil)
}
// JCS specifics: <,>,& and non-ASCII are NOT escaped; control chars are.
s := string(out)
if !strings.Contains(s, "</>&") {
t.Error("JCS must not HTML-escape <,>,&")
}
if !strings.Contains(s, "\u4e16\u754c") {
t.Error("JCS must emit non-ASCII as literal UTF-8")
}
if !strings.Contains(s, "\\u0000") || !strings.Contains(s, "\\u0007") {
t.Error("JCS must \\u-escape C0 control chars without short forms (NUL, BEL)")
}
if !strings.Contains(s, "\\b") || !strings.Contains(s, "\\t") || !strings.Contains(s, "\\n") || !strings.Contains(s, "\\f") || !strings.Contains(s, "\\r") {
t.Error("JCS must use short escapes for \\b \\t \\n \\f \\r")
}
}