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.
274 lines
6.5 KiB
Go
274 lines
6.5 KiB
Go
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
// Package pedersen implements Pedersen hash/commitment precompile over BN254.
|
|
// Address: 0x0500000000000000000000000000000000000006 (Hashing range)
|
|
//
|
|
// Pedersen commitments are homomorphic: C(v1) + C(v2) = C(v1+v2).
|
|
// They are hiding and binding under DLOG hardness (NOT post-quantum).
|
|
//
|
|
// Operations:
|
|
// - 0x01: Commit — value(32) + blinding(32) -> commitment(32)
|
|
// - 0x02: Verify — commitment(32) + value(32) + blinding(32) -> bool(32)
|
|
// - 0x03: Add — c1(32) + c2(32) -> c1+c2(32)
|
|
// - 0x04: VectorCommit — n(1) + values(n*32) + blinding(32) -> commitment(32)
|
|
//
|
|
// Used by: Zcash, Aztec, commitment schemes, range proofs.
|
|
package pedersen
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"errors"
|
|
"math/big"
|
|
|
|
"github.com/consensys/gnark-crypto/ecc/bn254"
|
|
"github.com/consensys/gnark-crypto/ecc/bn254/fp"
|
|
"github.com/consensys/gnark-crypto/ecc/bn254/fr"
|
|
|
|
"github.com/luxfi/geth/common"
|
|
"github.com/luxfi/precompile/contract"
|
|
)
|
|
|
|
var (
|
|
ContractAddress = common.HexToAddress("0x0500000000000000000000000000000000000006")
|
|
|
|
PedersenPrecompile = &pedersenPrecompile{}
|
|
|
|
_ contract.StatefulPrecompiledContract = &pedersenPrecompile{}
|
|
|
|
ErrInvalidInput = contract.ErrInvalidInput
|
|
ErrInvalidOp = errors.New("invalid pedersen operation")
|
|
ErrTooManyVals = errors.New("too many values for vector commitment (max 32)")
|
|
)
|
|
|
|
const (
|
|
OpCommit = 0x01
|
|
OpVerify = 0x02
|
|
OpAdd = 0x03
|
|
OpVectorCommit = 0x04
|
|
|
|
GasCommit = 6000
|
|
GasVerify = 7000
|
|
GasAdd = 500
|
|
GasVectorBase = 6000
|
|
GasVectorPerVal = 3000
|
|
)
|
|
|
|
// Generators derived deterministically
|
|
var (
|
|
genG bn254.G1Affine // base generator
|
|
genH bn254.G1Affine // blinding generator
|
|
genVi [32]bn254.G1Affine
|
|
)
|
|
|
|
func init() {
|
|
_, _, g1Gen, _ := bn254.Generators()
|
|
genG = g1Gen
|
|
genH = hashToG1("Lux_Pedersen_H_Generator")
|
|
for i := range 32 {
|
|
genVi[i] = hashToG1("Lux_Pedersen_Gen_" + string(rune('A'+i)))
|
|
}
|
|
|
|
// Verify all generators are distinct (G, H, V_0..V_31)
|
|
all := make([]bn254.G1Affine, 0, 34)
|
|
all = append(all, genG, genH)
|
|
all = append(all, genVi[:]...)
|
|
for i := 0; i < len(all); i++ {
|
|
for j := i + 1; j < len(all); j++ {
|
|
if all[i].Equal(&all[j]) {
|
|
panic("pedersen: duplicate generator detected at init")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
type pedersenPrecompile struct{}
|
|
|
|
func (p *pedersenPrecompile) RequiredGas(input []byte) uint64 {
|
|
if len(input) < 1 {
|
|
return 0
|
|
}
|
|
switch input[0] {
|
|
case OpCommit:
|
|
return GasCommit
|
|
case OpVerify:
|
|
return GasVerify
|
|
case OpAdd:
|
|
return GasAdd
|
|
case OpVectorCommit:
|
|
if len(input) < 2 {
|
|
return 0
|
|
}
|
|
n := int(input[1])
|
|
return GasVectorBase + uint64(n)*GasVectorPerVal
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
func (p *pedersenPrecompile) Run(
|
|
accessibleState contract.AccessibleState,
|
|
caller common.Address,
|
|
addr common.Address,
|
|
input []byte,
|
|
suppliedGas uint64,
|
|
readOnly bool,
|
|
) ([]byte, uint64, error) {
|
|
gasCost := p.RequiredGas(input)
|
|
gas, err := contract.DeductGas(suppliedGas, gasCost)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
if len(input) < 1 {
|
|
return nil, gas, ErrInvalidInput
|
|
}
|
|
|
|
switch input[0] {
|
|
case OpCommit:
|
|
return p.commit(input[1:], gas)
|
|
case OpVerify:
|
|
return p.verify(input[1:], gas)
|
|
case OpAdd:
|
|
return p.add(input[1:], gas)
|
|
case OpVectorCommit:
|
|
return p.vectorCommit(input[1:], gas)
|
|
default:
|
|
return nil, gas, ErrInvalidOp
|
|
}
|
|
}
|
|
|
|
// commit: C = v*G + r*H
|
|
func (p *pedersenPrecompile) commit(data []byte, gas uint64) ([]byte, uint64, error) {
|
|
if len(data) < 64 {
|
|
return nil, gas, ErrInvalidInput
|
|
}
|
|
var v, r fr.Element
|
|
v.SetBytes(data[:32])
|
|
r.SetBytes(data[32:64])
|
|
|
|
var vG, rH bn254.G1Affine
|
|
vG.ScalarMultiplication(&genG, v.BigInt(new(big.Int)))
|
|
rH.ScalarMultiplication(&genH, r.BigInt(new(big.Int)))
|
|
|
|
var c bn254.G1Affine
|
|
c.Add(&vG, &rH)
|
|
|
|
cBytes := c.Marshal()
|
|
h := sha256.Sum256(cBytes)
|
|
return h[:], gas, nil
|
|
}
|
|
|
|
// verify: check C == v*G + r*H
|
|
func (p *pedersenPrecompile) verify(data []byte, gas uint64) ([]byte, uint64, error) {
|
|
if len(data) < 96 {
|
|
return nil, gas, ErrInvalidInput
|
|
}
|
|
commitHash := data[:32]
|
|
recomputed, _, err := p.commit(data[32:96], gas)
|
|
if err != nil {
|
|
return nil, gas, err
|
|
}
|
|
result := make([]byte, 32)
|
|
if equal(commitHash, recomputed) {
|
|
result[31] = 1
|
|
}
|
|
return result, gas, nil
|
|
}
|
|
|
|
// add: C1 + C2 homomorphic addition (on compressed hashes -- re-serialize from raw points)
|
|
// For the precompile, we work with raw G1 points (64 bytes each)
|
|
func (p *pedersenPrecompile) add(data []byte, gas uint64) ([]byte, uint64, error) {
|
|
if len(data) < 128 {
|
|
return nil, gas, ErrInvalidInput
|
|
}
|
|
var p1, p2 bn254.G1Affine
|
|
if err := p1.Unmarshal(data[:64]); err != nil {
|
|
return nil, gas, ErrInvalidInput
|
|
}
|
|
if err := p2.Unmarshal(data[64:128]); err != nil {
|
|
return nil, gas, ErrInvalidInput
|
|
}
|
|
var sum bn254.G1Affine
|
|
sum.Add(&p1, &p2)
|
|
return sum.Marshal(), gas, nil
|
|
}
|
|
|
|
// vectorCommit: C = sum(v_i * G_i) + r * H
|
|
func (p *pedersenPrecompile) vectorCommit(data []byte, gas uint64) ([]byte, uint64, error) {
|
|
if len(data) < 1 {
|
|
return nil, gas, ErrInvalidInput
|
|
}
|
|
n := int(data[0])
|
|
if n > 32 {
|
|
return nil, gas, ErrTooManyVals
|
|
}
|
|
if len(data) < 1+n*32+32 {
|
|
return nil, gas, ErrInvalidInput
|
|
}
|
|
|
|
var sum bn254.G1Jac
|
|
for i := range n {
|
|
var vi fr.Element
|
|
vi.SetBytes(data[1+i*32 : 1+(i+1)*32])
|
|
var viG bn254.G1Affine
|
|
viG.ScalarMultiplication(&genVi[i], vi.BigInt(new(big.Int)))
|
|
var viGJac bn254.G1Jac
|
|
viGJac.FromAffine(&viG)
|
|
sum.AddAssign(&viGJac)
|
|
}
|
|
|
|
// Blinding factor
|
|
var r fr.Element
|
|
r.SetBytes(data[1+n*32 : 1+n*32+32])
|
|
var rH bn254.G1Affine
|
|
rH.ScalarMultiplication(&genH, r.BigInt(new(big.Int)))
|
|
var rHJac bn254.G1Jac
|
|
rHJac.FromAffine(&rH)
|
|
sum.AddAssign(&rHJac)
|
|
|
|
var result bn254.G1Affine
|
|
result.FromJacobian(&sum)
|
|
cBytes := result.Marshal()
|
|
h := sha256.Sum256(cBytes)
|
|
return h[:], gas, nil
|
|
}
|
|
|
|
func equal(a, b []byte) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
var diff byte
|
|
for i := range a {
|
|
diff |= a[i] ^ b[i]
|
|
}
|
|
return diff == 0
|
|
}
|
|
|
|
func hashToG1(seed string) bn254.G1Affine {
|
|
seedBytes := []byte(seed)
|
|
for counter := byte(0); ; counter++ {
|
|
data := append(seedBytes, counter)
|
|
hash := sha256.Sum256(data)
|
|
var x fp.Element
|
|
x.SetBytes(hash[:])
|
|
var x2, x3, rhs fp.Element
|
|
x2.Square(&x)
|
|
x3.Mul(&x2, &x)
|
|
var three fp.Element
|
|
three.SetInt64(3)
|
|
rhs.Add(&x3, &three)
|
|
var y fp.Element
|
|
if y.Sqrt(&rhs) != nil {
|
|
var pt bn254.G1Affine
|
|
pt.X = x
|
|
pt.Y = y
|
|
if pt.IsOnCurve() && !pt.IsInfinity() {
|
|
return pt
|
|
}
|
|
}
|
|
if counter == 255 {
|
|
panic("pedersen: failed to derive independent generator after 256 tries")
|
|
}
|
|
}
|
|
}
|