Skip to content

Commit 40de7f5

Browse files
authored
Extend Keystore Interface with Decrypt function (#1467)
* Enable decryption in keystore service * fix comment * update decrypter interface * update keystore impls * Restore old tests and add test for signerdecrypter * restore p2p signer account * improve test coverage * improve test coverage * fix merge conflicts * refactor SignerDecrypter to check for nil signers & decrypters * add comment recommending embedding the UnimplementedKeytore struct * Add CI step for Solana build relay to allow for forward interface compatibility. * fix ci * Remove mocks before generating them fresh * Update mockery version in keystore.pb.go
1 parent 68af090 commit 40de7f5

15 files changed

Lines changed: 1177 additions & 600 deletions

File tree

.github/workflows/build_external.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,12 @@ jobs:
104104
- name: Install Solana CLI
105105
run: ./scripts/install-solana-ci.sh
106106

107+
- name: Update mocks
108+
run: |
109+
make mockery
110+
make rm-mocked
111+
mockery
112+
107113
- name: Build & Test
108114
run: make test_relay_unit
109115

pkg/loop/internal/core/services/keystore/keystore.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ func (k Client) Sign(ctx context.Context, account string, data []byte) ([]byte,
3535
return resp.SignedData, nil
3636
}
3737

38+
func (k Client) Decrypt(ctx context.Context, account string, data []byte) ([]byte, error) {
39+
resp, err := k.grpc.Decrypt(ctx, &pb.DecryptRequest{Account: account, Data: data})
40+
if err != nil {
41+
return nil, fmt.Errorf("failed to decrypt data for account: %s: %w", account, err)
42+
}
43+
44+
return resp.DecryptedData, nil
45+
}
46+
3847
func NewClient(cc grpc.ClientConnInterface) *Client {
3948
return &Client{pb.NewKeystoreClient(cc)}
4049
}
@@ -66,3 +75,12 @@ func (s Server) Sign(ctx context.Context, req *pb.SignRequest) (*pb.SignReply, e
6675

6776
return &pb.SignReply{SignedData: signedData}, nil
6877
}
78+
79+
func (s Server) Decrypt(ctx context.Context, req *pb.DecryptRequest) (*pb.DecryptReply, error) {
80+
decryptedData, err := s.impl.Decrypt(ctx, req.Account, req.Data)
81+
if err != nil {
82+
return nil, fmt.Errorf("failed to decrypt data for account: %s: %w", req.Account, err)
83+
}
84+
85+
return &pb.DecryptReply{DecryptedData: decryptedData}, nil
86+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package keystore
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"google.golang.org/grpc"
9+
"google.golang.org/protobuf/types/known/emptypb"
10+
11+
"github.com/smartcontractkit/chainlink-common/pkg/loop/internal/pb"
12+
)
13+
14+
func Test_KeyStoreClient(t *testing.T) {
15+
ctx := t.Context()
16+
17+
account := "testAccount"
18+
signedData := []byte("signedData")
19+
decryptedData := []byte("decryptedData")
20+
client := Client{grpc: &testGrpcClient{
21+
account: account,
22+
signedData: signedData,
23+
decryptedData: decryptedData,
24+
}}
25+
accounts, err := client.Accounts(ctx)
26+
assert.NoError(t, err)
27+
assert.Equal(t, []string{account}, accounts)
28+
29+
sig, err := client.Sign(ctx, account, []byte("123"))
30+
assert.NoError(t, err)
31+
assert.Equal(t, signedData, sig)
32+
33+
dec, err := client.Decrypt(ctx, account, sig)
34+
assert.NoError(t, err)
35+
assert.Equal(t, decryptedData, dec)
36+
}
37+
38+
func Test_KeyValueStoreServer(t *testing.T) {
39+
ctx := t.Context()
40+
41+
account := "testAccount"
42+
signedData := []byte("signedData")
43+
decryptedData := []byte("decryptedData")
44+
server := Server{impl: &testKeyStore{
45+
account: account,
46+
signedData: signedData,
47+
decryptedData: decryptedData,
48+
}}
49+
50+
accounts, err := server.Accounts(ctx, &emptypb.Empty{})
51+
assert.NoError(t, err)
52+
assert.Equal(t, []string{account}, accounts.Accounts)
53+
54+
sig, err := server.Sign(ctx, &pb.SignRequest{Account: account, Data: []byte("123")})
55+
assert.NoError(t, err)
56+
assert.Equal(t, signedData, sig.SignedData)
57+
58+
dec, err := server.Decrypt(ctx, &pb.DecryptRequest{Account: account, Data: []byte("456")})
59+
assert.NoError(t, err)
60+
assert.Equal(t, decryptedData, dec.DecryptedData)
61+
}
62+
63+
type testGrpcClient struct {
64+
account string
65+
signedData []byte
66+
decryptedData []byte
67+
}
68+
69+
func (t *testGrpcClient) Accounts(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*pb.AccountsReply, error) {
70+
return &pb.AccountsReply{
71+
Accounts: []string{t.account},
72+
}, nil
73+
}
74+
75+
func (t *testGrpcClient) Sign(ctx context.Context, req *pb.SignRequest, opts ...grpc.CallOption) (*pb.SignReply, error) {
76+
return &pb.SignReply{
77+
SignedData: t.signedData,
78+
}, nil
79+
}
80+
81+
func (t *testGrpcClient) Decrypt(ctx context.Context, req *pb.DecryptRequest, opts ...grpc.CallOption) (*pb.DecryptReply, error) {
82+
return &pb.DecryptReply{
83+
DecryptedData: t.decryptedData,
84+
}, nil
85+
}
86+
87+
type testKeyStore struct {
88+
account string
89+
signedData []byte
90+
decryptedData []byte
91+
}
92+
93+
func (t *testKeyStore) Accounts(ctx context.Context) ([]string, error) {
94+
return []string{t.account}, nil
95+
}
96+
97+
func (t *testKeyStore) Sign(ctx context.Context, account string, data []byte) ([]byte, error) {
98+
return t.signedData, nil
99+
}
100+
101+
func (t *testKeyStore) Decrypt(ctx context.Context, account string, data []byte) ([]byte, error) {
102+
return t.decryptedData, nil
103+
}

pkg/loop/internal/core/services/keystore/test/keystore.go

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,21 @@ import (
1313

1414
var Keystore = staticKeystore{
1515
staticKeystoreConfig: staticKeystoreConfig{
16-
Account: libocr.Account("testaccount"),
17-
encoded: []byte{5: 11},
18-
signed: []byte{13: 37},
16+
Account: libocr.Account("testaccount"),
17+
encoded: []byte{5: 11},
18+
signed: []byte{13: 37},
19+
decrypted: []byte{17: 41},
1920
},
2021
}
2122

2223
var _ core.Keystore = (*staticKeystore)(nil)
2324
var _ testtypes.Evaluator[core.Keystore] = (*staticKeystore)(nil)
2425

2526
type staticKeystoreConfig struct {
26-
Account libocr.Account
27-
encoded []byte
28-
signed []byte
27+
Account libocr.Account
28+
encoded []byte
29+
signed []byte
30+
decrypted []byte
2931
}
3032

3133
type staticKeystore struct {
@@ -46,6 +48,16 @@ func (s staticKeystore) Sign(ctx context.Context, id string, data []byte) ([]byt
4648
return s.signed, nil
4749
}
4850

51+
func (s staticKeystore) Decrypt(ctx context.Context, id string, encrypted []byte) ([]byte, error) {
52+
if string(s.Account) != id {
53+
return nil, fmt.Errorf("expected id %q but got %q", s.Account, id)
54+
}
55+
if !bytes.Equal(s.encoded, encrypted) {
56+
return nil, fmt.Errorf("expected encoded data %x but got %x", s.encoded, encrypted)
57+
}
58+
return s.decrypted, nil
59+
}
60+
4961
func (s staticKeystore) Evaluate(ctx context.Context, other core.Keystore) error {
5062
accounts, err := s.Accounts(ctx)
5163
if err != nil {
@@ -65,5 +77,14 @@ func (s staticKeystore) Evaluate(ctx context.Context, other core.Keystore) error
6577
if !bytes.Equal(s.signed, signed) {
6678
return fmt.Errorf("expected signed data %x but got %x", s.signed, signed)
6779
}
80+
81+
decrypted, err := other.Decrypt(ctx, string(s.Account), s.encoded)
82+
if err != nil {
83+
return fmt.Errorf("failed to decrypt: %w", err)
84+
}
85+
if !bytes.Equal(s.decrypted, decrypted) {
86+
return fmt.Errorf("expected decrypted data %x but got %x", s.decrypted, decrypted)
87+
}
88+
6889
return nil
6990
}

pkg/loop/internal/pb/generate.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
//go:generate protoc --proto_path=../../../ --go_out=../../../ --go_opt=paths=source_relative --go-grpc_out=../../../ --go-grpc_opt=paths=source_relative loop/internal/pb/contract_reader.proto
55
//go:generate go run ./generate_with_values contract_writer.proto capabilities_registry.proto
66
//go:generate protoc --proto_path=../../../ --go_out=../../../ --go_opt=paths=source_relative --go-grpc_out=../../../ --go-grpc_opt=paths=source_relative loop/internal/pb/median_datasource.proto
7+
//go:generate protoc --proto_path=../../../ --go_out=../../../ --go_opt=paths=source_relative --go-grpc_out=../../../ --go-grpc_opt=paths=source_relative loop/internal/pb/keystore.proto
78
//go:generate protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative reporting_plugin_service.proto
89
//go:generate protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative telemetry.proto
910
//go:generate protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative pipeline_runner.proto

0 commit comments

Comments
 (0)