Skip to content

Commit 21925ff

Browse files
authored
Merge branch 'main' into INFOPLAT-3099-chip-ingress-batching
2 parents 3cff2cc + 7f47b3a commit 21925ff

23 files changed

Lines changed: 1354 additions & 145 deletions

keystore/admin.go

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"encoding/json"
1010
"fmt"
1111
"maps"
12+
"slices"
1213
"time"
1314

1415
gethkeystore "github.com/ethereum/go-ethereum/accounts/keystore"
@@ -21,6 +22,11 @@ import (
2122
"github.com/smartcontractkit/chainlink-common/keystore/serialization"
2223
)
2324

25+
const (
26+
MaxKeyNameLength = 1000
27+
MaxMetadataLength = 1024 * 1024 // 1mb
28+
)
29+
2430
var (
2531
ErrKeyAlreadyExists = fmt.Errorf("key already exists")
2632
ErrInvalidKeyName = fmt.Errorf("invalid key name")
@@ -165,8 +171,8 @@ func ValidKeyName(name string) error {
165171
return fmt.Errorf("key name cannot be empty")
166172
}
167173
// Just a sanity bound.
168-
if len(name) > 1_000 {
169-
return fmt.Errorf("key name cannot be longer than 1000 characters")
174+
if len(name) > MaxKeyNameLength {
175+
return fmt.Errorf("key name cannot be longer than %d characters", MaxKeyNameLength)
170176
}
171177
return nil
172178
}
@@ -202,9 +208,8 @@ func (ks *keystore) CreateKeys(ctx context.Context, req CreateKeysRequest) (Crea
202208
if err != nil {
203209
return CreateKeysResponse{}, fmt.Errorf("failed to generate ECDSA_S256 key: %w", err)
204210
}
205-
// Must copy the private key into 32 byte slice because leading zeros are stripped.
206211
privateKeyBytes := make([]byte, 32)
207-
copy(privateKeyBytes, privateKey.D.Bytes())
212+
privateKey.D.FillBytes(privateKeyBytes)
208213
publicKey, err := publicKeyFromPrivateKey(internal.NewRaw(privateKeyBytes), keyReq.KeyType)
209214
if err != nil {
210215
return CreateKeysResponse{}, fmt.Errorf("failed to get public key from private key: %w", err)
@@ -291,6 +296,9 @@ func (ks *keystore) ImportKeys(ctx context.Context, req ImportKeysRequest) (Impo
291296
}
292297
pkRaw := internal.NewRaw(keypb.PrivateKey)
293298
keyType := KeyType(keypb.KeyType)
299+
if !slices.Contains(AllKeyTypes, keyType) {
300+
return ImportKeysResponse{}, fmt.Errorf("%w: %s, available key types: %s", ErrUnsupportedKeyType, keyType, AllKeyTypes.String())
301+
}
294302
publicKey, err := publicKeyFromPrivateKey(pkRaw, keyType)
295303
if err != nil {
296304
return ImportKeysResponse{}, fmt.Errorf("key num = %d, failed to get public key from private key: %w", i, err)
@@ -301,6 +309,9 @@ func (ks *keystore) ImportKeys(ctx context.Context, req ImportKeysRequest) (Impo
301309
if metadata == nil {
302310
metadata = []byte{}
303311
}
312+
if len(metadata) > MaxMetadataLength {
313+
return ImportKeysResponse{}, fmt.Errorf("key num = %d, metadata of length %d exceeds maximum length of %d bytes", i, len(metadata), MaxMetadataLength)
314+
}
304315

305316
keyName := keyReq.NewKeyName
306317
if keyName == "" {
@@ -366,6 +377,9 @@ func (ks *keystore) SetMetadata(ctx context.Context, req SetMetadataRequest) (Se
366377

367378
ksCopy := maps.Clone(ks.keystore)
368379
for _, metReq := range req.Updates {
380+
if len(metReq.Metadata) > MaxMetadataLength {
381+
return SetMetadataResponse{}, fmt.Errorf("metadata for key %s exceeds maximum length of %d bytes", metReq.KeyName, MaxMetadataLength)
382+
}
369383
key, ok := ksCopy[metReq.KeyName]
370384
if !ok {
371385
return SetMetadataResponse{}, fmt.Errorf("%w: %s", ErrKeyNotFound, metReq.KeyName)

keystore/admin_test.go

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@ package keystore_test
33
import (
44
"context"
55
"fmt"
6+
"math/big"
67
"sort"
78
"sync"
89
"testing"
910

1011
"github.com/stretchr/testify/assert"
1112
"github.com/stretchr/testify/require"
1213

14+
gethcrypto "github.com/ethereum/go-ethereum/crypto"
15+
1316
"github.com/smartcontractkit/chainlink-common/keystore"
1417
)
1518

@@ -258,7 +261,16 @@ func TestKeystore_ExportImport(t *testing.T) {
258261
key1ks1, err := ks1.GetKeys(t.Context(), keystore.GetKeysRequest{KeyNames: []string{"key1"}})
259262
require.NoError(t, err)
260263
key1ks2, err := ks2.GetKeys(t.Context(), keystore.GetKeysRequest{KeyNames: []string{"key1"}})
261-
require.Equal(t, key1ks1, key1ks2)
264+
require.NoError(t, err)
265+
// Test equality of the keys except of the CreatedAt field.
266+
require.Len(t, key1ks1.Keys, 1)
267+
require.Len(t, key1ks2.Keys, 1)
268+
key1ks1Info := key1ks1.Keys[0].KeyInfo
269+
key1ks2Info := key1ks2.Keys[0].KeyInfo
270+
require.Equal(t, key1ks1Info.Name, key1ks2Info.Name)
271+
require.Equal(t, key1ks1Info.PublicKey, key1ks2Info.PublicKey)
272+
require.Equal(t, key1ks1Info.KeyType, key1ks2Info.KeyType)
273+
require.Equal(t, key1ks1Info.Metadata, key1ks2Info.Metadata)
262274

263275
testData := []byte("hello world")
264276
signature, err := ks2.Sign(t.Context(), keystore.SignRequest{
@@ -411,3 +423,20 @@ func TestKeystore_RenameKey(t *testing.T) {
411423
require.EqualError(t, err, "key not found: key1")
412424
})
413425
}
426+
427+
func TestECDSA_Serialization_WithPadding(t *testing.T) {
428+
// This test ensures that ECDSA private keys that serialize to less than 32 bytes
429+
// are correctly padded with leading zeros during serialization and deserialization.
430+
// This is important for compatibility with Ethereum's crypto library which expects
431+
// 32-byte private keys.
432+
433+
// The example key has been found randomly such that it has 2 leading zero bytes when serialized.
434+
key, ok := big.NewInt(0).SetString("57269542458293433845411819226400606954116463824740942170224417652371448", 10)
435+
require.True(t, ok)
436+
privateKeyBytes := make([]byte, 32)
437+
key.FillBytes(privateKeyBytes)
438+
require.Equal(t, []byte{0, 0, 8, 76, 62, 209, 247, 104, 97, 108, 141, 217, 255, 150, 114, 196, 223, 66, 254, 101, 209, 14, 233, 174, 149, 89, 207, 141, 2, 188, 111, 248}, privateKeyBytes)
439+
deserializedKey, err := gethcrypto.ToECDSA(privateKeyBytes)
440+
require.NoError(t, err)
441+
require.Equal(t, key, deserializedKey.D)
442+
}

keystore/cli/cli.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const (
2525

2626
func NewRootCmd() *cobra.Command {
2727
cmd := &cobra.Command{
28-
Use: "./keystore <command>",
28+
Use: "keys",
2929
Long: `
3030
CLI for managing keystore keys. Must specify KEYSTORE_FILE_PATH or KEYSTORE_DB_URL
3131
and KEYSTORE_PASSWORD in order to load the keystore.
@@ -48,9 +48,9 @@ KEYSTORE_PASSWORD is the password used to encrypt the key material before storag
4848
Short: "CLI for managing keystore keys",
4949
SilenceUsage: true,
5050
}
51-
cmd.PersistentFlags().String("keystore-file-path", "", "Overrides KEYSTORE_FILE_PATH environment variable")
52-
cmd.PersistentFlags().String("keystore-db-url", "", "Overrides KEYSTORE_DB_URL environment variable")
53-
cmd.PersistentFlags().String("keystore-password", "", "Overrides KEYSTORE_PASSWORD environment variable. Not recommended as will leave shell traces.")
51+
cmd.PersistentFlags().String("file-path", "", "Overrides KEYSTORE_FILE_PATH environment variable")
52+
cmd.PersistentFlags().String("db-url", "", "Overrides KEYSTORE_DB_URL environment variable")
53+
cmd.PersistentFlags().String("password", "", "Overrides KEYSTORE_PASSWORD environment variable. Not recommended as will leave shell traces.")
5454

5555
cmd.AddCommand(NewListCmd(), NewGetCmd(), NewCreateCmd(), NewDeleteCmd(), NewExportCmd(), NewImportCmd(), NewSetMetadataCmd(), NewSignCmd(), NewVerifyCmd(), NewEncryptCmd(), NewDecryptCmd())
5656
return cmd
@@ -336,16 +336,18 @@ func NewDecryptCmd() *cobra.Command {
336336
}
337337

338338
func loadKeystore(ctx context.Context, cmd *cobra.Command) (ks.Keystore, error) {
339-
root := cmd.Root()
340-
filePath, err := root.Flags().GetString("keystore-file-path")
339+
// Use parent command which has the persistent flags.
340+
// This works whether keystore CLI is standalone or embedded as a subcommand.
341+
parent := cmd.Parent()
342+
filePath, err := parent.Flags().GetString("file-path")
341343
if err != nil {
342344
return nil, err
343345
}
344-
dbURL, err := root.Flags().GetString("keystore-db-url")
346+
dbURL, err := parent.Flags().GetString("db-url")
345347
if err != nil {
346348
return nil, err
347349
}
348-
password, err := cmd.Flags().GetString("keystore-password")
350+
password, err := parent.Flags().GetString("password")
349351
if err != nil {
350352
return nil, err
351353
}

keystore/internal/raw.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// only available for use in the keystore sub-tree.
33
package internal
44

5+
import "fmt"
6+
57
// Raw is a wrapper type that holds private key bytes
68
// and is designed to prevent accidental logging.
79
// The only way to access the internal bytes (without reflection) is to use Bytes,
@@ -22,6 +24,10 @@ func (raw Raw) GoString() string {
2224
return raw.String()
2325
}
2426

27+
func (raw Raw) Format(state fmt.State, _ rune) {
28+
_, _ = fmt.Fprint(state, raw.String())
29+
}
30+
2531
// Bytes is a func for accessing the internal bytes field of Raw.
2632
// It is not declared as a method, because that would allow access from callers which cannot otherwise access this internal package.
2733
func Bytes(raw Raw) []byte { return raw.bytes }

keystore/keystore.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,13 @@ import (
88
"errors"
99
"fmt"
1010
"io"
11+
"log/slog"
1112
"slices"
1213
"strings"
1314
"sync"
1415
"testing"
1516
"time"
1617

17-
"log/slog"
18-
1918
"golang.org/x/crypto/curve25519"
2019

2120
gethkeystore "github.com/ethereum/go-ethereum/accounts/keystore"
@@ -201,6 +200,9 @@ type EncryptionParams struct {
201200
func publicKeyFromPrivateKey(privateKeyBytes internal.Raw, keyType KeyType) ([]byte, error) {
202201
switch keyType {
203202
case Ed25519:
203+
if len(internal.Bytes(privateKeyBytes)) != ed25519.PrivateKeySize {
204+
return nil, fmt.Errorf("invalid Ed25519 private key size: %d", len(internal.Bytes(privateKeyBytes)))
205+
}
204206
privateKey := ed25519.PrivateKey(internal.Bytes(privateKeyBytes))
205207
publicKey := privateKey.Public().(ed25519.PublicKey)
206208
return publicKey, nil
@@ -216,6 +218,9 @@ func publicKeyFromPrivateKey(privateKeyBytes internal.Raw, keyType KeyType) ([]b
216218
pubKey := gethcrypto.FromECDSAPub(&privateKey.PublicKey)
217219
return pubKey, nil
218220
case X25519:
221+
if len(internal.Bytes(privateKeyBytes)) != curve25519.ScalarSize {
222+
return nil, fmt.Errorf("invalid X25519 private key size: %d", len(internal.Bytes(privateKeyBytes)))
223+
}
219224
pubKey, err := curve25519.X25519(internal.Bytes(privateKeyBytes)[:], curve25519.Basepoint)
220225
if err != nil {
221226
return nil, fmt.Errorf("failed to derive shared secret: %w", err)

keystore/reader.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,7 @@ func (k *keystore) GetKeys(ctx context.Context, req GetKeysRequest) (GetKeysResp
6363
}
6464
seen[name] = true
6565
responses = append(responses, GetKeyResponse{
66-
KeyInfo: KeyInfo{
67-
Name: name,
68-
KeyType: key.keyType,
69-
PublicKey: key.publicKey,
70-
Metadata: key.metadata,
71-
},
66+
KeyInfo: newKeyInfo(name, key.keyType, key.createdAt, key.publicKey, key.metadata),
7267
})
7368
}
7469
sort.Slice(responses, func(i, j int) bool { return responses[i].KeyInfo.Name < responses[j].KeyInfo.Name })

pkg/workflows/ring/factory.go

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ import (
44
"context"
55
"errors"
66

7+
"github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types"
8+
79
"github.com/smartcontractkit/chainlink-common/pkg/logger"
810
"github.com/smartcontractkit/chainlink-common/pkg/services"
911
"github.com/smartcontractkit/chainlink-common/pkg/types/core"
1012
"github.com/smartcontractkit/chainlink-common/pkg/workflows/ring/pb"
11-
"github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types"
13+
"github.com/smartcontractkit/chainlink-common/pkg/workflows/shardorchestrator"
1214
)
1315

1416
const (
@@ -20,15 +22,16 @@ const (
2022
var _ core.OCR3ReportingPluginFactory = &Factory{}
2123

2224
type Factory struct {
23-
store *Store
24-
arbiterScaler pb.ArbiterScalerClient
25-
config *ConsensusConfig
26-
lggr logger.Logger
25+
ringStore *Store
26+
shardOrchestratorStore *shardorchestrator.Store
27+
arbiterScaler pb.ArbiterScalerClient
28+
config *ConsensusConfig
29+
lggr logger.Logger
2730

2831
services.StateMachine
2932
}
3033

31-
func NewFactory(s *Store, arbiterScaler pb.ArbiterScalerClient, lggr logger.Logger, cfg *ConsensusConfig) (*Factory, error) {
34+
func NewFactory(s *Store, shardOrchestratorStore *shardorchestrator.Store, arbiterScaler pb.ArbiterScalerClient, lggr logger.Logger, cfg *ConsensusConfig) (*Factory, error) {
3235
if arbiterScaler == nil {
3336
return nil, errors.New("arbiterScaler is required")
3437
}
@@ -38,15 +41,16 @@ func NewFactory(s *Store, arbiterScaler pb.ArbiterScalerClient, lggr logger.Logg
3841
}
3942
}
4043
return &Factory{
41-
store: s,
42-
arbiterScaler: arbiterScaler,
43-
config: cfg,
44-
lggr: logger.Named(lggr, "RingPluginFactory"),
44+
ringStore: s,
45+
shardOrchestratorStore: shardOrchestratorStore,
46+
arbiterScaler: arbiterScaler,
47+
config: cfg,
48+
lggr: logger.Named(lggr, "RingPluginFactory"),
4549
}, nil
4650
}
4751

4852
func (o *Factory) NewReportingPlugin(_ context.Context, config ocr3types.ReportingPluginConfig) (ocr3types.ReportingPlugin[[]byte], ocr3types.ReportingPluginInfo, error) {
49-
plugin, err := NewPlugin(o.store, o.arbiterScaler, config, o.lggr, o.config)
53+
plugin, err := NewPlugin(o.ringStore, o.arbiterScaler, config, o.lggr, o.config)
5054
pluginInfo := ocr3types.ReportingPluginInfo{
5155
Name: "RingPlugin",
5256
Limits: ocr3types.ReportingPluginLimits{

pkg/workflows/ring/factory_test.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,18 @@ import (
44
"context"
55
"testing"
66

7-
"github.com/smartcontractkit/chainlink-common/pkg/logger"
8-
"github.com/smartcontractkit/chainlink-common/pkg/workflows/ring/pb"
97
"github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types"
108
"github.com/stretchr/testify/require"
9+
10+
"github.com/smartcontractkit/chainlink-common/pkg/logger"
11+
"github.com/smartcontractkit/chainlink-common/pkg/workflows/ring/pb"
12+
"github.com/smartcontractkit/chainlink-common/pkg/workflows/shardorchestrator"
1113
)
1214

1315
func TestFactory_NewFactory(t *testing.T) {
1416
lggr := logger.Test(t)
1517
store := NewStore()
18+
shardOrchestratorStore := shardorchestrator.NewStore(lggr)
1619
arbiter := &mockArbiter{}
1720

1821
tests := []struct {
@@ -45,7 +48,7 @@ func TestFactory_NewFactory(t *testing.T) {
4548

4649
for _, tt := range tests {
4750
t.Run(tt.name, func(t *testing.T) {
48-
f, err := NewFactory(store, tt.arbiter, lggr, tt.config)
51+
f, err := NewFactory(store, shardOrchestratorStore, tt.arbiter, lggr, tt.config)
4952
if tt.wantErr {
5053
require.Error(t, err)
5154
require.Contains(t, err.Error(), tt.errSubstr)
@@ -60,7 +63,7 @@ func TestFactory_NewFactory(t *testing.T) {
6063
func TestFactory_NewReportingPlugin(t *testing.T) {
6164
lggr := logger.Test(t)
6265
store := NewStore()
63-
f, err := NewFactory(store, &mockArbiter{}, lggr, nil)
66+
f, err := NewFactory(store, nil, &mockArbiter{}, lggr, nil)
6467
require.NoError(t, err)
6568

6669
config := ocr3types.ReportingPluginConfig{N: 4, F: 1}
@@ -75,7 +78,7 @@ func TestFactory_NewReportingPlugin(t *testing.T) {
7578
func TestFactory_Lifecycle(t *testing.T) {
7679
lggr := logger.Test(t)
7780
store := NewStore()
78-
f, err := NewFactory(store, &mockArbiter{}, lggr, nil)
81+
f, err := NewFactory(store, nil, &mockArbiter{}, lggr, nil)
7982
require.NoError(t, err)
8083

8184
err = f.Start(context.Background())

pkg/workflows/ring/pb/generate.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
//go:generate protoc --go_out=. --go_opt=paths=source_relative shared.proto
22
//go:generate protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative arbiter.proto
3-
//go:generate protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative shard_orchestrator.proto
43
//go:generate protoc --go_out=. --go_opt=paths=source_relative consensus.proto
54

65
package pb

0 commit comments

Comments
 (0)