Files
precompile/ai/cross_chain_mining_test.go
zeekayandHanzo Dev d722de8465 ai: close markSpent, and bound what one attested device can claim
markSpent was an unauthenticated public selector on the double-spend guard.
workId = BLAKE3(deviceId‖nonce‖chainId) is computable offline, so any address
could mark any claim spent before its owner presented it — denying the reward,
and, where a missing claim reads as a missing attestation, forcing a slash. The
Solidity interface has always documented "only callable by authorized
contracts"; no check existed and ErrUnauthorized sat declared and unused. It now
fails closed: the atomic paths mark spent internally as the last step of
admitting a claim, which is the only safe ordering, so nothing legitimate is
lost and there is no authority to guess at.

The mint paths already require a TEE quote and bind it to the signing key
(ErrMissingAttestation / ErrDeviceKeyMismatch), which establishes that a real
device signed the claim. It does not establish that the claim is true: an
attested device still writes its own computeMinutes and dataSize, and the
reward schedule multiplies them. Unbounded, one sovereign-privacy data claim
settles 1e18 * (2^64-1) * 1.5 / 1e4 = 2.767e19 AI. Both magnitudes are now
bounded per claim — a ceiling on how wrong one claim can be, not a substitute
for the binding above.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-05 15:07:14 -07:00

155 lines
5.3 KiB
Go

// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package ai
import (
"encoding/binary"
"math/big"
"testing"
)
func makeWorkProof(privacy uint16, mins uint32) []byte {
p := make([]byte, WorkProofMinSize)
p[31] = 0xAA // deviceId (last byte)
p[63] = 0xBB // nonce (last byte)
binary.BigEndian.PutUint64(p[64:72], 1718000000) // timestamp
binary.BigEndian.PutUint16(p[72:74], privacy)
binary.BigEndian.PutUint32(p[74:78], mins)
return p
}
func TestMintWorkAndDoubleSpend(t *testing.T) {
st := NewMockStateDB()
proof := makeWorkProof(3, 60) // confidential, 60 compute-minutes
chainId := uint64(200202)
want, err := CalculateReward(proof, chainId)
if err != nil {
t.Fatalf("CalculateReward: %v", err)
}
reward, err := mintWork(st, proof, chainId)
if err != nil {
t.Fatalf("mintWork: %v", err)
}
if reward.Sign() <= 0 || reward.Cmp(want) != 0 {
t.Fatalf("reward = %s, want %s", reward, want)
}
// Same work, same chain → double-spend rejected.
if _, err := mintWork(st, proof, chainId); err != ErrWorkAlreadySpent {
t.Fatalf("double-spend: got %v, want ErrWorkAlreadySpent", err)
}
// Same work on a DIFFERENT chain is a distinct workId (chainId-bound) → allowed.
if _, err := mintWork(st, proof, chainId+1); err != nil {
t.Fatalf("cross-chain work should be distinct: %v", err)
}
}
func TestMintDataAndDoubleSpend(t *testing.T) {
st := NewMockStateDB()
desc := make([]byte, 42)
desc[31] = 0xCD // dataHash (last byte)
binary.BigEndian.PutUint64(desc[32:40], 1000) // dataSize units
binary.BigEndian.PutUint16(desc[40:42], 3) // privacy: confidential
chainId := uint64(200202)
reward, err := mintData(st, desc, chainId)
if err != nil {
t.Fatalf("mintData: %v", err)
}
want := new(big.Int).Set(baseRewardPerMinute)
want.Mul(want, big.NewInt(1000))
want.Mul(want, big.NewInt(int64(privacyMultipliers[3])))
want.Div(want, big.NewInt(10000))
if reward.Sign() <= 0 || reward.Cmp(want) != 0 {
t.Fatalf("data reward = %s, want %s", reward, want)
}
// Same dataset → double-claim rejected.
if _, err := mintData(st, desc, chainId); err != ErrWorkAlreadySpent {
t.Fatalf("double-claim data: got %v, want ErrWorkAlreadySpent", err)
}
}
func TestParseTriple(t *testing.T) {
a, b, c := []byte("workproof-blob"), []byte("pubkey"), []byte("signature-bytes")
var in []byte
put := func(blob []byte) {
var l [4]byte
binary.BigEndian.PutUint32(l[:], uint32(len(blob)))
in = append(in, l[:]...)
in = append(in, blob...)
}
put(a)
put(b)
put(c)
var cid [8]byte
binary.BigEndian.PutUint64(cid[:], 200202)
in = append(in, cid[:]...)
ga, gb, gc, gcid, err := parseTriple(in)
if err != nil {
t.Fatalf("parseTriple: %v", err)
}
if string(ga) != string(a) || string(gb) != string(b) || string(gc) != string(c) || gcid != 200202 {
t.Fatalf("parseTriple mismatch: %q %q %q %d", ga, gb, gc, gcid)
}
if _, _, _, _, err := parseTriple(in[:len(in)-2]); err == nil {
t.Fatal("expected truncated parseTriple to error")
}
}
// makeDataDescriptor builds the 42-byte contribution header: dataHash(32) |
// dataSize(8) | privacyLevel(2).
func makeDataDescriptor(size uint64, privacy uint16) []byte {
d := make([]byte, DataContributionSize)
d[31] = 0xCC
binary.BigEndian.PutUint64(d[32:40], size)
binary.BigEndian.PutUint16(d[40:42], privacy)
return d
}
// A magnitude bound is what stops one attested-but-lying device from settling
// an unbounded reward. The device binding establishes that a real,
// embedded-root-certified device signed the claim; it does NOT establish that
// the size that device wrote is honest. Unbounded, a single sovereign-privacy
// data claim settles 1e18 * (2^64-1) * 1.5 / 1e4 = 2.767e19 AI in one call.
func TestClaimMagnitudeIsBounded(t *testing.T) {
chainId := uint64(200202)
t.Run("compute minutes above the bound are refused", func(t *testing.T) {
if _, err := CalculateReward(makeWorkProof(PrivacySovereign, MaxComputeMinutes+1), chainId); err != ErrClaimTooLarge {
t.Fatalf("got %v, want ErrClaimTooLarge", err)
}
// The uint32 maximum was the real exposure, not an off-by-one.
if _, err := CalculateReward(makeWorkProof(PrivacySovereign, ^uint32(0)), chainId); err != ErrClaimTooLarge {
t.Fatalf("max uint32: got %v, want ErrClaimTooLarge", err)
}
})
t.Run("compute minutes at the bound are admitted", func(t *testing.T) {
if _, err := CalculateReward(makeWorkProof(PrivacySovereign, MaxComputeMinutes), chainId); err != nil {
t.Fatalf("boundary claim refused: %v", err)
}
})
t.Run("data size above the bound is refused", func(t *testing.T) {
if _, err := mintData(NewMockStateDB(), makeDataDescriptor(MaxDataSize+1, PrivacySovereign), chainId); err != ErrClaimTooLarge {
t.Fatalf("got %v, want ErrClaimTooLarge", err)
}
// The uint64 maximum is the 2.767e19 AI claim.
if _, err := mintData(NewMockStateDB(), makeDataDescriptor(^uint64(0), PrivacySovereign), chainId); err != ErrClaimTooLarge {
t.Fatalf("max uint64: got %v, want ErrClaimTooLarge", err)
}
})
t.Run("data size at the bound is admitted", func(t *testing.T) {
if _, err := mintData(NewMockStateDB(), makeDataDescriptor(MaxDataSize, PrivacySovereign), chainId); err != nil {
t.Fatalf("boundary claim refused: %v", err)
}
})
}