LPsLux Proposals
Precompiles
LP-5301

AI Bridge Precompile (aivmbridge) - C->A Inference On-Ramp at 0x0300...0004

Draft

The C-Chain EVM precompile that on-ramps a contract to A-Chain large-model inference via two consensus-safe ops - SubmitInferenceIntent (Pattern A) and VerifyInferenceReceipt (Pattern B)

Category
Core
Created
2026-06-21

LP-5301: AI Bridge Precompile (aivmbridge) — C→A Inference On-Ramp at 0x0300…0004

Companion to LP-5300. LP-5300 specifies the Thinking Chains protocol (A-Chain quorum settlement, the bridge law, the wire spec, subsampled cognitive consensus). This LP specifies the C-Chain EVM precompile that is the entry point for Patterns A and B — exactly as LP-9010 / LP-9999 spec the DEX precompile against the D-Chain VM (LP-032).

Abstract

LP-5301 specifies the AI Bridge Precompile (aivmbridge) at address 0x0300000000000000000000000000000000000004, the C-Chain entry point that lets a smart contract request inference from a large model running on the A-Chain (AIVM) and later consume the result with native-opcode fork-safety. It exposes exactly two ops, both consensus-safe by construction:

  • submitInferenceIntent (Pattern A) — write a committed C-side outbox intent and return a deterministic intent_id. No A query, no A mutation.
  • verifyInferenceReceipt (Pattern B) — verify a committed A receipt + Merkle proof against a receipt_root C already holds committed, match a pending intent, and return the canonical output. No A query.

The precompile is a leaf library: it never reaches up into the node chain manager, and the C-Chain StateRoot is derivable without observing any live A process — the bridge law of LP-5300. It is the AI analogue of the DEX on-ramp, and its AChainClient is the exact shape of precompile/dex/dchain_client.go (DChainClient), adapted to inference and made proof/receipt-oriented (never a live-query surface).

Mining scope (normative)

This precompile verifies cognitive task receipts; it does not make C-Chain a Proof-of-AI authority. Under LP-5200, only PoAI consensus on A-Chain may admit useful mining work, validate that its bound computation was actually performed, consume the global work nullifier, and authorize subsidy. Z-Chain batches and settles that finalized transition through the activated Plonky3-derived P3Q proof path; it does not admit or validate raw mining work. Lux Quasar and the activated PQ validator policy secure the corresponding A-Chain and Z-Chain roots. If a receipt carries a mining payout, the destination verifies that Z-settled, finalized A-Chain receipt and releases only its bound amount to its bound recipient.

Neither this precompile nor the 0x0300…0000 mining slot may accept a raw miner signature, TEE quote, Freivalds opening, quorum output, or destination-local spent key as an alternative to A-Chain finality. Any legacy direct verify-and-mint selector MUST remain disabled or be replaced by receipt-only settlement before activation.

Until the exact PoAI AIR, A-to-Z state-transition binding, recursive policy, and P3Q verifier are audited and activated, this precompile MAY consume a directly authenticated A-Chain receipt root but MUST NOT describe that path as succinct Z-Chain proof settlement.

Motivation

A contract cannot call an inference server during Run() without forking consensus (see LP-5300 §Motivation). The DEX faced the identical problem with the moving order book and solved it with an on-ramp precompile that submits intents and verifies committed receipts (LP-9010, LP-9999). This LP provides the same on-ramp for cognition. It is deliberately a separate precompile from:

  • the deterministic in-consensus inference precompile 0x0300…0003 ([LP-0303], precompile/inference/module.go) — Tier 1, small models, run by every validator; left byte-identically unchanged;
  • the model registry precompile 0x0300…0002 (precompile/modelregistry/module.go) — governance model adoption;
  • the AI-mining precompile 0x0300…0000.

Keeping the on-ramp in its own slot keeps each concern orthogonal and lets a node that does not run a local A-Chain leave the on-ramp closed (every selector reverts cleanly) without affecting the other AI precompiles.

Specification

Precompile Address

0x0300000000000000000000000000000000000004

The AI reserved range is 0x0300…0000 .. 0x0300…00FF (the significant byte 0x03 is at the front; the slot is the last byte). Slot map:

SlotAddressPrecompile
0x000x0300…0000A-Chain mining-receipt settlement (no raw work verification)
0x020x0300…0002Model Registry (governance model adoption)
0x030x0300…0003Deterministic in-consensus inference (Tier 1)
0x040x0300…0004AI Bridge (this LP)

Cite: precompile/aivmbridge/module.go (ContractAddress), precompile/inference/module.go (0x0300…0003), precompile/modelregistry/module.go (0x0300…0002).

Method Selectors

submitInferenceIntent  selector = 0x10000000
verifyInferenceReceipt selector = 0x11000000

Chosen in the AI range, disjoint from the inference precompile's SelectorGenerate = 0x01000000. Each is the first 4 bytes of the calldata.

Cite: precompile/aivmbridge/bridge.go (SelectorSubmitInferenceIntent, SelectorVerifyInferenceReceipt).

Interface Definition

// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2026 Lux Industries Inc.
pragma solidity ^0.8.24;

/// @title IAIBridge - C->A inference on-ramp precompile (LP-5301)
/// @notice Precompile at 0x0300000000000000000000000000000000000004.
/// @dev Pattern A submits a committed intent; Pattern B verifies a committed
///      A-Chain receipt. Neither op observes live A-Chain state (the bridge law,
///      LP-5300). Both ops mutate the C outbox, so neither is callable in a
///      static (read-only) context.
interface IAIBridge {
    /// @notice Pattern A — record a committed C-side inference INTENT for the
    ///         A-Chain to import under its own consensus, and return the
    ///         deterministic intent id.
    /// @param modelSpecHash Weight-commitment of the model (must be non-zero;
    ///        must be a governance-adopted model — see Model Registry).
    /// @param promptHash    Commitment to the prompt/input (must be non-zero).
    /// @param n             Fan-out: number of independent provider executions,
    ///        in [1, 256].
    /// @param threshold     M-of-N agreement required, in [1, n] (the A-Chain
    ///        further requires floor(n/2)+1 <= threshold).
    /// @param fee           Reward offered, 256-bit (funds the A escrow + burn).
    /// @param routing       Opaque routing hint; carried ONLY in the transport
    ///        ZAP nudge, NOT consensus state, NOT in the intent_id preimage.
    /// @return intentID     keccak over the pinned preimage (LP-5300 wire spec).
    function submitInferenceIntent(
        bytes32 modelSpecHash,
        bytes32 promptHash,
        uint16  n,
        uint16  threshold,
        uint256 fee,
        bytes32 routing
    ) external returns (bytes32 intentID);

    /// @notice Pattern B — verify a committed A-Chain receipt against a
    ///         receipt_root C already holds committed, settle the matching
    ///         pending intent, and return the canonical output.
    /// @param receiptBytes Canonical 355-byte AInferenceReceipt encoding.
    /// @param proofBytes   Merkle inclusion proof (DecodeProof wire frame).
    /// @return intentID            The settled intent id.
    /// @return canonicalOutputHash The agreed output digest C now trusts.
    /// @return status              Always 2 (Completed) on success.
    function verifyInferenceReceipt(bytes calldata receiptBytes, bytes calldata proofBytes)
        external
        returns (bytes32 intentID, bytes32 canonicalOutputHash, uint8 status);
}

The Solidity interface is the intended ABI surface. The precompile decodes a tight fixed-width frame internally (no Solidity-ABI offset ambiguity); see the calldata layouts below. Cite: precompile/aivmbridge/bridge.go.

Calldata Layout (normative)

submitInferenceIntent — after the 4-byte selector, a fixed 6-word (192-byte) frame:

[0:32]    modelSpecHash (bytes32)
[32:64]   promptHash    (bytes32)
[64:96]   n             (uint16, right-aligned; high 30 bytes MUST be zero)
[96:128]  threshold     (uint16, right-aligned; high 30 bytes MUST be zero)
[128:160] fee           (uint256)
[160:192] routing       (bytes32, opaque; read for the notifier, NOT stored on-state)
-> returns bytes32 intentID

verifyInferenceReceipt — after the 4-byte selector, a tight length-prefixed frame:

[0:2]      u16be receiptLen
[2:4]      u16be proofLen
[4:4+rl]   receipt bytes (canonical 355-byte AInferenceReceipt encoding)
[..]       proof bytes   (ReceiptRoot(32) | u64be Index(8) | u16be pathLen(2) | pathLen*32)
-> returns (bytes32 intentID, bytes32 canonicalOutputHash, uint8 status)  // 96 bytes

Both frames are exact-length: the precompile rejects a short frame AND a frame with trailing junk (ErrInputTooShort / ErrInputOversized). Numeric words reject dirty high bytes (ErrDirtyWord). This is the calldata-hardening discipline.

Cite: precompile/aivmbridge/bridge.go (submitIntentArgsLen, verifyReceipt framing, readUint16Word), proof.go (DecodeProof).

AChainClient — the node-local A-Chain on-ramp

The precompile resolves through an AChainClient, the EXACT shape of dex.DChainClient, adapted to inference. This is not a backend swap. There is exactly one valid target: the co-located A-Chain this node runs.

type AChainClient interface {
    Brand() string              // white-label identity; MUST be non-empty
    AChainID() [32]byte         // committed C<->A rail peer id; MUST be non-zero

    // Pattern A: write a committed C outbox intent; return its deterministic id.
    // MUST NOT call into or mutate aivm.
    SubmitInferenceIntent(ctx, store IntentStore, in InferenceIntent) ([32]byte, error)

    // Pattern B: verify a committed receipt against a committed receipt root.
    // MUST NOT live-query aivm; receipt + proof arrive as calldata.
    VerifyInferenceReceipt(ctx, vs ReceiptVerifierState, r AInferenceReceipt, p AInferenceProof) (VerifiedAInferenceReceipt, error)
}

There is deliberately no GetInferenceReceipt(id) method — that name invites a live mutable A query, exactly the fork-unsafe shape a prior build shipped. Verification takes the receipt + proof as calldata, so the C side never reaches into A to fetch anything.

Cite: precompile/aivmbridge/achain_client.go (AChainClient, VerifiedAInferenceReceipt, the naming note); template precompile/dex/dchain_client.go (DChainClient, the CONSENSUS-SAFETY RULE).

Install Discipline (fail-secure, install-once)

The host binary calls InstallAChainClient exactly once at boot, BEFORE the VM serves precompiles. A node without its local A on-ramp leaves the default achainUnavailable client in place; Pattern A then reverts cleanly (ErrAChainUnavailable) and Pattern B still works (it is pure C-state verification, no client needed). Install-time sanity (fail-secure):

  • c MUST be non-nil (a nil client would NPE the bridge).
  • c.Brand() MUST be non-empty (white-label / log discipline).
  • c.AChainID() MUST be non-zero (an unscoped peer mints cross-rail-aliasable intent ids — the rail binding is load-bearing for replay isolation).
  • Install is once: a second call errors rather than swapping the live client, so a misconfigured plugin cannot race a late re-resolution against in-flight Run() (TOCTOU). The client lives behind an atomic.Pointer for race-free reads.

Cite: precompile/aivmbridge/achain_client.go (InstallAChainClient, installed, aChainClient, currentAChainClient); mirror of dex.InstallDChainClient.

Gas Costs

OperationGasNotes
submitInferenceIntent40,000write-class: derive id, replay/calldata checks, write outbox slots (dex native-intent tier)
verifyInferenceReceipt (base)30,000decode + recompute receipt_hash + committed-root lookup + match/consume intent
verifyInferenceReceipt (per proof node)+1,000 / nodethe Merkle walk; pathLen hard-capped by MaxProofDepth = 64

RequiredGas is a pure function of the selector (and, for verify, the proof depth declared in calldata). It NEVER depends on any A-Chain observation. A malformed or short frame falls back to the base verify gas.

Cite: precompile/aivmbridge/gas.go (GasSubmitInferenceIntent, GasVerifyInferenceReceiptBase, GasVerifyPerProofNode, verifyGas), module.go (RequiredGas).

Revert Discipline

geth-canonical, mirroring the inference + dex paths:

  • Out-of-gas(nil, 0, err): charged gas consumed.
  • Decode / state / client error(nil, remainingGas, err): only the gas already charged for this call is consumed; the leftover is returned. There is NO all-gas-burn beyond the charged amount.

Every error reverts cleanly and mutates NOTHING (no partial consume). Fail-secure is the rule: when in doubt, deny.

Cite: precompile/aivmbridge/bridge.go (revert discipline header), errors.go (the full error surface).

Effects on C State

The precompile owns one auditable storage region under aivmbridge.v1.* at its own address:

PotKeyMeaning
outboxintent_id -> packed OutboxIntentthe committed Pattern-A record (binds model/prompt/requester/chain/fan-out)
consumedintent_id -> blockNumber+1Pattern-B double-consume guard
receipt-root checkpointreceipt_root -> committedHeight+1the A→C atomic-import seam's landing slot (root authenticity)

Pattern A appends to the outbox. Pattern B reads the checkpoint + outbox and marks an intent consumed. The receipt-root checkpoint is written by the A→C atomic boundary / Warp handler at block accept (the same shape as dex's accepted-atomic-op flush, in the A→C direction); this package ships the C-side store + verification, and the authenticity of a committed root rests on that import handler — clearly marked, never papered over.

Cite: precompile/aivmbridge/state.go (stateNamespace, the three prefixes, OutboxStatus, OutboxIntent, the seam-boundary note).

Rationale

  • Why a separate precompile. Orthogonal concern (large-model on-ramp) at its own slot, disjoint from Tier-1 deterministic inference and the model registry. A node can run Tier 1 without the A on-ramp, or vice versa.
  • Why proof/receipt-oriented, not query. A live query forks consensus. The on-ramp records intents and verifies committed receipts — both deterministic. This is the whole reason the AChainClient has no Get…(id) method.
  • Why a tight fixed-width frame instead of full Solidity ABI decode. Exact, length-checked frames remove offset ambiguity and make the calldata-hardening (reject short/oversized/dirty) total and cheap.
  • Why install-once. A live client swap is a TOCTOU against concurrent Run() threads; install-once + atomic publish removes the race.

Backwards Compatibility

The precompile is purely additive: a new address in the AI range, no change to any existing ABI or to the Tier-1 inference precompile (0x0300…0003), the model registry (0x0300…0002), or the AI-mining precompile (0x0300…0000). Contracts opt in by calling the new address; existing tooling (Foundry, Hardhat) interacts with it as a normal precompile.

Test Cases

The package ships an extensive harness (golden wire vectors, cross-module seam equality, and an adversarial red-team suite). Representative cases:

wire_test.go        — intent_id / receipt encoding golden vectors (byte-for-byte)
decode_test.go      — receipt/proof decode: reject short, oversized, bad version
achain_submit_test.go — Pattern A: deterministic id, replay revert, calldata bounds
achain_verify_test.go — Pattern B: 8-step verify, bind-mismatch, double-consume
crossmodule_test.go — TestCrossModuleMerkleSeam: leaf/node hashing == chains/aivm
install_test.go     — install-once, nil/empty-brand/zero-rail rejection
gas_test.go         — RequiredGas == verifyGas(pathLen); OOG burns charged gas
zap_test.go         — routing hint is transport-only, never on-state
redteam_test.go     — the 12 attack vectors (live read, forged proof, replay, ...)

Illustrative Solidity (consumer side):

// Pattern A: request a 5-of-... judgment from an adopted model.
bytes32 intentID = IAIBridge(0x0300...0004).submitInferenceIntent(
    modelSpecHash, promptHash, /*n*/ 5, /*threshold*/ 3, /*fee*/ 1 ether, routing
);

// ...later, after A settles and the A->C boundary commits the receipt_root...

// Pattern B: verify the certified receipt and act on the canonical output.
(bytes32 settled, bytes32 outputHash, uint8 status) =
    IAIBridge(0x0300...0004).verifyInferenceReceipt(receiptBytes, proofBytes);
require(status == 2 && settled == intentID, "not settled");
// outputHash is now trusted C-side; the contract chooses what to do with it.

Reference Implementation

Location: github.com/luxfi/node/precompile/aivmbridge/

precompile/aivmbridge/
├── module.go             # precompile registration, address 0x0300...0004, RequiredGas
├── bridge.go             # Run() body: selector routing, calldata hardening, Pattern A/B
├── intent.go             # InferenceIntent + DeriveIntentID (pinned wire)
├── receipt.go            # AInferenceReceipt + Encode/Decode + ReceiptHash (355 bytes)
├── proof.go              # AInferenceProof + VerifyMerkle + DecodeProof
├── verify.go             # verifyInferenceReceipt — the 8-step Pattern-B core
├── state.go              # C outbox + consumed set + receipt-root checkpoint
├── achain_client.go      # AChainClient interface + install-once + achainUnavailable
├── native_achain_client.go # the staged native on-ramp (test/integration)
├── gas.go                # GasSubmitInferenceIntent / Verify base + per-node
└── errors.go             # the fail-secure error surface

The A-Chain settlement side this precompile bridges to is specified in LP-5300 and implemented in github.com/luxfi/chains/aivm/.

Security Considerations

The precompile-level attack surface (a subset of the LP-5300 §Security twelve vectors, focused on the EVM entry point). Each maps to a code citation.

  1. Live A read. No live-read method exists; Pattern B verifies committed C state + the calldata proof only. achain_client.go, verify.go.
  2. Direct A mutation. Leaf library; cannot reach the chain manager; Pattern A writes only C outbox. achain_client.go, state.go.
  3. Intent replay. Deterministic injective intent_id; OutboxPending guard (ErrIntentReplay). intent.go, state.go, bridge.go.
  4. Receipt replay / double-consume. OutboxConsumed guard, CEI order (ErrIntentConsumed). verify.go.
  5. Wrong model / prompt. Bound-field equality check (ErrReceiptBindMismatch). verify.go step 7.
  6. Pending → credit. Only Completed + non-zero output is actionable (ErrReceiptNotCompleted, ErrZeroOutput). verify.go steps 1–2.
  7. Forged proof. Root must be C-committed (ErrReceiptRootNotCommitted); leaf recomputed from receipt bytes; index high-bit aliasing rejected. verify.go step 3–4, proof.go.
  8. ZAP-without-proof. Routing hint is transport-only, NOT consensus state, NOT in the intent_id preimage. bridge.go, state.go, zap_test.go.
  9. Install TOCTOU. Install-once CompareAndSwap + atomic publish; second install errors. achain_client.go.
  10. Calldata. Exact-length frames (reject short AND oversized); dirty-high-byte rejection; fan-out bounds (MaxFanout = 256). bridge.go, errors.go.
  11. Read-only abuse. Both ops mutate the C outbox, so a static call reverts (ErrReadOnly). bridge.go.
  12. Missing atomic capability. Without the cross-chain atomic capability the bridge cannot mint a rail-scoped id and reverts (ErrNoAtomicState) rather than guess a peer; a zero c_tx_hash (broken host wiring) also reverts (ErrZeroTxHash). bridge.go.

The off-chain attack surface (beacon grinding, slash-grief, recursion runaway) and the value-conservation argument live with the settlement engine in LP-5300 §Security.

  • LP-5200 — A-Chain-only mining authority and destination receipt settlement.
  • LP-5300 — the Thinking Chains protocol this precompile is the C-Chain entry point for.
  • LP-5000 — A-Chain core.
  • LP-9010 — the on-ramp precompile pattern this mirrors.
  • LP-9999 — the receipt-verify-and-settle pattern this mirrors.
  • LP-032 — the D-Chain VM (the "matches/settles" split).
  • Zoo "Beluga L3" ZIP — first deployment; Hanzo "Cognitive Sidecar / Hanzo Engine provider" HIP — the provider (operator) side; Zoo "Thinking Chains" paper — the research framing. (Cross-referenced in LP-5300 §Related Work.)

Copyright (C) 2026, Lux Partners Limited. All rights reserved.

Licensed under the MIT License.