Skip to content

Commit 87b446d

Browse files
committed
Merge branch 'main' into ARCH-339-cobra-cli
2 parents e64c1d2 + ec6d837 commit 87b446d

36 files changed

+1773
-138
lines changed

.github/workflows/keystore.yml

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,34 @@ name: Keystore Checks
22
permissions:
33
contents: read
44

5-
on:
6-
push:
7-
paths:
8-
- "keystore/**"
5+
on: push
96

107
jobs:
8+
changes:
9+
name: detect changes
10+
runs-on: ubuntu-latest
11+
outputs:
12+
keystore-src: ${{ steps.keystore-changes.outputs.src }}
13+
steps:
14+
- name: Checkout the repo
15+
uses: actions/checkout@v4
16+
with:
17+
persist-credentials: false
18+
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
19+
id: keystore-changes
20+
with:
21+
filters: |
22+
src:
23+
- 'keystore/**'
24+
1125
run-tests:
26+
name: run tests
27+
runs-on: ubuntu-latest
28+
needs: changes
29+
if: needs.changes.outputs.keystore-src == 'true'
1230
defaults:
1331
run:
1432
working-directory: keystore
15-
runs-on: ubuntu-latest
1633
steps:
1734
- name: Checkout
1835
uses: actions/checkout@v4
@@ -30,7 +47,13 @@ jobs:
3047
run: go test ./... -coverpkg=./... -coverprofile=coverage.txt
3148

3249
build-race-tests:
50+
name: race tests
3351
runs-on: ubuntu-latest
52+
needs: changes
53+
if: needs.changes.outputs.keystore-src == 'true'
54+
defaults:
55+
run:
56+
working-directory: keystore
3457
steps:
3558
- name: Checkout
3659
uses: actions/checkout@v4
@@ -61,4 +84,17 @@ jobs:
6184
with:
6285
name: go-race-results
6386
path: |
64-
./race.*
87+
./race.*
88+
89+
gate:
90+
name: summary gate
91+
runs-on: ubuntu-latest
92+
needs: [changes, run-tests, build-race-tests]
93+
if: always()
94+
steps:
95+
- name: Fail if any job ran and failed
96+
if: needs.changes.outputs.keystore-src == 'true' &&
97+
(needs.run-tests.result != 'success' ||
98+
needs.build-race-tests.result != 'success')
99+
run: exit 1
100+

go.mod

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ require (
2020
github.com/google/uuid v1.6.0
2121
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1
2222
github.com/hashicorp/go-hclog v1.6.3
23-
github.com/hashicorp/go-plugin v1.6.3
23+
github.com/hashicorp/go-plugin v1.7.0
2424
github.com/iancoleman/strcase v0.3.0
2525
github.com/invopop/jsonschema v0.13.0
2626
github.com/jackc/pgx/v4 v4.18.3
@@ -83,7 +83,6 @@ require (
8383
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 // indirect
8484
github.com/bahlo/generic-list-go v0.2.0 // indirect
8585
github.com/beorn7/perks v1.0.1 // indirect
86-
github.com/bufbuild/protocompile v0.14.1 // indirect
8786
github.com/buger/goterm v1.0.4 // indirect
8887
github.com/buger/jsonparser v1.1.1 // indirect
8988
github.com/cenkalti/backoff/v5 v5.0.2 // indirect
@@ -112,7 +111,6 @@ require (
112111
github.com/jackc/pgproto3/v2 v2.3.3 // indirect
113112
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
114113
github.com/jackc/pgtype v1.14.4 // indirect
115-
github.com/jhump/protoreflect v1.15.3 // indirect
116114
github.com/json-iterator/go v1.1.12 // indirect
117115
github.com/klauspost/compress v1.18.0 // indirect
118116
github.com/klauspost/cpuid/v2 v2.2.10 // indirect

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,8 @@ github.com/hako/durafmt v0.0.0-20200710122514-c0fb7b4da026 h1:BpJ2o0OR5FV7vrkDYf
143143
github.com/hako/durafmt v0.0.0-20200710122514-c0fb7b4da026/go.mod h1:5Scbynm8dF1XAPwIwkGPqzkM/shndPm79Jd1003hTjE=
144144
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
145145
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
146-
github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0UUrwg=
147-
github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0=
146+
github.com/hashicorp/go-plugin v1.7.0 h1:YghfQH/0QmPNc/AZMTFE3ac8fipZyZECHdDPshfk+mA=
147+
github.com/hashicorp/go-plugin v1.7.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8=
148148
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
149149
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
150150
github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=
@@ -202,8 +202,8 @@ github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0f
202202
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
203203
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
204204
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
205-
github.com/jhump/protoreflect v1.15.3 h1:6SFRuqU45u9hIZPJAoZ8c28T3nK64BNdp9w6jFonzls=
206-
github.com/jhump/protoreflect v1.15.3/go.mod h1:4ORHmSBmlCW8fh3xHmJMGyul1zNqZK4Elxc8qKP+p1k=
205+
github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94=
206+
github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8=
207207
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
208208
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
209209
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=

keystore/core_keystore.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package keystore
2+
3+
import (
4+
"context"
5+
)
6+
7+
// CoreKeystore implements the core.Keystore interface for backwards compatibility with the old keystore
8+
// https://github.com/smartcontractkit/chainlink-common/blob/main/pkg/types/core/keystore.go#L23
9+
// We don't add a dependency directly to chainlink-common here to avoid circular dependencies.
10+
type CoreKeystore struct {
11+
ks Keystore
12+
}
13+
14+
func NewCoreKeystore(ks Keystore) *CoreKeystore {
15+
return &CoreKeystore{ks: ks}
16+
}
17+
18+
func (c *CoreKeystore) Accounts(ctx context.Context) ([]string, error) {
19+
// List all the keys in the keystore.
20+
keys, err := c.ks.GetKeys(ctx, GetKeysRequest{})
21+
if err != nil {
22+
return nil, err
23+
}
24+
accounts := make([]string, 0, len(keys.Keys))
25+
for _, key := range keys.Keys {
26+
accounts = append(accounts, key.KeyInfo.Name)
27+
}
28+
return accounts, nil
29+
}
30+
31+
func (c *CoreKeystore) Sign(ctx context.Context, account string, data []byte) ([]byte, error) {
32+
resp, err := c.ks.Sign(ctx, SignRequest{
33+
KeyName: account,
34+
Data: data,
35+
})
36+
if err != nil {
37+
return nil, err
38+
}
39+
return resp.Signature, nil
40+
}
41+
42+
func (c *CoreKeystore) Decrypt(ctx context.Context, account string, data []byte) ([]byte, error) {
43+
resp, err := c.ks.Decrypt(ctx, DecryptRequest{
44+
KeyName: account,
45+
EncryptedData: data,
46+
})
47+
if err != nil {
48+
return nil, err
49+
}
50+
return resp.Data, nil
51+
}

keystore/core_keystore_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package keystore_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/ethereum/go-ethereum/crypto"
7+
"github.com/stretchr/testify/require"
8+
9+
"github.com/smartcontractkit/chainlink-common/keystore"
10+
)
11+
12+
func TestCoreKeystore(t *testing.T) {
13+
ctx := t.Context()
14+
15+
ks, err := keystore.LoadKeystore(t.Context(), keystore.NewMemoryStorage(), "test-password", keystore.WithScryptParams(keystore.FastScryptParams))
16+
require.NoError(t, err)
17+
coreKs := keystore.NewCoreKeystore(ks)
18+
19+
keysResp, err := ks.CreateKeys(ctx, keystore.CreateKeysRequest{
20+
Keys: []keystore.CreateKeyRequest{
21+
{KeyName: "encrypt", KeyType: keystore.X25519},
22+
{KeyName: "sign", KeyType: keystore.ECDSA_S256},
23+
},
24+
})
25+
require.NoError(t, err)
26+
require.Equal(t, 2, len(keysResp.Keys))
27+
28+
accounts, err := coreKs.Accounts(ctx)
29+
require.NoError(t, err)
30+
require.Equal(t, []string{"encrypt", "sign"}, accounts)
31+
32+
signature, err := coreKs.Sign(ctx, "sign", crypto.Keccak256([]byte("test-data-to-sign")))
33+
require.NoError(t, err)
34+
require.NotEmpty(t, signature)
35+
verifyResp, err := ks.Verify(ctx, keystore.VerifyRequest{
36+
KeyType: keysResp.Keys[1].KeyInfo.KeyType,
37+
PublicKey: keysResp.Keys[1].KeyInfo.PublicKey,
38+
Data: crypto.Keccak256([]byte("test-data-to-sign")),
39+
Signature: signature,
40+
})
41+
require.NoError(t, err)
42+
require.True(t, verifyResp.Valid)
43+
44+
encryptedData, err := ks.Encrypt(ctx, keystore.EncryptRequest{
45+
RemoteKeyType: keysResp.Keys[0].KeyInfo.KeyType,
46+
RemotePubKey: keysResp.Keys[0].KeyInfo.PublicKey,
47+
Data: []byte("test-data-to-encrypt"),
48+
})
49+
require.NoError(t, err)
50+
require.NotEmpty(t, encryptedData)
51+
52+
decryptedData, err := coreKs.Decrypt(ctx, "encrypt", encryptedData.EncryptedData)
53+
require.NoError(t, err)
54+
require.Equal(t, []byte("test-data-to-encrypt"), decryptedData)
55+
}

keystore/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ require (
88
github.com/lib/pq v1.10.9
99
github.com/natefinch/atomic v1.0.1
1010
github.com/smartcontractkit/chainlink-common v0.9.6
11+
github.com/smartcontractkit/libocr v0.0.0-20250707144819-babe0ec4e358
1112
github.com/spf13/cobra v1.8.1
1213
github.com/stretchr/testify v1.10.0
1314
golang.org/x/crypto v0.42.0
@@ -78,7 +79,6 @@ require (
7879
github.com/shopspring/decimal v1.4.0 // indirect
7980
github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.4 // indirect
8081
github.com/smartcontractkit/freeport v0.1.3-0.20250716200817-cb5dfd0e369e // indirect
81-
github.com/smartcontractkit/libocr v0.0.0-20250707144819-babe0ec4e358 // indirect
8282
github.com/spf13/pflag v1.0.6 // indirect
8383
github.com/supranational/blst v0.3.14 // indirect
8484
github.com/tklauser/go-sysconf v0.3.15 // indirect

keystore/keystore.go

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"fmt"
1010
"io"
1111
"slices"
12+
"strings"
1213
"sync"
1314
"testing"
1415
"time"
@@ -25,6 +26,38 @@ import (
2526
"github.com/smartcontractkit/chainlink-common/keystore/serialization"
2627
)
2728

29+
type KeyPath []string
30+
31+
func (k KeyPath) String() string {
32+
return joinKeySegments(k...)
33+
}
34+
35+
func (k KeyPath) Base() string {
36+
return k[len(k)-1]
37+
}
38+
39+
func NewKeyPath(segments ...string) KeyPath {
40+
return segments
41+
}
42+
43+
func NewKeyPathFromString(fullName string) KeyPath {
44+
return strings.Split(fullName, "/")
45+
}
46+
47+
// joinKeySegments joins path-like key name segments using "/" and avoids double slashes.
48+
// Empty segments are skipped so joinKeySegments("EVM", "TX", "my-key") => "EVM/TX/my-key".
49+
func joinKeySegments(segments ...string) string {
50+
cleaned := make([]string, 0, len(segments))
51+
for _, s := range segments {
52+
s = strings.Trim(s, "/")
53+
if s == "" {
54+
continue
55+
}
56+
cleaned = append(cleaned, s)
57+
}
58+
return strings.Join(cleaned, "/")
59+
}
60+
2861
type KeyType string
2962

3063
func (k KeyType) String() string {
@@ -52,17 +85,17 @@ const (
5285
// ECDH_P256:
5386
// - ECDH on P-256
5487
// - Encryption with AES-GCM and HKDF-SHA256
55-
ECDH_P256 KeyType = "ecdh-p256"
88+
ECDH_P256 KeyType = "ECDH_P256"
5689

5790
// Digital signature key types.
5891
// Ed25519:
5992
// - Ed25519 for digital signatures.
6093
// - Supports arbitrary messages sizes, no hashing required.
61-
Ed25519 KeyType = "ed25519"
94+
Ed25519 KeyType = "Ed25519"
6295
// ECDSA_S256:
6396
// - ECDSA on secp256k1 for digital signatures.
6497
// - Only signs 32 byte digests. Caller must hash the data before signing.
65-
ECDSA_S256 KeyType = "ecdsa-secp256k1"
98+
ECDSA_S256 KeyType = "ECDSA_S256"
6699
)
67100

68101
var AllKeyTypes = []KeyType{X25519, ECDH_P256, Ed25519, ECDSA_S256}

keystore/keystore_internal_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,23 @@ func TestPublicKeyFromPrivateKey(t *testing.T) {
3030
// We use SEC1 (uncompressed) format for ECDH public keys.
3131
require.Equal(t, 65, len(pubKey))
3232
}
33+
34+
func TestJoinKeySegments(t *testing.T) {
35+
tests := []struct {
36+
segments []string
37+
expected string
38+
}{
39+
{segments: []string{"EVM", "TX", "my-key"}, expected: "EVM/TX/my-key"},
40+
{segments: []string{"EVM", "/TX", "my-key"}, expected: "EVM/TX/my-key"},
41+
{segments: []string{"EVM", "TX/", "my-key"}, expected: "EVM/TX/my-key"},
42+
{segments: []string{"EVM", "TX", "/my-key"}, expected: "EVM/TX/my-key"},
43+
{segments: []string{"EVM", "TX", "my-key", ""}, expected: "EVM/TX/my-key"},
44+
{segments: []string{"EVM", "TX", "my-key", "/"}, expected: "EVM/TX/my-key"},
45+
{segments: []string{"EVM", "TX", "my-key", "//"}, expected: "EVM/TX/my-key"},
46+
{segments: []string{"EVM", "TX", "my-key", "///"}, expected: "EVM/TX/my-key"},
47+
{segments: []string{"EVM", "TX", "my-key", "////"}, expected: "EVM/TX/my-key"},
48+
}
49+
for _, tt := range tests {
50+
require.Equal(t, tt.expected, joinKeySegments(tt.segments...))
51+
}
52+
}

0 commit comments

Comments
 (0)