mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-12 09:51:36 +00:00
Adding MLSDA65 precompile to begin adding quantum hardening support to Ethereum smart contracts
This commit is contained in:
parent
453d0f9299
commit
57e8860dfe
8 changed files with 529 additions and 40 deletions
|
|
@ -147,6 +147,12 @@ var PrecompiledContractsBLS = PrecompiledContractsPrague
|
|||
|
||||
var PrecompiledContractsVerkle = PrecompiledContractsBerlin
|
||||
|
||||
var PrecompiledContractsMLDSA = PrecompiledContracts{
|
||||
mldsa65VerifyAddr: &mldsa65VerifyPrecompile{},
|
||||
}
|
||||
|
||||
var PrecompiledAddressesMLDSA = []common.Address{mldsa65VerifyAddr}
|
||||
|
||||
// PrecompiledContractsOsaka contains the set of pre-compiled Ethereum
|
||||
// contracts used in the Osaka release.
|
||||
var PrecompiledContractsOsaka = PrecompiledContracts{
|
||||
|
|
@ -234,27 +240,37 @@ func activePrecompiledContracts(rules params.Rules) PrecompiledContracts {
|
|||
|
||||
// ActivePrecompiledContracts returns a copy of precompiled contracts enabled with the current configuration.
|
||||
func ActivePrecompiledContracts(rules params.Rules) PrecompiledContracts {
|
||||
return maps.Clone(activePrecompiledContracts(rules))
|
||||
precompiles := maps.Clone(activePrecompiledContracts(rules))
|
||||
if rules.IsMLDSAPrecompile {
|
||||
maps.Copy(precompiles, PrecompiledContractsMLDSA)
|
||||
}
|
||||
return precompiles
|
||||
}
|
||||
|
||||
// ActivePrecompiles returns the precompile addresses enabled with the current configuration.
|
||||
func ActivePrecompiles(rules params.Rules) []common.Address {
|
||||
var active []common.Address
|
||||
switch {
|
||||
case rules.IsOsaka:
|
||||
return PrecompiledAddressesOsaka
|
||||
active = PrecompiledAddressesOsaka
|
||||
case rules.IsPrague:
|
||||
return PrecompiledAddressesPrague
|
||||
active = PrecompiledAddressesPrague
|
||||
case rules.IsCancun:
|
||||
return PrecompiledAddressesCancun
|
||||
active = PrecompiledAddressesCancun
|
||||
case rules.IsBerlin:
|
||||
return PrecompiledAddressesBerlin
|
||||
active = PrecompiledAddressesBerlin
|
||||
case rules.IsIstanbul:
|
||||
return PrecompiledAddressesIstanbul
|
||||
active = PrecompiledAddressesIstanbul
|
||||
case rules.IsByzantium:
|
||||
return PrecompiledAddressesByzantium
|
||||
active = PrecompiledAddressesByzantium
|
||||
default:
|
||||
return PrecompiledAddressesHomestead
|
||||
active = PrecompiledAddressesHomestead
|
||||
}
|
||||
if !rules.IsMLDSAPrecompile {
|
||||
return active
|
||||
}
|
||||
addresses := append([]common.Address{}, active...)
|
||||
return append(addresses, PrecompiledAddressesMLDSA...)
|
||||
}
|
||||
|
||||
// RunPrecompiledContract runs and evaluates the output of a precompiled contract.
|
||||
|
|
|
|||
|
|
@ -24,7 +24,9 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/circl/sign/mldsa/mldsa65"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
// precompiledTest defines the input/output pairs for precompiled contract tests.
|
||||
|
|
@ -68,6 +70,7 @@ var allPrecompiles = map[common.Address]PrecompiledContract{
|
|||
common.BytesToAddress([]byte{0x0f, 0x10}): &bls12381MapG2{},
|
||||
|
||||
common.BytesToAddress([]byte{0x0b}): &p256Verify{},
|
||||
mldsa65VerifyAddr: &mldsa65VerifyPrecompile{},
|
||||
}
|
||||
|
||||
// EIP-152 test vectors
|
||||
|
|
@ -300,6 +303,150 @@ func TestPrecompileBlake2FMalformedInput(t *testing.T) {
|
|||
|
||||
func TestPrecompiledEcrecover(t *testing.T) { testJson("ecRecover", "01", t) }
|
||||
|
||||
func TestActivePrecompiledContractsMLDSA65(t *testing.T) {
|
||||
rules := params.Rules{IsCancun: true}
|
||||
if _, ok := ActivePrecompiledContracts(rules)[mldsa65VerifyAddr]; ok {
|
||||
t.Fatal("unexpected ML-DSA precompile before activation")
|
||||
}
|
||||
for _, addr := range ActivePrecompiles(rules) {
|
||||
if addr == mldsa65VerifyAddr {
|
||||
t.Fatal("unexpected ML-DSA precompile address before activation")
|
||||
}
|
||||
}
|
||||
|
||||
rules.IsMLDSAPrecompile = true
|
||||
if _, ok := ActivePrecompiledContracts(rules)[mldsa65VerifyAddr]; !ok {
|
||||
t.Fatal("missing ML-DSA precompile after activation")
|
||||
}
|
||||
found := false
|
||||
for _, addr := range ActivePrecompiles(rules) {
|
||||
if addr == mldsa65VerifyAddr {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatal("missing ML-DSA precompile address after activation")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrecompiledMLDSA65Verify(t *testing.T) {
|
||||
pk, sk, err := mldsa65.GenerateKey(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
msg := []byte("hello geth pq")
|
||||
|
||||
sig := make([]byte, mldsa65.SignatureSize)
|
||||
if err := mldsa65.SignTo(sk, msg, nil, true, sig); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pkBytes, err := pk.MarshalBinary()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
in := append(append(pkBytes, sig...), msg...)
|
||||
out, _, err := RunPrecompiledContract(&mldsa65VerifyPrecompile{}, in, 10_000_000, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if want := common.LeftPadBytes([]byte{1}, 32); !bytes.Equal(out, want) {
|
||||
t.Fatalf("expected 1, got %x", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrecompiledMLDSA65VerifyInvalidInputs(t *testing.T) {
|
||||
zero := make([]byte, 32)
|
||||
|
||||
t.Run("too short", func(t *testing.T) {
|
||||
in := make([]byte, mldsa65.PublicKeySize+mldsa65.SignatureSize-1)
|
||||
out, _, err := RunPrecompiledContract(&mldsa65VerifyPrecompile{}, in, 10_000_000, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(out, zero) {
|
||||
t.Fatalf("expected 0, got %x", out)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("tampered msg", func(t *testing.T) {
|
||||
pk, sk, err := mldsa65.GenerateKey(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
msg := []byte("hello geth pq")
|
||||
sig := make([]byte, mldsa65.SignatureSize)
|
||||
if err := mldsa65.SignTo(sk, msg, nil, true, sig); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pkBytes, err := pk.MarshalBinary()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
badMsg := []byte("hello geth PQ")
|
||||
in := append(append(pkBytes, sig...), badMsg...)
|
||||
out, _, err := RunPrecompiledContract(&mldsa65VerifyPrecompile{}, in, 10_000_000, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(out, zero) {
|
||||
t.Fatalf("expected 0, got %x", out)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("tampered sig", func(t *testing.T) {
|
||||
pk, sk, err := mldsa65.GenerateKey(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
msg := []byte("hello geth pq")
|
||||
sig := make([]byte, mldsa65.SignatureSize)
|
||||
if err := mldsa65.SignTo(sk, msg, nil, true, sig); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sig[0] ^= 0xff
|
||||
pkBytes, err := pk.MarshalBinary()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
in := append(append(pkBytes, sig...), msg...)
|
||||
out, _, err := RunPrecompiledContract(&mldsa65VerifyPrecompile{}, in, 10_000_000, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(out, zero) {
|
||||
t.Fatalf("expected 0, got %x", out)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkPrecompiledMLDSA65Verify(b *testing.B) {
|
||||
pk, sk, err := mldsa65.GenerateKey(nil)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
msg := make([]byte, 1024)
|
||||
sig := make([]byte, mldsa65.SignatureSize)
|
||||
if err := mldsa65.SignTo(sk, msg, nil, true, sig); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
pkBytes, err := pk.MarshalBinary()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
in := append(append(pkBytes, sig...), msg...)
|
||||
p := &mldsa65VerifyPrecompile{}
|
||||
gas := p.RequiredGas(in)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, _, err := RunPrecompiledContract(p, in, gas, nil); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testJson(name, addr string, t *testing.T) {
|
||||
tests, err := loadJson(name)
|
||||
if err != nil {
|
||||
|
|
|
|||
54
core/vm/precompile_mldsa65.go
Normal file
54
core/vm/precompile_mldsa65.go
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
package vm
|
||||
|
||||
import (
|
||||
"github.com/cloudflare/circl/sign/mldsa/mldsa65"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
// Address: 0x0000000000000000000000000000000000000101.
|
||||
var mldsa65VerifyAddr = common.HexToAddress("0101")
|
||||
|
||||
const (
|
||||
mldsa65GasBase uint64 = 250_000
|
||||
mldsa65GasPerWord uint64 = 12
|
||||
)
|
||||
|
||||
type mldsa65VerifyPrecompile struct{}
|
||||
|
||||
func (p *mldsa65VerifyPrecompile) RequiredGas(input []byte) uint64 {
|
||||
pkLen := mldsa65.PublicKeySize
|
||||
sigLen := mldsa65.SignatureSize
|
||||
if len(input) <= pkLen+sigLen {
|
||||
return mldsa65GasBase
|
||||
}
|
||||
msgLen := len(input) - pkLen - sigLen
|
||||
words := uint64((msgLen + 31) / 32)
|
||||
return mldsa65GasBase + mldsa65GasPerWord*words
|
||||
}
|
||||
|
||||
func (p *mldsa65VerifyPrecompile) Run(input []byte) ([]byte, error) {
|
||||
one := common.LeftPadBytes([]byte{1}, 32)
|
||||
zero := make([]byte, 32)
|
||||
|
||||
pkLen := mldsa65.PublicKeySize
|
||||
sigLen := mldsa65.SignatureSize
|
||||
if len(input) < pkLen+sigLen {
|
||||
return zero, nil
|
||||
}
|
||||
pkBytes := input[:pkLen]
|
||||
sig := input[pkLen : pkLen+sigLen]
|
||||
msg := input[pkLen+sigLen:]
|
||||
|
||||
var pk mldsa65.PublicKey
|
||||
if err := pk.UnmarshalBinary(pkBytes); err != nil {
|
||||
return zero, nil
|
||||
}
|
||||
if mldsa65.Verify(&pk, msg, nil, sig) {
|
||||
return one, nil
|
||||
}
|
||||
return zero, nil
|
||||
}
|
||||
|
||||
func (p *mldsa65VerifyPrecompile) Name() string {
|
||||
return "MLDSA65V"
|
||||
}
|
||||
230
docs/mldsa65-precompile-example.md
Normal file
230
docs/mldsa65-precompile-example.md
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
# ML-DSA-65 Precompile Contract Integration
|
||||
|
||||
This note documents the Solidity-side calling convention for the ML-DSA-65 verification precompile introduced at address `0x0000000000000000000000000000000000000101`.
|
||||
|
||||
It is intended as an implementation example for smart wallets, account abstraction flows, and EIP companion documentation.
|
||||
|
||||
## What The Precompile Does
|
||||
|
||||
The precompile verifies an ML-DSA-65 signature and returns a single 32-byte word:
|
||||
|
||||
- `0x000...0001` when verification succeeds
|
||||
- `0x000...0000` otherwise
|
||||
|
||||
It does not revert on invalid signatures.
|
||||
|
||||
## Call Interface
|
||||
|
||||
The precompile does not expose a Solidity ABI in the usual sense. Contracts call it via `staticcall` with raw bytes.
|
||||
|
||||
Input encoding:
|
||||
|
||||
```text
|
||||
input = publicKey || signature || message
|
||||
```
|
||||
|
||||
For ML-DSA-65, the fixed sizes are:
|
||||
|
||||
- public key: `1952` bytes
|
||||
- signature: `3309` bytes
|
||||
- message: variable length
|
||||
|
||||
Minimal adapter shape:
|
||||
|
||||
```solidity
|
||||
library MLDSA65 {
|
||||
address internal constant PRECOMPILE = address(0x0101);
|
||||
uint256 internal constant PUBLIC_KEY_SIZE = 1952;
|
||||
uint256 internal constant SIGNATURE_SIZE = 3309;
|
||||
|
||||
function verify(
|
||||
bytes memory publicKey,
|
||||
bytes memory signature,
|
||||
bytes memory message
|
||||
) internal view returns (bool) {
|
||||
require(publicKey.length == PUBLIC_KEY_SIZE, "bad ML-DSA public key length");
|
||||
require(signature.length == SIGNATURE_SIZE, "bad ML-DSA signature length");
|
||||
|
||||
bytes memory input = bytes.concat(publicKey, signature, message);
|
||||
(bool ok, bytes memory out) = PRECOMPILE.staticcall(input);
|
||||
if (!ok || out.length != 32) {
|
||||
return false;
|
||||
}
|
||||
uint256 word;
|
||||
assembly {
|
||||
word := mload(add(out, 0x20))
|
||||
}
|
||||
return word == 1;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Recommended Signing Payload
|
||||
|
||||
Applications should not verify signatures over arbitrary free-form messages. The signed payload should bind the authorization to a specific chain, contract, and action.
|
||||
|
||||
At minimum, include:
|
||||
|
||||
- chain ID
|
||||
- contract address
|
||||
- nonce
|
||||
- target address
|
||||
- ETH value
|
||||
- calldata hash
|
||||
- optional deadline
|
||||
|
||||
That avoids replay across chains, contracts, or previously authorized actions.
|
||||
|
||||
## Example Smart Wallet Pattern
|
||||
|
||||
The following pattern uses:
|
||||
|
||||
- a stored ML-DSA-65 public key
|
||||
- per-wallet nonce replay protection
|
||||
- a deterministic signed digest
|
||||
- precompile-backed verification before executing a call
|
||||
|
||||
```solidity
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.24;
|
||||
|
||||
library MLDSA65 {
|
||||
address internal constant PRECOMPILE = address(0x0101);
|
||||
uint256 internal constant PUBLIC_KEY_SIZE = 1952;
|
||||
uint256 internal constant SIGNATURE_SIZE = 3309;
|
||||
|
||||
function verify(
|
||||
bytes memory publicKey,
|
||||
bytes memory signature,
|
||||
bytes memory message
|
||||
) internal view returns (bool) {
|
||||
require(publicKey.length == PUBLIC_KEY_SIZE, "bad ML-DSA public key length");
|
||||
require(signature.length == SIGNATURE_SIZE, "bad ML-DSA signature length");
|
||||
|
||||
bytes memory input = bytes.concat(publicKey, signature, message);
|
||||
(bool ok, bytes memory out) = PRECOMPILE.staticcall(input);
|
||||
if (!ok || out.length != 32) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint256 word;
|
||||
assembly {
|
||||
word := mload(add(out, 0x20))
|
||||
}
|
||||
return word == 1;
|
||||
}
|
||||
}
|
||||
|
||||
contract PQSmartWallet {
|
||||
bytes public ownerPublicKey;
|
||||
uint256 public nonce;
|
||||
|
||||
event Executed(address indexed target, uint256 value, bytes data, uint256 nonce);
|
||||
event PublicKeyRotated(bytes newPublicKey);
|
||||
|
||||
constructor(bytes memory initialPublicKey) {
|
||||
require(initialPublicKey.length == MLDSA65.PUBLIC_KEY_SIZE, "bad ML-DSA public key length");
|
||||
ownerPublicKey = initialPublicKey;
|
||||
}
|
||||
|
||||
function digestForExecute(
|
||||
address target,
|
||||
uint256 value,
|
||||
bytes calldata data,
|
||||
uint256 expectedNonce,
|
||||
uint256 deadline
|
||||
) public view returns (bytes32) {
|
||||
return keccak256(
|
||||
abi.encode(
|
||||
bytes32("MLDSA65_WALLET_EXECUTE"),
|
||||
block.chainid,
|
||||
address(this),
|
||||
expectedNonce,
|
||||
deadline,
|
||||
target,
|
||||
value,
|
||||
keccak256(data)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function execute(
|
||||
address target,
|
||||
uint256 value,
|
||||
bytes calldata data,
|
||||
uint256 deadline,
|
||||
bytes calldata signature
|
||||
) external payable {
|
||||
require(block.timestamp <= deadline, "signature expired");
|
||||
|
||||
uint256 currentNonce = nonce;
|
||||
bytes32 digest = digestForExecute(target, value, data, currentNonce, deadline);
|
||||
bytes memory message = abi.encodePacked(digest);
|
||||
|
||||
require(MLDSA65.verify(ownerPublicKey, signature, message), "invalid ML-DSA signature");
|
||||
|
||||
nonce = currentNonce + 1;
|
||||
|
||||
(bool ok, ) = target.call{value: value}(data);
|
||||
require(ok, "call failed");
|
||||
|
||||
emit Executed(target, value, data, currentNonce);
|
||||
}
|
||||
|
||||
function rotateKey(
|
||||
bytes calldata newPublicKey,
|
||||
uint256 deadline,
|
||||
bytes calldata signature
|
||||
) external {
|
||||
require(newPublicKey.length == MLDSA65.PUBLIC_KEY_SIZE, "bad ML-DSA public key length");
|
||||
require(block.timestamp <= deadline, "signature expired");
|
||||
|
||||
uint256 currentNonce = nonce;
|
||||
bytes32 digest = keccak256(
|
||||
abi.encode(
|
||||
bytes32("MLDSA65_WALLET_ROTATE"),
|
||||
block.chainid,
|
||||
address(this),
|
||||
currentNonce,
|
||||
deadline,
|
||||
keccak256(newPublicKey)
|
||||
)
|
||||
);
|
||||
|
||||
require(
|
||||
MLDSA65.verify(ownerPublicKey, signature, abi.encodePacked(digest)),
|
||||
"invalid ML-DSA signature"
|
||||
);
|
||||
|
||||
ownerPublicKey = newPublicKey;
|
||||
nonce = currentNonce + 1;
|
||||
|
||||
emit PublicKeyRotated(newPublicKey);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Security Notes
|
||||
|
||||
This precompile only provides post-quantum verification as a building block. Whether a contract is quantum-hardened depends on the authorization policy around it.
|
||||
|
||||
Good pattern:
|
||||
|
||||
- contract state changes require a valid ML-DSA signature
|
||||
- no ECDSA owner bypass exists
|
||||
- key rotation is also ML-DSA authorized
|
||||
- replay protection is present
|
||||
|
||||
Weak pattern:
|
||||
|
||||
- `msg.sender` remains the real authority
|
||||
- ECDSA is accepted as a fallback
|
||||
- signed messages omit chain ID, nonce, or contract binding
|
||||
|
||||
The security gain is in authorization of state transitions. This does not encrypt storage or make legacy EOAs quantum-safe.
|
||||
|
||||
## Reference Files
|
||||
|
||||
- Client precompile implementation: `core/vm/precompile_mldsa65.go`
|
||||
- Precompile activation wiring: `params/config.go`
|
||||
- Client-side tests: `core/vm/contracts_test.go`
|
||||
1
go.mod
1
go.mod
|
|
@ -11,6 +11,7 @@ require (
|
|||
github.com/aws/aws-sdk-go-v2/credentials v1.13.43
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.30.2
|
||||
github.com/cespare/cp v0.1.0
|
||||
github.com/cloudflare/circl v1.6.3
|
||||
github.com/cloudflare/cloudflare-go v0.114.0
|
||||
github.com/cockroachdb/pebble v1.1.5
|
||||
github.com/consensys/gnark-crypto v0.18.1
|
||||
|
|
|
|||
2
go.sum
2
go.sum
|
|
@ -61,6 +61,8 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
|
|||
github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=
|
||||
github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic=
|
||||
github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
|
||||
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
||||
github.com/cloudflare/cloudflare-go v0.114.0 h1:ucoti4/7Exo0XQ+rzpn1H+IfVVe++zgiM+tyKtf0HUA=
|
||||
github.com/cloudflare/cloudflare-go v0.114.0/go.mod h1:O7fYfFfA6wKqKFn2QIR9lhj7FDw6VQCGOY6hd2TBtd0=
|
||||
github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4=
|
||||
|
|
|
|||
|
|
@ -456,17 +456,18 @@ type ChainConfig struct {
|
|||
|
||||
// Fork scheduling was switched from blocks to timestamps here
|
||||
|
||||
ShanghaiTime *uint64 `json:"shanghaiTime,omitempty"` // Shanghai switch time (nil = no fork, 0 = already on shanghai)
|
||||
CancunTime *uint64 `json:"cancunTime,omitempty"` // Cancun switch time (nil = no fork, 0 = already on cancun)
|
||||
PragueTime *uint64 `json:"pragueTime,omitempty"` // Prague switch time (nil = no fork, 0 = already on prague)
|
||||
OsakaTime *uint64 `json:"osakaTime,omitempty"` // Osaka switch time (nil = no fork, 0 = already on osaka)
|
||||
BPO1Time *uint64 `json:"bpo1Time,omitempty"` // BPO1 switch time (nil = no fork, 0 = already on bpo1)
|
||||
BPO2Time *uint64 `json:"bpo2Time,omitempty"` // BPO2 switch time (nil = no fork, 0 = already on bpo2)
|
||||
BPO3Time *uint64 `json:"bpo3Time,omitempty"` // BPO3 switch time (nil = no fork, 0 = already on bpo3)
|
||||
BPO4Time *uint64 `json:"bpo4Time,omitempty"` // BPO4 switch time (nil = no fork, 0 = already on bpo4)
|
||||
BPO5Time *uint64 `json:"bpo5Time,omitempty"` // BPO5 switch time (nil = no fork, 0 = already on bpo5)
|
||||
AmsterdamTime *uint64 `json:"amsterdamTime,omitempty"` // Amsterdam switch time (nil = no fork, 0 = already on amsterdam)
|
||||
VerkleTime *uint64 `json:"verkleTime,omitempty"` // Verkle switch time (nil = no fork, 0 = already on verkle)
|
||||
ShanghaiTime *uint64 `json:"shanghaiTime,omitempty"` // Shanghai switch time (nil = no fork, 0 = already on shanghai)
|
||||
CancunTime *uint64 `json:"cancunTime,omitempty"` // Cancun switch time (nil = no fork, 0 = already on cancun)
|
||||
PragueTime *uint64 `json:"pragueTime,omitempty"` // Prague switch time (nil = no fork, 0 = already on prague)
|
||||
OsakaTime *uint64 `json:"osakaTime,omitempty"` // Osaka switch time (nil = no fork, 0 = already on osaka)
|
||||
BPO1Time *uint64 `json:"bpo1Time,omitempty"` // BPO1 switch time (nil = no fork, 0 = already on bpo1)
|
||||
BPO2Time *uint64 `json:"bpo2Time,omitempty"` // BPO2 switch time (nil = no fork, 0 = already on bpo2)
|
||||
BPO3Time *uint64 `json:"bpo3Time,omitempty"` // BPO3 switch time (nil = no fork, 0 = already on bpo3)
|
||||
BPO4Time *uint64 `json:"bpo4Time,omitempty"` // BPO4 switch time (nil = no fork, 0 = already on bpo4)
|
||||
BPO5Time *uint64 `json:"bpo5Time,omitempty"` // BPO5 switch time (nil = no fork, 0 = already on bpo5)
|
||||
AmsterdamTime *uint64 `json:"amsterdamTime,omitempty"` // Amsterdam switch time (nil = no fork, 0 = already on amsterdam)
|
||||
MLDSAPrecompileTime *uint64 `json:"mldsaPrecompileTime,omitempty"` // ML-DSA precompile switch time (nil = disabled)
|
||||
VerkleTime *uint64 `json:"verkleTime,omitempty"` // Verkle switch time (nil = no fork, 0 = already on verkle)
|
||||
|
||||
// TerminalTotalDifficulty is the amount of total difficulty reached by
|
||||
// the network that triggers the consensus upgrade.
|
||||
|
|
@ -595,6 +596,9 @@ func (c *ChainConfig) String() string {
|
|||
if c.AmsterdamTime != nil {
|
||||
result += fmt.Sprintf(", AmsterdamTime: %v", *c.AmsterdamTime)
|
||||
}
|
||||
if c.MLDSAPrecompileTime != nil {
|
||||
result += fmt.Sprintf(", MLDSAPrecompileTime: %v", *c.MLDSAPrecompileTime)
|
||||
}
|
||||
if c.VerkleTime != nil {
|
||||
result += fmt.Sprintf(", VerkleTime: %v", *c.VerkleTime)
|
||||
}
|
||||
|
|
@ -690,10 +694,13 @@ func (c *ChainConfig) Description() string {
|
|||
if c.AmsterdamTime != nil {
|
||||
banner += fmt.Sprintf(" - Amsterdam: @%-10v blob: (%s)\n", *c.AmsterdamTime, c.BlobScheduleConfig.Amsterdam)
|
||||
}
|
||||
if c.MLDSAPrecompileTime != nil {
|
||||
banner += fmt.Sprintf(" - ML-DSA precompile: @%-10v\n", *c.MLDSAPrecompileTime)
|
||||
}
|
||||
if c.VerkleTime != nil {
|
||||
banner += fmt.Sprintf(" - Verkle: @%-10v blob: (%s)\n", *c.VerkleTime, c.BlobScheduleConfig.Verkle)
|
||||
}
|
||||
banner += fmt.Sprintf("\nAll fork specifications can be found at https://ethereum.github.io/execution-specs/src/ethereum/forks/\n")
|
||||
banner += "\nAll fork specifications can be found at https://ethereum.github.io/execution-specs/src/ethereum/forks/\n"
|
||||
return banner
|
||||
}
|
||||
|
||||
|
|
@ -866,6 +873,11 @@ func (c *ChainConfig) IsAmsterdam(num *big.Int, time uint64) bool {
|
|||
return c.IsLondon(num) && isTimestampForked(c.AmsterdamTime, time)
|
||||
}
|
||||
|
||||
// IsMLDSAPrecompile returns whether the ML-DSA precompile is enabled at the given time.
|
||||
func (c *ChainConfig) IsMLDSAPrecompile(num *big.Int, time uint64) bool {
|
||||
return c.IsLondon(num) && isTimestampForked(c.MLDSAPrecompileTime, time)
|
||||
}
|
||||
|
||||
// IsVerkle returns whether time is either equal to the Verkle fork time or greater.
|
||||
func (c *ChainConfig) IsVerkle(num *big.Int, time uint64) bool {
|
||||
return c.IsLondon(num) && isTimestampForked(c.VerkleTime, time)
|
||||
|
|
@ -1125,6 +1137,9 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, headNumber *big.Int,
|
|||
if isForkTimestampIncompatible(c.AmsterdamTime, newcfg.AmsterdamTime, headTimestamp) {
|
||||
return newTimestampCompatError("Amsterdam fork timestamp", c.AmsterdamTime, newcfg.AmsterdamTime)
|
||||
}
|
||||
if isForkTimestampIncompatible(c.MLDSAPrecompileTime, newcfg.MLDSAPrecompileTime, headTimestamp) {
|
||||
return newTimestampCompatError("ML-DSA precompile timestamp", c.MLDSAPrecompileTime, newcfg.MLDSAPrecompileTime)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -1380,7 +1395,7 @@ type Rules struct {
|
|||
IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool
|
||||
IsBerlin, IsLondon bool
|
||||
IsMerge, IsShanghai, IsCancun, IsPrague, IsOsaka bool
|
||||
IsAmsterdam, IsVerkle bool
|
||||
IsAmsterdam, IsMLDSAPrecompile, IsVerkle bool
|
||||
}
|
||||
|
||||
// Rules ensures c's ChainID is not nil.
|
||||
|
|
@ -1389,24 +1404,25 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules
|
|||
isMerge = isMerge && c.IsLondon(num)
|
||||
isVerkle := isMerge && c.IsVerkle(num, timestamp)
|
||||
return Rules{
|
||||
IsHomestead: c.IsHomestead(num),
|
||||
IsEIP150: c.IsEIP150(num),
|
||||
IsEIP155: c.IsEIP155(num),
|
||||
IsEIP158: c.IsEIP158(num),
|
||||
IsByzantium: c.IsByzantium(num),
|
||||
IsConstantinople: c.IsConstantinople(num),
|
||||
IsPetersburg: c.IsPetersburg(num),
|
||||
IsIstanbul: c.IsIstanbul(num),
|
||||
IsBerlin: c.IsBerlin(num),
|
||||
IsEIP2929: c.IsBerlin(num) && !isVerkle,
|
||||
IsLondon: c.IsLondon(num),
|
||||
IsMerge: isMerge,
|
||||
IsShanghai: isMerge && c.IsShanghai(num, timestamp),
|
||||
IsCancun: isMerge && c.IsCancun(num, timestamp),
|
||||
IsPrague: isMerge && c.IsPrague(num, timestamp),
|
||||
IsOsaka: isMerge && c.IsOsaka(num, timestamp),
|
||||
IsAmsterdam: isMerge && c.IsAmsterdam(num, timestamp),
|
||||
IsVerkle: isVerkle,
|
||||
IsEIP4762: isVerkle,
|
||||
IsHomestead: c.IsHomestead(num),
|
||||
IsEIP150: c.IsEIP150(num),
|
||||
IsEIP155: c.IsEIP155(num),
|
||||
IsEIP158: c.IsEIP158(num),
|
||||
IsByzantium: c.IsByzantium(num),
|
||||
IsConstantinople: c.IsConstantinople(num),
|
||||
IsPetersburg: c.IsPetersburg(num),
|
||||
IsIstanbul: c.IsIstanbul(num),
|
||||
IsBerlin: c.IsBerlin(num),
|
||||
IsEIP2929: c.IsBerlin(num) && !isVerkle,
|
||||
IsLondon: c.IsLondon(num),
|
||||
IsMerge: isMerge,
|
||||
IsShanghai: isMerge && c.IsShanghai(num, timestamp),
|
||||
IsCancun: isMerge && c.IsCancun(num, timestamp),
|
||||
IsPrague: isMerge && c.IsPrague(num, timestamp),
|
||||
IsOsaka: isMerge && c.IsOsaka(num, timestamp),
|
||||
IsAmsterdam: isMerge && c.IsAmsterdam(num, timestamp),
|
||||
IsMLDSAPrecompile: c.IsMLDSAPrecompile(num, timestamp),
|
||||
IsVerkle: isVerkle,
|
||||
IsEIP4762: isVerkle,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,6 +110,17 @@ func TestCheckCompatible(t *testing.T) {
|
|||
RewindToTime: 9,
|
||||
},
|
||||
},
|
||||
{
|
||||
stored: &ChainConfig{MLDSAPrecompileTime: newUint64(10)},
|
||||
new: &ChainConfig{MLDSAPrecompileTime: newUint64(20)},
|
||||
headTimestamp: 25,
|
||||
wantErr: &ConfigCompatError{
|
||||
What: "ML-DSA precompile timestamp",
|
||||
StoredTime: newUint64(10),
|
||||
NewTime: newUint64(20),
|
||||
RewindToTime: 9,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
|
@ -137,6 +148,18 @@ func TestConfigRules(t *testing.T) {
|
|||
if r := c.Rules(big.NewInt(0), true, stamp); !r.IsShanghai {
|
||||
t.Errorf("expected %v to be shanghai", stamp)
|
||||
}
|
||||
|
||||
mldsaTime := uint64(700)
|
||||
c = &ChainConfig{
|
||||
LondonBlock: new(big.Int),
|
||||
MLDSAPrecompileTime: &mldsaTime,
|
||||
}
|
||||
if r := c.Rules(big.NewInt(0), false, mldsaTime-1); r.IsMLDSAPrecompile {
|
||||
t.Errorf("expected ML-DSA precompile to stay disabled before timestamp")
|
||||
}
|
||||
if r := c.Rules(big.NewInt(0), false, mldsaTime); !r.IsMLDSAPrecompile {
|
||||
t.Errorf("expected ML-DSA precompile to activate without merge gating")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimestampCompatError(t *testing.T) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue