mirror of
https://github.com/luxfi/precompile.git
synced 2026-07-27 03:33:45 +00:00
aivmbridge: native compute-proof precompile (computeattest op) — closes audit G10
verifyComputeProof at 0x0300…0004 selector 0x12000000: decodes a proof-of-inference opening (crypto/poi wire v1.19.24) and returns (included | freivaldsOK<<1) — Merkle inclusion under the committed transcript root + Freivalds C==A·B over F_p, native via crypto/poi.CheckOpening. One call serves both paths (genuine = included&ok, fraud = included&!ok). Gas = base + per-field-op, priced so a 256-slice ~1M gas and a full layer is unaffordable (forces bounded slices); RequiredGas==Run. This is what makes a real-model-sized opening verifiable on-chain (Solidity Freivalds is too heavy). Tests 5/5; full aivmbridge suite green. luxfi/crypto v1.19.22→v1.19.24.
This commit is contained in:
@@ -35,6 +35,12 @@ const (
|
||||
// verifyInferenceReceipt(bytes receiptBytes, bytes proofBytes)
|
||||
// -> (bytes32 intentID, bytes32 canonicalOutputHash, uint8 status)
|
||||
SelectorVerifyInferenceReceipt uint32 = 0x11000000
|
||||
|
||||
// verifyComputeProof(bytes openingWire) -> bytes32 verdict
|
||||
// the NATIVE proof-of-inference check: decode a beacon-selected matmul opening and return
|
||||
// (included | freivaldsOK<<1) so the gate can tell a genuine opening from a fraud. See
|
||||
// computeproof.go. The opening wire is crypto/poi.EncodeOpening.
|
||||
SelectorVerifyComputeProof uint32 = 0x12000000
|
||||
)
|
||||
|
||||
// MaxFanout bounds N so a single intent cannot request an unbounded provider fan-out
|
||||
@@ -71,6 +77,9 @@ func runBridge(
|
||||
return submitIntent(accessibleState, caller, args, suppliedGas, readOnly)
|
||||
case SelectorVerifyInferenceReceipt:
|
||||
return verifyReceipt(accessibleState, args, suppliedGas, readOnly)
|
||||
case SelectorVerifyComputeProof:
|
||||
// pure verification — no state, so readOnly is fine.
|
||||
return verifyComputeProof(args, suppliedGas)
|
||||
default:
|
||||
return nil, suppliedGas, fmt.Errorf("aivmbridge: unknown selector %#x", selector)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
// computeproof.go — the NATIVE proof-of-inference verification (the `computeattest` op).
|
||||
//
|
||||
// A prover commits a forward pass as a keccak Merkle root over per-matmul exact-integer operands
|
||||
// (hanzo-engine emits it; crypto/poi defines the wire). To enforce "no real compute, no mint", a
|
||||
// challenger opens a beacon-selected matmul and this precompile re-checks it NATIVELY: Merkle
|
||||
// inclusion under the committed root + Freivalds C == A·B over F_p (p = 2^61-1). Native because a
|
||||
// real-model-sized opening is far too much arithmetic for gas-bounded EVM bytecode; the Solidity
|
||||
// ComputeWitnessLib is the byte-identical reference for small slices, this is the production path.
|
||||
//
|
||||
// Pure (reads no state, writes none) → safe under a static call. Returns one 32-byte word whose
|
||||
// low bits carry the verdict, so one native call serves both the attest and the slash paths.
|
||||
package aivmbridge
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/luxfi/crypto/poi"
|
||||
)
|
||||
|
||||
// computeProofK is the number of Freivalds challenge vectors checked on-chain. k = 2 gives a
|
||||
// soundness error <= (1/p)^2 ~ 2^-122 — a fabricated matmul survives with negligible probability.
|
||||
const computeProofK = 2
|
||||
|
||||
// Result bits in the returned word's last byte.
|
||||
const (
|
||||
resultIncluded = 1 // the operands are committed under the transcript root (Merkle inclusion)
|
||||
resultFreivaldsOK = 2 // C == A·B (the matmul is genuine)
|
||||
)
|
||||
|
||||
// computeProofGas is the gas for an opening: a base plus the Freivalds work (O(tk+kn+tn) per
|
||||
// challenge vector). Identical formula in RequiredGas and Run so accounting agrees. The per-op
|
||||
// price makes a large opening expensive, which is what forces a challenger to open a bounded
|
||||
// SLICE of a layer rather than a whole 4096-wide matmul.
|
||||
func computeProofGas(op poi.Opening) uint64 {
|
||||
return GasVerifyComputeProofBase + poi.FieldOps(op, computeProofK)*GasVerifyComputeProofPerFieldOp
|
||||
}
|
||||
|
||||
// verifyComputeProof decodes a proof-of-inference opening and returns (included, freivaldsOK) in
|
||||
// the low bits of a 32-byte word. The caller derives genuine = included && freivaldsOK and
|
||||
// fraud = included && !freivaldsOK. Charges base before any work (bounds griefing on a malformed
|
||||
// frame), then the Freivalds cost computed from the decoded — and dimension-bounded — operands.
|
||||
func verifyComputeProof(args []byte, suppliedGas uint64) ([]byte, uint64, error) {
|
||||
if suppliedGas < GasVerifyComputeProofBase {
|
||||
return nil, 0, fmt.Errorf("aivmbridge: out of gas: need %d have %d", GasVerifyComputeProofBase, suppliedGas)
|
||||
}
|
||||
root, beacon, op, err := poi.DecodeOpening(args)
|
||||
if err != nil {
|
||||
// the base was affordable; consume it and surface the decode error.
|
||||
return nil, suppliedGas - GasVerifyComputeProofBase, err
|
||||
}
|
||||
cost := computeProofGas(op)
|
||||
if suppliedGas < cost {
|
||||
return nil, 0, fmt.Errorf("aivmbridge: out of gas: compute proof needs %d have %d", cost, suppliedGas)
|
||||
}
|
||||
included, ok, err := poi.CheckDecoded(root, beacon, op, computeProofK)
|
||||
if err != nil {
|
||||
return nil, suppliedGas - cost, err
|
||||
}
|
||||
var out [32]byte
|
||||
if included {
|
||||
out[31] |= resultIncluded
|
||||
}
|
||||
if ok {
|
||||
out[31] |= resultFreivaldsOK
|
||||
}
|
||||
return out[:], suppliedGas - cost, nil
|
||||
}
|
||||
|
||||
// computeProofRequiredGas is the EVM's pre-charge estimate, matching {computeProofGas}. A
|
||||
// malformed frame falls back to the base (the call reverts on decode anyway).
|
||||
func computeProofRequiredGas(args []byte) uint64 {
|
||||
_, _, op, err := poi.DecodeOpening(args)
|
||||
if err != nil {
|
||||
return GasVerifyComputeProofBase
|
||||
}
|
||||
return computeProofGas(op)
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package aivmbridge
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/luxfi/crypto/poi"
|
||||
)
|
||||
|
||||
// buildOpening makes a small proof-of-inference transcript and returns the wire-encoded opening of
|
||||
// its first matmul. With fabricate=true that matmul's output has one entry flipped (a claimed
|
||||
// result never produced by A·B).
|
||||
func buildOpening(fabricate bool) []byte {
|
||||
s := uint64(0x1234)
|
||||
rnd := func(rows, cols int) poi.Mat {
|
||||
d := make([]int64, rows*cols)
|
||||
for i := range d {
|
||||
s = s*6364136223846793005 + 1442695040888963407
|
||||
d[i] = int64((s>>33)%127) - 63
|
||||
}
|
||||
return poi.NewMat(rows, cols, d)
|
||||
}
|
||||
tr := poi.NewTranscript()
|
||||
a, b := rnd(4, 16), rnd(16, 8)
|
||||
if fabricate {
|
||||
c := poi.ExactMatmul(a, b)
|
||||
c.Data[3]++ // fabricate one output entry
|
||||
tr.CommitClaimed(a, b, c)
|
||||
} else {
|
||||
tr.Matmul(a, b)
|
||||
}
|
||||
tr.Matmul(rnd(2, 4), rnd(4, 3)) // a second matmul so the tree has a real proof path
|
||||
root := tr.Root()
|
||||
return poi.EncodeOpening(root, []byte("beacon:precompile"), tr.Open(0))
|
||||
}
|
||||
|
||||
// An honest opening verifies natively: included AND Freivalds-OK → verdict 0b11.
|
||||
func TestPrecompile_VerifyComputeProof_Honest(t *testing.T) {
|
||||
ret, rem, err := verifyComputeProof(buildOpening(false), 50_000_000)
|
||||
if err != nil {
|
||||
t.Fatalf("verify: %v", err)
|
||||
}
|
||||
if ret[31] != resultIncluded|resultFreivaldsOK {
|
||||
t.Fatalf("honest verdict = %d, want %d (included|ok)", ret[31], resultIncluded|resultFreivaldsOK)
|
||||
}
|
||||
if rem >= 50_000_000 {
|
||||
t.Fatal("gas must be consumed")
|
||||
}
|
||||
}
|
||||
|
||||
// A fabricated output is caught natively: included but NOT Freivalds-OK → verdict 0b01 (= fraud).
|
||||
func TestPrecompile_VerifyComputeProof_Fraud(t *testing.T) {
|
||||
ret, _, err := verifyComputeProof(buildOpening(true), 50_000_000)
|
||||
if err != nil {
|
||||
t.Fatalf("verify: %v", err)
|
||||
}
|
||||
if ret[31] != resultIncluded {
|
||||
t.Fatalf("fraud verdict = %d, want %d (included, not ok)", ret[31], resultIncluded)
|
||||
}
|
||||
}
|
||||
|
||||
// Below the base, the op is out of gas (bounds griefing on a malformed/huge frame).
|
||||
func TestPrecompile_VerifyComputeProof_OOG(t *testing.T) {
|
||||
if _, _, err := verifyComputeProof(buildOpening(false), GasVerifyComputeProofBase-1); err == nil {
|
||||
t.Fatal("must be out of gas below the base")
|
||||
}
|
||||
}
|
||||
|
||||
// RequiredGas (the EVM's pre-charge) matches what Run actually consumes.
|
||||
func TestPrecompile_GasAccountingConsistent(t *testing.T) {
|
||||
in := buildOpening(false)
|
||||
want := computeProofRequiredGas(in)
|
||||
if want <= GasVerifyComputeProofBase {
|
||||
t.Fatal("a real opening must cost more than the base")
|
||||
}
|
||||
_, rem, err := verifyComputeProof(in, want)
|
||||
if err != nil {
|
||||
t.Fatalf("verify at RequiredGas budget: %v", err)
|
||||
}
|
||||
if rem != 0 {
|
||||
t.Fatalf("Run consumed %d less than RequiredGas estimated (rem=%d) — accounting drift", want-rem, rem)
|
||||
}
|
||||
}
|
||||
|
||||
// A garbage frame decodes-errs and consumes only the base (no panic, no over-charge).
|
||||
func TestPrecompile_VerifyComputeProof_Garbage(t *testing.T) {
|
||||
_, rem, err := verifyComputeProof([]byte{1, 2, 3, 4, 5}, 1_000_000)
|
||||
if err == nil {
|
||||
t.Fatal("garbage frame must error")
|
||||
}
|
||||
if rem != 1_000_000-GasVerifyComputeProofBase {
|
||||
t.Fatalf("garbage frame must consume exactly the base, rem=%d", rem)
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,16 @@ const (
|
||||
|
||||
// GasVerifyPerProofNode is charged per merkle path node hashed during verify.
|
||||
GasVerifyPerProofNode uint64 = 1_000
|
||||
|
||||
// GasVerifyComputeProofBase is the base for a native proof-of-inference opening check: the
|
||||
// wire decode + the Merkle inclusion of the opened matmul. The Freivalds re-execution adds
|
||||
// the per-field-op price below.
|
||||
GasVerifyComputeProofBase uint64 = 30_000
|
||||
|
||||
// GasVerifyComputeProofPerFieldOp prices each F_p mul-add of the Freivalds check
|
||||
// (O(tk+kn+tn) per challenge vector). Sized so a 256-wide slice costs ~1M gas and a whole
|
||||
// 4096-wide layer is unaffordable — which forces a challenger to open a bounded SLICE.
|
||||
GasVerifyComputeProofPerFieldOp uint64 = 2
|
||||
)
|
||||
|
||||
// verifyGas returns the total gas for a VerifyInferenceReceipt call given the
|
||||
|
||||
@@ -126,6 +126,8 @@ func (c *BridgeContract) RequiredGas(input []byte) uint64 {
|
||||
return GasVerifyInferenceReceiptBase
|
||||
}
|
||||
return verifyGas(pathLen)
|
||||
case SelectorVerifyComputeProof:
|
||||
return computeProofRequiredGas(input[4:])
|
||||
default:
|
||||
return GasVerifyInferenceReceiptBase
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ require (
|
||||
github.com/luxfi/ai v0.1.0
|
||||
github.com/luxfi/chains v1.3.18
|
||||
github.com/luxfi/corona v0.7.9
|
||||
github.com/luxfi/crypto v1.19.22
|
||||
github.com/luxfi/crypto v1.19.24
|
||||
github.com/luxfi/database v1.20.4
|
||||
github.com/luxfi/dex v1.5.16
|
||||
github.com/luxfi/fhe v1.8.2
|
||||
@@ -78,7 +78,6 @@ require (
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/luxfi/age v1.5.0 // indirect
|
||||
github.com/luxfi/bft v0.1.5 // indirect
|
||||
github.com/luxfi/mdns v0.1.1 // indirect
|
||||
github.com/luxfi/node v1.30.6 // indirect
|
||||
github.com/luxfi/timer v1.0.2 // indirect
|
||||
|
||||
@@ -203,20 +203,14 @@ github.com/luxfi/ai v0.1.0 h1:PwTGob0GJivbdqNpUs82bvwcE8/qxyTokxfY7BZVPoo=
|
||||
github.com/luxfi/ai v0.1.0/go.mod h1:lTuY32dGjEJUgVheYuanlt1O0jHj0AZ09sUSpRsmH6M=
|
||||
github.com/luxfi/atomic v1.0.0 h1:xUV60MuzRvXngaQ1sM0yVC2v4TRoLlUGkkH7M9PS4yw=
|
||||
github.com/luxfi/atomic v1.0.0/go.mod h1:0G2mTlQ6TXWHICUHrUUPu1/qAiIyR4gSZ2tva9ci/bI=
|
||||
github.com/luxfi/bft v0.1.5 h1:5xVLPkog4e5LTgaVlb9pgxA0EWE6tkrKwHPZVRz+RZw=
|
||||
github.com/luxfi/bft v0.1.5/go.mod h1:5I8Ft8yA69xZlDe3RB0i4MgbqFKLZe65o/sha8JuKvU=
|
||||
github.com/luxfi/cache v1.2.1 h1:kAzOS55/hmYeNKR+0HAKv4ma48Y6JjkI8UQeqdZ8bfI=
|
||||
github.com/luxfi/cache v1.2.1/go.mod h1:co7JTxZZHpKT31Yh01LFp5aZOxmoUg157FhBLQdQHVU=
|
||||
github.com/luxfi/chains v1.3.14 h1:b/rGNlXiXQ8PoDMr6BWBgdbeQM4uwKp7wgav6QPW/YM=
|
||||
github.com/luxfi/chains v1.3.14/go.mod h1:lpVNHfHR8bk1y3BswNdK7DFpci0FAHPtO9XuMal8Aoo=
|
||||
github.com/luxfi/chains v1.3.18 h1:CaKtVZ2LkPgBiaZkDEl+RPsO2iXTXsOfVDwSj2ukhdE=
|
||||
github.com/luxfi/chains v1.3.18/go.mod h1:MArifjc+fRtZH6M009L47YPJFDMEjjJbHy2BJ/8nxTE=
|
||||
github.com/luxfi/compress v0.0.5 h1:4tEUHw5MK1bu5UOjfYCt4OKMiH7yykIgmGPRA/BfJTM=
|
||||
github.com/luxfi/compress v0.0.5/go.mod h1:Cc1yxD2pfzrvpO32W2GDwLKff+CylHEvzZh2Ko8RSIU=
|
||||
github.com/luxfi/concurrent v0.0.3 h1:eJyv1fhaC0jMLMw6+QS774cUmp7GK+ouMgvLCqnC7cc=
|
||||
github.com/luxfi/concurrent v0.0.3/go.mod h1:Aj/FR5NpM0cB2P4Nt3+tz9+dV6V+LUW4HuMgSjwq5hw=
|
||||
github.com/luxfi/consensus v1.25.18 h1:wegSPk1VyjV9k2J+IZ6cJf6flqiMhzbOuFidaQ31b24=
|
||||
github.com/luxfi/consensus v1.25.18/go.mod h1:cNhLp215lFwiOPxdj8I+6JBLz3/vDRMV2vqLigulv04=
|
||||
github.com/luxfi/consensus v1.25.21 h1:izB/XRJ+jWEbXHc4ro1MAA6uuQApfzDq+SSNCJ69mac=
|
||||
github.com/luxfi/consensus v1.25.21/go.mod h1:8gAYHLYsMvXoYz262gvusIoDRxHBrMqj6Fyj8uSnaYE=
|
||||
github.com/luxfi/constants v1.5.8 h1:iNP9AWNUcM4Tps7jYnx49CwtCWAC9mYRxJfGou2za0g=
|
||||
@@ -225,10 +219,8 @@ github.com/luxfi/container v0.0.4 h1:BXhF82WyfqVP5mjlNcr7tP0Fcnvl0Ap1rkiu+rq5XuM
|
||||
github.com/luxfi/container v0.0.4/go.mod h1:Z3SpmMF5d4t77MM0nHYXURpn+EMVaeu1fhbd/3BGaek=
|
||||
github.com/luxfi/corona v0.7.9 h1:NQe9V/80CdKLvbaVRE2uepxvxg9KHbWfcGRKWrzLSHc=
|
||||
github.com/luxfi/corona v0.7.9/go.mod h1:SfS7xo/k4uoteEYwYy+QCMPzTU8EIEbLnbWKx5ENVCw=
|
||||
github.com/luxfi/crypto v1.19.21 h1:x7s/Yy1BYMv5sbbWsZk+mUU007Z0HX/Ta4/RkrNHiw0=
|
||||
github.com/luxfi/crypto v1.19.21/go.mod h1:0tfz+EbAjsW1QBWB0cte9kdjB5XhhYFmCr8BkZRux48=
|
||||
github.com/luxfi/crypto v1.19.22 h1:qVXLyPR+nf6qqLbxA5KTbZmgNNxpqT9E7+3nppdRCq0=
|
||||
github.com/luxfi/crypto v1.19.22/go.mod h1:0tfz+EbAjsW1QBWB0cte9kdjB5XhhYFmCr8BkZRux48=
|
||||
github.com/luxfi/crypto v1.19.24 h1:bXhgA4g3x8Ac1RU6jYFQnP6IzUtDpFvyCsgvUbH0YmU=
|
||||
github.com/luxfi/crypto v1.19.24/go.mod h1:0tfz+EbAjsW1QBWB0cte9kdjB5XhhYFmCr8BkZRux48=
|
||||
github.com/luxfi/crypto/ipa v1.2.4 h1:6xfwhI9/HrcDkF3Ti5/NxsNQIWbwYDJmRSNIHRQ/xfU=
|
||||
github.com/luxfi/crypto/ipa v1.2.4/go.mod h1:43J6f6rcfUMrZt4cQectMOZb6Ps+fAEj8ZTPC3Kk+gE=
|
||||
github.com/luxfi/database v1.20.4 h1:WOt2GIGJxf8AFpg49odMz8DZ8RFSLDrozGhZtmorN70=
|
||||
|
||||
Reference in New Issue
Block a user