Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 63 additions & 9 deletions core/vm/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,13 +350,21 @@ var PrecompiledContractsOsaka = PrecompiledContracts{
common.BytesToAddress([]byte{0x1, 0x00}): &p256Verify{eip7951: true},
}

var PrecompiledContractsMendel = func() PrecompiledContracts {
precompiles := maps.Clone(PrecompiledContractsOsaka)
precompiles[common.BytesToAddress([]byte{0x66})] = &blsSignatureVerifyMendel{}
precompiles[common.BytesToAddress([]byte{0x67})] = &cometBFTLightBlockValidateHertzMendel{}
return precompiles
}()

// PrecompiledContractsP256Verify contains the precompiled Ethereum
// contract specified in EIP-7212. This is exported for testing purposes.
var PrecompiledContractsP256Verify = PrecompiledContracts{
common.BytesToAddress([]byte{0x1, 0x00}): &p256Verify{},
}

var (
PrecompiledAddressesMendel []common.Address
PrecompiledAddressesOsaka []common.Address
PrecompiledAddressesPrague []common.Address
PrecompiledAddressesHaber []common.Address
Expand Down Expand Up @@ -417,6 +425,9 @@ func init() {
for k := range PrecompiledContractsPrague {
PrecompiledAddressesPrague = append(PrecompiledAddressesPrague, k)
}
for k := range PrecompiledContractsMendel {
PrecompiledAddressesMendel = append(PrecompiledAddressesMendel, k)
}
for k := range PrecompiledContractsOsaka {
PrecompiledAddressesOsaka = append(PrecompiledAddressesOsaka, k)
}
Expand All @@ -426,6 +437,8 @@ func activePrecompiledContracts(rules params.Rules) PrecompiledContracts {
switch {
case rules.IsVerkle:
return PrecompiledContractsVerkle
case rules.IsMendel:
return PrecompiledContractsMendel
case rules.IsOsaka:
return PrecompiledContractsOsaka
case rules.IsPrague:
Expand Down Expand Up @@ -467,6 +480,8 @@ func ActivePrecompiledContracts(rules params.Rules) PrecompiledContracts {
// ActivePrecompiles returns the precompile addresses enabled with the current configuration.
func ActivePrecompiles(rules params.Rules) []common.Address {
switch {
case rules.IsMendel:
return PrecompiledAddressesMendel
case rules.IsOsaka:
return PrecompiledAddressesOsaka
case rules.IsPrague:
Expand Down Expand Up @@ -1602,6 +1617,10 @@ func (c *bls12381MapG2) Name() string {
// blsSignatureVerify implements bls signature verification precompile.
type blsSignatureVerify struct{}

type blsSignatureVerifyMendel struct {
blsSignatureVerify
}

const (
msgHashLength = uint64(32)
signatureLength = uint64(96)
Expand All @@ -1620,16 +1639,13 @@ func (c *blsSignatureVerify) RequiredGas(input []byte) uint64 {
return params.BlsSignatureVerifyBaseGas + pubKeyNumber*params.BlsSignatureVerifyPerKeyGas
}

// Run input:
// msg | signature | [{bls pubkey}] |
// 32 bytes | 96 bytes | [{48 bytes}] |
func (c *blsSignatureVerify) Run(input []byte) ([]byte, error) {
func parseBlsSignatureVerifyInput(input []byte, requireUniquePubKeys bool) ([32]byte, bls.Signature, []bls.PublicKey, error) {
msgAndSigLength := msgHashLength + signatureLength
inputLen := uint64(len(input))
if inputLen <= msgAndSigLength ||
(inputLen-msgAndSigLength)%singleBlsPubkeyLength != 0 {
log.Debug("blsSignatureVerify input size wrong", "inputLen", inputLen)
return nil, ErrExecutionReverted
return [32]byte{}, nil, nil, ErrExecutionReverted
}

var msg [32]byte
Expand All @@ -1640,38 +1656,76 @@ func (c *blsSignatureVerify) Run(input []byte) ([]byte, error) {
sig, err := bls.SignatureFromBytes(signatureBytes)
if err != nil {
log.Debug("blsSignatureVerify invalid signature", "err", err)
return nil, ErrExecutionReverted
return [32]byte{}, nil, nil, ErrExecutionReverted
}

pubKeyNumber := (inputLen - msgAndSigLength) / singleBlsPubkeyLength
pubKeys := make([]bls.PublicKey, pubKeyNumber)
var seenPubKeys map[string]struct{}
if requireUniquePubKeys {
seenPubKeys = make(map[string]struct{}, pubKeyNumber)
}
for i := uint64(0); i < pubKeyNumber; i++ {
pubKeyBytes := getData(input, msgAndSigLength+i*singleBlsPubkeyLength, singleBlsPubkeyLength)
pubKey, err := bls.PublicKeyFromBytes(pubKeyBytes)
if err != nil {
log.Debug("blsSignatureVerify invalid pubKey", "err", err)
return nil, ErrExecutionReverted
return [32]byte{}, nil, nil, ErrExecutionReverted
}
if requireUniquePubKeys {
key := string(pubKeyBytes)
if _, ok := seenPubKeys[key]; ok {
return msg, sig, nil, nil
}
seenPubKeys[key] = struct{}{}
}
pubKeys[i] = pubKey
}

if pubKeyNumber > 1 {
return msg, sig, pubKeys, nil
}

func runBlsSignatureVerify(input []byte, requireUniquePubKeys bool) ([]byte, error) {
msg, sig, pubKeys, err := parseBlsSignatureVerifyInput(input, requireUniquePubKeys)
if err != nil {
return nil, err
}
if pubKeys == nil {
return common.Big0.Bytes(), nil
}

if len(pubKeys) > 1 {
if !sig.FastAggregateVerify(pubKeys, msg) {
return common.Big0.Bytes(), nil
}
} else {
if !sig.Verify(pubKeys[0], msgBytes) {
if !sig.Verify(pubKeys[0], msg[:]) {
return common.Big0.Bytes(), nil
}
}

return common.Big1.Bytes(), nil
}

// Run input:
// msg | signature | [{bls pubkey}] |
// 32 bytes | 96 bytes | [{48 bytes}] |
func (c *blsSignatureVerify) Run(input []byte) ([]byte, error) {
return runBlsSignatureVerify(input, false)
}

func (c *blsSignatureVerify) Name() string {
return "BLS_SIGNATURE_VERIFY"
}

func (c *blsSignatureVerifyMendel) Run(input []byte) ([]byte, error) {
return runBlsSignatureVerify(input, true)
}

func (c *blsSignatureVerifyMendel) Name() string {
return "BLS_SIGNATURE_VERIFY_MENDEL"
}

// kzgPointEvaluation implements the EIP-4844 point evaluation precompile.
type kzgPointEvaluation struct{}

Expand Down
29 changes: 25 additions & 4 deletions core/vm/contracts_lightclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"net/url"
"strings"

"github.com/cometbft/cometbft/types"
"github.com/cosmos/iavl"
"github.com/tendermint/tendermint/crypto/merkle"
"github.com/tendermint/tendermint/crypto/secp256k1"
Expand Down Expand Up @@ -391,14 +392,22 @@ func (c *cometBFTLightBlockValidate) RequiredGas(input []byte) uint64 {
return params.CometBFTLightBlockValidateGas
}

func (c *cometBFTLightBlockValidate) run(input []byte, isHertz bool) (result []byte, err error) {
func (c *cometBFTLightBlockValidate) run(input []byte, isHertz bool, requireUniqueValidators bool) (result []byte, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("internal error: %v\n", r)
}
}()

cs, block, err := v2.DecodeLightBlockValidationInput(input)
var (
cs *v2.ConsensusState
block *types.LightBlock
)
if requireUniqueValidators {
cs, block, err = v2.DecodeLightBlockValidationInputWithValidation(input)
} else {
cs, block, err = v2.DecodeLightBlockValidationInput(input)
}
if err != nil {
return nil, err
}
Expand All @@ -418,7 +427,7 @@ func (c *cometBFTLightBlockValidate) run(input []byte, isHertz bool) (result []b
}

func (c *cometBFTLightBlockValidate) Run(input []byte) (result []byte, err error) {
return c.run(input, false)
return c.run(input, false, false)
}

func (c *cometBFTLightBlockValidate) Name() string {
Expand All @@ -430,13 +439,25 @@ type cometBFTLightBlockValidateHertz struct {
}

func (c *cometBFTLightBlockValidateHertz) Run(input []byte) (result []byte, err error) {
return c.run(input, true)
return c.run(input, true, false)
}

func (c *cometBFTLightBlockValidateHertz) Name() string {
return "COMET_BFT_LIGHT_BLOCK_VALIDATE_HERTZ"
}

type cometBFTLightBlockValidateHertzMendel struct {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about
cometBFTLightBlockValidateHertzMendel --> cometBFTLightBlockValidateMendel?
because when Mendel enabled, Hertz must be already enalbed

cometBFTLightBlockValidate
}

func (c *cometBFTLightBlockValidateHertzMendel) Run(input []byte) (result []byte, err error) {
return c.run(input, true, true)
}

func (c *cometBFTLightBlockValidateHertzMendel) Name() string {
return "COMET_BFT_LIGHT_BLOCK_VALIDATE_HERTZ_MENDEL"
}

// secp256k1SignatureRecover implemented as a native contract.
type secp256k1SignatureRecover struct{}

Expand Down
37 changes: 37 additions & 0 deletions core/vm/contracts_lightclient_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@ import (
"encoding/hex"
"testing"

"github.com/cometbft/cometbft/types"
"github.com/cosmos/iavl"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/crypto/merkle"
cmn "github.com/tendermint/tendermint/libs/common"

v1 "github.com/ethereum/go-ethereum/core/vm/lightclient/v1"
v2 "github.com/ethereum/go-ethereum/core/vm/lightclient/v2"
)

const (
Expand Down Expand Up @@ -374,6 +378,39 @@ func TestCometBFTLightBlockValidateHertz(t *testing.T) {
require.Equal(t, expectOutputStr, hex.EncodeToString(res))
}

func TestCometBFTLightBlockValidateRejectsDuplicateTrustedValidatorsAtMendel(t *testing.T) {
inputStr := "000000000000000000000000000000000000000000000000000000000000018c677265656e6669656c645f393030302d3132310000000000000000000000000000000000000000013c350cd55b99dc6c2b7da9bef5410fbfb869fede858e7b95bf7ca294e228bb40e33f6e876d63791ebd05ff617a1b4f4ad1aa2ce65e3c3a9cdfb33e0ffa7e8423000000000098968015154514f68ce65a0d9eecc578c0ab12da0a2a28a0805521b5b7ae56eb3fb24555efbfe59e1622bfe9f7be8c9022e9b3f2442739c1ce870b9adee169afe60f674edd7c86451c5363d89052fde8351895eeea166ce5373c36e31b518ed191d0c599aa0f5b0000000000989680432f6c4908a9aa5f3444421f466b11645235c99b831b2a2de9e504d7ea299e52a202ce529808618eb3bfc0addf13d8c5f2df821d81e18f9bc61583510b322d067d46323b0a572635c06a049c0a2a929e3c8184a50cf6a8b95708c25834ade456f399015a0000000000989680864cb9828254d712f8e59b164fc6a9402dc4e6c59065e38cff24f5323c8c5da888a0f97e5ee4ba1e11b0674b0a0d06204c1dfa247c370cd4be3e799fc4f6f48d977ac7ca0aeb060adb030a02080b1213677265656e6669656c645f393030302d3132311802220c08b2d7f3a10610e8d2adb3032a480a20ec6ecb5db4ffb17fabe40c60ca7b8441e9c5d77585d0831186f3c37aa16e9c15122408011220a2ab9e1eb9ea52812f413526e424b326aff2f258a56e00d690db9f805b60fe7e32200f40aeff672e8309b7b0aefbb9a1ae3d4299b5c445b7d54e8ff398488467f0053a20e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b85542203c350cd55b99dc6c2b7da9bef5410fbfb869fede858e7b95bf7ca294e228bb404a203c350cd55b99dc6c2b7da9bef5410fbfb869fede858e7b95bf7ca294e228bb405220294d8fbd0b94b767a7eba9840f299a3586da7fe6b5dead3b7eecba193c400f935a20bc50557c12d7392b0d07d75df0b61232d48f86a74fdea6d1485d9be6317d268c6220e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b8556a20e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b85572146699336aa109d1beab3946198c8e59f3b2cbd92f7a4065e3cd89e315ca39d87dee92835b98f8b8ec0861d6d9bb2c60156df5d375b3ceb1fbe71af6a244907d62548a694165caa660fec7a9b4e7b9198191361c71be0b128a0308021a480a20726abd0fdbfb6f779b0483e6e4b4b6f12241f6ea2bf374233ab1a316692b6415122408011220159f10ff15a8b58fc67a92ffd7f33c8cd407d4ce81b04ca79177dfd00ca19a67226808021214050cff76cc632760ba9db796c046004c900967361a0c08b3d7f3a10610808cadba03224080713027ffb776a702d78fd0406205c629ba473e1f8d6af646190f6eb9262cd67d69be90d10e597b91e06d7298eb6fa4b8f1eb7752ebf352a1f51560294548042268080212146699336aa109d1beab3946198c8e59f3b2cbd92f1a0c08b3d7f3a10610b087c1c00322405e2ddb70acfe4904438be3d9f4206c0ace905ac4fc306a42cfc9e86268950a0fbfd6ec5f526d3e41a3ef52bf9f9f358e3cb4c3feac76c762fa3651c1244fe004226808021214c55765fd2d0570e869f6ac22e7f2916a35ea300d1a0c08b3d7f3a10610f0b3d492032240ca17898bd22232fc9374e1188636ee321a396444a5b1a79f7628e4a11f265734b2ab50caf21e8092c55d701248e82b2f011426cb35ba22043b497a6b4661930612a0050aa8010a14050cff76cc632760ba9db796c046004c9009673612220a20e33f6e876d63791ebd05ff617a1b4f4ad1aa2ce65e3c3a9cdfb33e0ffa7e84231880ade2042080a6bbf6ffffffffff012a30a0805521b5b7ae56eb3fb24555efbfe59e1622bfe9f7be8c9022e9b3f2442739c1ce870b9adee169afe60f674edd7c86321415154514f68ce65a0d9eecc578c0ab12da0a2a283a14ee7a2a6a44d427f6949eeb8f12ea9fbb2501da880aa2010a146699336aa109d1beab3946198c8e59f3b2cbd92f12220a20451c5363d89052fde8351895eeea166ce5373c36e31b518ed191d0c599aa0f5b1880ade2042080ade2042a30831b2a2de9e504d7ea299e52a202ce529808618eb3bfc0addf13d8c5f2df821d81e18f9bc61583510b322d067d46323b3214432f6c4908a9aa5f3444421f466b11645235c99b3a14a0a7769429468054e19059af4867da0a495567e50aa2010a14c55765fd2d0570e869f6ac22e7f2916a35ea300d12220a200a572635c06a049c0a2a929e3c8184a50cf6a8b95708c25834ade456f399015a1880ade2042080ade2042a309065e38cff24f5323c8c5da888a0f97e5ee4ba1e11b0674b0a0d06204c1dfa247c370cd4be3e799fc4f6f48d977ac7ca3214864cb9828254d712f8e59b164fc6a9402dc4e6c53a143139916d97df0c589312b89950b6ab9795f34d1a12a8010a14050cff76cc632760ba9db796c046004c9009673612220a20e33f6e876d63791ebd05ff617a1b4f4ad1aa2ce65e3c3a9cdfb33e0ffa7e84231880ade2042080a6bbf6ffffffffff012a30a0805521b5b7ae56eb3fb24555efbfe59e1622bfe9f7be8c9022e9b3f2442739c1ce870b9adee169afe60f674edd7c86321415154514f68ce65a0d9eecc578c0ab12da0a2a283a14ee7a2a6a44d427f6949eeb8f12ea9fbb2501da88"

input, err := hex.DecodeString(inputStr)
require.NoError(t, err)

cs, block, err := v2.DecodeLightBlockValidationInput(input)
require.NoError(t, err)

duplicate := cs.ValidatorSet.Validators[0].Copy()
cs.ValidatorSet = &types.ValidatorSet{Validators: []*types.Validator{duplicate.Copy(), duplicate.Copy(), duplicate.Copy()}}
csBytes, err := cs.EncodeConsensusState()
require.NoError(t, err)

blockProto, err := block.ToProto()
require.NoError(t, err)
blockBytes, err := blockProto.Marshal()
require.NoError(t, err)

mutatedInput := make([]byte, 32)
binary.BigEndian.PutUint64(mutatedInput[24:], uint64(len(csBytes)))
mutatedInput = append(mutatedInput, csBytes...)
mutatedInput = append(mutatedInput, blockBytes...)

mendelPrecompile := ActivePrecompiledContracts(params.Rules{IsOsaka: true, IsMendel: true})[common.BytesToAddress([]byte{0x67})]
_, err = mendelPrecompile.Run(mutatedInput)
require.ErrorContains(t, err, "duplicate validator")

legacyPrecompile := ActivePrecompiledContracts(params.Rules{IsOsaka: true})[common.BytesToAddress([]byte{0x67})]
_, err = legacyPrecompile.Run(mutatedInput)
require.NoError(t, err)
}

func TestSecp256k1SignatureRecover(t *testing.T) {
// local key
{
Expand Down
81 changes: 81 additions & 0 deletions core/vm/contracts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ import (
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params"
"github.com/prysmaticlabs/prysm/v5/crypto/bls"
blscommon "github.com/prysmaticlabs/prysm/v5/crypto/bls/common"
)

// precompiledTest defines the input/output pairs for precompiled contract tests.
Expand Down Expand Up @@ -353,6 +356,84 @@ func TestPrecompiledBLS12381MapG2(t *testing.T) { testJson("blsMapG2", "f10

func TestPrecompiledBlsSignatureVerify(t *testing.T) { testJson("blsSignatureVerify", "66", t) }

func TestActivePrecompiledContractsUsesMendelVariants(t *testing.T) {
rules := params.Rules{IsOsaka: true, IsMendel: true}
precompiles := ActivePrecompiledContracts(rules)

requirePrecompile := func(addr byte) PrecompiledContract {
t.Helper()

precompile, ok := precompiles[common.BytesToAddress([]byte{addr})]
if !ok {
t.Fatalf("missing precompile 0x%02x", addr)
}
return precompile
}

if got := requirePrecompile(0x66).Name(); got != "BLS_SIGNATURE_VERIFY_MENDEL" {
t.Fatalf("unexpected Mendel 0x66 precompile: %s", got)
}
if got := requirePrecompile(0x67).Name(); got != "COMET_BFT_LIGHT_BLOCK_VALIDATE_HERTZ_MENDEL" {
t.Fatalf("unexpected Mendel 0x67 precompile: %s", got)
}
}

func TestBlsSignatureVerifyRejectsDuplicatePubKeysAtMendel(t *testing.T) {
msg := [32]byte{'d', 'u', 'p'}

sk1, err := bls.RandKey()
if err != nil {
t.Fatal(err)
}
sk2, err := bls.RandKey()
if err != nil {
t.Fatal(err)
}

pk1 := sk1.PublicKey()
pk2 := sk2.PublicKey()
sig1 := sk1.Sign(msg[:])
dupAgg := bls.AggregateSignatures([]blscommon.Signature{sig1, sig1, sig1})

mendelRules := params.Rules{IsOsaka: true, IsMendel: true}
mendelVerify := ActivePrecompiledContracts(mendelRules)[common.BytesToAddress([]byte{0x66})]

input := append(msg[:], dupAgg.Marshal()...)
input = append(input, pk1.Marshal()...)
input = append(input, pk1.Marshal()...)
input = append(input, pk1.Marshal()...)

res, err := mendelVerify.Run(input)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(res) != 0 {
t.Fatalf("expected Mendel duplicate-pubkey verification to fail, got %x", res)
}

osakaRules := params.Rules{IsOsaka: true}
osakaVerify := ActivePrecompiledContracts(osakaRules)[common.BytesToAddress([]byte{0x66})]
legacyRes, err := osakaVerify.Run(input)
if err != nil {
t.Fatalf("unexpected Osaka error: %v", err)
}
if len(legacyRes) == 0 {
t.Fatalf("expected pre-Mendel duplicate-pubkey verification to succeed")
}

mixedInput := append(msg[:], dupAgg.Marshal()...)
mixedInput = append(mixedInput, pk1.Marshal()...)
mixedInput = append(mixedInput, pk2.Marshal()...)
mixedInput = append(mixedInput, pk2.Marshal()...)
mixedRes, err := mendelVerify.Run(mixedInput)
if err != nil {
t.Fatalf("unexpected mixed-key error: %v", err)
}
if len(mixedRes) != 0 {
t.Fatalf("expected mixed duplicate-signature verification to fail, got %x", mixedRes)
}
}

func TestPrecompiledPointEvaluation(t *testing.T) { testJson("pointEvaluation", "0a", t) }

func BenchmarkPrecompiledPointEvaluation(b *testing.B) { benchJson("pointEvaluation", "0a", b) }
Expand Down
Loading
Loading