Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
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 PrecompiledContractsPasteur = func() PrecompiledContracts {
precompiles := maps.Clone(PrecompiledContractsOsaka)
precompiles[common.BytesToAddress([]byte{0x66})] = &blsSignatureVerifyPasteur{}
precompiles[common.BytesToAddress([]byte{0x67})] = &cometBFTLightBlockValidatePasteur{}
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 (
PrecompiledAddressesPasteur []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 PrecompiledContractsPasteur {
PrecompiledAddressesPasteur = append(PrecompiledAddressesPasteur, 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.IsPasteur:
return PrecompiledContractsPasteur
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.IsPasteur:
return PrecompiledAddressesPasteur
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 blsSignatureVerifyPasteur 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 *blsSignatureVerifyPasteur) Run(input []byte) ([]byte, error) {
return runBlsSignatureVerify(input, true)
}

func (c *blsSignatureVerifyPasteur) Name() string {
return "BLS_SIGNATURE_VERIFY_PASTEUR"
}

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

Expand Down
25 changes: 21 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,18 @@ 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
)
cs, block, err = v2.DecodeLightBlockValidationInput(input, requireUniqueValidators)
if err != nil {
return nil, err
}
Expand All @@ -418,7 +423,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 +435,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 cometBFTLightBlockValidatePasteur struct {
cometBFTLightBlockValidate
}

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

func (c *cometBFTLightBlockValidatePasteur) Name() string {
return "COMET_BFT_LIGHT_BLOCK_VALIDATE_PASTEUR"
}

// 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 TestCometBFTLightBlockValidateRejectsDuplicateTrustedValidatorsAtPasteur(t *testing.T) {
inputStr := "000000000000000000000000000000000000000000000000000000000000018c677265656e6669656c645f393030302d3132310000000000000000000000000000000000000000013c350cd55b99dc6c2b7da9bef5410fbfb869fede858e7b95bf7ca294e228bb40e33f6e876d63791ebd05ff617a1b4f4ad1aa2ce65e3c3a9cdfb33e0ffa7e8423000000000098968015154514f68ce65a0d9eecc578c0ab12da0a2a28a0805521b5b7ae56eb3fb24555efbfe59e1622bfe9f7be8c9022e9b3f2442739c1ce870b9adee169afe60f674edd7c86451c5363d89052fde8351895eeea166ce5373c36e31b518ed191d0c599aa0f5b0000000000989680432f6c4908a9aa5f3444421f466b11645235c99b831b2a2de9e504d7ea299e52a202ce529808618eb3bfc0addf13d8c5f2df821d81e18f9bc61583510b322d067d46323b0a572635c06a049c0a2a929e3c8184a50cf6a8b95708c25834ade456f399015a0000000000989680864cb9828254d712f8e59b164fc6a9402dc4e6c59065e38cff24f5323c8c5da888a0f97e5ee4ba1e11b0674b0a0d06204c1dfa247c370cd4be3e799fc4f6f48d977ac7ca0aeb060adb030a02080b1213677265656e6669656c645f393030302d3132311802220c08b2d7f3a10610e8d2adb3032a480a20ec6ecb5db4ffb17fabe40c60ca7b8441e9c5d77585d0831186f3c37aa16e9c15122408011220a2ab9e1eb9ea52812f413526e424b326aff2f258a56e00d690db9f805b60fe7e32200f40aeff672e8309b7b0aefbb9a1ae3d4299b5c445b7d54e8ff398488467f0053a20e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b85542203c350cd55b99dc6c2b7da9bef5410fbfb869fede858e7b95bf7ca294e228bb404a203c350cd55b99dc6c2b7da9bef5410fbfb869fede858e7b95bf7ca294e228bb405220294d8fbd0b94b767a7eba9840f299a3586da7fe6b5dead3b7eecba193c400f935a20bc50557c12d7392b0d07d75df0b61232d48f86a74fdea6d1485d9be6317d268c6220e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b8556a20e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b85572146699336aa109d1beab3946198c8e59f3b2cbd92f7a4065e3cd89e315ca39d87dee92835b98f8b8ec0861d6d9bb2c60156df5d375b3ceb1fbe71af6a244907d62548a694165caa660fec7a9b4e7b9198191361c71be0b128a0308021a480a20726abd0fdbfb6f779b0483e6e4b4b6f12241f6ea2bf374233ab1a316692b6415122408011220159f10ff15a8b58fc67a92ffd7f33c8cd407d4ce81b04ca79177dfd00ca19a67226808021214050cff76cc632760ba9db796c046004c900967361a0c08b3d7f3a10610808cadba03224080713027ffb776a702d78fd0406205c629ba473e1f8d6af646190f6eb9262cd67d69be90d10e597b91e06d7298eb6fa4b8f1eb7752ebf352a1f51560294548042268080212146699336aa109d1beab3946198c8e59f3b2cbd92f1a0c08b3d7f3a10610b087c1c00322405e2ddb70acfe4904438be3d9f4206c0ace905ac4fc306a42cfc9e86268950a0fbfd6ec5f526d3e41a3ef52bf9f9f358e3cb4c3feac76c762fa3651c1244fe004226808021214c55765fd2d0570e869f6ac22e7f2916a35ea300d1a0c08b3d7f3a10610f0b3d492032240ca17898bd22232fc9374e1188636ee321a396444a5b1a79f7628e4a11f265734b2ab50caf21e8092c55d701248e82b2f011426cb35ba22043b497a6b4661930612a0050aa8010a14050cff76cc632760ba9db796c046004c9009673612220a20e33f6e876d63791ebd05ff617a1b4f4ad1aa2ce65e3c3a9cdfb33e0ffa7e84231880ade2042080a6bbf6ffffffffff012a30a0805521b5b7ae56eb3fb24555efbfe59e1622bfe9f7be8c9022e9b3f2442739c1ce870b9adee169afe60f674edd7c86321415154514f68ce65a0d9eecc578c0ab12da0a2a283a14ee7a2a6a44d427f6949eeb8f12ea9fbb2501da880aa2010a146699336aa109d1beab3946198c8e59f3b2cbd92f12220a20451c5363d89052fde8351895eeea166ce5373c36e31b518ed191d0c599aa0f5b1880ade2042080ade2042a30831b2a2de9e504d7ea299e52a202ce529808618eb3bfc0addf13d8c5f2df821d81e18f9bc61583510b322d067d46323b3214432f6c4908a9aa5f3444421f466b11645235c99b3a14a0a7769429468054e19059af4867da0a495567e50aa2010a14c55765fd2d0570e869f6ac22e7f2916a35ea300d12220a200a572635c06a049c0a2a929e3c8184a50cf6a8b95708c25834ade456f399015a1880ade2042080ade2042a309065e38cff24f5323c8c5da888a0f97e5ee4ba1e11b0674b0a0d06204c1dfa247c370cd4be3e799fc4f6f48d977ac7ca3214864cb9828254d712f8e59b164fc6a9402dc4e6c53a143139916d97df0c589312b89950b6ab9795f34d1a12a8010a14050cff76cc632760ba9db796c046004c9009673612220a20e33f6e876d63791ebd05ff617a1b4f4ad1aa2ce65e3c3a9cdfb33e0ffa7e84231880ade2042080a6bbf6ffffffffff012a30a0805521b5b7ae56eb3fb24555efbfe59e1622bfe9f7be8c9022e9b3f2442739c1ce870b9adee169afe60f674edd7c86321415154514f68ce65a0d9eecc578c0ab12da0a2a283a14ee7a2a6a44d427f6949eeb8f12ea9fbb2501da88"

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

cs, block, err := v2.DecodeLightBlockValidationInput(input, false)
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...)

pasteurPrecompile := ActivePrecompiledContracts(params.Rules{IsOsaka: true, IsMendel: true, IsPasteur: true})[common.BytesToAddress([]byte{0x67})]
_, err = pasteurPrecompile.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 TestActivePrecompiledContractsUsesPasteurVariants(t *testing.T) {
rules := params.Rules{IsOsaka: true, IsMendel: true, IsPasteur: 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_PASTEUR" {
t.Fatalf("unexpected Pasteur 0x66 precompile: %s", got)
}
if got := requirePrecompile(0x67).Name(); got != "COMET_BFT_LIGHT_BLOCK_VALIDATE_PASTEUR" {
t.Fatalf("unexpected Pasteur 0x67 precompile: %s", got)
}
}

func TestBlsSignatureVerifyRejectsDuplicatePubKeysAtPasteur(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})

pasteurRules := params.Rules{IsOsaka: true, IsMendel: true, IsPasteur: true}
pasteurVerify := ActivePrecompiledContracts(pasteurRules)[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 := pasteurVerify.Run(input)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(res) != 0 {
t.Fatalf("expected Pasteur 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-Pasteur 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 := pasteurVerify.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