Skip to content

Commit

Permalink
Implement PIP-33: Napoli Hardfork (#8975)
Browse files Browse the repository at this point in the history
Initial support of the upcoming Napoli hard fork on Polygon – see
[PIP-33](https://forum.polygon.technology/t/pip-33-napoli-upgrade). Per
[PIP-31](https://github.com/maticnetwork/Polygon-Improvement-Proposals/blob/main/PIPs/PIP-31.md),
it parallels the
[Cancun](https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/cancun.md)
upgrade of Ethereum, but does not include
[EIP-4788](https://eips.ethereum.org/EIPS/eip-4788),
[EIP-4844](https://eips.ethereum.org/EIPS/eip-4844),
[EIP-7516](https://eips.ethereum.org/EIPS/eip-7516). In other words,
Napoli includes [EIP-1153](https://eips.ethereum.org/EIPS/eip-1153),
[EIP-5656](https://eips.ethereum.org/EIPS/eip-5656),
[EIP-6780](https://eips.ethereum.org/EIPS/eip-6780) from Cancun.

This PR implements
[PIP-31](https://github.com/maticnetwork/Polygon-Improvement-Proposals/blob/main/PIPs/PIP-31.md),
[PIP-16: Transaction Dependency
Data](https://github.com/maticnetwork/Polygon-Improvement-Proposals/blob/main/PIPs/PIP-16.md)
(by merging `ParallelUniverseBlock` into `NapoliBlock`; the bulk of
PIP-16 was implemented in PR #8037), and [PIP-27: Precompiled for
secp256r1 Curve
Support](https://github.com/maticnetwork/Polygon-Improvement-Proposals/blob/main/PIPs/PIP-27.md)
([EIP-7212](https://eips.ethereum.org/EIPS/eip-7212); see also
maticnetwork/bor#1069 &
ethereum/go-ethereum#27540).

---------

Co-authored-by: Anshal Shukla <shukla.anshal85@gmail.com>
  • Loading branch information
yperbasis and anshalshukla authored Jan 17, 2024
1 parent 8f6fe88 commit b38e17e
Show file tree
Hide file tree
Showing 15 changed files with 231 additions and 51 deletions.
9 changes: 7 additions & 2 deletions core/forkid/forkid.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,13 @@ func GatherForks(config *chain.Config, genesisTime uint64) (heightForks []uint64
heightForks = append(heightForks, *config.Aura.PosdaoTransition)
}

if config.Bor != nil && config.Bor.GetAgraBlock() != nil {
heightForks = append(heightForks, config.Bor.GetAgraBlock().Uint64())
if config.Bor != nil {
if config.Bor.GetAgraBlock() != nil {
heightForks = append(heightForks, config.Bor.GetAgraBlock().Uint64())
}
if config.Bor.GetNapoliBlock() != nil {
heightForks = append(heightForks, config.Bor.GetNapoliBlock().Uint64())
}
}

// Sort the fork block numbers & times to permit chronological XOR
Expand Down
2 changes: 1 addition & 1 deletion core/types/block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import (

// the following 2 functions are replica for the test
// This is a replica of `bor.GetValidatorBytes` function
// This was needed because currently, `IsParallelUniverse` will always return false.
// This was needed because currently, `IsNapoli` will always return false.
func GetValidatorBytesTest(h *Header) []byte {
if len(h.Extra) < ExtraVanityLength+ExtraSealLength {
log.Error("length of extra is less than vanity and seal")
Expand Down
57 changes: 55 additions & 2 deletions core/vm/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,21 @@ import (
"crypto/sha256"
"encoding/binary"
"errors"
"github.com/ledgerwatch/erigon-lib/crypto/blake2b"
"math/big"

"github.com/holiman/uint256"

"github.com/ledgerwatch/erigon-lib/chain"
libcommon "github.com/ledgerwatch/erigon-lib/common"
"github.com/ledgerwatch/erigon-lib/crypto/blake2b"
libkzg "github.com/ledgerwatch/erigon-lib/crypto/kzg"

"github.com/ledgerwatch/erigon/common"
"github.com/ledgerwatch/erigon/common/math"
"github.com/ledgerwatch/erigon/crypto"
"github.com/ledgerwatch/erigon/crypto/bls12381"
"github.com/ledgerwatch/erigon/crypto/bn256"

"github.com/ledgerwatch/erigon/crypto/secp256r1"
"github.com/ledgerwatch/erigon/params"

//lint:ignore SA1019 Needed for precompile
Expand Down Expand Up @@ -112,6 +112,19 @@ var PrecompiledContractsCancun = map[libcommon.Address]PrecompiledContract{
libcommon.BytesToAddress([]byte{0x0a}): &pointEvaluation{},
}

var PrecompiledContractsNapoli = map[libcommon.Address]PrecompiledContract{
libcommon.BytesToAddress([]byte{0x01}): &ecrecover{},
libcommon.BytesToAddress([]byte{0x02}): &sha256hash{},
libcommon.BytesToAddress([]byte{0x03}): &ripemd160hash{},
libcommon.BytesToAddress([]byte{0x04}): &dataCopy{},
libcommon.BytesToAddress([]byte{0x05}): &bigModExp{eip2565: true},
libcommon.BytesToAddress([]byte{0x06}): &bn256AddIstanbul{},
libcommon.BytesToAddress([]byte{0x07}): &bn256ScalarMulIstanbul{},
libcommon.BytesToAddress([]byte{0x08}): &bn256PairingIstanbul{},
libcommon.BytesToAddress([]byte{0x09}): &blake2F{},
libcommon.BytesToAddress([]byte{0x01, 0x00}): &p256Verify{},
}

// PrecompiledContractsBLS contains the set of pre-compiled Ethereum
// contracts specified in EIP-2537. These are exported for testing purposes.
var PrecompiledContractsBLS = map[libcommon.Address]PrecompiledContract{
Expand All @@ -127,6 +140,7 @@ var PrecompiledContractsBLS = map[libcommon.Address]PrecompiledContract{
}

var (
PrecompiledAddressesNapoli []libcommon.Address
PrecompiledAddressesCancun []libcommon.Address
PrecompiledAddressesBerlin []libcommon.Address
PrecompiledAddressesIstanbul []libcommon.Address
Expand All @@ -150,11 +164,16 @@ func init() {
for k := range PrecompiledContractsCancun {
PrecompiledAddressesCancun = append(PrecompiledAddressesCancun, k)
}
for k := range PrecompiledContractsNapoli {
PrecompiledAddressesNapoli = append(PrecompiledAddressesNapoli, k)
}
}

// ActivePrecompiles returns the precompiles enabled with the current configuration.
func ActivePrecompiles(rules *chain.Rules) []libcommon.Address {
switch {
case rules.IsNapoli:
return PrecompiledAddressesNapoli
case rules.IsCancun:
return PrecompiledAddressesCancun
case rules.IsBerlin:
Expand Down Expand Up @@ -1098,3 +1117,37 @@ func (c *pointEvaluation) RequiredGas(input []byte) uint64 {
func (c *pointEvaluation) Run(input []byte) ([]byte, error) {
return libkzg.PointEvaluationPrecompile(input)
}

// P256VERIFY (secp256r1 signature verification)
// implemented as a native contract
type p256Verify struct{}

// RequiredGas returns the gas required to execute the precompiled contract
func (c *p256Verify) RequiredGas(input []byte) uint64 {
return params.P256VerifyGas
}

// Run executes the precompiled contract with given 160 bytes of param, returning the output and the used gas
func (c *p256Verify) Run(input []byte) ([]byte, error) {
// Required input length is 160 bytes
const p256VerifyInputLength = 160
// Check the input length
if len(input) != p256VerifyInputLength {
// Input length is invalid
return nil, nil
}

// Extract the hash, r, s, x, y from the input
hash := input[0:32]
r, s := new(big.Int).SetBytes(input[32:64]), new(big.Int).SetBytes(input[64:96])
x, y := new(big.Int).SetBytes(input[96:128]), new(big.Int).SetBytes(input[128:160])

// Verify the secp256r1 signature
if secp256r1.Verify(hash, r, s, x, y) {
// Signature is valid
return common.LeftPadBytes(big1.Bytes(), 32), nil
} else {
// Signature is invalid
return nil, nil
}
}
57 changes: 37 additions & 20 deletions core/vm/contracts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,26 +48,27 @@ type precompiledFailureTest struct {
// allPrecompiles does not map to the actual set of precompiles, as it also contains
// repriced versions of precompiles at certain slots
var allPrecompiles = map[libcommon.Address]PrecompiledContract{
libcommon.BytesToAddress([]byte{1}): &ecrecover{},
libcommon.BytesToAddress([]byte{2}): &sha256hash{},
libcommon.BytesToAddress([]byte{3}): &ripemd160hash{},
libcommon.BytesToAddress([]byte{4}): &dataCopy{},
libcommon.BytesToAddress([]byte{5}): &bigModExp{eip2565: false},
libcommon.BytesToAddress([]byte{0xf5}): &bigModExp{eip2565: true},
libcommon.BytesToAddress([]byte{6}): &bn256AddIstanbul{},
libcommon.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{},
libcommon.BytesToAddress([]byte{8}): &bn256PairingIstanbul{},
libcommon.BytesToAddress([]byte{9}): &blake2F{},
libcommon.BytesToAddress([]byte{10}): &bls12381G1Add{},
libcommon.BytesToAddress([]byte{11}): &bls12381G1Mul{},
libcommon.BytesToAddress([]byte{12}): &bls12381G1MultiExp{},
libcommon.BytesToAddress([]byte{13}): &bls12381G2Add{},
libcommon.BytesToAddress([]byte{14}): &bls12381G2Mul{},
libcommon.BytesToAddress([]byte{15}): &bls12381G2MultiExp{},
libcommon.BytesToAddress([]byte{16}): &bls12381Pairing{},
libcommon.BytesToAddress([]byte{17}): &bls12381MapG1{},
libcommon.BytesToAddress([]byte{18}): &bls12381MapG2{},
libcommon.BytesToAddress([]byte{20}): &pointEvaluation{},
libcommon.BytesToAddress([]byte{1}): &ecrecover{},
libcommon.BytesToAddress([]byte{2}): &sha256hash{},
libcommon.BytesToAddress([]byte{3}): &ripemd160hash{},
libcommon.BytesToAddress([]byte{4}): &dataCopy{},
libcommon.BytesToAddress([]byte{5}): &bigModExp{eip2565: false},
libcommon.BytesToAddress([]byte{0xf5}): &bigModExp{eip2565: true},
libcommon.BytesToAddress([]byte{6}): &bn256AddIstanbul{},
libcommon.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{},
libcommon.BytesToAddress([]byte{8}): &bn256PairingIstanbul{},
libcommon.BytesToAddress([]byte{9}): &blake2F{},
libcommon.BytesToAddress([]byte{10}): &bls12381G1Add{},
libcommon.BytesToAddress([]byte{11}): &bls12381G1Mul{},
libcommon.BytesToAddress([]byte{12}): &bls12381G1MultiExp{},
libcommon.BytesToAddress([]byte{13}): &bls12381G2Add{},
libcommon.BytesToAddress([]byte{14}): &bls12381G2Mul{},
libcommon.BytesToAddress([]byte{15}): &bls12381G2MultiExp{},
libcommon.BytesToAddress([]byte{16}): &bls12381Pairing{},
libcommon.BytesToAddress([]byte{17}): &bls12381MapG1{},
libcommon.BytesToAddress([]byte{18}): &bls12381MapG2{},
libcommon.BytesToAddress([]byte{20}): &pointEvaluation{},
libcommon.BytesToAddress([]byte{0x01, 0x00}): &p256Verify{},
}

// EIP-152 test vectors
Expand Down Expand Up @@ -400,3 +401,19 @@ func BenchmarkPrecompiledBLS12381G2MultiExpWorstCase(b *testing.B) {
}
benchmarkPrecompiled(b, "0f", testcase)
}

// Benchmarks the sample inputs from the P256VERIFY precompile.
func BenchmarkPrecompiledP256Verify(b *testing.B) {
testcase := precompiledTest{
Input: "4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e",
Expected: "0000000000000000000000000000000000000000000000000000000000000001",
Name: "p256Verify",
}
benchmarkPrecompiled(b, "100", testcase)
}

func TestPrecompiledP256Verify(t *testing.T) {
t.Parallel()

testJson("p256Verify", "100", t)
}
2 changes: 2 additions & 0 deletions core/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ var emptyCodeHash = crypto.Keccak256Hash(nil)
func (evm *EVM) precompile(addr libcommon.Address) (PrecompiledContract, bool) {
var precompiles map[libcommon.Address]PrecompiledContract
switch {
case evm.chainRules.IsNapoli:
precompiles = PrecompiledContractsNapoli
case evm.chainRules.IsCancun:
precompiles = PrecompiledContractsCancun
case evm.chainRules.IsBerlin:
Expand Down
2 changes: 2 additions & 0 deletions core/vm/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
jt = &pragueInstructionSet
case evm.ChainRules().IsCancun:
jt = &cancunInstructionSet
case evm.ChainRules().IsNapoli:
jt = &napoliInstructionSet
case evm.ChainRules().IsShanghai:
jt = &shanghaiInstructionSet
case evm.ChainRules().IsLondon:
Expand Down
11 changes: 9 additions & 2 deletions core/vm/jump_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ var (
berlinInstructionSet = newBerlinInstructionSet()
londonInstructionSet = newLondonInstructionSet()
shanghaiInstructionSet = newShanghaiInstructionSet()
napoliInstructionSet = newNapoliInstructionSet()
cancunInstructionSet = newCancunInstructionSet()
pragueInstructionSet = newPragueInstructionSet()
)
Expand Down Expand Up @@ -99,12 +100,18 @@ func newPragueInstructionSet() JumpTable {
// constantinople, istanbul, petersburg, berlin, london, paris, shanghai,
// and cancun instructions.
func newCancunInstructionSet() JumpTable {
instructionSet := newNapoliInstructionSet()
enable4844(&instructionSet) // BLOBHASH opcode
enable7516(&instructionSet) // BLOBBASEFEE opcode
validateAndFillMaxStack(&instructionSet)
return instructionSet
}

func newNapoliInstructionSet() JumpTable {
instructionSet := newShanghaiInstructionSet()
enable1153(&instructionSet) // Transient storage opcodes
enable4844(&instructionSet) // BLOBHASH opcode
enable5656(&instructionSet) // MCOPY opcode
enable6780(&instructionSet) // SELFDESTRUCT only in same transaction
enable7516(&instructionSet) // BLOBBASEFEE opcode
validateAndFillMaxStack(&instructionSet)
return instructionSet
}
Expand Down
37 changes: 37 additions & 0 deletions core/vm/testdata/precompiles/p256Verify.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[
{
"Input": "4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e",
"Expected": "0000000000000000000000000000000000000000000000000000000000000001",
"Gas": 3450,
"Name": "CallP256Verify",
"NoBenchmark": false
},
{
"Input": "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9414de3726ee4d237b410c1d85ebcb05553dc578561d9f7942b7250795beb9b9027b657067322fc00ab35263fde0acabf998cd9fcf1282df9555f85dba7bdbbe2dc90f74c9e210bc3e0c60aeaa03729c9e6acde4a048ee58fd2e466c1e7b0374e606b8c22ad2985df7d792ff344f03ce94a079da801006b13640bc5af7932a7b9",
"Expected": "0000000000000000000000000000000000000000000000000000000000000001",
"Gas": 3450,
"Name": "CallP256Verify",
"NoBenchmark": false
},
{
"Input": "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9b35d6a4f7f6fc5620c97d4287696f5174b3d37fa537b74b5fc26997ba79c725d62fe5e5fe6da76eec924e822c5ef853ede6c17069a9e9133a38f87d61599f68e7d5f3c812a255436846ee84a262b79ec4d0783afccf2433deabdca9ecf62bef5ff24e90988c7f139d378549c3a8bc6c94e6a1c911c1e02e6f48ed65aaf3d296e",
"Expected": "0000000000000000000000000000000000000000000000000000000000000001",
"Gas": 3450,
"Name": "CallP256Verify",
"NoBenchmark": false
},
{
"Input": "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9c29c3df6ce3431b6f030b1b68b1589508ad9d1a149830489c638653aa4b08af93f6e86a9a7643403b6f5c593410d9f7234a8cd27309bce90447073ce17476850615ff147863bc8652be1e369444f90bbc5f9df05a26362e609f73ab1f1839fe3cd34fd2ae672c110671d49115825fc56b5148321aabe5ba39f2b46f71149cff9",
"Expected": "",
"Gas": 3450,
"Name": "CallP256Verify",
"NoBenchmark": false
},
{
"Input": "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
"Expected": "",
"Gas": 3450,
"Name": "CallP256Verify",
"NoBenchmark": false
}
]
26 changes: 26 additions & 0 deletions crypto/secp256r1/publickey.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package secp256r1

import (
"crypto/ecdsa"
"crypto/elliptic"
"math/big"
)

// Generates approptiate public key format from given coordinates
func newPublicKey(x, y *big.Int) *ecdsa.PublicKey {
// Check if the given coordinates are valid
if x == nil || y == nil || !elliptic.P256().IsOnCurve(x, y) {
return nil
}

// Check if the given coordinates are the reference point (infinity)
if x.Sign() == 0 && y.Sign() == 0 {
return nil
}

return &ecdsa.PublicKey{
Curve: elliptic.P256(),
X: x,
Y: y,
}
}
21 changes: 21 additions & 0 deletions crypto/secp256r1/verifier.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package secp256r1

import (
"crypto/ecdsa"
"math/big"
)

// Verifies the given signature (r, s) for the given hash and public key (x, y).
func Verify(hash []byte, r, s, x, y *big.Int) bool {
// Create the public key format
publicKey := newPublicKey(x, y)

// Check if they are invalid public key coordinates
if publicKey == nil {
return false
}

// Verify the signature with the public key,
// then return true if it's valid, false otherwise
return ecdsa.Verify(publicKey, hash, r, s)
}
20 changes: 15 additions & 5 deletions erigon-lib/chain/chain_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ type BorConfig interface {
fmt.Stringer
IsAgra(num uint64) bool
GetAgraBlock() *big.Int
IsNapoli(num uint64) bool
GetNapoliBlock() *big.Int
}

func (c *Config) String() string {
Expand Down Expand Up @@ -214,6 +216,11 @@ func (c *Config) IsAgra(num uint64) bool {
return (c != nil) && (c.Bor != nil) && c.Bor.IsAgra(num)
}

// Refer to https://forum.polygon.technology/t/pip-33-napoli-upgrade
func (c *Config) IsNapoli(num uint64) bool {
return (c != nil) && (c.Bor != nil) && c.Bor.IsNapoli(num)
}

// IsCancun returns whether time is either equal to the Cancun fork time or greater.
func (c *Config) IsCancun(time uint64) bool {
return isForked(c.CancunTime, time)
Expand Down Expand Up @@ -484,11 +491,13 @@ func borKeyValueConfigHelper[T uint64 | common.Address](field map[string]T, numb
// Rules is a one time interface meaning that it shouldn't be used in between transition
// phases.
type Rules struct {
ChainID *big.Int
IsHomestead, IsTangerineWhistle, IsSpuriousDragon bool
IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool
IsBerlin, IsLondon, IsShanghai, IsCancun, IsPrague bool
IsAura bool
ChainID *big.Int
IsHomestead, IsTangerineWhistle, IsSpuriousDragon bool
IsByzantium, IsConstantinople, IsPetersburg bool
IsIstanbul, IsBerlin, IsLondon, IsShanghai bool
IsCancun, IsNapoli bool
IsPrague bool
IsAura bool
}

// Rules ensures c's ChainID is not nil and returns a new Rules instance
Expand All @@ -511,6 +520,7 @@ func (c *Config) Rules(num uint64, time uint64) *Rules {
IsLondon: c.IsLondon(num),
IsShanghai: c.IsShanghai(time) || c.IsAgra(num),
IsCancun: c.IsCancun(time),
IsNapoli: c.IsNapoli(num),
IsPrague: c.IsPrague(time),
IsAura: c.Aura != nil,
}
Expand Down
2 changes: 1 addition & 1 deletion erigon-lib/txpool/pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -945,7 +945,7 @@ func (p *TxPool) isShanghai() bool {
}

func (p *TxPool) isAgra() bool {
// once this flag has been set for the first time we no longer need to check the timestamp
// once this flag has been set for the first time we no longer need to check the block
set := p.isPostAgra.Load()
if set {
return true
Expand Down
Loading

0 comments on commit b38e17e

Please sign in to comment.