Files
zeekay 2656b30487 fhevm: F-Chain coordinates confidential compute without holding it
F-Chain existed as a reserved vmID and a spec. luxfi/constants declares
FHEVMID and node/node/vms.go lists fhevm in OptionalVMs, so luxd scanned
--plugin-dir for a binary nothing produced. This builds it.

F is the chain of record for encrypted values it never holds. It stores a
ciphertext's 32-byte handle, the digest of the off-chain body, its owner,
the capability permits granted over it, and the threshold decryptions
asked for and answered — and no ciphertext body, no FHE key, no share.
invariant_test.go proves that by walking the type graph and scanning the
package source for the runtime's secret-bearing entry points.

Six operations, each fee-settled through consensus on the shared
chains/fee primitive and priced by scheme and ring dimension, because a
TFHE boolean and a CKKS n=2^15 vector cost the committee amounts that
differ by an order of magnitude. Two of them are threshold decisions: a
decryption completes when Threshold distinct members attest the same
result handle, and an epoch installs when Threshold members of the
sitting committee attest the same successor. Votes are tallied per value
and a member votes once, so one member naming a false result delays
nothing and pays for the attempt; the deciding transaction carries the
proposal, so nothing pending is ever stored.

It WRAPS mpcvm/fhe rather than restating it — the records embed that
package's types and the public parameters come from its threshold
configuration — but owns its persistence, because that package's Registry
stamps time.Now(). That is right for the off-chain daemon it was written
for and wrong for a state root: two validators replaying one block would
write different bytes. Every timestamp F writes comes from the accepting
block.

Shape follows keyvm: versiondb with a single commit and a rollback that
reloads caches, fail-closed verification, a nonce replay guard, and a
per-payer cumulative fee check within a block. Two things go further.
GetBlockIDAtHeight answers from a real index written in the block's own
commit rather than refusing. And a transaction names the state entry it
writes, so admission refuses a second claim on it and consensus refuses a
peer's block carrying both — without that, one payer's nonces n and n+1
can each pass every check and only collide in Accept, and a rejected
block requeues, so the pair rebuilds indefinitely.

The Makefile names its targets instead of globbing the directory: the
glob swept in dexvm, fee, ownership, internal and cmd, and stopped at the
first with no plugin entrypoint, so `make` built nothing at all.
2026-08-27 16:23:29 -07:00

65 lines
2.5 KiB
Go

// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package fhevm
import (
"testing"
"github.com/luxfi/constants"
"github.com/luxfi/ids"
)
// canonicalVMIDCB58 is F-Chain's vmID in the encoding the node's plugin
// registry uses. The plugin binary MUST be installed under exactly this
// filename; the registry resolves a CreateChainTx's vmID to an implementation
// by looking for a file with this name in the plugin directory.
//
// It is written out literally rather than computed so that a change to the
// vmID — for any reason, including an "obviously equivalent" refactor of the
// constant — fails this test instead of silently producing a chain that no
// node can start. A vmID is an immutable one-way door: it is baked into the
// genesis CreateChainTx and stored by the P-Chain forever.
const canonicalVMIDCB58 = "n6sSsSfbpQBrU9sY4R29U6z8VrmnTo2CntW6da4rRS7qmnGdv"
func TestVMID_IsCanonicalAndStable(t *testing.T) {
if VMID != constants.FHEVMID {
t.Fatalf("VMID = %s, want constants.FHEVMID = %s", VMID, constants.FHEVMID)
}
if got := VMID.String(); got != canonicalVMIDCB58 {
t.Fatalf("VMID CB58 = %q, want %q\n"+
"A vmID cannot change after a chain is created with it. If this is an\n"+
"intentional pre-launch change, every declaration must move together:\n"+
" luxfi/constants vm_ids.go FHEVMID\n"+
" node/node vms.go OptionalVMs\n"+
" node/Dockerfile plugin build -o, verify list, runtime COPY\n"+
" chains/fhevm factory.go, cmd/plugin/main.go, this test",
got, canonicalVMIDCB58)
}
if VMID == ids.Empty {
t.Fatal("VMID is the empty id")
}
// M-Chain and F-Chain were split out of the retired thresholdvm package
// (LP-7050). They are separate chains and must never share an id.
if VMID == constants.MPCVMID {
t.Fatal("F-Chain's vmID collides with M-Chain's")
}
}
// The bytes themselves, spelled out: an ASCII name left-padded into 32 bytes.
func TestVMID_Bytes(t *testing.T) {
want := ids.ID{'f', 'h', 'e', 'v', 'm'}
if VMID != want {
t.Fatalf("VMID bytes = %v, want %v", VMID[:8], want[:8])
}
}
// TestVMID_NameResolves proves luxfi/constants maps this id back to the plugin
// name the node scans --plugin-dir for. If the two ever disagree, luxd looks
// for a binary nobody builds and the chain silently never starts.
func TestVMID_NameResolves(t *testing.T) {
if got := constants.VMName(VMID); got != VMName {
t.Fatalf("constants.VMName(VMID) = %q, want %q", got, VMName)
}
}