Skip to content

Commit

Permalink
shutter: validate decryption keys based on eon info and signatures (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
taratorio authored Feb 13, 2025
1 parent 239cd94 commit 0b6b1ab
Show file tree
Hide file tree
Showing 23 changed files with 1,516 additions and 488 deletions.
6 changes: 3 additions & 3 deletions eth/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -1591,7 +1591,7 @@ func (s *Ethereum) Start() error {
diagnostics.Send(diagnostics.SyncStageList{StagesList: diagnostics.InitStagesFromList(s.stagedSync.StagesIdsList())})
s.waitForStageLoopStop = nil // Shutdown is handled by context
s.bgComponentsEg.Go(func() error {
defer func() { s.logger.Info("polygon sync goroutine terminated") }()
defer s.logger.Info("polygon sync goroutine terminated")
// when we're running in stand alone mode we need to run the downloader before we start the
// polygon services becuase they will wait for it to complete before opening thier stores
// which make use of snapshots and expect them to be initialize
Expand Down Expand Up @@ -1650,7 +1650,7 @@ func (s *Ethereum) Start() error {
// to initialize it properly.
// 2) we cannot propose for block 1 regardless.
s.bgComponentsEg.Go(func() error {
defer func() { s.logger.Info("devp2p txn pool goroutine terminated") }()
defer s.logger.Info("devp2p txn pool goroutine terminated")
err := s.txPool.Run(s.sentryCtx)
if err != nil && !errors.Is(err, context.Canceled) {
s.logger.Error("txPool.Run error", "err", err)
Expand All @@ -1661,7 +1661,7 @@ func (s *Ethereum) Start() error {

if s.shutterPool != nil {
s.bgComponentsEg.Go(func() error {
defer func() { s.logger.Info("shutter pool goroutine terminated") }()
defer s.logger.Info("shutter pool goroutine terminated")
err := s.shutterPool.Run(s.sentryCtx)
if err != nil && !errors.Is(err, context.Canceled) {
s.logger.Error("shutterPool.Run error", "err", err)
Expand Down
2 changes: 1 addition & 1 deletion polygon/sync/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ type Service struct {
}

func (s *Service) Run(parentCtx context.Context) error {
defer func() { s.logger.Info(syncLogPrefix("sync service component stopped")) }()
defer s.logger.Info(syncLogPrefix("sync service component stopped"))
s.logger.Info(syncLogPrefix("running sync service component"))

group, ctx := errgroup.WithContext(parentCtx)
Expand Down
3 changes: 2 additions & 1 deletion txnprovider/shutter/block_listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ func (bl BlockListener) RegisterObserver(o event.Observer[BlockEvent]) event.Unr
}

func (bl BlockListener) Run(ctx context.Context) error {
defer bl.logger.Info("block listener stopped")
bl.logger.Info("running block listener")

sub, err := bl.stateChangesClient.StateChanges(ctx, &remoteproto.StateChangeRequest{})
Expand All @@ -57,7 +58,7 @@ func (bl BlockListener) Run(ctx context.Context) error {

// note the changes stream is ctx-aware so Recv should terminate with err if ctx gets done
var batch *remoteproto.StateChangeBatch
for batch, err = sub.Recv(); err != nil; batch, err = sub.Recv() {
for batch, err = sub.Recv(); err == nil; batch, err = sub.Recv() {
if batch == nil || len(batch.ChangeBatch) == 0 {
continue
}
Expand Down
12 changes: 6 additions & 6 deletions txnprovider/shutter/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ type Config struct {
KeyBroadcastContractAddress string
KeyperSetManagerContractAddress string
MaxNumKeysPerMessage uint64
MaxRecentEons int
ReorgDepthAwareness uint64
}

type P2pConfig struct {
Expand Down Expand Up @@ -87,7 +87,7 @@ var (
KeyBroadcastContractAddress: "0x9D31865BEffcE842FBd36CDA587aDDA8bef804B7",
KeyperSetManagerContractAddress: "0xC4DE9FAf4ec882b33dA0162CBE628B0D8205D0c0",
MaxNumKeysPerMessage: defaultMaxNumKeysPerMessage,
MaxRecentEons: defaultMaxRecentEons,
ReorgDepthAwareness: defaultReorgDepthAwarenessEpochs * clparams.BeaconConfigs[clparams.ChiadoNetwork].SlotsPerEpoch,
P2pConfig: P2pConfig{
ListenPort: defaultP2PListenPort,
BootstrapNodes: []string{
Expand All @@ -107,7 +107,7 @@ var (
KeyBroadcastContractAddress: "0x626dB87f9a9aC47070016A50e802dd5974341301",
KeyperSetManagerContractAddress: "0x7C2337f9bFce19d8970661DA50dE8DD7d3D34abb",
MaxNumKeysPerMessage: defaultMaxNumKeysPerMessage,
MaxRecentEons: defaultMaxRecentEons,
ReorgDepthAwareness: defaultReorgDepthAwarenessEpochs * clparams.BeaconConfigs[clparams.GnosisNetwork].SlotsPerEpoch,
P2pConfig: P2pConfig{
ListenPort: defaultP2PListenPort,
BootstrapNodes: []string{
Expand All @@ -119,7 +119,7 @@ var (
)

const (
defaultP2PListenPort = 23_102
defaultMaxNumKeysPerMessage = 500
defaultMaxRecentEons = 100
defaultP2PListenPort = 23_102
defaultMaxNumKeysPerMessage = 500
defaultReorgDepthAwarenessEpochs = 3
)
24 changes: 11 additions & 13 deletions txnprovider/shutter/decryption_keys_listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,18 @@ const (
)

type DecryptionKeysListener struct {
logger log.Logger
config Config
slotCalculator SlotCalculator
eonTracker EonTracker
observers *event.Observers[*proto.DecryptionKeys]
logger log.Logger
config Config
validator pubsub.ValidatorEx
observers *event.Observers[*proto.DecryptionKeys]
}

func NewDecryptionKeysListener(logger log.Logger, config Config, sc SlotCalculator, et EonTracker) DecryptionKeysListener {
func NewDecryptionKeysListener(logger log.Logger, config Config, validator pubsub.ValidatorEx) DecryptionKeysListener {
return DecryptionKeysListener{
logger: logger,
config: config,
slotCalculator: sc,
eonTracker: et,
observers: event.NewObservers[*proto.DecryptionKeys](),
logger: logger,
config: config,
validator: validator,
observers: event.NewObservers[*proto.DecryptionKeys](),
}
}

Expand All @@ -63,6 +61,7 @@ func (dkl DecryptionKeysListener) RegisterObserver(observer event.Observer[*prot
}

func (dkl DecryptionKeysListener) Run(ctx context.Context) error {
defer dkl.logger.Info("decryption keys listener stopped")
dkl.logger.Info("running decryption keys listener")

p2pHost, err := dkl.initP2pHost()
Expand Down Expand Up @@ -211,8 +210,7 @@ func (dkl DecryptionKeysListener) connectBootstrapNodes(ctx context.Context, hos
}

func (dkl DecryptionKeysListener) listenLoop(ctx context.Context, pubSub *pubsub.PubSub) error {
topicValidator := NewDecryptionKeysP2pValidatorEx(dkl.logger, dkl.config, dkl.slotCalculator, dkl.eonTracker)
err := pubSub.RegisterTopicValidator(DecryptionKeysTopic, topicValidator)
err := pubSub.RegisterTopicValidator(DecryptionKeysTopic, dkl.validator)
if err != nil {
return err
}
Expand Down
1 change: 1 addition & 0 deletions txnprovider/shutter/decryption_keys_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func (dkp DecryptionKeysProcessor) Enqueue(msg *proto.DecryptionKeys) {
}

func (dkp DecryptionKeysProcessor) Run(ctx context.Context) error {
defer dkp.logger.Info("decryption keys processor stopped")
dkp.logger.Info("running decryption keys processor")

for {
Expand Down
139 changes: 139 additions & 0 deletions txnprovider/shutter/decryption_keys_signature_data.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// Copyright 2025 The Erigon Authors
// This file is part of Erigon.
//
// Erigon is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Erigon is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Erigon. If not, see <http://www.gnu.org/licenses/>.

package shutter

import (
"crypto/ecdsa"
"errors"
"fmt"
"slices"

libcommon "github.com/erigontech/erigon-lib/common"
"github.com/erigontech/erigon-lib/common/hexutil"
"github.com/erigontech/erigon-lib/crypto"
"github.com/erigontech/erigon-lib/types/clonable"
"github.com/erigontech/erigon/cl/cltypes/solid"
merkletree "github.com/erigontech/erigon/cl/merkle_tree"
)

var (
ErrTooManyIdentityPreimages = errors.New("too many identity preimages")
ErrIncorrectIdentityPreimageSize = errors.New("incorrect identity preimage size")
)

const (
identityPreimageSize = 52
identityPreimagesLimit = 1024
)

type IdentityPreimage [identityPreimageSize]byte

func (ip *IdentityPreimage) EncodingSizeSSZ() int {
return identityPreimageSize
}

func (ip *IdentityPreimage) EncodeSSZ(dst []byte) ([]byte, error) {
return append(dst, ip[:]...), nil
}

func (ip *IdentityPreimage) DecodeSSZ(buf []byte, _ int) error {
if len(buf) != identityPreimageSize {
return fmt.Errorf("%w: len=%d", ErrIncorrectIdentityPreimageSize, len(ip))
}

var newIp IdentityPreimage
copy(newIp[:], buf)
*ip = newIp
return nil
}

func (ip *IdentityPreimage) Clone() clonable.Clonable {
clone := IdentityPreimage(slices.Clone(ip[:]))
return &clone
}

func (ip *IdentityPreimage) HashSSZ() ([32]byte, error) {
return merkletree.BytesRoot(ip[:])
}

func (ip *IdentityPreimage) String() string {
return hexutil.Encode(ip[:])
}

func IdentityPreimageFromSSZ(b []byte) (*IdentityPreimage, error) {
ip := new(IdentityPreimage)
err := ip.DecodeSSZ(b, 0)
return ip, err
}

type IdentityPreimages []*IdentityPreimage

func (ips IdentityPreimages) ToListSSZ() *solid.ListSSZ[*IdentityPreimage] {
return solid.NewStaticListSSZFromList(ips, identityPreimagesLimit, identityPreimageSize)
}

type DecryptionKeysSignatureData struct {
InstanceId uint64
Eon EonIndex
Slot uint64
TxnPointer uint64
IdentityPreimages *solid.ListSSZ[*IdentityPreimage]
}

func (d DecryptionKeysSignatureData) HashSSZ() ([32]byte, error) {
if err := d.Validate(); err != nil {
return [32]byte{}, err
}

r, err := merkletree.HashTreeRoot(d.InstanceId, uint64(d.Eon), d.Slot, d.TxnPointer, d.IdentityPreimages)
if err != nil {
return [32]byte{}, fmt.Errorf("%w: slot=%d, eon=%d", err, d.Slot, d.Eon)
}

return r, nil
}

func (d DecryptionKeysSignatureData) Sign(key *ecdsa.PrivateKey) ([]byte, error) {
h, err := d.HashSSZ()
if err != nil {
return nil, err
}

return crypto.Sign(h[:], key)
}

func (d DecryptionKeysSignatureData) Verify(signature []byte, address libcommon.Address) (bool, error) {
h, err := d.HashSSZ()
if err != nil {
return false, err
}

pubKey, err := crypto.SigToPub(h[:], signature)
if err != nil {
return false, err
}

return crypto.PubkeyToAddress(*pubKey) == address, nil
}

func (d DecryptionKeysSignatureData) Validate() error {
if d.IdentityPreimages.Len() > identityPreimagesLimit {
return fmt.Errorf("%w: len=%d", ErrTooManyIdentityPreimages, d.IdentityPreimages.Len())
}

return nil
}
49 changes: 49 additions & 0 deletions txnprovider/shutter/decryption_keys_signature_data_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package shutter_test

import (
"testing"

"github.com/stretchr/testify/require"

libcommon "github.com/erigontech/erigon-lib/common"
"github.com/erigontech/erigon/txnprovider/shutter"
"github.com/erigontech/erigon/txnprovider/shutter/internal/testhelpers"
)

func TestIdentityPreimageEncodeDecodeSSZ(t *testing.T) {
ip := testhelpers.Uint64ToIdentityPreimage(t, 123)
buf, err := ip.EncodeSSZ(nil)
require.NoError(t, err)
ip2, err := shutter.IdentityPreimageFromSSZ(buf)
require.NoError(t, err)
require.Equal(t, ip, ip2)
}

func TestIdentityPreimageDecodeSSZWithInvalidLength(t *testing.T) {
buf := make([]byte, 39)
_, err := shutter.IdentityPreimageFromSSZ(buf)
require.ErrorIs(t, err, shutter.ErrIncorrectIdentityPreimageSize)
buf = make([]byte, 64)
_, err = shutter.IdentityPreimageFromSSZ(buf)
require.ErrorIs(t, err, shutter.ErrIncorrectIdentityPreimageSize)
}

func TestDecryptionKeysSignatureDataWithInvalidPreimagesLength(t *testing.T) {
ips := testhelpers.MockIdentityPreimages(t, 1025)
sigData := shutter.DecryptionKeysSignatureData{
InstanceId: 1,
Eon: 2,
Slot: 3,
TxnPointer: 4,
IdentityPreimages: ips.ToListSSZ(),
}

err := sigData.Validate()
require.ErrorIs(t, err, shutter.ErrTooManyIdentityPreimages)
_, err = sigData.HashSSZ()
require.ErrorIs(t, err, shutter.ErrTooManyIdentityPreimages)
_, err = sigData.Verify(nil, libcommon.Address{})
require.ErrorIs(t, err, shutter.ErrTooManyIdentityPreimages)
_, err = sigData.Sign(nil)
require.ErrorIs(t, err, shutter.ErrTooManyIdentityPreimages)
}
Loading

0 comments on commit 0b6b1ab

Please sign in to comment.