Skip to content

Commit 0696bdd

Browse files
Allow importing CSA keys/OCR2 key bundles from the keystorelib
1 parent 60c6e5c commit 0696bdd

File tree

26 files changed

+479
-61
lines changed

26 files changed

+479
-61
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ debug.env
3333
operator_ui/install
3434
.devenv
3535
event_dump.ndjson
36+
CLAUDE.md
3637

3738
# neovim
3839
.nvim.lua

core/cmd/shell_local.go

Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import (
3737
evmtypes "github.com/smartcontractkit/chainlink-evm/pkg/types"
3838

3939
"github.com/smartcontractkit/chainlink/v2/core/build"
40+
"github.com/smartcontractkit/chainlink/v2/core/config"
4041
"github.com/smartcontractkit/chainlink/v2/core/logger"
4142
"github.com/smartcontractkit/chainlink/v2/core/services/keystore"
4243
"github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype"
@@ -317,6 +318,28 @@ func (s *Shell) RunNode(c *cli.Context) error {
317318
return nil
318319
}
319320

321+
type ks[K any] interface {
322+
Import(ctx context.Context, keyJSON []byte, password string) (K, error)
323+
}
324+
325+
func importKeyIfProvided[K any](ctx context.Context, lggr logger.Logger, keyType string, importableKey config.ImportableKey, k ks[K]) error {
326+
keyJSON := importableKey.JSON()
327+
if keyJSON == "" {
328+
return nil
329+
}
330+
331+
lggr.Debugf("Importing %s %s", keyType, keyJSON)
332+
_, err := k.Import(ctx, []byte(keyJSON), importableKey.Password())
333+
if errors.Is(err, keystore.ErrKeyExists) {
334+
lggr.Debugf("%s key already exists %s", keyType, keyJSON)
335+
return nil
336+
} else if err != nil {
337+
return fmt.Errorf("error importing %s key: %w", keyType, err)
338+
}
339+
340+
return nil
341+
}
342+
320343
func (s *Shell) runNode(c *cli.Context) error {
321344
ctx := s.ctx()
322345
lggr := logger.Sugared(s.Logger.Named("RunNode"))
@@ -423,6 +446,11 @@ func (s *Shell) runNode(c *cli.Context) error {
423446
}
424447
}
425448
if s.Config.OCR2().Enabled() {
449+
err2 := importKeyIfProvided(rootCtx, lggr, "OCR2Key", s.Config.ImportedOCR2Key(), app.GetKeyStore().OCR2())
450+
if err2 != nil {
451+
return s.errorOut(err2)
452+
}
453+
426454
var enabledChains []chaintype.ChainType
427455
if s.Config.EVMEnabled() {
428456
enabledChains = append(enabledChains, chaintype.EVM)
@@ -448,23 +476,19 @@ func (s *Shell) runNode(c *cli.Context) error {
448476
if s.Config.SuiEnabled() {
449477
enabledChains = append(enabledChains, chaintype.Sui)
450478
}
451-
err2 := app.GetKeyStore().OCR2().EnsureKeys(rootCtx, enabledChains...)
479+
err2 = app.GetKeyStore().OCR2().EnsureKeys(rootCtx, enabledChains...)
452480
if err2 != nil {
453481
return fmt.Errorf("failed to ensure ocr key: %w", err2)
454482
}
455483
}
456484

457485
if s.Config.P2P().Enabled() {
458-
if s.Config.ImportedP2PKey().JSON() != "" {
459-
lggr.Debugf("Importing p2p key %s", s.Config.ImportedP2PKey().JSON())
460-
_, err2 := app.GetKeyStore().P2P().Import(rootCtx, []byte(s.Config.ImportedP2PKey().JSON()), s.Config.ImportedP2PKey().Password())
461-
if errors.Is(err2, keystore.ErrKeyExists) {
462-
lggr.Debugf("P2P key already exists %s", s.Config.ImportedP2PKey().JSON())
463-
} else if err2 != nil {
464-
return s.errorOut(fmt.Errorf("error importing p2p key: %w", err2))
465-
}
486+
err2 := importKeyIfProvided(rootCtx, lggr, "P2P", s.Config.ImportedP2PKey(), app.GetKeyStore().P2P())
487+
if err2 != nil {
488+
return s.errorOut(err2)
466489
}
467-
err2 := app.GetKeyStore().P2P().EnsureKey(rootCtx)
490+
491+
err2 = app.GetKeyStore().P2P().EnsureKey(rootCtx)
468492
if err2 != nil {
469493
return fmt.Errorf("failed to ensure p2p key: %w", err2)
470494
}
@@ -525,17 +549,12 @@ func (s *Shell) runNode(c *cli.Context) error {
525549
}
526550
}
527551
if s.Config.CRE().EnableDKGRecipient() {
528-
if s.Config.ImportedDKGRecipientKey().JSON() != "" {
529-
lggr.Debugf("Importing DKG recipient key %s", s.Config.ImportedDKGRecipientKey().JSON())
530-
_, err2 := app.GetKeyStore().DKGRecipient().Import(rootCtx, []byte(s.Config.ImportedDKGRecipientKey().JSON()), s.Config.ImportedDKGRecipientKey().Password())
531-
if errors.Is(err2, keystore.ErrKeyExists) {
532-
lggr.Debugf("DKG recipient key already exists %s", s.Config.ImportedDKGRecipientKey().JSON())
533-
} else if err2 != nil {
534-
return s.errorOut(fmt.Errorf("error importing dkg recipient key: %w", err2))
535-
}
552+
err2 := importKeyIfProvided(rootCtx, lggr, "DKG Recipient", s.Config.ImportedDKGRecipientKey(), app.GetKeyStore().DKGRecipient())
553+
if err2 != nil {
554+
return s.errorOut(err2)
536555
}
537556

538-
err2 := app.GetKeyStore().DKGRecipient().EnsureKey(rootCtx)
557+
err2 = app.GetKeyStore().DKGRecipient().EnsureKey(rootCtx)
539558
if err2 != nil {
540559
return fmt.Errorf("failed to ensure dkg recipient key: %w", err2)
541560
}
@@ -546,6 +565,11 @@ func (s *Shell) runNode(c *cli.Context) error {
546565
return fmt.Errorf("failed to ensure workflow key: %w", err2)
547566
}
548567

568+
err2 = importKeyIfProvided(rootCtx, lggr, "CSA", s.Config.ImportedCSAKey(), app.GetKeyStore().CSA())
569+
if err2 != nil {
570+
return s.errorOut(err2)
571+
}
572+
549573
err2 = app.GetKeyStore().CSA().EnsureKey(rootCtx)
550574
if err2 != nil {
551575
return fmt.Errorf("failed to ensure CSA key: %w", err2)

core/config/toml/types.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,8 @@ type Secrets struct {
146146

147147
P2PKey P2PKey `toml:",omitempty"`
148148
DKGRecipientKey DKGRecipientKey `toml:",omitempty"`
149+
CSAKey CSAKey `toml:",omitempty"`
150+
OCR2Key OCR2Key `toml:",omitempty"`
149151

150152
CRE CreSecrets `toml:",omitempty"`
151153
CCV CCVSecrets `toml:",omitempty"`
@@ -496,6 +498,78 @@ func (p *DKGRecipientKey) ValidateConfig() (err error) {
496498
return err
497499
}
498500

501+
type CSAKey struct {
502+
JSON *models.Secret
503+
Password *models.Secret
504+
}
505+
506+
func (p *CSAKey) SetFrom(f *CSAKey) (err error) {
507+
err = p.validateMerge(f)
508+
if err != nil {
509+
return err
510+
}
511+
if v := f.JSON; v != nil {
512+
p.JSON = v
513+
}
514+
if v := f.Password; v != nil {
515+
p.Password = v
516+
}
517+
return nil
518+
}
519+
520+
func (p *CSAKey) validateMerge(f *CSAKey) (err error) {
521+
if p.JSON != nil && f.JSON != nil {
522+
err = errors.Join(err, configutils.ErrOverride{Name: "JSON"})
523+
}
524+
if p.Password != nil && f.Password != nil {
525+
err = errors.Join(err, configutils.ErrOverride{Name: "Password"})
526+
}
527+
return err
528+
}
529+
530+
func (p *CSAKey) ValidateConfig() (err error) {
531+
if (p.JSON != nil) != (p.Password != nil) {
532+
err = errors.Join(err, configutils.ErrInvalid{Name: "CSAKey", Value: p.JSON, Msg: "all fields must be nil or non-nil"})
533+
}
534+
return err
535+
}
536+
537+
type OCR2Key struct {
538+
JSON *models.Secret
539+
Password *models.Secret
540+
}
541+
542+
func (p *OCR2Key) SetFrom(f *OCR2Key) (err error) {
543+
err = p.validateMerge(f)
544+
if err != nil {
545+
return err
546+
}
547+
if v := f.JSON; v != nil {
548+
p.JSON = v
549+
}
550+
if v := f.Password; v != nil {
551+
p.Password = v
552+
}
553+
return nil
554+
}
555+
556+
func (p *OCR2Key) validateMerge(f *OCR2Key) (err error) {
557+
if p.JSON != nil && f.JSON != nil {
558+
err = errors.Join(err, configutils.ErrOverride{Name: "JSON"})
559+
}
560+
if p.Password != nil && f.Password != nil {
561+
err = errors.Join(err, configutils.ErrOverride{Name: "Password"})
562+
}
563+
return err
564+
}
565+
566+
func (p *OCR2Key) ValidateConfig() (err error) {
567+
if (p.JSON != nil) != (p.Password != nil) {
568+
err = errors.Join(err, configutils.ErrInvalid{Name: "OCR2Key", Value: p.JSON, Msg: "all fields must be nil or non-nil"})
569+
}
570+
return err
571+
}
572+
499573
type Passwords struct {
500574
Keystore *models.Secret
501575
VRF *models.Secret

core/scripts/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,7 @@ require (
494494
github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260119161343-499241536dea // indirect
495495
github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20250912190424-fd2e35d7deb5 // indirect
496496
github.com/smartcontractkit/chainlink-ccv v0.0.0-20260122132406-0ada7a3fe04a // indirect
497+
github.com/smartcontractkit/chainlink-common/keystore v1.0.1 // indirect
497498
github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20251211140724-319861e514c4 // indirect
498499
github.com/smartcontractkit/chainlink-evm/contracts/cre/gobindings v0.0.0-20260107191744-4b93f62cffe3 // indirect
499500
github.com/smartcontractkit/chainlink-feeds v0.1.2-0.20250227211209-7cd000095135 // indirect

core/scripts/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1631,6 +1631,8 @@ github.com/smartcontractkit/chainlink-ccv v0.0.0-20260122132406-0ada7a3fe04a h1:
16311631
github.com/smartcontractkit/chainlink-ccv v0.0.0-20260122132406-0ada7a3fe04a/go.mod h1:Xe0SH5IHtGkCW6sy/EdBRPKD5L+U52HgoGfl0KDP/lw=
16321632
github.com/smartcontractkit/chainlink-common v0.9.6-0.20260129031639-d791b142161a h1:802owE4JWLyTMEJOMPyX9vD/ikw35w9Lga9to+9y1rY=
16331633
github.com/smartcontractkit/chainlink-common v0.9.6-0.20260129031639-d791b142161a/go.mod h1:Eg5rz/fQINjR9H0TxHw7j+zGZeYxprUpEQZzC5JGHG4=
1634+
github.com/smartcontractkit/chainlink-common/keystore v1.0.1 h1:yvg+wR1HZJ18f2+saZsfnwTAo3/NFEATKN3JbhWDVhU=
1635+
github.com/smartcontractkit/chainlink-common/keystore v1.0.1/go.mod h1:pd4CKmMJHQxzDk1xxxX0/uhQwq8N0iyKd0/RCv54/S8=
16341636
github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20251211140724-319861e514c4 h1:NOUsjsMzNecbjiPWUQGlRSRAutEvCFrqqyETDJeh5q4=
16351637
github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20251211140724-319861e514c4/go.mod h1:Zpvul9sTcZNAZOVzt5vBl1XZGNvQebFpnpn3/KOQvOQ=
16361638
github.com/smartcontractkit/chainlink-common/pkg/monitoring v0.0.0-20251215152504-b1e41f508340 h1:PsjEI+5jZIz9AS4eOsLS5VpSWJINf38clXV3wryPyMk=

core/services/chainlink/config_general.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,14 @@ func (g *generalConfig) ImportedP2PKey() coreconfig.ImportableKey {
573573
return &importedP2PKeyConfig{s: g.secrets.P2PKey}
574574
}
575575

576+
func (g *generalConfig) ImportedCSAKey() coreconfig.ImportableKey {
577+
return &importedCSAKeyConfig{s: g.secrets.CSAKey}
578+
}
579+
580+
func (g *generalConfig) ImportedOCR2Key() coreconfig.ImportableKey {
581+
return &importedOCR2KeyConfig{s: g.secrets.OCR2Key}
582+
}
583+
576584
func (g *generalConfig) Tracing() coreconfig.Tracing {
577585
return &tracingConfig{s: g.c.Tracing}
578586
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package chainlink
2+
3+
import "github.com/smartcontractkit/chainlink/v2/core/config/toml"
4+
5+
type importedCSAKeyConfig struct {
6+
s toml.CSAKey
7+
}
8+
9+
func (t *importedCSAKeyConfig) JSON() string {
10+
if t.s.JSON == nil {
11+
return ""
12+
}
13+
return string(*t.s.JSON)
14+
}
15+
16+
func (t *importedCSAKeyConfig) Password() string {
17+
if t.s.Password == nil {
18+
return ""
19+
}
20+
return string(*t.s.Password)
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package chainlink
2+
3+
import "github.com/smartcontractkit/chainlink/v2/core/config/toml"
4+
5+
type importedOCR2KeyConfig struct {
6+
s toml.OCR2Key
7+
}
8+
9+
func (t *importedOCR2KeyConfig) JSON() string {
10+
if t.s.JSON == nil {
11+
return ""
12+
}
13+
return string(*t.s.JSON)
14+
}
15+
16+
func (t *importedOCR2KeyConfig) Password() string {
17+
if t.s.Password == nil {
18+
return ""
19+
}
20+
return string(*t.s.Password)
21+
}

core/services/chainlink/mocks/general_config.go

Lines changed: 94 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/services/chainlink/types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,6 @@ type ImportedSecretConfig interface {
3030
ImportedEthKeys() coreconfig.ImportableChainKeyLister
3131
ImportedSolKeys() coreconfig.ImportableChainKeyLister
3232
ImportedDKGRecipientKey() coreconfig.ImportableKey
33+
ImportedCSAKey() coreconfig.ImportableKey
34+
ImportedOCR2Key() coreconfig.ImportableKey
3335
}

0 commit comments

Comments
 (0)