mirror of
https://github.com/luxfi/precompile.git
synced 2026-09-24 00:26:21 +00:00
113 lines
5.0 KiB
Go
113 lines
5.0 KiB
Go
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
// intent.go — the C->A inference INTENT and its deterministic id.
|
|
//
|
|
// THE SHARED WIRE SPEC (pinned byte-for-byte; Blue-B's chains/aivm importer MUST
|
|
// match; aivmbridge_wire_test.go asserts the golden vectors). intent_id is a PURE
|
|
// DETERMINISTIC FUNCTION of the calldata + the C tx identity — never of any A-Chain
|
|
// observation — so every validator executing the same C block derives the SAME id.
|
|
// That is the whole reason Pattern A is consensus-safe.
|
|
//
|
|
// DOMAIN_INTENT = "lux/aivmbridge/intent/v1" (raw utf8, no length prefix)
|
|
//
|
|
// intent_id = keccak256(
|
|
// DOMAIN_INTENT
|
|
// || c_chain_id(32) || a_chain_id(32)
|
|
// || c_tx_hash(32) || u32be(call_index)
|
|
// || caller(20)
|
|
// || model_spec_hash(32) || prompt_hash(32)
|
|
// || u16be(N) || u16be(threshold)
|
|
// || u256be(fee, 32)
|
|
// )
|
|
//
|
|
// Every component is fixed width, so the concatenation is unambiguous (no
|
|
// length-prefix needed). c_chain_id / a_chain_id scope the id to exactly one C<->A
|
|
// rail (an intent minted on one network/chain pair can never alias another);
|
|
// c_tx_hash + call_index make it injective per C tx call; caller + model + prompt +
|
|
// N + threshold + fee bind the economic payload so the id cannot be reused for a
|
|
// different request.
|
|
|
|
package aivmbridge
|
|
|
|
import (
|
|
"encoding/binary"
|
|
|
|
"github.com/luxfi/crypto"
|
|
"github.com/luxfi/geth/common"
|
|
)
|
|
|
|
// DomainIntent is the keccak domain separator for intent-id derivation. Raw utf8
|
|
// bytes (no length prefix) — pinned by the shared wire spec.
|
|
const DomainIntent = "lux/aivmbridge/intent/v1"
|
|
|
|
// InferenceIntent is the full C->A inference job intent a Pattern-A call records.
|
|
// Identity (Caller) is bound from the C tx caller, so two callers requesting the
|
|
// same prompt get DISTINCT intent ids — no cross-caller collision, no replay across
|
|
// callers. The realized result settles on A under A consensus and is later verified
|
|
// on C via Pattern B; it is NEVER folded into C state synchronously.
|
|
type InferenceIntent struct {
|
|
// CChainID is the C-Chain (this EVM chain) id — sourced from the host atomic
|
|
// capability (AtomicState.CChainID()). Bound into the id so an intent is scoped
|
|
// to its source chain.
|
|
CChainID [32]byte
|
|
// AChainID is the A-Chain (aivm) peer id the intent targets. The bridge holds it
|
|
// as committed configuration (the on-ramp's declared peer); bound into the id so
|
|
// an intent is scoped to exactly one C<->A rail.
|
|
AChainID [32]byte
|
|
// CTxHash is the originating C tx hash (StateDB.TxHash()). Durable +
|
|
// consensus-shared, so a re-executed / reorged tx maps to the SAME intent_id
|
|
// (idempotent) and two genuinely distinct txs get distinct ids.
|
|
CTxHash [32]byte
|
|
// CallIndex is this precompile invocation's index within the C tx
|
|
// (AtomicState.CallIndex(), else 0 when the atomic capability is absent —
|
|
// documented). Disambiguates two bridge calls in one tx.
|
|
CallIndex uint32
|
|
// Caller is the C tx caller — the requesting account, bound into the id so the
|
|
// intent (and its eventual settlement) is attributable + distinct per caller.
|
|
Caller common.Address
|
|
// ModelSpecHash is the model identity (weight-commitment hash). ONE model
|
|
// identity shared by C and A.
|
|
ModelSpecHash [32]byte
|
|
// PromptHash is the commitment to the prompt/input. The large prompt body lives
|
|
// off-chain (the operator fetches it by hash); the hash binds the request.
|
|
ModelPromptHash [32]byte
|
|
// N is the number of independent provider executions requested (fan-out).
|
|
N uint16
|
|
// Threshold is the M of M-of-N agreement required to finalize (1 <= M <= N).
|
|
Threshold uint16
|
|
// Fee is the reward offered to the winning provider(s), as a 256-bit big-endian
|
|
// value. Recorded in the committed intent; the A settlement layer pays it.
|
|
Fee [32]byte
|
|
}
|
|
|
|
// DeriveIntentID computes the deterministic, injective intent id per the pinned
|
|
// shared wire spec (keccak256 over the fixed-width preimage above). It binds the
|
|
// FULL identity so the same intent in a re-executed/reorged tx derives the SAME id
|
|
// (idempotent), and any change to chain/tx/call/caller/model/prompt/N/threshold/fee
|
|
// derives a DISTINCT id. NO A-Chain input enters the preimage.
|
|
func DeriveIntentID(in InferenceIntent) [32]byte {
|
|
// Preimage width: domain + 32+32 + 32+4 + 20 + 32+32 + 2+2 + 32.
|
|
buf := make([]byte, 0, len(DomainIntent)+32+32+32+4+20+32+32+2+2+32)
|
|
buf = append(buf, []byte(DomainIntent)...)
|
|
buf = append(buf, in.CChainID[:]...)
|
|
buf = append(buf, in.AChainID[:]...)
|
|
buf = append(buf, in.CTxHash[:]...)
|
|
var u4 [4]byte
|
|
binary.BigEndian.PutUint32(u4[:], in.CallIndex)
|
|
buf = append(buf, u4[:]...)
|
|
buf = append(buf, in.Caller.Bytes()...) // common.Address.Bytes() is exactly 20 bytes
|
|
buf = append(buf, in.ModelSpecHash[:]...)
|
|
buf = append(buf, in.ModelPromptHash[:]...)
|
|
var u2 [2]byte
|
|
binary.BigEndian.PutUint16(u2[:], in.N)
|
|
buf = append(buf, u2[:]...)
|
|
binary.BigEndian.PutUint16(u2[:], in.Threshold)
|
|
buf = append(buf, u2[:]...)
|
|
buf = append(buf, in.Fee[:]...)
|
|
|
|
var out [32]byte
|
|
copy(out[:], crypto.Keccak256(buf))
|
|
return out
|
|
}
|