Skip to content

Commit 081f3f3

Browse files
Merge branch 'main' into CAPPL-1084-dontime-metrics
2 parents f161838 + 39714ba commit 081f3f3

File tree

69 files changed

+2814
-574
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+2814
-574
lines changed

go.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ flowchart LR
1515
chainlink-common --> grpc-proxy
1616
chainlink-common --> libocr
1717
click chainlink-common href "https://github.com/smartcontractkit/chainlink-common"
18+
chainlink-common/keystore --> chainlink-common
19+
click chainlink-common/keystore href "https://github.com/smartcontractkit/chainlink-common"
1820
chainlink-common/pkg/chipingress
1921
click chainlink-common/pkg/chipingress href "https://github.com/smartcontractkit/chainlink-common"
2022
chainlink-common/pkg/monitoring --> chainlink-common
@@ -40,6 +42,7 @@ flowchart LR
4042
4143
subgraph chainlink-common-repo[chainlink-common]
4244
chainlink-common
45+
chainlink-common/keystore
4346
chainlink-common/pkg/chipingress
4447
chainlink-common/pkg/monitoring
4548
chainlink-common/pkg/values

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ require (
3737
github.com/scylladb/go-reflectx v1.0.1
3838
github.com/shopspring/decimal v1.4.0
3939
github.com/smartcontractkit/chain-selectors v1.0.67
40-
github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.9-0.20251020164035-ab562b473fe2
40+
github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10
4141
github.com/smartcontractkit/chainlink-protos/billing/go v0.0.0-20251024234028-0988426d98f4
4242
github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20251021010742-3f8d3dba17d8
4343
github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20251002192024-d2ad9222409b

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -326,8 +326,8 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB
326326
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
327327
github.com/smartcontractkit/chain-selectors v1.0.67 h1:gxTqP/JC40KDe3DE1SIsIKSTKTZEPyEU1YufO1admnw=
328328
github.com/smartcontractkit/chain-selectors v1.0.67/go.mod h1:xsKM0aN3YGcQKTPRPDDtPx2l4mlTN1Djmg0VVXV40b8=
329-
github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.9-0.20251020164035-ab562b473fe2 h1:p79eZtyBbZYumftwZGCkyKSNDvUralW7lqcTD99Ovmw=
330-
github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.9-0.20251020164035-ab562b473fe2/go.mod h1:oiDa54M0FwxevWwyAX773lwdWvFYYlYHHQV1LQ5HpWY=
329+
github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10 h1:FJAFgXS9oqASnkS03RE1HQwYQQxrO4l46O5JSzxqLgg=
330+
github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10/go.mod h1:oiDa54M0FwxevWwyAX773lwdWvFYYlYHHQV1LQ5HpWY=
331331
github.com/smartcontractkit/chainlink-protos/billing/go v0.0.0-20251024234028-0988426d98f4 h1:GCzrxDWn3b7jFfEA+WiYRi8CKoegsayiDoJBCjYkneE=
332332
github.com/smartcontractkit/chainlink-protos/billing/go v0.0.0-20251024234028-0988426d98f4/go.mod h1:HHGeDUpAsPa0pmOx7wrByCitjQ0mbUxf0R9v+g67uCA=
333333
github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20251021010742-3f8d3dba17d8 h1:hPeEwcvRVtwhyNXH45qbzqmscqlbygu94cROwbjyzNQ=

keystore/admin.go

Lines changed: 107 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,19 @@ import (
66
"crypto/ecdsa"
77
"crypto/ed25519"
88
"crypto/rand"
9+
"encoding/json"
910
"fmt"
1011
"maps"
1112
"time"
1213

14+
gethkeystore "github.com/ethereum/go-ethereum/accounts/keystore"
1315
"golang.org/x/crypto/curve25519"
16+
"google.golang.org/protobuf/proto"
1417

1518
"github.com/ethereum/go-ethereum/crypto"
19+
1620
"github.com/smartcontractkit/chainlink-common/keystore/internal"
21+
"github.com/smartcontractkit/chainlink-common/keystore/serialization"
1722
)
1823

1924
var (
@@ -51,9 +56,9 @@ type ImportKeysRequest struct {
5156
}
5257

5358
type ImportKeyRequest struct {
54-
KeyName string
55-
KeyType KeyType
56-
Data []byte
59+
KeyName string
60+
Data []byte
61+
Password string
5762
}
5863

5964
type ImportKeysResponse struct{}
@@ -227,14 +232,110 @@ func (k *keystore) DeleteKeys(ctx context.Context, req DeleteKeysRequest) (Delet
227232
return DeleteKeysResponse{}, nil
228233
}
229234

230-
func (k *keystore) ImportKeys(ctx context.Context, req ImportKeysRequest) (ImportKeysResponse, error) {
235+
func (ks *keystore) ImportKeys(ctx context.Context, req ImportKeysRequest) (ImportKeysResponse, error) {
236+
ks.mu.Lock()
237+
defer ks.mu.Unlock()
238+
239+
ksCopy := maps.Clone(ks.keystore)
240+
for _, keyReq := range req.Keys {
241+
if err := ValidKeyName(keyReq.KeyName); err != nil {
242+
return ImportKeysResponse{}, fmt.Errorf("%w: %s", ErrInvalidKeyName, err)
243+
}
244+
if _, ok := ksCopy[keyReq.KeyName]; ok {
245+
return ImportKeysResponse{}, fmt.Errorf("%w: %s", ErrKeyAlreadyExists, keyReq.KeyName)
246+
}
247+
encData := gethkeystore.CryptoJSON{}
248+
err := json.Unmarshal(keyReq.Data, &encData)
249+
if err != nil {
250+
return ImportKeysResponse{}, fmt.Errorf("key = %s, failed to unmarshal encrypted import data: %w", keyReq.KeyName, err)
251+
}
252+
decData, err := gethkeystore.DecryptDataV3(encData, keyReq.Password)
253+
if err != nil {
254+
return ImportKeysResponse{}, fmt.Errorf("key = %s, failed to decrypt key: %w", keyReq.KeyName, err)
255+
}
256+
keypb := &serialization.Key{}
257+
err = proto.Unmarshal(decData, keypb)
258+
if err != nil {
259+
return ImportKeysResponse{}, fmt.Errorf("key = %s, failed to unmarshal key: %w", keyReq.KeyName, err)
260+
}
261+
pkRaw := internal.NewRaw(keypb.PrivateKey)
262+
keyType := KeyType(keypb.KeyType)
263+
publicKey, err := publicKeyFromPrivateKey(pkRaw, keyType)
264+
if err != nil {
265+
return ImportKeysResponse{}, fmt.Errorf("key = %s, failed to get public key from private key: %w", keyReq.KeyName, err)
266+
}
267+
metadata := keypb.Metadata
268+
// The proto compiler sets empty slices to nil during the serialization (https://github.com/golang/protobuf/issues/1348).
269+
// We set metadata back to empty slice to be consistent with the Create method which initializes it as such.
270+
if metadata == nil {
271+
metadata = []byte{}
272+
}
273+
ksCopy[keyReq.KeyName] = newKey(keyType, pkRaw, publicKey, time.Unix(keypb.CreatedAt, 0), metadata)
274+
}
275+
// Persist it to storage.
276+
if err := ks.save(ctx, ksCopy); err != nil {
277+
return ImportKeysResponse{}, fmt.Errorf("failed to save keystore: %w", err)
278+
}
279+
// If we succeed to save, update the in memory keystore.
280+
ks.keystore = ksCopy
231281
return ImportKeysResponse{}, nil
232282
}
233283

234-
func (k *keystore) ExportKeys(ctx context.Context, req ExportKeysRequest) (ExportKeysResponse, error) {
235-
return ExportKeysResponse{}, nil
284+
func (ks *keystore) ExportKeys(_ context.Context, req ExportKeysRequest) (ExportKeysResponse, error) {
285+
ks.mu.RLock()
286+
defer ks.mu.RUnlock()
287+
288+
result := ExportKeysResponse{}
289+
for _, keyReq := range req.Keys {
290+
key, ok := ks.keystore[keyReq.KeyName]
291+
if !ok {
292+
return ExportKeysResponse{}, fmt.Errorf("%w: %s", ErrKeyNotFound, keyReq.KeyName)
293+
}
294+
keypb := &serialization.Key{
295+
Name: keyReq.KeyName,
296+
KeyType: string(key.keyType),
297+
PrivateKey: internal.Bytes(key.privateKey),
298+
CreatedAt: key.createdAt.Unix(),
299+
Metadata: key.metadata,
300+
}
301+
serialized, err := proto.Marshal(keypb)
302+
if err != nil {
303+
return ExportKeysResponse{}, fmt.Errorf("key = %s, failed to marshal key: %w", keyReq.KeyName, err)
304+
}
305+
encData, err := gethkeystore.EncryptDataV3(serialized, []byte(keyReq.Enc.Password), keyReq.Enc.ScryptParams.N, keyReq.Enc.ScryptParams.P)
306+
if err != nil {
307+
return ExportKeysResponse{}, fmt.Errorf("key = %s, failed to encrypt key: %w", keyReq.KeyName, err)
308+
}
309+
encDataBytes, err := json.Marshal(encData)
310+
if err != nil {
311+
return ExportKeysResponse{}, fmt.Errorf("key = %s, failed to marshal encrypted key: %w", keyReq.KeyName, err)
312+
}
313+
result.Keys = append(result.Keys, ExportKeyResponse{
314+
KeyName: keyReq.KeyName,
315+
Data: encDataBytes,
316+
})
317+
}
318+
return result, nil
236319
}
237320

238321
func (ks *keystore) SetMetadata(ctx context.Context, req SetMetadataRequest) (SetMetadataResponse, error) {
322+
ks.mu.Lock()
323+
defer ks.mu.Unlock()
324+
325+
ksCopy := maps.Clone(ks.keystore)
326+
for _, metReq := range req.Updates {
327+
key, ok := ksCopy[metReq.KeyName]
328+
if !ok {
329+
return SetMetadataResponse{}, fmt.Errorf("%w: %s", ErrKeyNotFound, metReq.KeyName)
330+
}
331+
key.metadata = metReq.Metadata
332+
ksCopy[metReq.KeyName] = key
333+
}
334+
// Persist it to storage.
335+
if err := ks.save(ctx, ksCopy); err != nil {
336+
return SetMetadataResponse{}, fmt.Errorf("failed to save keystore: %w", err)
337+
}
338+
// If we succeed to save, update the in memory keystore.
339+
ks.keystore = ksCopy
239340
return SetMetadataResponse{}, nil
240341
}

keystore/admin_test.go

Lines changed: 109 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,7 @@ func TestKeystore_CreateDeleteReadKeys(t *testing.T) {
111111
for _, tt := range tt {
112112
t.Run(tt.name, func(t *testing.T) {
113113
storage := keystore.NewMemoryStorage()
114-
ks, err := keystore.LoadKeystore(ctx, storage, keystore.EncryptionParams{
115-
Password: "test-password",
116-
ScryptParams: keystore.FastScryptParams,
117-
})
114+
ks, err := keystore.LoadKeystore(ctx, storage, "test-password", keystore.WithScryptParams(keystore.FastScryptParams))
118115
require.NoError(t, err)
119116
for _, op := range tt.keyOps {
120117
switch op.op {
@@ -163,10 +160,7 @@ func TestKeystore_ConcurrentCreateAndRead(t *testing.T) {
163160

164161
ctx := context.Background()
165162
st := keystore.NewMemoryStorage()
166-
ks, err := keystore.LoadKeystore(ctx, st, keystore.EncryptionParams{
167-
Password: "test",
168-
ScryptParams: keystore.FastScryptParams,
169-
})
163+
ks, err := keystore.LoadKeystore(ctx, st, "test", keystore.WithScryptParams(keystore.FastScryptParams))
170164
require.NoError(t, err)
171165

172166
const (
@@ -210,3 +204,110 @@ func TestKeystore_ConcurrentCreateAndRead(t *testing.T) {
210204
require.NoError(t, err)
211205
require.Equal(t, numWriters*keysPerWriter, len(resp.Keys))
212206
}
207+
208+
func TestKeystore_ExportImport(t *testing.T) {
209+
ks1, err := keystore.LoadKeystore(t.Context(), keystore.NewMemoryStorage(), "ks1")
210+
require.NoError(t, err)
211+
ks2, err := keystore.LoadKeystore(t.Context(), keystore.NewMemoryStorage(), "ks2")
212+
require.NoError(t, err)
213+
214+
t.Run("export and import", func(t *testing.T) {
215+
exportParams := keystore.EncryptionParams{
216+
Password: "export-pass",
217+
ScryptParams: keystore.FastScryptParams,
218+
}
219+
_, err = ks1.CreateKeys(t.Context(), keystore.CreateKeysRequest{
220+
Keys: []keystore.CreateKeyRequest{
221+
{KeyName: "key1", KeyType: keystore.Ed25519},
222+
},
223+
})
224+
require.NoError(t, err)
225+
exportResponse, err := ks1.ExportKeys(t.Context(), keystore.ExportKeysRequest{
226+
Keys: []keystore.ExportKeyParam{
227+
{KeyName: "key1", Enc: exportParams},
228+
},
229+
})
230+
require.NoError(t, err)
231+
require.Len(t, exportResponse.Keys, 1)
232+
_, err = ks2.ImportKeys(t.Context(), keystore.ImportKeysRequest{
233+
Keys: []keystore.ImportKeyRequest{
234+
{KeyName: "key1", Password: exportParams.Password, Data: exportResponse.Keys[0].Data},
235+
},
236+
})
237+
require.NoError(t, err)
238+
key1ks1, err := ks1.GetKeys(t.Context(), keystore.GetKeysRequest{KeyNames: []string{"key1"}})
239+
require.NoError(t, err)
240+
key1ks2, err := ks2.GetKeys(t.Context(), keystore.GetKeysRequest{KeyNames: []string{"key1"}})
241+
require.Equal(t, key1ks1, key1ks2)
242+
243+
// We cannot compare private keys directly, so we test that signing with key1 from ks1 and verifying
244+
// with key1 from ks2 works as if the two keys are the same.
245+
testData := []byte("hello world")
246+
signature, err := ks2.Sign(t.Context(), keystore.SignRequest{
247+
KeyName: "key1",
248+
Data: testData,
249+
})
250+
require.NoError(t, err)
251+
verifyResp, err := ks1.Verify(t.Context(), keystore.VerifyRequest{
252+
KeyType: keystore.Ed25519,
253+
PublicKey: key1ks1.Keys[0].KeyInfo.PublicKey,
254+
Data: testData,
255+
Signature: signature.Signature,
256+
})
257+
require.NoError(t, err)
258+
require.True(t, verifyResp.Valid)
259+
})
260+
261+
t.Run("export non-existent key", func(t *testing.T) {
262+
_, err = ks1.ExportKeys(t.Context(), keystore.ExportKeysRequest{
263+
Keys: []keystore.ExportKeyParam{
264+
{KeyName: "key2", Enc: keystore.EncryptionParams{}},
265+
},
266+
})
267+
require.ErrorIs(t, err, keystore.ErrKeyNotFound)
268+
})
269+
270+
t.Run("import existing key", func(t *testing.T) {
271+
_, err = ks2.ImportKeys(t.Context(), keystore.ImportKeysRequest{
272+
Keys: []keystore.ImportKeyRequest{
273+
{KeyName: "key1", Password: "", Data: []byte{}},
274+
},
275+
})
276+
require.ErrorIs(t, err, keystore.ErrKeyAlreadyExists)
277+
})
278+
}
279+
280+
func TestKeystore_SetMetadata(t *testing.T) {
281+
ks, err := keystore.LoadKeystore(t.Context(), keystore.NewMemoryStorage(), "ks")
282+
require.NoError(t, err)
283+
284+
t.Run("update existing key", func(t *testing.T) {
285+
_, err = ks.CreateKeys(t.Context(), keystore.CreateKeysRequest{
286+
Keys: []keystore.CreateKeyRequest{
287+
{KeyName: "key1", KeyType: keystore.Ed25519},
288+
},
289+
})
290+
require.NoError(t, err)
291+
292+
_, err = ks.SetMetadata(t.Context(), keystore.SetMetadataRequest{
293+
[]keystore.SetMetadataUpdate{
294+
{KeyName: "key1", Metadata: []byte("my-metadata")},
295+
},
296+
})
297+
require.NoError(t, err)
298+
299+
keysResp, err := ks.GetKeys(t.Context(), keystore.GetKeysRequest{KeyNames: []string{"key1"}})
300+
require.NoError(t, err)
301+
require.Len(t, keysResp.Keys, 1)
302+
assert.Equal(t, []byte("my-metadata"), keysResp.Keys[0].KeyInfo.Metadata)
303+
})
304+
305+
t.Run("update non-existent key", func(t *testing.T) {
306+
_, err = ks.SetMetadata(t.Context(), keystore.SetMetadataRequest{
307+
[]keystore.SetMetadataUpdate{
308+
{KeyName: "key2", Metadata: []byte("")},
309+
},
310+
})
311+
require.ErrorIs(t, err, keystore.ErrKeyNotFound)
312+
})
313+
}

0 commit comments

Comments
 (0)