Files
precompile/fhe
zeekay a7ddcc1212 fix(accel): eliminate CPU/GPU verdict divergence in verify precompiles (consensus-split + forgery)
Under the enable-everything precompile policy these verify-only precompiles are
no longer refused under strict-PQ, so any divergence between the luxfi/accel
GPU/SIMD path and the pure-CPU path is a LIVE consensus split: GPU-equipped and
CPU-only validators would return different verify verdicts on the same input and
the chain forks. Several accel paths were also outright wrong-primitive forgery
surfaces.

LAW applied: the GPU verdict must be byte-identical to the CPU verdict on every
input, or the accel path is a consensus hazard and is removed. CPU (pure-Go,
CGO_ENABLED=0) is now the single source of truth for every on-chain verdict.
None of these GPU kernels can be proven byte-identical here, and several are
provably NOT, so every verdict-trusting accel branch is deleted and routed to
the canonical CPU verifier. Determinism over speed.

Active forgery/wrong-output in the DEFAULT build (accelcrypto CPU fallback
leaked non-CPU semantics, err==nil):
- sr25519: verified via the accel Ed25519 kernel -> a valid Ed25519 signature
  was ACCEPTED as a valid sr25519/Schnorrkel signature (wrong-curve forgery).
- blake3: accelcrypto.Hash(HashBlake3) returns BLAKE2b, not BLAKE3; the "real
  blake3" fallback was dead code. OpHash256 + MerkleRoot emitted BLAKE2b.

Latent splits (reachable only under -tags accel + a GPU backend):
- frost: FROST-Schnorr verified through the SigECDSA kernel (wrong primitive +
  32B x-only key mis-framed as a 33B ECDSA key).
- cggmp21: SigECDSA kernel truncated the 65B key to 33B and skipped the CPU
  path's recover-and-compare binding (ignored the v byte).
- ed25519: GPU Ed25519 kernel not proven byte-identical to the canonical
  verifier on cofactor / non-canonical-S / small-order edge cases.
- secp256r1: P-256 inputs sent to the fixed-curve secp256k1 ECDSA kernel.
- mldsa: GPU verified with EMPTY context; precompile binds the FIPS-204 ctx
  "lux-evm-precompile-mldsa-v1" -> different mu, different verdict.
- bls12381 / zk(groth16): GPU MSM result trusted without re-verification and
  with no curve binding carried to the kernel.
- kzg4844: GPU commitment used the backend's OWN embedded trusted setup (empty
  SRS) instead of the canonical EIP-4844 SRS -> breaks KZG binding.
- ai_mining (BatchVerifyMLDSA/BatchAccumulator): accel batch kernel truncates
  messages to 32 bytes and is mode-fixed -> diverges from VerifyMLDSA.
- fhe: GPU computed BFV arithmetic while CPU computes TFHE (different scheme,
  incompatible ciphertext, nil relin key) -> corrupt + divergent ciphertext.

Cleanups (no split, vestigial): removed dead mlkem encapsulateGPU; removed the
hpke GPU pre-warm that discarded its result and returned the CPU output anyway.
slhdsa was already CPU-only (prior identical fix).

Tests (differential / forgery proofs, all green CGO_ENABLED=0 and CGO -race):
- sr25519: a valid Ed25519 sig is now REJECTED; real sr25519 still verifies.
- blake3: OpHash256/MerkleRoot output == BLAKE3 and != BLAKE2b.
- cggmp21: flipped recovery byte is rejected (recover-compare authoritative).
- frost: a valid ECDSA sig is rejected as FROST-Schnorr.
- ed25519: verdict matches the canonical ed25519.Verify oracle over 64x4 vectors.
- mldsa: empty-ctx sig (the accel framing) rejected; precompileCtx sig accepted.

Net -593 LOC. No consensus code touched.
2026-06-27 19:46:11 -07:00
..
2026-01-01 01:22:27 +00:00

FHE Precompile

Fully Homomorphic Encryption (FHE) precompile for Lux blockchain, enabling privacy-preserving smart contracts.

Overview

This precompile provides FHE operations using the CKKS (Cheon-Kim-Kim-Song) scheme implemented in pure Go via luxfi/lattice. It supports threshold decryption through the T-Chain (67-of-100 validators via LP-333).

Architecture

┌─────────────────────────────────────────────────────────────────┐
│                        C-Chain (EVM)                            │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │                  Smart Contracts                         │   │
│  │   ┌─────────────┐  ┌─────────────┐  ┌─────────────────┐ │   │
│  │   │ FHERC20.sol │  │ Auction.sol │  │ PrivateVote.sol │ │   │
│  │   └──────┬──────┘  └──────┬──────┘  └────────┬────────┘ │   │
│  └──────────┼────────────────┼──────────────────┼──────────┘   │
│             │                │                  │               │
│  ┌──────────▼────────────────▼──────────────────▼──────────┐   │
│  │                    FHE Precompiles                       │   │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────────┐  │   │
│  │  │ FHE (0x80)  │  │ ACL (0x81)  │  │ Gateway (0x83)  │  │   │
│  │  │ Operations  │  │ Access Ctrl │  │ Decryption      │  │   │
│  │  └──────┬──────┘  └─────────────┘  └────────┬────────┘  │   │
│  └─────────┼───────────────────────────────────┼───────────┘   │
└────────────┼───────────────────────────────────┼───────────────┘
             │ Warp Message                      │ Warp Message
             ▼                                   ▼
┌────────────────────────┐         ┌─────────────────────────────┐
│      Z-Chain (zkVM)    │         │     T-Chain (Threshold)     │
│  ┌──────────────────┐  │         │  ┌───────────────────────┐  │
│  │   FHE Processor  │  │         │  │ Threshold Decryptor   │  │
│  │   - CKKS Ops     │  │         │  │ - 67-of-100 shares    │  │
│  │   - Coprocessor  │  │         │  │ - CKKS Multiparty     │  │
│  │   - luxfi/lattice│  │         │  │ - Share Combiner      │  │
│  └──────────────────┘  │         │  └───────────────────────┘  │
└────────────────────────┘         └─────────────────────────────┘

Precompile Addresses

Precompile Address Purpose
FHE 0x0200000000000000000000000000000000000080 Core FHE operations
ACL 0x0200000000000000000000000000000000000081 Access control
InputVerifier 0x0200000000000000000000000000000000000082 Input validation
FHEDecrypt 0x0200000000000000000000000000000000000083 Threshold decryption via T-Chain

Encrypted Types

Type Description Solidity
ebool Encrypted boolean type ebool is bytes32
euint8 Encrypted 8-bit uint type euint8 is bytes32
euint16 Encrypted 16-bit uint type euint16 is bytes32
euint32 Encrypted 32-bit uint type euint32 is bytes32
euint64 Encrypted 64-bit uint type euint64 is bytes32
euint128 Encrypted 128-bit uint type euint128 is bytes32
euint256 Encrypted 256-bit uint type euint256 is bytes32
eaddress Encrypted address type eaddress is bytes32

Operations

Arithmetic

  • add(a, b) - Addition
  • sub(a, b) - Subtraction
  • mul(a, b) - Multiplication
  • div(a, b) - Division
  • rem(a, b) - Remainder
  • neg(a) - Negation

Comparison

  • lt(a, b) - Less than
  • le(a, b) - Less than or equal
  • gt(a, b) - Greater than
  • ge(a, b) - Greater than or equal
  • eq(a, b) - Equal
  • ne(a, b) - Not equal
  • min(a, b) - Minimum
  • max(a, b) - Maximum

Bitwise

  • and(a, b) - Bitwise AND
  • or(a, b) - Bitwise OR
  • xor(a, b) - Bitwise XOR
  • not(a) - Bitwise NOT
  • shl(a, bits) - Shift left
  • shr(a, bits) - Shift right

Conditional

  • select(cond, ifTrue, ifFalse) - Conditional select

Randomness

  • rand(type) - Generate encrypted random value

Gas Costs

Operation Gas Cost
Encryption 50,000
Add/Sub 65,000
Mul 150,000
Div/Rem 500,000
Comparison 60,000
Bitwise 50,000
Select 100,000
Random 100,000
Decrypt Request 10,000

Usage Example

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {FHE, euint64, ebool} from "lux-standard/contracts/fhe/FHE.sol";

contract PrivateBalance {
    mapping(address => euint64) private balances;
    
    function deposit(uint64 amount) external {
        euint64 encrypted = FHE.asEuint64(amount);
        balances[msg.sender] = FHE.add(balances[msg.sender], encrypted);
        FHE.allowThis(balances[msg.sender]);
    }
    
    function transfer(address to, uint64 amount) external {
        euint64 encAmount = FHE.asEuint64(amount);
        
        // Check sufficient balance (encrypted comparison)
        ebool sufficient = FHE.ge(balances[msg.sender], encAmount);
        
        // Conditional update (works on encrypted data)
        balances[msg.sender] = FHE.select(
            sufficient,
            FHE.sub(balances[msg.sender], encAmount),
            balances[msg.sender]
        );
        
        balances[to] = FHE.select(
            sufficient,
            FHE.add(balances[to], encAmount),
            balances[to]
        );
    }
}

Decryption Flow

  1. Request: Contract calls FHE.decrypt(value) or Gateway.decrypt(handle, type)
  2. Warp: Request sent to T-Chain via Warp messaging
  3. Threshold: 67-of-100 validators contribute decryption shares
  4. Combine: Shares combined to recover plaintext
  5. Fulfill: Result returned via fulfill(requestId, result) callback
  6. Poll/Callback: Contract retrieves result via reveal(requestId) or receives callback

Files

  • module.go - Module registration
  • contract.go - FHE precompile implementation
  • acl.go - Access control implementation (in evm/precompile)
  • gateway.go - Decryption gateway (in evm/precompile)
  • IFHE.sol - Solidity interfaces
  • luxfi/lattice - Pure Go CKKS implementation
  • node/vms/zkvm/fhe - Z-Chain FHE processor
  • node/vms/thresholdvm - T-Chain threshold operations
  • standard/contracts/fhe - Solidity FHE library

License

Copyright (C) 2019-2024, Lux Partners Limited. All rights reserved. See the file LICENSE for licensing terms.