mirror of
https://github.com/luxfi/precompile.git
synced 2026-07-27 03:33:45 +00:00
Public permissionless launch policy: enable basically every precompile for builder convenience, ESPECIALLY wallet-curve VERIFY so users sign natively on Lux from other chains (ed25519=Solana, sr25519=Polkadot, secp256r1=WebAuthn, secp256k1/ecrecover=Ethereum). Disable ONLY actual security risks. Lux's own consensus and identity stay PQ (quasar/p3q) — enforced in the consensus layer, never by refusing an EVM verifier a dapp asked for. Two layers, decomplected: - builder EVM precompile surface : enable-all-verify (this change) - chain consensus / finality : PQ-strict (consensus module, untouched) Removed the RefuseUnderStrictPQ gate from 17 verify-only / key-safe custom precompiles: ed25519, sr25519, secp256r1, bls12381 (EIP-2537, x7 ops), kzg4844 (EIP-4844), blake3, poseidon, pedersen, babyjubjub, pasta, ring, vrf, hpke, curve25519, x25519, cggmp21, frost, and the classical SNARK verifiers in zk (Groth16/PLONK/Halo2/KZG/IPA/range/batch/commitment). zk fflonk (0x03) stays DISABLED — but on its OWN forge-bug mechanism (ErrFflonkDisabled, returned at dispatch), NOT strict-PQ: verifyFflonk has a nil-vk soundness hole that forges any statement. Security disable, fully PQ-independent. The RefuseUnderStrictPQ helper + ErrClassicalForbiddenInPQ + StrictPQReporter had zero remaining code callers (evm uses a local structural interface) — deleted contract/strict_pq.go and its test. Rewrote zk's gate test to assert the new policy (classical ops enabled, fflonk disabled). Removed the now- orphaned isPedersenCommitment; fixed stale comments referencing deleted symbols. Build: full module green. Tests: contract + zk + all 17 edited packages pass. NOTE: the STANDARD eth precompiles (ecrecover, p256Verify, sha256, ripemd160, blake2f, bls12381, kzg) are still refused by LuxStrictPQ() in the evm plugin — a follow-up commit flips that to Permissive so ecrecover (every Ethereum dapp) works at launch.
335 lines
9.0 KiB
Go
335 lines
9.0 KiB
Go
// Copyright (C) 2025, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
// Package blake3 implements Blake3 hash precompile for the Lux EVM.
|
|
// Address: 0x0504
|
|
//
|
|
// Blake3 is a fast cryptographic hash function that:
|
|
// - Is 6-17x faster than SHA-3 and SHA-256
|
|
// - Supports arbitrary-length output (XOF)
|
|
// - Is suitable for Merkle tree and KDF use cases
|
|
// - Is NOT post-quantum but provides excellent performance
|
|
//
|
|
// Operations:
|
|
// - Hash256: Standard 32-byte hash
|
|
// - Hash512: Extended 64-byte hash
|
|
// - HashXOF: Extended output function (arbitrary length)
|
|
// - HashWithDomain: Domain-separated hash
|
|
// - MerkleRoot: Merkle tree root computation
|
|
//
|
|
// Gas costs are based on input size and operation complexity.
|
|
package blake3
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
|
|
"github.com/luxfi/crypto/hash/blake3"
|
|
"github.com/luxfi/geth/common"
|
|
"github.com/luxfi/precompile/contract"
|
|
)
|
|
|
|
var (
|
|
// ContractAddress is the address of the Blake3 precompile (Graph/Hashing range 0x0500)
|
|
ContractAddress = common.HexToAddress("0x0500000000000000000000000000000000000004")
|
|
|
|
// Singleton instance
|
|
Blake3Precompile = &blake3Precompile{}
|
|
|
|
_ contract.StatefulPrecompiledContract = &blake3Precompile{}
|
|
|
|
ErrInvalidInput = contract.ErrInvalidInput
|
|
ErrInvalidOperation = errors.New("invalid operation selector")
|
|
ErrOutputTooLarge = errors.New("requested output exceeds maximum")
|
|
ErrInvalidDataLength = errors.New("invalid data length")
|
|
)
|
|
|
|
// Output limits
|
|
const (
|
|
MaxOutputLength = 1024 // Maximum XOF output in bytes
|
|
MaxInputLength = 1024 * 1024 // Maximum input: 1MB
|
|
DigestLength32 = 32 // Standard hash length
|
|
DigestLength64 = 64 // Extended hash length
|
|
MaxMerkleLeaves = 1024 // Maximum Merkle tree leaves
|
|
)
|
|
|
|
// Operation selectors (first byte of input)
|
|
const (
|
|
OpHash256 = 0x01 // 32-byte hash
|
|
OpHash512 = 0x02 // 64-byte hash
|
|
OpHashXOF = 0x03 // Arbitrary length output
|
|
OpHashWithDomain = 0x04 // Domain-separated hash
|
|
OpMerkleRoot = 0x10 // Merkle tree root
|
|
OpDeriveKey = 0x20 // Key derivation
|
|
)
|
|
|
|
// Gas costs (optimized for high throughput)
|
|
const (
|
|
GasBase256 = 100 // Base cost for 32-byte hash
|
|
GasBase512 = 150 // Base cost for 64-byte hash
|
|
GasBaseXOF = 200 // Base cost for XOF
|
|
GasPerInputWord = 3 // Per 32-byte input word
|
|
GasPerOutputWord = 5 // Per 32-byte output word (XOF)
|
|
GasDomainSetup = 50 // Domain separator setup
|
|
GasMerkleBase = 500 // Merkle tree base cost
|
|
GasMerklePerLeaf = 100 // Per leaf in Merkle tree
|
|
GasDeriveKey = 300 // Key derivation
|
|
)
|
|
|
|
type blake3Precompile struct{}
|
|
|
|
// Address returns the precompile address
|
|
func (p *blake3Precompile) Address() common.Address {
|
|
return ContractAddress
|
|
}
|
|
|
|
// RequiredGas calculates gas for Blake3 operations
|
|
func (p *blake3Precompile) RequiredGas(input []byte) uint64 {
|
|
if len(input) < 1 {
|
|
return 0
|
|
}
|
|
|
|
op := input[0]
|
|
dataLen := uint64(len(input) - 1)
|
|
inputWords := (dataLen + 31) / 32
|
|
|
|
switch op {
|
|
case OpHash256:
|
|
return GasBase256 + inputWords*GasPerInputWord
|
|
|
|
case OpHash512:
|
|
return GasBase512 + inputWords*GasPerInputWord
|
|
|
|
case OpHashXOF:
|
|
if len(input) < 5 {
|
|
return 0
|
|
}
|
|
outputLen := binary.BigEndian.Uint32(input[1:5])
|
|
outputWords := (uint64(outputLen) + 31) / 32
|
|
return GasBaseXOF + inputWords*GasPerInputWord + outputWords*GasPerOutputWord
|
|
|
|
case OpHashWithDomain:
|
|
return GasDomainSetup + GasBase256 + inputWords*GasPerInputWord
|
|
|
|
case OpMerkleRoot:
|
|
if len(input) < 5 {
|
|
return 0
|
|
}
|
|
numLeaves := binary.BigEndian.Uint32(input[1:5])
|
|
return GasMerkleBase + uint64(numLeaves)*GasMerklePerLeaf
|
|
|
|
case OpDeriveKey:
|
|
return GasDeriveKey + inputWords*GasPerInputWord
|
|
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
// Run executes the Blake3 precompile
|
|
func (p *blake3Precompile) Run(
|
|
accessibleState contract.AccessibleState,
|
|
caller common.Address,
|
|
addr common.Address,
|
|
input []byte,
|
|
suppliedGas uint64,
|
|
readOnly bool,
|
|
) (ret []byte, remainingGas uint64, err error) {
|
|
// Calculate required gas
|
|
requiredGas := p.RequiredGas(input)
|
|
remainingGas, err = contract.DeductGas(suppliedGas, requiredGas)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
if len(input) < 1 {
|
|
return nil, remainingGas, ErrInvalidInput
|
|
}
|
|
|
|
op := input[0]
|
|
data := input[1:]
|
|
|
|
switch op {
|
|
case OpHash256:
|
|
return p.hash256(data), remainingGas, nil
|
|
|
|
case OpHash512:
|
|
return p.hash512(data), remainingGas, nil
|
|
|
|
case OpHashXOF:
|
|
return p.hashXOF(data)
|
|
|
|
case OpHashWithDomain:
|
|
return p.hashWithDomain(data)
|
|
|
|
case OpMerkleRoot:
|
|
return p.merkleRoot(data)
|
|
|
|
case OpDeriveKey:
|
|
return p.deriveKey(data)
|
|
|
|
default:
|
|
return nil, remainingGas, ErrInvalidOperation
|
|
}
|
|
}
|
|
|
|
// hash256 computes a 32-byte Blake3 hash.
|
|
//
|
|
// This is BLAKE3 only. The luxfi/accel "HashBlake3" path actually computes
|
|
// BLAKE2b (accel/ops/crypto/crypto_cpu.go) and its GPU branch has no BLAKE3
|
|
// kernel at all -- wiring it here returned a BLAKE2b digest from a precompile
|
|
// that advertises BLAKE3, a wrong-output bug and a CPU/GPU consensus split.
|
|
// The pure-Go luxfi/crypto/hash/blake3 reference is the single source of truth.
|
|
func (p *blake3Precompile) hash256(data []byte) []byte {
|
|
if len(data) > MaxInputLength {
|
|
data = data[:MaxInputLength]
|
|
}
|
|
|
|
h := blake3.New()
|
|
h.Write(data)
|
|
result := make([]byte, DigestLength32)
|
|
h.Reader().Read(result)
|
|
return result
|
|
}
|
|
|
|
// hash512 computes a 64-byte Blake3 hash
|
|
func (p *blake3Precompile) hash512(data []byte) []byte {
|
|
if len(data) > MaxInputLength {
|
|
data = data[:MaxInputLength]
|
|
}
|
|
digest := blake3.HashBytes(data)
|
|
return digest[:]
|
|
}
|
|
|
|
// hashXOF computes an arbitrary-length hash using XOF mode
|
|
// Input format: [4 bytes output_length][data...]
|
|
func (p *blake3Precompile) hashXOF(data []byte) ([]byte, uint64, error) {
|
|
if len(data) < 4 {
|
|
return nil, 0, ErrInvalidDataLength
|
|
}
|
|
|
|
outputLen := binary.BigEndian.Uint32(data[:4])
|
|
if outputLen > MaxOutputLength {
|
|
return nil, 0, ErrOutputTooLarge
|
|
}
|
|
|
|
inputData := data[4:]
|
|
if len(inputData) > MaxInputLength {
|
|
inputData = inputData[:MaxInputLength]
|
|
}
|
|
|
|
h := blake3.New()
|
|
h.Write(inputData)
|
|
result := make([]byte, outputLen)
|
|
h.Reader().Read(result)
|
|
return result, 0, nil
|
|
}
|
|
|
|
// hashWithDomain computes a domain-separated hash
|
|
// Input format: [1 byte domain_len][domain...][data...]
|
|
func (p *blake3Precompile) hashWithDomain(data []byte) ([]byte, uint64, error) {
|
|
if len(data) < 1 {
|
|
return nil, 0, ErrInvalidDataLength
|
|
}
|
|
|
|
domainLen := int(data[0])
|
|
if len(data) < 1+domainLen {
|
|
return nil, 0, ErrInvalidDataLength
|
|
}
|
|
|
|
domain := string(data[1 : 1+domainLen])
|
|
inputData := data[1+domainLen:]
|
|
|
|
if len(inputData) > MaxInputLength {
|
|
inputData = inputData[:MaxInputLength]
|
|
}
|
|
|
|
digest := blake3.HashWithDomain(domain, inputData)
|
|
return digest[:DigestLength32], 0, nil
|
|
}
|
|
|
|
// merkleRoot computes a Merkle tree root from leaf hashes
|
|
// Input format: [4 bytes num_leaves][32 bytes leaf_0][32 bytes leaf_1]...
|
|
func (p *blake3Precompile) merkleRoot(data []byte) ([]byte, uint64, error) {
|
|
if len(data) < 4 {
|
|
return nil, 0, ErrInvalidDataLength
|
|
}
|
|
|
|
numLeaves := min(binary.BigEndian.Uint32(data[:4]), MaxMerkleLeaves)
|
|
if numLeaves == 0 {
|
|
return make([]byte, DigestLength32), 0, nil
|
|
}
|
|
|
|
expectedLen := 4 + int(numLeaves)*DigestLength32
|
|
if len(data) < expectedLen {
|
|
return nil, 0, ErrInvalidDataLength
|
|
}
|
|
|
|
// Extract leaves
|
|
leaves := make([][]byte, numLeaves)
|
|
for i := uint32(0); i < numLeaves; i++ {
|
|
start := 4 + i*DigestLength32
|
|
leaves[i] = data[start : start+DigestLength32]
|
|
}
|
|
|
|
// Compute Merkle root
|
|
return p.computeMerkleRoot(leaves), 0, nil
|
|
}
|
|
|
|
// computeMerkleRoot computes the Merkle root with GPU-accelerated hashing.
|
|
func (p *blake3Precompile) computeMerkleRoot(leaves [][]byte) []byte {
|
|
if len(leaves) == 0 {
|
|
return make([]byte, DigestLength32)
|
|
}
|
|
if len(leaves) == 1 {
|
|
result := make([]byte, DigestLength32)
|
|
copy(result, leaves[0])
|
|
return result
|
|
}
|
|
|
|
// Pad to power of 2 if necessary
|
|
for len(leaves)&(len(leaves)-1) != 0 {
|
|
leaves = append(leaves, leaves[len(leaves)-1])
|
|
}
|
|
|
|
// Build tree bottom-up. BLAKE3 only (see hash256): the accel "HashBlake3"
|
|
// path is BLAKE2b, so a GPU branch here would produce a different Merkle
|
|
// root than CPU validators. Determinism over speed.
|
|
for len(leaves) > 1 {
|
|
nextLevel := make([][]byte, len(leaves)/2)
|
|
for i := 0; i < len(leaves); i += 2 {
|
|
h := blake3.New()
|
|
h.Write(leaves[i])
|
|
h.Write(leaves[i+1])
|
|
hash := make([]byte, DigestLength32)
|
|
h.Reader().Read(hash)
|
|
nextLevel[i/2] = hash
|
|
}
|
|
leaves = nextLevel
|
|
}
|
|
|
|
return leaves[0]
|
|
}
|
|
|
|
// deriveKey derives a key using Blake3 KDF
|
|
// Input format: [1 byte context_len][context...][32 bytes key_material]
|
|
func (p *blake3Precompile) deriveKey(data []byte) ([]byte, uint64, error) {
|
|
if len(data) < 1 {
|
|
return nil, 0, ErrInvalidDataLength
|
|
}
|
|
|
|
contextLen := int(data[0])
|
|
if len(data) < 1+contextLen+32 {
|
|
return nil, 0, ErrInvalidDataLength
|
|
}
|
|
|
|
context := string(data[1 : 1+contextLen])
|
|
keyMaterial := data[1+contextLen : 1+contextLen+32]
|
|
|
|
// Use domain-separated hash for KDF
|
|
h := blake3.NewWithDomain("BLAKE3 KDF: " + context)
|
|
h.Write(keyMaterial)
|
|
result := make([]byte, DigestLength32)
|
|
h.Reader().Read(result)
|
|
return result, 0, nil
|
|
}
|