docs(poi): add package README (#5)

The poi package (Freivalds-over-F_p verification) is hard-consumed
by luxfi/precompile/aivmbridge/computeproof.go but has no README.
Adds Overview + API + wire codec spec + soundness bound + worked
example so a new contributor can audit a fraud proof without
reading the engine.

Pure docs. No behavior change.
This commit is contained in:
Abhishek Krishna
2026-07-04 10:48:49 -07:00
committed by GitHub
parent 733b315ce2
commit 447c43314d
+172
View File
@@ -0,0 +1,172 @@
# `poi` — Proof-of-Inference verifier primitive
Freivalds-over-`F_p` matrix-product verification, plus a Merkle transcript and
wire codec for on-chain openings. This is the canonical Go verifier that
`luxfi/precompile/aivmbridge/computeproof.go` consumes and that
`hanzo-engine/src/poi.rs` mirrors on the prover side.
See **LP-5300** (Thinking Chains / Proof-of-Thought) for the receipt model this
serves, and **LP-5301** (AIVMBridge) for the precompile that calls this code
in-EVM.
## Overview
Proof-of-AI binds mint to genuine computation. An LLM forward pass is ~95%
matrix multiplications `C = A·B`. Recomputing every `C` on-chain is
prohibitive; Freivalds (1977) instead samples a random vector `r` and checks
A·(B·r) == C·r
in `O(t·k + k·n + t·n)` — an order cheaper than the `O(t·k·n)` recompute — and
catches any fabricated `C` (a claimed output the prover never multiplied for)
with probability `>= 1 - 1/p` per vector.
The verifier operates on the **exact-integer** accumulator of the engine's
int8 matmul path (`i8·i8 -> i32` promoted to `int64`). Working on the exact
accumulator, not on floats, means the check is bit-exact with **zero
false-reject across CPU / GPU / backends** — the determinism the whole scheme
relies on.
## Mathematical setup
- Field: `F_p` with prime modulus `P = 2^61 - 1` (a Mersenne prime; see
`freivalds.go``const P uint64 = (1 << 61) - 1`).
- Signed inputs are reduced into `[0, p)` by `toField`; a single Freivalds
intermediate stays within 61 bits, so a 128-bit multiply never overflows.
- Soundness: `1/p ~ 2^-61` per challenge vector. `k` independent vectors give
a soundness error of `(1/p)^k`. `k = 2` yields `~2^-122`, which is what the
precompile ships with.
- Challenge derivation is FiatShamir: `DeriveChallenges(seed, n, k)` folds
keccak of `seed || (j, i)` into `[0, p)`. In production the seed is
`keccak(beacon || root || index)` (see `OpeningSeed`), so a prover cannot
anticipate its challenges before committing the transcript root.
## Public API
Freivalds core (`freivalds.go`):
| symbol | signature | purpose |
| --- | --- | --- |
| `P` | `const uint64 = (1 << 61) - 1` | Field modulus. |
| `Mat` | `struct { Rows, Cols int; Data []int64 }` | Row-major integer matrix. |
| `NewMat` | `func(rows, cols int, data []int64) Mat` | Constructor (panics on length mismatch). |
| `Verify` | `func(a, b, c Mat, r []uint64) bool` | One-vector Freivalds check. |
| `VerifyMulti` | `func(a, b, c Mat, challenges [][]uint64) bool` | k-vector check; false if any fails. |
| `DeriveChallenges` | `func(seed []byte, n, k int) [][]uint64` | FiatShamir challenge derivation. |
Transcript + Merkle (`transcript.go`):
| symbol | signature | purpose |
| --- | --- | --- |
| `DomainMatmulLeaf` | `[]byte("hanzo/poi/matmul-leaf/v1")` | Leaf domain tag. |
| `ExactMatmul` | `func(a, b Mat) Mat` | Whole-K `int64` exact matmul (the proof-bearing GEMM). |
| `MatmulLeaf` | `func(a, b, c Mat) [32]byte` | `keccak(DOMAIN || mat(A) || mat(B) || mat(C))`. |
| `MerkleRoot` | `func(leaves [][32]byte) [32]byte` | Duplicate-last-on-odd keccak fold. |
| `MerkleProof` | `func(leaves [][32]byte, index int) [][32]byte` | Sibling path, bottom-up. |
| `MerkleVerify` | `func(leaf, root [32]byte, index int, proof [][32]byte) bool` | Inclusion check (matches `AICoinMiner._verifyMerkle`). |
| `Opening` | `struct { Index int; A, B, C Mat; Proof [][32]byte }` | What a prover reveals for one matmul. |
| `ProofTranscript` | `struct` w/ `Matmul`, `CommitClaimed`, `Len`, `Root`, `Open` | Append-only commitment to a forward pass's matmuls. |
| `NewTranscript` | `func() *ProofTranscript` | Constructor. |
| `ChallengeIndex` | `func(beacon []byte, root [32]byte, length int) int` | `keccak(beacon || root) mod length`. |
| `OpeningSeed` | `func(beacon []byte, root [32]byte, index int) []byte` | `keccak(beacon || root || BE64(index))`. |
| `VerifyOpening` | `func(root [32]byte, beacon []byte, op Opening, k int) bool` | Merkle inclusion **and** k-vector Freivalds. |
Wire codec + precompile payload (`wire.go`):
| symbol | signature | purpose |
| --- | --- | --- |
| `MaxOpeningDim` | `const = 4096` | Per-dimension bound (see below). |
| `ErrShortOpening`, `ErrOpeningDims` | `error` | Decode errors. |
| `EncodeOpening` | `func(root [32]byte, beacon []byte, op Opening) []byte` | Canonical wire frame. |
| `DecodeOpening` | `func(b []byte) (root [32]byte, beacon []byte, op Opening, err error)` | Bounds-checked parse. |
| `CheckOpening` | `func(input []byte, k int) (included, freivaldsOK bool, err error)` | Precompile payload: decode + verify. |
| `CheckDecoded` | `func(root [32]byte, beacon []byte, op Opening, k int) (bool, bool, error)` | Same, on an already-parsed opening. |
| `FieldOps` | `func(op Opening, k int) uint64` | `k · (t·k + k·n + t·n)` — drives precompile gas. |
## Wire codec
`EncodeOpening` produces (all integers big-endian):
root[32] | index u64 | beaconLen u32 | beacon | mat(A) | mat(B) | mat(C) | proofLen u32 | proof[32]*
mat(m) = rows u32 | cols u32 | data[i] as int64-BE (rows*cols entries)
`DecodeOpening` reverses it, bounds-checking every length. The proof-length
field is capped at 64 (a `2^64`-leaf tree is absurd) to prevent OOM from a
malicious frame; each matrix dimension is capped at `MaxOpeningDim`.
## Bounds
`MaxOpeningDim = 4096`. A full LLM layer is challenged in **slices** no larger
than this, so a malicious frame cannot allocate unboundedly inside the
precompile. In practice a real-model opening is on the order of a few hundred
per side, well under the cap; the constant exists to make the on-chain
decoder's worst-case allocation finite and known.
## Worked example
Prover commits two matmuls, opens the second, encodes it, hands the bytes to a
verifier that decodes and checks:
```go
package main
import (
"encoding/hex"
"fmt"
"github.com/luxfi/crypto/poi"
)
func main() {
// Prover side: build the exact-integer operands (the int8 accumulators in
// production; here small int64 values for a runnable demo).
A := poi.NewMat(2, 3, []int64{1, 2, 3, 4, 5, 6})
B := poi.NewMat(3, 2, []int64{7, 8, 9, 10, 11, 12})
// Commit two matmuls to the transcript.
tr := poi.NewTranscript()
_ = tr.Matmul(A, B) // index 0
_ = tr.Matmul(A, B) // index 1
root := tr.Root()
// Challenger picks an opening (in production: keccak(beacon || root) mod N).
beacon := []byte("beacon:demo")
idx := poi.ChallengeIndex(beacon, root, tr.Len())
op := tr.Open(idx)
// Prover ships bytes.
wire := poi.EncodeOpening(root, beacon, op)
// Verifier side: decode + check with k=2 challenge vectors.
rRoot, rBeacon, rOp, err := poi.DecodeOpening(wire)
if err != nil {
panic(err)
}
included, freivaldsOK, err := poi.CheckDecoded(rRoot, rBeacon, rOp, 2)
if err != nil {
panic(err)
}
fmt.Printf("root=%s included=%v freivaldsOK=%v\n", hex.EncodeToString(rRoot[:8]), included, freivaldsOK)
// Expected: included=true freivaldsOK=true
}
```
Fraud path: mutate `rOp.C.Data[0]` before `CheckDecoded` and the second
return flips to `false` with probability `>= 1 - (1/p)^2 ~ 1 - 2^-122`. The
on-chain gate reads that as `included && !freivaldsOK` and slashes.
## References
- **LP-5300** — Thinking Chains / Cognitive Consensus / Proof-of-Thought.
§Security is what this package operationalizes: the audit-ability promise
is only real because a third party can decode an opening and rerun
`CheckDecoded` against the on-chain root.
- **LP-5301** — AIVMBridge. The `computeproof` precompile consumes exactly
the wire format above.
- **`luxfi/precompile/aivmbridge/computeproof.go`** — line 17,
`import "github.com/luxfi/crypto/poi"`. That file is the sole in-tree caller
of `CheckOpening`; keep the two in lockstep when changing the wire.
- **`hanzo-engine/src/poi.rs`** — the Rust prover-side mirror. Byte-for-byte
identical serialization and keccak fold; an opening produced there verifies
here (and on-chain) without re-hashing.