fix(ai,attestation): close two live AI-precompile forges

CRITICAL #1 -- ai mint forge (0x0300 verifyAndMint{Work,Data}):
verification ran VerifyMLDSA against an attacker-supplied calldata pubkey,
so a self-signed work proof minted unbounded reward. Gate the mint on a real
TEE quote embedded in the work proof: the cert chain must terminate at the
embedded vendor root (ai.verifyAttestation, checked at the report's own
timestamp) and the attested deviceID must equal BLAKE3(pubkey). The signing
key is vouched for on chain, never asserted in calldata. Fused gas now meters
the cert-chain verify (GasVerifyTEE). Roots threaded explicitly, no global state.

CRITICAL #2 -- attestation fake-verify + consensus split (0x0301):
VerifyNVTrust returned Verified:true for zero-byte evidence, used time.Now(),
and mutated a process-global verifier map. Route every selector to the real
verifier (ai.VerifyTEE), delete the fake path, derive attestation expiry from
the block timestamp, and drop the process-global registry (TEE quotes are
self-validating -- stateless). The fake luxfi/ai/pkg/attestation dependency
is removed.

Tests: self-signed/forged mints rejected (ErrMissingAttestation,
ErrTEESignatureInvalid, ErrDeviceKeyMismatch); zero/garbage/self-rooted
attestation rejected by the real verifier; CreateAttestation deterministic
on (input, blockTS). 66 pass, 0 fail. go.mod/go.sum untouched.
This commit is contained in:
2026-06-27 19:06:40 -07:00
parent 8437c31119
commit 64893371a5
7 changed files with 706 additions and 908 deletions
+7
View File
@@ -90,8 +90,15 @@ var (
ErrInvalidTEEReceipt = errors.New("invalid TEE attestation receipt")
ErrTEESignatureInvalid = errors.New("TEE attestation signature verification failed")
ErrUnauthorized = errors.New("unauthorized caller")
ErrMissingAttestation = errors.New("work proof missing TEE attestation quote")
ErrDeviceKeyMismatch = errors.New("attested device identity does not match signing key")
)
// DataContributionSize is the fixed size of a data-contribution descriptor:
// dataHash(32) | dataSize(uint64,8) | privacyLevel(uint16,2). A TEE attestation
// envelope binding the signing key to a real device follows these 42 bytes.
const DataContributionSize = 42
// StateDB interface for accessing and modifying state
type StateDB interface {
GetState(addr [20]byte, key [32]byte) [32]byte
+99 -4
View File
@@ -19,17 +19,22 @@
package ai
import (
"crypto/x509"
"encoding/binary"
"fmt"
"math/big"
"github.com/zeebo/blake3"
"github.com/luxfi/precompile/contract"
)
// Gas: the fused cost of the primitives each method composes.
// Gas: the fused cost of the primitives each method composes. A mint now also
// verifies a TEE attestation certificate chain (GasVerifyTEE) — the X.509 work
// that proves the signing key belongs to a real device — so the cost is metered.
const (
GasVerifyAndMintWork uint64 = GasVerifyMLDSA + GasCalculateReward + GasMarkSpent
GasVerifyAndMintData uint64 = GasVerifyMLDSA + GasCalculateReward + GasMarkSpent
GasVerifyAndMintWork uint64 = GasVerifyMLDSA + GasVerifyTEE + GasCalculateReward + GasMarkSpent
GasVerifyAndMintData uint64 = GasVerifyMLDSA + GasVerifyTEE + GasCalculateReward + GasMarkSpent
)
// ErrInputTooShort is returned when calldata is truncated.
@@ -101,7 +106,7 @@ func mintWork(stateDB StateDB, workProof []byte, chainId uint64) (*big.Int, erro
// all of these to the chain, so the same dataset can't be double-claimed. Reward scales
// with data size and the privacy multiplier (the same schedule as compute mining).
func mintData(stateDB StateDB, descriptor []byte, chainId uint64) (*big.Int, error) {
if len(descriptor) < 42 {
if len(descriptor) < DataContributionSize {
return nil, ErrInvalidWorkProof
}
var dataHash [32]byte
@@ -135,11 +140,67 @@ func mintData(stateDB StateDB, descriptor []byte, chainId uint64) (*big.Int, err
return reward, nil
}
// verifyDeviceBinding is the trust gate that makes a mint impossible without a
// chain-trusted signature. The attestation envelope appended to a work proof /
// data descriptor is:
//
// envelope = [4]sigLen | teeSig | receipt
// receipt = report(48) | certChainDER (report = deviceID(32)|ts(8)|nonce(8))
//
// It establishes, in order: (1) the certificate chain in the receipt terminates
// at an embedded vendor root (a genuine TEE device), checked at the report's own
// timestamp so every validator agrees; (2) teeSig is the device leaf's signature
// over the 48-byte report; and (3) the attested deviceID equals BLAKE3(pubkey) —
// so the trust runs embedded-root → device leaf → this exact ML-DSA key. A miner
// that presents a self-generated key with no embedded-root-anchored quote is
// rejected: the signing key is vouched for on chain, never asserted in calldata.
//
// roots is threaded explicitly (production passes teeRootPool(); tests pass a
// generated PKI) so the gate carries no hidden global state.
func verifyDeviceBinding(pubkey, envelope []byte, roots *x509.CertPool) error {
if len(envelope) < 4 {
return ErrInvalidTEEReceipt
}
sigLen := uint64(binary.BigEndian.Uint32(envelope[0:4]))
if sigLen == 0 || 4+sigLen > uint64(len(envelope)) {
return ErrInvalidTEEReceipt
}
teeSig := envelope[4 : 4+sigLen]
receipt := envelope[4+sigLen:]
ok, err := verifyAttestation(receipt, teeSig, roots)
if err != nil {
return err
}
if !ok {
return ErrTEESignatureInvalid
}
// receipt is guaranteed >= teeReportLen here (verifyAttestation enforced it),
// so receipt[0:32] (the attested deviceID) is safe to read.
var attestedDevice [32]byte
copy(attestedDevice[:], receipt[:32])
if attestedDevice != blake3.Sum256(pubkey) {
return ErrDeviceKeyMismatch
}
return nil
}
func (c *AIMiningContract) runVerifyAndMintWork(
accessibleState contract.AccessibleState,
input []byte,
suppliedGas uint64,
readOnly bool,
) ([]byte, uint64, error) {
return c.verifyAndMintWork(accessibleState, input, suppliedGas, readOnly, teeRootPool())
}
// verifyAndMintWork is runVerifyAndMintWork with the trust-anchor pool injected.
func (c *AIMiningContract) verifyAndMintWork(
accessibleState contract.AccessibleState,
input []byte,
suppliedGas uint64,
readOnly bool,
roots *x509.CertPool,
) ([]byte, uint64, error) {
if readOnly {
return nil, suppliedGas, fmt.Errorf("cannot mint in read-only mode")
@@ -153,6 +214,7 @@ func (c *AIMiningContract) runVerifyAndMintWork(
if err != nil {
return nil, remaining, err
}
// 1. The post-quantum key signed THIS exact work claim.
valid, err := VerifyMLDSA(pubkey, workProof, signature)
if err != nil {
return nil, remaining, err
@@ -160,6 +222,19 @@ func (c *AIMiningContract) runVerifyAndMintWork(
if !valid {
return nil, remaining, fmt.Errorf("ml-dsa attestation invalid")
}
// 2. A TEE quote binds that key to a real, embedded-root-certified device.
if len(workProof) <= WorkProofTEEQuoteOffset {
return nil, remaining, ErrMissingAttestation
}
if err := verifyDeviceBinding(pubkey, workProof[WorkProofTEEQuoteOffset:], roots); err != nil {
return nil, remaining, err
}
// 3. The work proof's own deviceID must equal the attested key identity, so
// the settled workId is scoped to the chain-trusted device, not a value
// the attacker grinds freely.
if [32]byte(workProof[:32]) != blake3.Sum256(pubkey) {
return nil, remaining, ErrDeviceKeyMismatch
}
stateDB := accessibleState.GetStateDB()
reward, err := mintWork(&stateDBAdapter{stateDB, ContractAddress}, workProof, chainId)
@@ -176,6 +251,17 @@ func (c *AIMiningContract) runVerifyAndMintData(
input []byte,
suppliedGas uint64,
readOnly bool,
) ([]byte, uint64, error) {
return c.verifyAndMintData(accessibleState, input, suppliedGas, readOnly, teeRootPool())
}
// verifyAndMintData is runVerifyAndMintData with the trust-anchor pool injected.
func (c *AIMiningContract) verifyAndMintData(
accessibleState contract.AccessibleState,
input []byte,
suppliedGas uint64,
readOnly bool,
roots *x509.CertPool,
) ([]byte, uint64, error) {
if readOnly {
return nil, suppliedGas, fmt.Errorf("cannot mint in read-only mode")
@@ -189,6 +275,7 @@ func (c *AIMiningContract) runVerifyAndMintData(
if err != nil {
return nil, remaining, err
}
// 1. The post-quantum key signed THIS exact data descriptor.
valid, err := VerifyMLDSA(pubkey, descriptor, signature)
if err != nil {
return nil, remaining, err
@@ -196,6 +283,14 @@ func (c *AIMiningContract) runVerifyAndMintData(
if !valid {
return nil, remaining, fmt.Errorf("ml-dsa attestation invalid")
}
// 2. A TEE quote (appended after the 42-byte descriptor) binds that key to a
// real, embedded-root-certified device (the scrub service's enclave).
if len(descriptor) <= DataContributionSize {
return nil, remaining, ErrMissingAttestation
}
if err := verifyDeviceBinding(pubkey, descriptor[DataContributionSize:], roots); err != nil {
return nil, remaining, err
}
stateDB := accessibleState.GetStateDB()
reward, err := mintData(&stateDBAdapter{stateDB, ContractAddress}, descriptor, chainId)
+159 -22
View File
@@ -1,9 +1,14 @@
// Copyright (C) 2026, Lux Partners Limited. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
//
// End-to-end tests for the atomic cross-chain mining methods through the precompile's
// Run entry point: real ML-DSA signature -> calldata decode -> verify -> reward ->
// mark-spent. Exercises the full consensus-economic path, not just the core helpers.
// End-to-end tests for the atomic cross-chain mining methods. A mint is only
// honored for a post-quantum key that an embedded-root-certified TEE device has
// vouched for: the work proof must carry a TEE quote whose attested deviceID
// equals BLAKE3(pubkey). The happy path threads a generated test PKI through
// the roots-injected seam (verifyAndMint*); the forge path runs through the
// production Run entry point (embedded Intel root) where a self-signed key with
// no real device quote is rejected — that rejection is the proof the unbacked
// mint is closed.
package ai
@@ -16,6 +21,8 @@ import (
"github.com/cloudflare/circl/sign/mldsa/mldsa65"
"github.com/holiman/uint256"
"github.com/zeebo/blake3"
"github.com/luxfi/geth/common"
"github.com/luxfi/geth/core/tracing"
ethtypes "github.com/luxfi/geth/core/types"
@@ -78,32 +85,77 @@ func tripleCalldata(sel uint32, blobs [][]byte, chainId uint64) []byte {
return append(out, cid[:]...)
}
func mldsaKeyAndSig(t *testing.T, msg []byte) (pub, sig []byte) {
// --- ML-DSA + TEE-quote test fixtures ---------------------------------------
func genMLDSA(t *testing.T) ([]byte, *mldsa65.PrivateKey) {
t.Helper()
pk, sk, err := mldsa65.GenerateKey(rand.Reader)
if err != nil {
t.Fatalf("keygen: %v", err)
}
pub, err = pk.MarshalBinary()
pub, err := pk.MarshalBinary()
if err != nil {
t.Fatalf("marshal: %v", err)
}
sig = make([]byte, MLDSA65SignatureSize)
return pub, sk
}
func signMLDSA(t *testing.T, sk *mldsa65.PrivateKey, msg []byte) []byte {
t.Helper()
sig := make([]byte, MLDSA65SignatureSize)
if err := mldsa65.SignTo(sk, msg, nil, true, sig); err != nil {
t.Fatalf("sign: %v", err)
}
return pub, sig
return sig
}
// reportWithDevice builds a 48-byte Lux attestation report carrying a full
// 32-byte deviceID.
func reportWithDevice(dev [32]byte, ts int64) []byte {
r := make([]byte, teeReportLen)
copy(r[:32], dev[:])
binary.BigEndian.PutUint64(r[32:40], uint64(ts))
binary.BigEndian.PutUint64(r[40:48], 0xCAFE)
return r
}
// teeEnvelope frames a device-leaf signature and a receipt into the wire shape
// verifyDeviceBinding parses: [4]sigLen | teeSig | receipt.
func teeEnvelope(teeSig, receipt []byte) []byte {
out := make([]byte, 4)
binary.BigEndian.PutUint32(out, uint32(len(teeSig)))
out = append(out, teeSig...)
return append(out, receipt...)
}
// attestedQuote returns the envelope binding deviceID = BLAKE3(pub) to the test
// PKI in tc, plus the deviceID itself.
func attestedQuote(t *testing.T, tc *teeChain, pub []byte) (env []byte, dev [32]byte) {
t.Helper()
dev = blake3.Sum256(pub)
rep := reportWithDevice(dev, testAttestTime)
receipt := tc.receiptFor(rep)
teeSig := tc.signRaw(t, rep)
return teeEnvelope(teeSig, receipt), dev
}
// --- happy path (generated PKI injected through the roots seam) -------------
func TestVerifyAndMintWork_E2E(t *testing.T) {
const chainId = uint64(420420) // Beluga
proof := makeWorkProof(3, 60) // confidential, 60 compute-minutes
pub, sig := mldsaKeyAndSig(t, proof)
tc := newTEEChain(t, caNotAfter)
pub, sk := genMLDSA(t)
env, dev := attestedQuote(t, tc, pub)
var nonce [32]byte
nonce[31] = 0x01
proof := BuildWorkProof(dev, nonce, uint64(testAttestTime), 3, 60, env) // confidential, 60 min
sig := signMLDSA(t, sk, proof)
cd := tripleCalldata(SelectorVerifyAndMintWork, [][]byte{proof, pub, sig}, chainId)
acc := ccAcc{s: newCCDB()}
gas := AIMiningPrecompile.RequiredGas(cd)
ret, _, err := AIMiningPrecompile.Run(acc, common.Address{}, ContractAddress, cd, gas, false)
ret, _, err := AIMiningPrecompile.verifyAndMintWork(acc, cd[4:], gas, false, tc.roots)
if err != nil {
t.Fatalf("verifyAndMintWork: %v", err)
}
@@ -112,38 +164,57 @@ func TestVerifyAndMintWork_E2E(t *testing.T) {
}
// double-spend: same work id again -> rejected
if _, _, err := AIMiningPrecompile.Run(acc, common.Address{}, ContractAddress, cd, gas, false); err == nil {
if _, _, err := AIMiningPrecompile.verifyAndMintWork(acc, cd[4:], gas, false, tc.roots); err == nil {
t.Fatal("expected double-spend rejection")
}
// read-only: writes not allowed
if _, _, err := AIMiningPrecompile.Run(acc, common.Address{}, ContractAddress, cd, gas, true); err == nil {
if _, _, err := AIMiningPrecompile.verifyAndMintWork(acc, cd[4:], gas, true, tc.roots); err == nil {
t.Fatal("expected read-only rejection")
}
// invalid signature: fresh (unspent) work id, corrupted sig -> rejected on verify
fresh := makeWorkProof(3, 60)
fresh[63] = 0xCC // different nonce -> different workId (unspent)
pub2, sig2 := mldsaKeyAndSig(t, fresh)
sig2[0] ^= 0xFF
bad := tripleCalldata(SelectorVerifyAndMintWork, [][]byte{fresh, pub2, sig2}, chainId)
if _, _, err := AIMiningPrecompile.Run(acc, common.Address{}, ContractAddress, bad, AIMiningPrecompile.RequiredGas(bad), false); err == nil {
var nonce2 [32]byte
nonce2[31] = 0x02
fresh := BuildWorkProof(dev, nonce2, uint64(testAttestTime), 3, 60, env)
badSig := signMLDSA(t, sk, fresh)
badSig[0] ^= 0xFF
bad := tripleCalldata(SelectorVerifyAndMintWork, [][]byte{fresh, pub, badSig}, chainId)
if _, _, err := AIMiningPrecompile.verifyAndMintWork(acc, bad[4:], AIMiningPrecompile.RequiredGas(bad), false, tc.roots); err == nil {
t.Fatal("expected invalid-signature rejection")
}
// device-key mismatch: workProof deviceID != BLAKE3(pubkey) -> rejected even
// though the TEE quote itself is well-formed (covers gate check #3).
var nonce3 [32]byte
nonce3[31] = 0x03
var wrongDev [32]byte
wrongDev[0] = 0xFF
mismatch := BuildWorkProof(wrongDev, nonce3, uint64(testAttestTime), 3, 60, env)
mSig := signMLDSA(t, sk, mismatch)
mcd := tripleCalldata(SelectorVerifyAndMintWork, [][]byte{mismatch, pub, mSig}, chainId)
if _, _, err := AIMiningPrecompile.verifyAndMintWork(acc, mcd[4:], AIMiningPrecompile.RequiredGas(mcd), false, tc.roots); err != ErrDeviceKeyMismatch {
t.Fatalf("deviceID mismatch: got %v, want ErrDeviceKeyMismatch", err)
}
}
func TestVerifyAndMintData_E2E(t *testing.T) {
const chainId = uint64(420420)
desc := make([]byte, 42) // dataHash(32) | size(8) | privacy(2)
tc := newTEEChain(t, caNotAfter)
pub, sk := genMLDSA(t)
env, _ := attestedQuote(t, tc, pub)
desc := make([]byte, DataContributionSize)
desc[31] = 0xDD // dataHash
binary.BigEndian.PutUint64(desc[32:40], 1000) // 1000 data units
binary.BigEndian.PutUint16(desc[40:42], 3) // confidential
pub, sig := mldsaKeyAndSig(t, desc)
desc = append(desc, env...)
sig := signMLDSA(t, sk, desc)
cd := tripleCalldata(SelectorVerifyAndMintData, [][]byte{desc, pub, sig}, chainId)
acc := ccAcc{s: newCCDB()}
gas := AIMiningPrecompile.RequiredGas(cd)
ret, _, err := AIMiningPrecompile.Run(acc, common.Address{}, ContractAddress, cd, gas, false)
ret, _, err := AIMiningPrecompile.verifyAndMintData(acc, cd[4:], gas, false, tc.roots)
if err != nil {
t.Fatalf("verifyAndMintData: %v", err)
}
@@ -151,7 +222,73 @@ func TestVerifyAndMintData_E2E(t *testing.T) {
t.Fatal("data reward must be positive")
}
// same dataset -> double-claim rejected
if _, _, err := AIMiningPrecompile.Run(acc, common.Address{}, ContractAddress, cd, gas, false); err == nil {
if _, _, err := AIMiningPrecompile.verifyAndMintData(acc, cd[4:], gas, false, tc.roots); err == nil {
t.Fatal("expected double-claim rejection")
}
}
// --- forge rejection (production Run, embedded Intel root) ------------------
// TestVerifyAndMintWork_ForgeRejected is the proof that the unbacked-mint forge
// is closed: an attacker self-signs a work proof with their OWN ML-DSA key and
// submits it through the production entry point. Without an embedded-root TEE
// quote vouching for the key, the mint is rejected. The CRITICAL bug was that
// this calldata used to mint unlimited reward.
func TestVerifyAndMintWork_ForgeRejected(t *testing.T) {
const chainId = uint64(420420)
acc := ccAcc{s: newCCDB()}
// (a) attacker key, no TEE quote at all -> rejected.
pub, sk := genMLDSA(t)
dev := blake3.Sum256(pub)
var nonce [32]byte
nonce[31] = 0x01
bare := BuildWorkProof(dev, nonce, uint64(testAttestTime), 3, 60, nil)
sig := signMLDSA(t, sk, bare)
cd := tripleCalldata(SelectorVerifyAndMintWork, [][]byte{bare, pub, sig}, chainId)
if _, _, err := AIMiningPrecompile.Run(acc, common.Address{}, ContractAddress, cd, AIMiningPrecompile.RequiredGas(cd), false); err != ErrMissingAttestation {
t.Fatalf("self-signed, no quote: got %v, want ErrMissingAttestation", err)
}
// (b) attacker key, well-formed quote that chains to the attacker's OWN test
// root (NOT the embedded vendor root) -> rejected by the real verifier.
tc := newTEEChain(t, caNotAfter)
env, dev2 := attestedQuote(t, tc, pub)
var nonce2 [32]byte
nonce2[31] = 0x02
proof := BuildWorkProof(dev2, nonce2, uint64(testAttestTime), 3, 60, env)
sig2 := signMLDSA(t, sk, proof)
cd2 := tripleCalldata(SelectorVerifyAndMintWork, [][]byte{proof, pub, sig2}, chainId)
if _, _, err := AIMiningPrecompile.Run(acc, common.Address{}, ContractAddress, cd2, AIMiningPrecompile.RequiredGas(cd2), false); err != ErrTEESignatureInvalid {
t.Fatalf("self-rooted quote: got %v, want ErrTEESignatureInvalid", err)
}
}
// TestVerifyAndMintData_ForgeRejected mirrors the work case for data
// contributions through the production entry point.
func TestVerifyAndMintData_ForgeRejected(t *testing.T) {
const chainId = uint64(420420)
acc := ccAcc{s: newCCDB()}
pub, sk := genMLDSA(t)
// no attestation appended -> rejected.
desc := make([]byte, DataContributionSize)
desc[31] = 0xDD
binary.BigEndian.PutUint64(desc[32:40], 1000)
binary.BigEndian.PutUint16(desc[40:42], 3)
sig := signMLDSA(t, sk, desc)
cd := tripleCalldata(SelectorVerifyAndMintData, [][]byte{desc, pub, sig}, chainId)
if _, _, err := AIMiningPrecompile.Run(acc, common.Address{}, ContractAddress, cd, AIMiningPrecompile.RequiredGas(cd), false); err != ErrMissingAttestation {
t.Fatalf("self-signed data, no quote: got %v, want ErrMissingAttestation", err)
}
// attacker test-rooted quote -> rejected by the real verifier.
tc := newTEEChain(t, caNotAfter)
env, _ := attestedQuote(t, tc, pub)
desc2 := append(append([]byte{}, desc...), env...)
sig2 := signMLDSA(t, sk, desc2)
cd2 := tripleCalldata(SelectorVerifyAndMintData, [][]byte{desc2, pub, sig2}, chainId)
if _, _, err := AIMiningPrecompile.Run(acc, common.Address{}, ContractAddress, cd2, AIMiningPrecompile.RequiredGas(cd2), false); err != ErrTEESignatureInvalid {
t.Fatalf("self-rooted data quote: got %v, want ErrTEESignatureInvalid", err)
}
}
+137 -402
View File
@@ -1,16 +1,30 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// Package attestation implements EVM precompiles for TEE attestation verification.
// This wires EVM calls to the real attestation implementations in luxfi/ai/pkg/attestation.
// Package attestation implements the EVM precompile (0x0301) for TEE attestation
// verification. It is a thin, STATELESS adapter over the one real verifier in
// the AI precompile block — ai.VerifyTEE (0x0300 selector 0x03) — which checks a
// device certificate chain to an embedded vendor root at the report's own
// timestamp. There is exactly one way to verify a TEE quote across the block.
//
// Precompile addresses:
// - 0x0301: NVTrust GPU attestation
// - 0x0302: TPM attestation
// - 0x0303: Compute attestation
// - 0x0304: Attestation creation
// Design (decomplected from the prior fake duplicate):
// - Verification is real: a quote either chains to an embedded vendor root or
// it is rejected. Zero-byte / forged evidence fails closed. There is no
// "verified-by-default" path.
// - Verification is deterministic: validity is checked at the report's
// embedded timestamp (inside ai.verifyAttestation), never the wall clock.
// The only chain-time input — an attestation's expiry — is taken from the
// block timestamp passed in from AccessibleState, never time.Now().
// - Verification is stateless: there is no process-global device registry (the
// prior mutable map caused consensus splits). A TEE quote is self-contained,
// so no on-chain registry is required to prevent forgery; none is kept.
//
// All attestation is LOCAL - no cloud dependencies (blockchain requirement).
// Precompile selectors (first 4 bytes of input):
// - 0x01: VerifyNVTrust (GPU TEE quote)
// - 0x02: VerifyTPM (CPU TEE quote: SGX / SEV-SNP / TDX)
// - 0x03: VerifyCompute (AI compute-result TEE quote)
// - 0x04: CreateAttestation (verify a quote, issue a deterministic record)
// - 0x05: GetDeviceStatus (no on-chain registry → always not-found)
package attestation
import (
@@ -18,10 +32,10 @@ import (
"encoding/json"
"errors"
"math/big"
"sync"
"time"
"github.com/luxfi/ai/pkg/attestation"
"github.com/zeebo/blake3"
"github.com/luxfi/precompile/ai"
"github.com/luxfi/precompile/contract"
)
@@ -43,6 +57,17 @@ const (
GasGetDeviceStatus uint64 = 5000 // Query device status
)
// Deterministic trust scores returned for a verified quote, by TEE flavor. A
// quote that does not verify scores 0.
const (
trustScoreGPU = 90 // hardware-rooted GPU TEE (NVTrust)
trustScoreCPU = 85 // CPU TEE (SGX / SEV-SNP / TDX)
// attestationTTL is how long, in seconds, an issued attestation record is
// considered valid past the block in which it was created.
attestationTTL = 3600
)
// Errors
var (
ErrInvalidInput = contract.ErrInvalidInput
@@ -54,30 +79,31 @@ var (
ErrAttestationExpired = errors.New("attestation has expired")
)
// Global verifier instance (singleton for efficiency).
// verifierMu guards concurrent access to globalVerifier and nvtrustVerifier —
// both have internal mutable state (device registrations, job completions,
// trusted measurements) and are called from the EVM execution loop where
// multiple transactions may invoke attestation precompiles in parallel.
// Without this guard, concurrent writes would data-race and cause consensus
// splits across validators.
var (
verifierMu sync.RWMutex
globalVerifier = attestation.NewVerifier()
nvtrustVerifier = attestation.NewNvtrustVerifier(attestation.DefaultNvtrustConfig())
)
// QuoteInput is the TEE attestation envelope every verifying selector consumes.
// It is the same envelope the canonical verifier (ai.VerifyTEE) checks:
//
// Receipt = report(48) || certChainDER (report = deviceID|timestamp|nonce)
// Signature = device leaf-cert signature over the 48-byte report
type QuoteInput struct {
Receipt []byte `json:"receipt"`
Signature []byte `json:"signature"`
}
// VerifyNVTrustInput represents input for GPU attestation verification
type VerifyNVTrustInput struct {
DeviceID [32]byte `json:"device_id"`
Model string `json:"model"`
CCEnabled bool `json:"cc_enabled"`
TEEIOEnabled bool `json:"tee_io_enabled"`
DriverVersion string `json:"driver_version"`
VBIOSVersion string `json:"vbios_version"`
SPDMReport []byte `json:"spdm_report"`
CertChain []byte `json:"cert_chain"`
Nonce [32]byte `json:"nonce"`
// verifyQuote decodes a QuoteInput and verifies it with the real cert-chain
// verifier. A malformed JSON envelope is a hard input error; any verification
// failure (untrusted chain, bad signature, structurally invalid receipt) is a
// deterministic false, never a revert and never a default-true.
func verifyQuote(data []byte) (QuoteInput, bool, error) {
var q QuoteInput
if err := json.Unmarshal(data, &q); err != nil {
return q, false, ErrInvalidInput
}
ok, err := ai.VerifyTEE(q.Receipt, q.Signature)
if err != nil {
// Structural failure (short/absent receipt or signature): not verified.
return q, false, nil
}
return q, ok, nil
}
// VerifyNVTrustOutput represents output from GPU attestation verification
@@ -86,250 +112,66 @@ type VerifyNVTrustOutput struct {
TrustScore uint8 `json:"trust_score"`
HardwareCC bool `json:"hardware_cc"`
RIMVerified bool `json:"rim_verified"`
Mode uint8 `json:"mode"` // 0=Local, 1=Software
Mode uint8 `json:"mode"` // 0=Local
}
// VerifyNVTrust verifies NVIDIA GPU attestation using local nvtrust
// This is the PRIMARY attestation method - no cloud dependencies
// Input: ABI-encoded VerifyNVTrustInput
// Output: ABI-encoded VerifyNVTrustOutput
// VerifyNVTrust verifies a GPU TEE quote via the real cert-chain verifier.
// Gas: 50,000
func VerifyNVTrust(input []byte) ([]byte, error) {
if len(input) < 64 {
return nil, ErrInvalidInput
}
// Decode input
var vi VerifyNVTrustInput
if err := decodeInput(input, &vi); err != nil {
_, verified, err := verifyQuote(input)
if err != nil {
return nil, err
}
// Build GPU attestation from input
gpuAtt := &attestation.GPUAttestation{
DeviceID: string(vi.DeviceID[:]),
Model: vi.Model,
CCEnabled: vi.CCEnabled,
TEEIOEnabled: vi.TEEIOEnabled,
DriverVersion: vi.DriverVersion,
VBIOSVersion: vi.VBIOSVersion,
Timestamp: time.Now(),
Mode: attestation.ModeLocal,
LocalEvidence: &attestation.LocalGPUEvidence{
SPDMReport: vi.SPDMReport,
CertChain: vi.CertChain,
RIMVerified: false, // Will be set by verifier
Nonce: vi.Nonce,
},
out := &VerifyNVTrustOutput{Verified: verified}
if verified {
out.TrustScore = trustScoreGPU
out.HardwareCC = true
out.RIMVerified = true
}
// Verify using real attestation implementation.
// Lock (not RLock) because verifier updates internal device registry.
verifierMu.Lock()
status, err := globalVerifier.VerifyGPUAttestation(gpuAtt)
verifierMu.Unlock()
if err != nil {
// Return failure result instead of error for verification failures
return encodeOutput(&VerifyNVTrustOutput{
Verified: false,
TrustScore: 0,
HardwareCC: false,
Mode: uint8(attestation.ModeLocal),
})
}
return encodeOutput(&VerifyNVTrustOutput{
Verified: status.Attested,
TrustScore: status.TrustScore,
HardwareCC: status.HardwareCC,
RIMVerified: gpuAtt.LocalEvidence.RIMVerified,
Mode: uint8(status.Mode),
})
return encodeOutput(out)
}
// VerifyTPMInput represents input for TPM attestation verification
type VerifyTPMInput struct {
QuoteType uint8 `json:"quote_type"` // 1=SGX, 2=SEV-SNP, 3=TDX
Quote []byte `json:"quote"`
Measurement []byte `json:"measurement"`
ReportData []byte `json:"report_data"`
Nonce [32]byte `json:"nonce"`
ExpectedMeasure []byte `json:"expected_measurement"`
}
// VerifyTPMOutput represents output from TPM attestation verification
// VerifyTPMOutput represents output from CPU TEE attestation verification
type VerifyTPMOutput struct {
Verified bool `json:"verified"`
TEEType uint8 `json:"tee_type"`
TrustScore uint8 `json:"trust_score"`
Measurement []byte `json:"measurement"`
Verified bool `json:"verified"`
TrustScore uint8 `json:"trust_score"`
}
// VerifyTPM verifies CPU TEE attestation (SGX, SEV-SNP, TDX)
// Input: ABI-encoded VerifyTPMInput
// Output: ABI-encoded VerifyTPMOutput
// VerifyTPM verifies a CPU TEE quote (SGX / SEV-SNP / TDX) via the real
// cert-chain verifier.
// Gas: 25,000
func VerifyTPM(input []byte) ([]byte, error) {
if len(input) < 64 {
return nil, ErrInvalidInput
}
var vi VerifyTPMInput
if err := decodeInput(input, &vi); err != nil {
_, verified, err := verifyQuote(input)
if err != nil {
return nil, err
}
// Map input type to TEE type
var teeType attestation.TEEType
switch vi.QuoteType {
case 1:
teeType = attestation.TEETypeSGX
case 2:
teeType = attestation.TEETypeSEVSNP
case 3:
teeType = attestation.TEETypeTDX
default:
return nil, ErrInvalidTPMQuote
out := &VerifyTPMOutput{Verified: verified}
if verified {
out.TrustScore = trustScoreCPU
}
// Build attestation quote
quote := &attestation.AttestationQuote{
Type: teeType,
Version: 1,
Quote: vi.Quote,
Measurement: vi.Measurement,
ReportData: vi.ReportData,
Timestamp: time.Now(),
Nonce: vi.Nonce[:],
}
// Verify using real attestation implementation.
// Lock because verifier updates internal device registry.
verifierMu.Lock()
err := globalVerifier.VerifyCPUAttestation(quote, vi.ExpectedMeasure)
verifierMu.Unlock()
if err != nil {
return encodeOutput(&VerifyTPMOutput{
Verified: false,
TEEType: uint8(teeType),
TrustScore: 0,
Measurement: quote.Measurement,
})
}
// Calculate trust score based on TEE type
trustScore := calculateTPMTrustScore(teeType)
return encodeOutput(&VerifyTPMOutput{
Verified: true,
TEEType: uint8(teeType),
TrustScore: trustScore,
Measurement: quote.Measurement,
})
}
// calculateTPMTrustScore returns trust score based on TEE type
func calculateTPMTrustScore(teeType attestation.TEEType) uint8 {
switch teeType {
case attestation.TEETypeSGX:
return 85 // Intel SGX has strong attestation
case attestation.TEETypeSEVSNP:
return 90 // AMD SEV-SNP has hardware-rooted trust
case attestation.TEETypeTDX:
return 88 // Intel TDX is newer but well-designed
default:
return 50
}
}
// VerifyComputeInput represents input for compute result attestation
type VerifyComputeInput struct {
TaskID [32]byte `json:"task_id"`
ProviderID [32]byte `json:"provider_id"`
ResultHash [32]byte `json:"result_hash"`
ComputeTime uint64 `json:"compute_time_ms"`
ModelHash [32]byte `json:"model_hash"`
TEEQuote []byte `json:"tee_quote"`
Signature []byte `json:"signature"`
return encodeOutput(out)
}
// VerifyComputeOutput represents output from compute attestation verification
type VerifyComputeOutput struct {
Verified bool `json:"verified"`
TrustScore uint8 `json:"trust_score"`
ProviderOK bool `json:"provider_ok"`
ResultValid bool `json:"result_valid"`
}
// VerifyCompute verifies AI compute result attestation
// This combines device attestation with compute proof verification
// Input: ABI-encoded VerifyComputeInput
// Output: ABI-encoded VerifyComputeOutput
// VerifyCompute verifies an AI compute-result TEE quote via the real cert-chain
// verifier. The quote is the proof the computation ran inside a genuine TEE.
// Gas: 35,000
func VerifyCompute(input []byte) ([]byte, error) {
if len(input) < 128 {
return nil, ErrInvalidInput
}
var vi VerifyComputeInput
if err := decodeInput(input, &vi); err != nil {
_, verified, err := verifyQuote(input)
if err != nil {
return nil, err
}
// Check provider is attested
providerID := string(vi.ProviderID[:])
verifierMu.RLock()
status, ok := globalVerifier.GetDeviceStatus(providerID)
verifierMu.RUnlock()
if !ok || !status.Attested {
return encodeOutput(&VerifyComputeOutput{
Verified: false,
TrustScore: 0,
ProviderOK: false,
})
out := &VerifyComputeOutput{Verified: verified, ResultValid: verified}
if verified {
out.TrustScore = trustScoreGPU
}
// Verify TEE quote if provided
resultValid := true
if len(vi.TEEQuote) > 0 {
// The TEE quote should contain the result hash in report_data
// This proves the computation ran inside the TEE
if len(vi.TEEQuote) < 48 {
resultValid = false
}
}
// Verify signature over result
if len(vi.Signature) < 64 {
resultValid = false
}
trustScore := status.TrustScore
if !resultValid {
trustScore = trustScore / 2 // Reduce trust for invalid result
}
// Record job completion
taskID := string(vi.TaskID[:])
verifierMu.Lock()
globalVerifier.RecordJobCompletion(providerID, taskID)
verifierMu.Unlock()
return encodeOutput(&VerifyComputeOutput{
Verified: resultValid && status.TrustScore >= 50,
TrustScore: trustScore,
ProviderOK: status.Attested,
ResultValid: resultValid,
})
}
// CreateAttestationInput represents input for creating new attestation
type CreateAttestationInput struct {
DeviceType uint8 `json:"device_type"` // 0=GPU, 1=CPU_SGX, 2=CPU_SEVSNP, 3=CPU_TDX
DeviceID [32]byte `json:"device_id"`
Model string `json:"model"`
Evidence []byte `json:"evidence"` // Raw attestation evidence
Nonce [32]byte `json:"nonce"`
return encodeOutput(out)
}
// CreateAttestationOutput represents output from attestation creation
@@ -337,92 +179,28 @@ type CreateAttestationOutput struct {
Success bool `json:"success"`
AttestationID [32]byte `json:"attestation_id"`
TrustScore uint8 `json:"trust_score"`
ExpiresAt uint64 `json:"expires_at"` // Unix timestamp
ExpiresAt uint64 `json:"expires_at"` // block-time + TTL, deterministic
}
// CreateAttestation creates a new attestation record for a device
// This registers the device with the verifier
// Input: ABI-encoded CreateAttestationInput
// Output: ABI-encoded CreateAttestationOutput
// CreateAttestation verifies a TEE quote and, on success, issues a deterministic
// attestation record. The record is NOT persisted (the precompile is stateless);
// it is returned to the caller. Expiry is the block timestamp + TTL — never the
// wall clock — so every validator computes the same value.
// Gas: 75,000
func CreateAttestation(input []byte) ([]byte, error) {
if len(input) < 64 {
return nil, ErrInvalidInput
}
var ci CreateAttestationInput
if err := decodeInput(input, &ci); err != nil {
func CreateAttestation(input []byte, blockTS uint64) ([]byte, error) {
q, verified, err := verifyQuote(input)
if err != nil {
return nil, err
}
var trustScore uint8
var success bool
switch ci.DeviceType {
case 0: // GPU
gpuAtt := &attestation.GPUAttestation{
DeviceID: string(ci.DeviceID[:]),
Model: ci.Model,
CCEnabled: attestation.IsHardwareCCCapable(ci.Model),
Timestamp: time.Now(),
Mode: attestation.ModeLocal,
LocalEvidence: &attestation.LocalGPUEvidence{
SPDMReport: ci.Evidence,
CertChain: nil, // Would be extracted from evidence
Nonce: ci.Nonce,
},
}
verifierMu.Lock()
status, err := globalVerifier.VerifyGPUAttestation(gpuAtt)
verifierMu.Unlock()
if err == nil {
success = true
trustScore = status.TrustScore
}
case 1, 2, 3: // CPU TEE types
var teeType attestation.TEEType
switch ci.DeviceType {
case 1:
teeType = attestation.TEETypeSGX
case 2:
teeType = attestation.TEETypeSEVSNP
case 3:
teeType = attestation.TEETypeTDX
}
quote := &attestation.AttestationQuote{
Type: teeType,
Quote: ci.Evidence,
Timestamp: time.Now(),
Nonce: ci.Nonce[:],
}
verifierMu.Lock()
err := globalVerifier.VerifyCPUAttestation(quote, nil)
verifierMu.Unlock()
if err == nil {
success = true
trustScore = calculateTPMTrustScore(teeType)
}
default:
return nil, ErrInvalidInput
out := &CreateAttestationOutput{
Success: verified,
AttestationID: attestationID(q.Receipt),
ExpiresAt: blockTS + attestationTTL,
}
// Generate attestation ID from device ID and timestamp
attestationID := computeAttestationID(ci.DeviceID, ci.Nonce)
// Attestation valid for 1 hour
expiresAt := time.Now().Add(time.Hour).Unix()
return encodeOutput(&CreateAttestationOutput{
Success: success,
AttestationID: attestationID,
TrustScore: trustScore,
ExpiresAt: uint64(expiresAt),
})
if verified {
out.TrustScore = trustScoreGPU
}
return encodeOutput(out)
}
// GetDeviceStatusInput represents input for querying device status
@@ -430,87 +208,42 @@ type GetDeviceStatusInput struct {
DeviceID [32]byte `json:"device_id"`
}
// GetDeviceStatusOutput represents output from device status query
// GetDeviceStatusOutput represents output from a device status query
type GetDeviceStatusOutput struct {
Found bool `json:"found"`
Attested bool `json:"attested"`
TrustScore uint8 `json:"trust_score"`
LastSeen uint64 `json:"last_seen"` // Unix timestamp
HardwareCC bool `json:"hardware_cc"`
Mode uint8 `json:"mode"`
JobCount uint32 `json:"job_count"`
Found bool `json:"found"`
}
// GetDeviceStatus returns the attestation status of a device
// Input: ABI-encoded GetDeviceStatusInput
// Output: ABI-encoded GetDeviceStatusOutput
// GetDeviceStatus reports a device's attestation status. This precompile keeps
// no on-chain device registry (TEE quotes are self-validating and the prior
// process-global registry was a consensus-split hazard), so a status lookup by
// deviceID alone is always not-found. Verify a presented quote with the
// VerifyNVTrust/VerifyTPM/VerifyCompute selectors instead.
// Gas: 5,000
func GetDeviceStatus(input []byte) ([]byte, error) {
if len(input) < 32 {
var di GetDeviceStatusInput
if err := json.Unmarshal(input, &di); err != nil {
return nil, ErrInvalidInput
}
var di GetDeviceStatusInput
if err := decodeInput(input, &di); err != nil {
return nil, err
}
deviceID := string(di.DeviceID[:])
verifierMu.RLock()
status, ok := globalVerifier.GetDeviceStatus(deviceID)
verifierMu.RUnlock()
if !ok {
return encodeOutput(&GetDeviceStatusOutput{
Found: false,
})
}
return encodeOutput(&GetDeviceStatusOutput{
Found: true,
Attested: status.Attested,
TrustScore: status.TrustScore,
LastSeen: uint64(status.LastSeen.Unix()),
HardwareCC: status.HardwareCC,
Mode: uint8(status.Mode),
JobCount: uint32(len(status.JobHistory)),
})
return encodeOutput(&GetDeviceStatusOutput{Found: false})
}
// RegisterTrustedMeasurement registers a trusted measurement with the verifier
// This should only be called from governance/admin functions
func RegisterTrustedMeasurement(name string, measurement []byte) {
verifierMu.Lock()
globalVerifier.RegisterTrustedMeasurement(name, measurement)
verifierMu.Unlock()
// attestationID derives a deterministic id from the verified quote receipt.
func attestationID(receipt []byte) [32]byte {
return blake3.Sum256(receipt)
}
// Helper functions
func decodeInput(data []byte, v any) error {
return json.Unmarshal(data, v)
}
func encodeOutput(v any) ([]byte, error) {
return json.Marshal(v)
}
func computeAttestationID(deviceID, nonce [32]byte) [32]byte {
h := attestation.ComputeAttestationHash(&attestation.AttestationQuote{
Type: attestation.TEETypeNVIDIA,
Quote: deviceID[:],
Nonce: nonce[:],
})
return h
}
// IsHardwareCCCapable checks if GPU model supports hardware confidential computing
// Delegates to real implementation in luxfi/ai/pkg/attestation
// IsHardwareCCCapable reports whether a GPU model supports hardware confidential
// computing. Pure, static lookup — no external dependency.
func IsHardwareCCCapable(model string) bool {
return attestation.IsHardwareCCCapable(model)
for _, m := range SupportedGPUModels() {
if m == model {
return true
}
}
return false
}
// SupportedGPUModels returns list of CC-capable GPU models
// SupportedGPUModels returns the CC-capable GPU models.
func SupportedGPUModels() []string {
return []string{
"H100",
@@ -522,9 +255,8 @@ func SupportedGPUModels() []string {
}
}
// RequiredGas returns gas cost for attestation operation
// RequiredGas returns gas cost for an attestation operation.
func RequiredGas(selector [4]byte) uint64 {
// Function selectors (first 4 bytes of keccak256 hash)
switch {
case selector == [4]byte{0x01, 0x00, 0x00, 0x00}: // verifyNVTrust
return GasVerifyNVTrust
@@ -541,14 +273,13 @@ func RequiredGas(selector [4]byte) uint64 {
}
}
// Run executes the attestation precompile
// This is the main entry point for EVM precompile calls
func Run(input []byte) ([]byte, error) {
// Run executes the attestation precompile. blockTS is the deterministic block
// timestamp from AccessibleState, used only for attestation expiry.
func Run(input []byte, blockTS uint64) ([]byte, error) {
if len(input) < 4 {
return nil, ErrInvalidInput
}
// Extract function selector
var selector [4]byte
copy(selector[:], input[:4])
data := input[4:]
@@ -561,7 +292,7 @@ func Run(input []byte) ([]byte, error) {
case [4]byte{0x03, 0x00, 0x00, 0x00}:
return VerifyCompute(data)
case [4]byte{0x04, 0x00, 0x00, 0x00}:
return CreateAttestation(data)
return CreateAttestation(data, blockTS)
case [4]byte{0x05, 0x00, 0x00, 0x00}:
return GetDeviceStatus(data)
default:
@@ -569,7 +300,11 @@ func Run(input []byte) ([]byte, error) {
}
}
// ABIEncode encodes values for EVM ABI format
func encodeOutput(v any) ([]byte, error) {
return json.Marshal(v)
}
// ABIEncode encodes values for EVM ABI format.
func ABIEncode(values ...any) []byte {
var result []byte
for _, v := range values {
-99
View File
@@ -1,99 +0,0 @@
package attestation
import (
"encoding/json"
"testing"
)
func mustJSON(t *testing.T, v any) []byte {
t.Helper()
b, err := json.Marshal(v)
if err != nil {
t.Fatalf("marshal: %v", err)
}
return b
}
// VerifyCompute: input below the 128-byte floor is rejected.
func TestVerifyCompute_TooShort(t *testing.T) {
if _, err := VerifyCompute([]byte(`{}`)); err != ErrInvalidInput {
t.Fatalf("want ErrInvalidInput, got %v", err)
}
}
// VerifyCompute: an unattested provider verifies to false (covers the main
// body + GetDeviceStatus miss + encodeOutput — previously 0% covered).
func TestVerifyCompute_UnattestedProvider(t *testing.T) {
var pid [32]byte
copy(pid[:], []byte("unattested-provider-aaaaaaaaaaaa"))
in := VerifyComputeInput{
ProviderID: pid,
TEEQuote: make([]byte, 48),
Signature: make([]byte, 64),
}
b := mustJSON(t, in)
if len(b) < 128 {
t.Fatalf("test input must be >=128 bytes, got %d", len(b))
}
out, err := VerifyCompute(b)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
var o VerifyComputeOutput
if err := json.Unmarshal(out, &o); err != nil {
t.Fatalf("decode output: %v", err)
}
if o.ProviderOK {
t.Error("unattested provider must not be ProviderOK")
}
if o.Verified {
t.Error("unattested provider must not verify")
}
}
// Run dispatch: every known selector routes; unknown/short are rejected.
func TestRun_DispatchAllSelectors(t *testing.T) {
for _, sel := range [][4]byte{{1, 0, 0, 0}, {2, 0, 0, 0}, {3, 0, 0, 0}, {4, 0, 0, 0}, {5, 0, 0, 0}} {
input := append(sel[:], make([]byte, 256)...)
_, _ = Run(input) // inner may error on the dummy payload; we only need the dispatch arm
}
if _, err := Run([]byte{0xff, 0xff, 0xff, 0xff}); err != ErrInvalidInput {
t.Errorf("unknown selector: want ErrInvalidInput, got %v", err)
}
if _, err := Run([]byte{0x01}); err != ErrInvalidInput {
t.Errorf("short input: want ErrInvalidInput, got %v", err)
}
}
func TestSupportedGPUModels_Coverage(t *testing.T) {
models := SupportedGPUModels()
if len(models) == 0 {
t.Fatal("expected supported GPU models")
}
var hasH100 bool
for _, m := range models {
if m == "H100" {
hasH100 = true
}
}
if !hasH100 {
t.Error("expected H100 in supported models")
}
}
func TestRegisterTrustedMeasurement_Coverage(t *testing.T) {
// Smoke: previously 0% covered. Must not panic.
RegisterTrustedMeasurement("test-firmware-v1", []byte{0x01, 0x02, 0x03, 0x04})
}
func TestIsHardwareCCCapable_TwoAxis(t *testing.T) {
if !IsHardwareCCCapable("H100") {
t.Error("H100 must be confidential-compute capable")
}
if IsHardwareCCCapable("M4 Max") {
t.Error("Apple M4 Max is not NVTrust GPU-TEE capable")
}
if IsHardwareCCCapable("DGX Spark") {
t.Error("DGX Spark must NOT be GPU-TEE capable (neutered)")
}
}
+294 -380
View File
@@ -4,294 +4,328 @@
package attestation
import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"crypto/x509"
"crypto/x509/pkix"
"encoding/binary"
"encoding/json"
"math/big"
"testing"
"time"
"github.com/luxfi/geth/common"
"github.com/luxfi/precompile/contract"
"github.com/luxfi/precompile/precompileconfig"
)
func TestVerifyNVTrust_ValidH100(t *testing.T) {
// Create valid H100 attestation input
input := VerifyNVTrustInput{
DeviceID: [32]byte{0x01, 0x02, 0x03},
Model: "H100",
CCEnabled: true,
TEEIOEnabled: true,
DriverVersion: "535.104.05",
VBIOSVersion: "96.00.89.00.01",
SPDMReport: make([]byte, 512), // Valid SPDM report size
CertChain: make([]byte, 1024), // Valid cert chain size
Nonce: [32]byte{0xAA, 0xBB, 0xCC},
}
// --- helpers ----------------------------------------------------------------
data, err := json.Marshal(input)
func mustJSON(t *testing.T, v any) []byte {
t.Helper()
b, err := json.Marshal(v)
if err != nil {
t.Fatal(err)
}
result, err := VerifyNVTrust(data)
if err != nil {
t.Fatal(err)
}
var output VerifyNVTrustOutput
if err := json.Unmarshal(result, &output); err != nil {
t.Fatal(err)
}
// H100 should be verified with high trust score
if !output.Verified {
t.Error("expected H100 to be verified")
}
if output.TrustScore < 70 {
t.Errorf("expected trust score >= 70, got %d", output.TrustScore)
}
// HardwareCC depends on RIM verification which requires real RIM data
// For test purposes, we check that the model is CC-capable
if !IsHardwareCCCapable("H100") {
t.Error("H100 should be hardware CC capable")
t.Fatalf("marshal: %v", err)
}
return b
}
func TestVerifyNVTrust_NonCCGPU(t *testing.T) {
// Create attestation for non-CC GPU (RTX 5090)
input := VerifyNVTrustInput{
DeviceID: [32]byte{0x04, 0x05, 0x06},
Model: "RTX 5090", // Consumer GPU - no CC
CCEnabled: false,
TEEIOEnabled: false,
DriverVersion: "550.00.00",
VBIOSVersion: "100.00.00.00.01",
SPDMReport: make([]byte, 512),
CertChain: make([]byte, 1024),
Nonce: [32]byte{0xDD, 0xEE, 0xFF},
}
data, err := json.Marshal(input)
if err != nil {
t.Fatal(err)
}
result, err := VerifyNVTrust(data)
if err != nil {
t.Fatal(err)
}
var output VerifyNVTrustOutput
if err := json.Unmarshal(result, &output); err != nil {
t.Fatal(err)
}
// Non-CC GPU should fail verification for local mode
if output.Verified {
t.Error("expected non-CC GPU to fail verification in local mode")
}
func leftPad32(b []byte) []byte {
out := make([]byte, 32)
copy(out[32-len(b):], b)
return out
}
func TestVerifyTPM_SGX(t *testing.T) {
// Create valid SGX quote (minimum size 432 bytes)
quote := make([]byte, 512)
// Set MRENCLAVE at offset 112-144
copy(quote[112:144], []byte("expected_measurement_value_here!"))
// selfRootedQuote builds a structurally valid TEE quote signed by a leaf that
// chains to the attacker's OWN root — NOT the embedded vendor root. The real
// verifier (ai.VerifyTEE) must reject it. This is a "forge any GPU" attempt.
func selfRootedQuote(t *testing.T) (receipt, sig []byte) {
t.Helper()
rootKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
rootTmpl := &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{CommonName: "forged root"},
NotBefore: time.Unix(1_600_000_000, 0),
NotAfter: time.Unix(2_000_000_000, 0),
IsCA: true,
BasicConstraintsValid: true,
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature,
}
rootDER, _ := x509.CreateCertificate(rand.Reader, rootTmpl, rootTmpl, &rootKey.PublicKey, rootKey)
root, _ := x509.ParseCertificate(rootDER)
input := VerifyTPMInput{
QuoteType: 1, // SGX
Quote: quote,
Measurement: quote[112:144],
ReportData: make([]byte, 64),
Nonce: [32]byte{0x11, 0x22, 0x33},
ExpectedMeasure: nil, // No expected measurement
leafKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
leafTmpl := &x509.Certificate{
SerialNumber: big.NewInt(2),
Subject: pkix.Name{CommonName: "forged leaf"},
NotBefore: time.Unix(1_600_000_000, 0),
NotAfter: time.Unix(2_000_000_000, 0),
KeyUsage: x509.KeyUsageDigitalSignature,
}
leafDER, _ := x509.CreateCertificate(rand.Reader, leafTmpl, root, &leafKey.PublicKey, rootKey)
leaf, _ := x509.ParseCertificate(leafDER)
data, err := json.Marshal(input)
if err != nil {
t.Fatal(err)
}
report := make([]byte, 48)
report[0] = 0xAB
binary.BigEndian.PutUint64(report[32:40], 1_700_000_000)
receipt = append(append([]byte{}, report...), leaf.Raw...)
result, err := VerifyTPM(data)
if err != nil {
t.Fatal(err)
}
var output VerifyTPMOutput
if err := json.Unmarshal(result, &output); err != nil {
t.Fatal(err)
}
// SGX should verify with good trust score
if !output.Verified {
t.Error("expected SGX quote to be verified")
}
if output.TEEType != 1 {
t.Errorf("expected TEE type 1 (SGX), got %d", output.TEEType)
}
if output.TrustScore < 80 {
t.Errorf("expected trust score >= 80 for SGX, got %d", output.TrustScore)
}
digest := sha256.Sum256(report)
r, s, _ := ecdsa.Sign(rand.Reader, leafKey, digest[:])
sig = append(leftPad32(r.Bytes()), leftPad32(s.Bytes())...)
return receipt, sig
}
func TestVerifyTPM_SEVSNP(t *testing.T) {
// Create valid SEV-SNP report (minimum size 1184 bytes)
quote := make([]byte, 1200)
// minimal AccessibleState exposing only a block timestamp (the only consensus
// input the attestation precompile reads).
type attBC struct{ ts uint64 }
input := VerifyTPMInput{
QuoteType: 2, // SEV-SNP
Quote: quote,
Measurement: make([]byte, 48),
ReportData: make([]byte, 64),
Nonce: [32]byte{0x44, 0x55, 0x66},
ExpectedMeasure: nil,
}
func (b attBC) Number() *big.Int { return big.NewInt(1) }
func (b attBC) Timestamp() uint64 { return b.ts }
func (b attBC) GetPredicateResults(common.Hash, common.Address) []byte { return nil }
data, err := json.Marshal(input)
if err != nil {
t.Fatal(err)
}
type attAS struct{ ts uint64 }
result, err := VerifyTPM(data)
if err != nil {
t.Fatal(err)
}
func (a attAS) GetStateDB() contract.StateDB { return nil }
func (a attAS) GetBlockContext() contract.BlockContext { return attBC{a.ts} }
func (a attAS) GetConsensusContext() context.Context { return context.Background() }
func (a attAS) GetChainConfig() precompileconfig.ChainConfig { return nil }
func (a attAS) GetPrecompileEnv() contract.PrecompileEnvironment { return nil }
var output VerifyTPMOutput
if err := json.Unmarshal(result, &output); err != nil {
t.Fatal(err)
}
// --- forge rejection: zero / garbage / self-rooted evidence -----------------
if !output.Verified {
t.Error("expected SEV-SNP quote to be verified")
// TestVerifyNVTrust_ForgedRejected is the proof the GPU-attestation forge is
// closed. The prior fake returned Verified:true (score 93) for zero-filled
// evidence; the real verifier rejects zero-byte, garbage, and self-rooted
// quotes alike.
func TestVerifyNVTrust_ForgedRejected(t *testing.T) {
cases := map[string]QuoteInput{
"zero": {Receipt: make([]byte, 512), Signature: make([]byte, 64)},
"empty": {Receipt: nil, Signature: nil},
"garbage": {Receipt: []byte("not-a-tee-quote-at-all"), Signature: []byte("nope")},
"shortrcpt": {Receipt: make([]byte, 10), Signature: make([]byte, 64)},
}
if output.TEEType != 2 {
t.Errorf("expected TEE type 2 (SEV-SNP), got %d", output.TEEType)
}
if output.TrustScore < 85 {
t.Errorf("expected trust score >= 85 for SEV-SNP, got %d", output.TrustScore)
}
}
func TestCreateAttestation_GPU(t *testing.T) {
// For GPU attestation to succeed, we need valid evidence with proper sizes
// The attestation.VerifyGPUAttestation requires SPDM report >= 256 bytes
// and cert chain >= 256 bytes
input := CreateAttestationInput{
DeviceType: 0, // GPU
DeviceID: [32]byte{0x10, 0x20, 0x30},
Model: "H200",
Evidence: make([]byte, 512), // Used as SPDM report
Nonce: [32]byte{0x77, 0x88, 0x99},
}
data, err := json.Marshal(input)
if err != nil {
t.Fatal(err)
}
result, err := CreateAttestation(data)
if err != nil {
t.Fatal(err)
}
var output CreateAttestationOutput
if err := json.Unmarshal(result, &output); err != nil {
t.Fatal(err)
}
// GPU attestation requires both SPDM report AND cert chain
// Our test only provides Evidence as SPDM report, so it may fail
// The key test is that an AttestationID is generated
if output.AttestationID == [32]byte{} {
t.Error("expected attestation ID to be generated")
}
if output.ExpiresAt <= uint64(time.Now().Unix()) {
t.Error("expected expiration to be in the future")
}
}
func TestCreateAttestation_CPU_SGX(t *testing.T) {
input := CreateAttestationInput{
DeviceType: 1, // SGX
DeviceID: [32]byte{0xA0, 0xB0, 0xC0},
Model: "Intel Xeon",
Evidence: make([]byte, 512), // Valid SGX quote
Nonce: [32]byte{0xAA, 0xBB, 0xCC},
}
data, err := json.Marshal(input)
if err != nil {
t.Fatal(err)
}
result, err := CreateAttestation(data)
if err != nil {
t.Fatal(err)
}
var output CreateAttestationOutput
if err := json.Unmarshal(result, &output); err != nil {
t.Fatal(err)
}
if !output.Success {
t.Error("expected SGX attestation to succeed")
}
if output.TrustScore != 85 { // SGX trust score
t.Errorf("expected trust score 85 for SGX, got %d", output.TrustScore)
}
}
func TestGetDeviceStatus_NotFound(t *testing.T) {
input := GetDeviceStatusInput{
DeviceID: [32]byte{0xFF, 0xFF, 0xFF}, // Unknown device
}
data, err := json.Marshal(input)
if err != nil {
t.Fatal(err)
}
result, err := GetDeviceStatus(data)
if err != nil {
t.Fatal(err)
}
var output GetDeviceStatusOutput
if err := json.Unmarshal(result, &output); err != nil {
t.Fatal(err)
}
if output.Found {
t.Error("expected device not to be found")
}
}
func TestIsHardwareCCCapable(t *testing.T) {
tests := []struct {
model string
expected bool
}{
{"H100", true},
{"H200", true},
{"B100", true},
{"B200", true},
{"GB200", true},
{"RTX PRO 6000", true},
{"RTX 5090", false},
{"RTX 4090", false},
{"GB10", false},
{"A100", false},
}
for _, tt := range tests {
t.Run(tt.model, func(t *testing.T) {
result := IsHardwareCCCapable(tt.model)
if result != tt.expected {
t.Errorf("IsHardwareCCCapable(%s) = %v, want %v", tt.model, result, tt.expected)
for name, in := range cases {
t.Run(name, func(t *testing.T) {
out, err := VerifyNVTrust(mustJSON(t, in))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
var o VerifyNVTrustOutput
if err := json.Unmarshal(out, &o); err != nil {
t.Fatalf("decode: %v", err)
}
if o.Verified {
t.Fatal("forged GPU evidence must NOT verify")
}
if o.TrustScore != 0 {
t.Fatalf("unverified quote must score 0, got %d", o.TrustScore)
}
})
}
}
// TestVerifyNVTrust_SelfRootedRejected: a well-formed quote that chains to the
// attacker's own root (not the embedded vendor root) is rejected.
func TestVerifyNVTrust_SelfRootedRejected(t *testing.T) {
rcpt, sig := selfRootedQuote(t)
out, err := VerifyNVTrust(mustJSON(t, QuoteInput{Receipt: rcpt, Signature: sig}))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
var o VerifyNVTrustOutput
if err := json.Unmarshal(out, &o); err != nil {
t.Fatalf("decode: %v", err)
}
if o.Verified {
t.Fatal("a quote not anchored at an embedded vendor root must be REJECTED")
}
}
func TestVerifyTPM_ForgedRejected(t *testing.T) {
out, err := VerifyTPM(mustJSON(t, QuoteInput{Receipt: make([]byte, 1200), Signature: make([]byte, 64)}))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
var o VerifyTPMOutput
if err := json.Unmarshal(out, &o); err != nil {
t.Fatalf("decode: %v", err)
}
if o.Verified || o.TrustScore != 0 {
t.Fatalf("forged CPU TEE quote must be rejected, got verified=%v score=%d", o.Verified, o.TrustScore)
}
}
func TestVerifyCompute_ForgedRejected(t *testing.T) {
rcpt, sig := selfRootedQuote(t)
out, err := VerifyCompute(mustJSON(t, QuoteInput{Receipt: rcpt, Signature: sig}))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
var o VerifyComputeOutput
if err := json.Unmarshal(out, &o); err != nil {
t.Fatalf("decode: %v", err)
}
if o.Verified || o.ResultValid {
t.Fatal("compute result without a genuine TEE quote must be rejected")
}
}
func TestVerifyNVTrust_MalformedJSON(t *testing.T) {
if _, err := VerifyNVTrust([]byte("{not json")); err != ErrInvalidInput {
t.Fatalf("malformed JSON: want ErrInvalidInput, got %v", err)
}
}
// --- CreateAttestation: fail-closed + deterministic block-time expiry --------
func TestCreateAttestation_FailsClosed(t *testing.T) {
const blockTS = uint64(1_700_000_000)
out, err := CreateAttestation(mustJSON(t, QuoteInput{Receipt: make([]byte, 512), Signature: make([]byte, 64)}), blockTS)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
var o CreateAttestationOutput
if err := json.Unmarshal(out, &o); err != nil {
t.Fatalf("decode: %v", err)
}
if o.Success {
t.Fatal("forged evidence must NOT create a successful attestation")
}
if o.TrustScore != 0 {
t.Fatalf("failed attestation must score 0, got %d", o.TrustScore)
}
// ExpiresAt is derived from the block timestamp, never the wall clock.
if o.ExpiresAt != blockTS+attestationTTL {
t.Fatalf("expiry must be block-time+TTL=%d, got %d", blockTS+attestationTTL, o.ExpiresAt)
}
// A deterministic id is still issued, bound to the presented receipt.
if o.AttestationID == [32]byte{} {
t.Fatal("attestation id must be derived from the receipt")
}
}
// TestCreateAttestation_Deterministic proves the consensus-split non-determinism
// (time.Now + process-global map) is gone: identical (input, blockTS) yields
// byte-identical output, and the expiry tracks the block timestamp exactly.
func TestCreateAttestation_Deterministic(t *testing.T) {
in := mustJSON(t, QuoteInput{Receipt: make([]byte, 64), Signature: make([]byte, 64)})
a, err := CreateAttestation(in, 1000)
if err != nil {
t.Fatal(err)
}
b, err := CreateAttestation(in, 1000)
if err != nil {
t.Fatal(err)
}
if string(a) != string(b) {
t.Fatal("CreateAttestation must be deterministic for identical (input, blockTS)")
}
var o1, o2 CreateAttestationOutput
_ = json.Unmarshal(a, &o1)
c, _ := CreateAttestation(in, 2000)
_ = json.Unmarshal(c, &o2)
if o2.ExpiresAt-o1.ExpiresAt != 1000 {
t.Fatalf("expiry must move with block time: delta=%d, want 1000", o2.ExpiresAt-o1.ExpiresAt)
}
}
// --- GetDeviceStatus: stateless, always not-found ---------------------------
func TestGetDeviceStatus_NotFound(t *testing.T) {
out, err := GetDeviceStatus(mustJSON(t, GetDeviceStatusInput{DeviceID: [32]byte{0xFF}}))
if err != nil {
t.Fatal(err)
}
var o GetDeviceStatusOutput
if err := json.Unmarshal(out, &o); err != nil {
t.Fatal(err)
}
if o.Found {
t.Error("stateless precompile keeps no registry: status must be not-found")
}
}
// --- Run dispatch + stateful wrapper threading ------------------------------
func TestRun_DispatchAndDeterminism(t *testing.T) {
// Every known selector routes and verification fails closed on a dummy quote.
payload := mustJSON(t, QuoteInput{Receipt: make([]byte, 64), Signature: make([]byte, 64)})
for _, sel := range [][4]byte{{1, 0, 0, 0}, {2, 0, 0, 0}, {3, 0, 0, 0}, {4, 0, 0, 0}} {
input := append(sel[:], payload...)
out1, err1 := Run(input, 1700000000)
out2, err2 := Run(input, 1700000000)
if err1 != nil || err2 != nil {
t.Fatalf("selector %v: unexpected error %v / %v", sel, err1, err2)
}
if string(out1) != string(out2) {
t.Fatalf("selector %v: non-deterministic output", sel)
}
}
if _, err := Run([]byte{0xff, 0xff, 0xff, 0xff}, 0); err != ErrInvalidInput {
t.Errorf("unknown selector: want ErrInvalidInput, got %v", err)
}
if _, err := Run([]byte{0x01}, 0); err != ErrInvalidInput {
t.Errorf("short input: want ErrInvalidInput, got %v", err)
}
}
// TestStatefulRun_BlockTimestampThreaded proves the stateful wrapper passes the
// block timestamp from AccessibleState (not time.Now) into CreateAttestation.
func TestStatefulRun_BlockTimestampThreaded(t *testing.T) {
payload := mustJSON(t, QuoteInput{Receipt: make([]byte, 64), Signature: make([]byte, 64)})
input := append([]byte{0x04, 0x00, 0x00, 0x00}, payload...)
out, _, err := Precompile.Run(attAS{ts: 4242}, common.Address{}, ContractAddress, input, GasCreateAttest, false)
if err != nil {
t.Fatalf("stateful run: %v", err)
}
var o CreateAttestationOutput
if err := json.Unmarshal(out, &o); err != nil {
t.Fatal(err)
}
if o.ExpiresAt != 4242+attestationTTL {
t.Fatalf("expiry must come from block ts: got %d, want %d", o.ExpiresAt, 4242+attestationTTL)
}
}
// --- pure helpers + gas (unchanged surface) ---------------------------------
func TestIsHardwareCCCapable(t *testing.T) {
for _, tt := range []struct {
model string
want bool
}{
{"H100", true}, {"H200", true}, {"B100", true}, {"B200", true},
{"GB200", true}, {"RTX PRO 6000", true},
{"RTX 5090", false}, {"RTX 4090", false}, {"A100", false},
{"M4 Max", false}, {"DGX Spark", false},
} {
if got := IsHardwareCCCapable(tt.model); got != tt.want {
t.Errorf("IsHardwareCCCapable(%s) = %v, want %v", tt.model, got, tt.want)
}
}
}
func TestSupportedGPUModels(t *testing.T) {
models := SupportedGPUModels()
if len(models) == 0 {
t.Fatal("expected supported GPU models")
}
for _, m := range models {
if !IsHardwareCCCapable(m) {
t.Errorf("supported model %s must be CC capable", m)
}
}
}
func TestRequiredGas(t *testing.T) {
tests := []struct {
for _, tt := range []struct {
selector [4]byte
expected uint64
}{
@@ -300,142 +334,22 @@ func TestRequiredGas(t *testing.T) {
{[4]byte{0x03, 0x00, 0x00, 0x00}, GasVerifyCompute},
{[4]byte{0x04, 0x00, 0x00, 0x00}, GasCreateAttest},
{[4]byte{0x05, 0x00, 0x00, 0x00}, GasGetDeviceStatus},
}
for _, tt := range tests {
gas := RequiredGas(tt.selector)
if gas != tt.expected {
} {
if gas := RequiredGas(tt.selector); gas != tt.expected {
t.Errorf("RequiredGas(%v) = %d, want %d", tt.selector, gas, tt.expected)
}
}
}
func TestRun_InvalidInput(t *testing.T) {
// Test with too short input
_, err := Run([]byte{0x01, 0x02})
if err != ErrInvalidInput {
t.Errorf("expected ErrInvalidInput, got %v", err)
}
}
func TestRun_VerifyNVTrust(t *testing.T) {
input := VerifyNVTrustInput{
DeviceID: [32]byte{0x01},
Model: "H100",
CCEnabled: true,
TEEIOEnabled: true,
DriverVersion: "535.0",
SPDMReport: make([]byte, 256),
CertChain: make([]byte, 256),
Nonce: [32]byte{0x01},
}
data, _ := json.Marshal(input)
// Prepend selector
callData := append([]byte{0x01, 0x00, 0x00, 0x00}, data...)
result, err := Run(callData)
if err != nil {
t.Fatal(err)
}
var output VerifyNVTrustOutput
if err := json.Unmarshal(result, &output); err != nil {
t.Fatal(err)
}
// Should have a result (whether verified or not)
if output.TrustScore == 0 && output.Verified {
t.Error("inconsistent output: verified but zero trust score")
}
}
func TestSupportedGPUModels(t *testing.T) {
models := SupportedGPUModels()
if len(models) == 0 {
t.Error("expected at least one supported GPU model")
}
// All supported models should be CC capable
for _, model := range models {
if !IsHardwareCCCapable(model) {
t.Errorf("supported model %s should be CC capable", model)
}
}
}
func TestABIEncode(t *testing.T) {
// Test encoding various types
result := ABIEncode(true, uint8(42), uint64(1000))
// Should be 96 bytes (3 * 32)
if len(result) != 96 {
t.Errorf("expected 96 bytes, got %d", len(result))
}
// Check bool encoding (last byte should be 1)
if result[31] != 1 {
t.Error("bool true should encode to ...01")
}
// Check uint8 encoding
if result[63] != 42 {
t.Errorf("uint8(42) should encode to ...42, got %d", result[63])
}
}
func BenchmarkVerifyNVTrust(b *testing.B) {
input := VerifyNVTrustInput{
DeviceID: [32]byte{0x01},
Model: "H100",
CCEnabled: true,
TEEIOEnabled: true,
DriverVersion: "535.0",
SPDMReport: make([]byte, 512),
CertChain: make([]byte, 1024),
Nonce: [32]byte{0x01},
}
data, _ := json.Marshal(input)
b.ResetTimer()
for i := 0; i < b.N; i++ {
VerifyNVTrust(data)
}
}
func BenchmarkVerifyTPM(b *testing.B) {
input := VerifyTPMInput{
QuoteType: 2, // SEV-SNP
Quote: make([]byte, 1200),
Measurement: make([]byte, 48),
ReportData: make([]byte, 64),
Nonce: [32]byte{0x01},
ExpectedMeasure: nil,
}
data, _ := json.Marshal(input)
b.ResetTimer()
for i := 0; i < b.N; i++ {
VerifyTPM(data)
}
}
func BenchmarkCreateAttestation(b *testing.B) {
input := CreateAttestationInput{
DeviceType: 0,
DeviceID: [32]byte{0x01},
Model: "H100",
Evidence: make([]byte, 512),
Nonce: [32]byte{0x01},
}
data, _ := json.Marshal(input)
b.ResetTimer()
for i := 0; i < b.N; i++ {
CreateAttestation(data)
}
}
+10 -1
View File
@@ -94,7 +94,16 @@ func (p *attestationPrecompile) Run(
return nil, 0, err
}
result, err := Run(input)
// Deterministic block timestamp from consensus state; used only for
// attestation expiry. Never time.Now().
var blockTS uint64
if accessibleState != nil {
if bc := accessibleState.GetBlockContext(); bc != nil {
blockTS = bc.Timestamp()
}
}
result, err := Run(input, blockTS)
if err != nil {
return nil, gas, err
}