mirror of
https://github.com/luxfi/precompile.git
synced 2026-09-24 00:26:21 +00:00
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.
127 lines
3.8 KiB
Go
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
|
|
}
|