Files
zeekay e41cd6d4ab crypto precompiles: unvalidated points, a price that could not be paid, and free calls
Across the curve and hash precompiles: decode refuses a point that is not on
its curve or not canonically encoded, rather than handing an off-curve point to
an arithmetic routine; pasta's inversion no longer treats a multiple of p as
invertible, where ModInverse returns nil and leaves the divisor untouched;
compute refuses a quoted price wider than 256 bits instead of returning it
modulo, and enforces readOnly; poseidon's sponge fee grows with its input; hqc
reports an unwired backend as itself rather than as a verification failure; and
sr25519 pins that the cgo and non-cgo verifiers name the same backend and agree,
which is a consensus split when they do not. Every path deducts gas before it
works, so no call is free. KAT vectors accompany each.
2026-08-28 15:39:27 -07:00

127 lines
3.8 KiB
Go

// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// Package x25519 implements X25519 Diffie-Hellman key exchange precompile.
// Address: 0x0000000000000000000000000000000000009203 (Privacy range)
//
// Operations:
// - 0x01: ScalarMult — 32-byte private scalar + 32-byte public point -> 32-byte shared secret
// - 0x02: Basepoint — 32-byte private scalar -> 32-byte public key (mult with basepoint)
//
// Used by: TLS 1.3, Signal Protocol, age encryption, WireGuard.
//
// WARNING: x25519 ScalarMult accepts a scalar in calldata. This scalar is PUBLIC
// on-chain. Do NOT use this precompile with long-term identity keys. Only use
// with ephemeral session keys or public scalars where public visibility is
// acceptable (e.g., verifiable randomness from a hash, commit-reveal DH where
// the scalar is derived from a public seed, or one-time key agreement where
// leaking the scalar after use is harmless).
//
// The aes, chacha20, and ecies precompiles were deleted for the same reason:
// symmetric keys and private keys in calldata are irrecoverably exposed. This
// precompile is retained because X25519 key exchange with ephemeral scalars is
// a valid on-chain pattern — the shared secret is not in calldata, only the
// (disposable) scalar is.
package x25519
import (
"errors"
"golang.org/x/crypto/curve25519"
"github.com/luxfi/geth/common"
"github.com/luxfi/precompile/contract"
)
var (
ContractAddress = common.HexToAddress("0x0000000000000000000000000000000000009203")
X25519Precompile = &x25519Precompile{}
_ contract.StatefulPrecompiledContract = &x25519Precompile{}
ErrInvalidInput = contract.ErrInvalidInput
ErrInvalidOp = errors.New("invalid x25519 operation")
ErrDHFailed = errors.New("x25519 scalar multiplication failed")
)
const (
OpScalarMult = 0x01
OpBasepoint = 0x02
GasScalarMult = 3000
GasBasepoint = 3000
)
type x25519Precompile struct{}
func (p *x25519Precompile) RequiredGas(input []byte) uint64 {
if len(input) < 1 {
return 0
}
switch input[0] {
case OpScalarMult:
return GasScalarMult
case OpBasepoint:
return GasBasepoint
default:
return 0
}
}
func (p *x25519Precompile) 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 OpScalarMult:
return p.scalarMult(input[1:], gas)
case OpBasepoint:
return p.basepoint(input[1:], gas)
default:
return nil, gas, ErrInvalidOp
}
}
func (p *x25519Precompile) scalarMult(data []byte, gas uint64) ([]byte, uint64, error) {
if len(data) < 64 {
return nil, gas, ErrInvalidInput
}
return mult(data[:32], data[32:64], gas)
}
// basepoint is scalarMult against u = 9, so it goes through the same code.
// Splitting them gave the basepoint arm its own unreachable error branch:
// X25519 fails only on an all-zero result, and the base point generates the
// prime-order subgroup, so a clamped scalar can never land on the identity.
func (p *x25519Precompile) basepoint(data []byte, gas uint64) ([]byte, uint64, error) {
if len(data) < 32 {
return nil, gas, ErrInvalidInput
}
return mult(data[:32], curve25519.Basepoint, gas)
}
// mult is the one place the ladder is called and the one place its refusal --
// a low-order point, which drives the shared secret to zero for every scalar --
// turns into an error.
func mult(scalar, point []byte, gas uint64) ([]byte, uint64, error) {
out, err := curve25519.X25519(scalar, point)
if err != nil {
return nil, gas, ErrDHFailed
}
return out, gas, nil
}