Web3signer keymanager: key manager implementation (#10016)

* initial commit for work on web3signer-keymanager

* WIP key manager client

* adding in more valid unit tests

* refactoring some of my code to work better with current implementations

* adding comments, some error situations and fixing unit tests

* updating go.mod

* gaz

* addressing sandbox errors on test

* fixing more sand box debug items

* missed yet another error case on my test

* reverting go.mod

* addressing comments

* missed removal of unit test

* removing some deepsource issues

* adjusting based on review comments

* addressing comments

* adding the missed types

* changing public keys to only pull once

* fixing commented code format

* gaz

* Update validator/keymanager/remote-web3signer/keymanager.go

updating comments

Co-authored-by: Raul Jordan <raul@prysmaticlabs.com>

* Update validator/keymanager/remote-web3signer/keymanager.go

Co-authored-by: Raul Jordan <raul@prysmaticlabs.com>

* Update validator/keymanager/remote-web3signer/keymanager.go

Co-authored-by: Raul Jordan <raul@prysmaticlabs.com>

* Update validator/keymanager/remote-web3signer/keymanager.go

Co-authored-by: Raul Jordan <raul@prysmaticlabs.com>

* Update validator/keymanager/remote-web3signer/keymanager.go

Co-authored-by: Raul Jordan <raul@prysmaticlabs.com>

* Update validator/keymanager/remote-web3signer/keymanager.go

Co-authored-by: Raul Jordan <raul@prysmaticlabs.com>

* Update validator/keymanager/remote-web3signer/client.go

Co-authored-by: Raul Jordan <raul@prysmaticlabs.com>

* fixing conflicts

* addressing comments

* fixing deepsource issues

* more deep source issue fix

Co-authored-by: Raul Jordan <raul@prysmaticlabs.com>
This commit is contained in:
james-prysm
2022-01-05 14:32:46 -06:00
committed by GitHub
parent d6a02833dc
commit d28ae62d02
6 changed files with 370 additions and 54 deletions

BIN
beacon-chain/p2p/metaData Normal file

Binary file not shown.

View File

@@ -4,6 +4,7 @@ go_library(
name = "go_default_library",
srcs = [
"client.go",
"keymanager.go",
"log.go",
],
importpath = "github.com/prysmaticlabs/prysm/validator/keymanager/remote-web3signer",
@@ -12,7 +13,11 @@ go_library(
"//validator:__subpackages__",
],
deps = [
"//async/event:go_default_library",
"//crypto/bls:go_default_library",
"//encoding/bytesutil:go_default_library",
"//proto/prysm/v1alpha1/validator-client:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
],
@@ -20,7 +25,17 @@ go_library(
go_test(
name = "go_default_test",
srcs = ["client_test.go"],
srcs = [
"client_test.go",
"keymanager_test.go",
],
embed = [":go_default_library"],
deps = ["@com_github_stretchr_testify//assert:go_default_library"],
deps = [
"//crypto/bls:go_default_library",
"//encoding/bytesutil:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//proto/prysm/v1alpha1/validator-client:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
"@com_github_stretchr_testify//assert:go_default_library",
],
)

View File

@@ -2,17 +2,18 @@ package remote_web3signer
import (
"bytes"
"encoding/hex"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/crypto/bls"
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
)
const (
@@ -20,20 +21,25 @@ const (
maxTimeout = 3 * time.Second
)
// client a wrapper object around web3signer APIs. API docs found here https://consensys.github.io/web3signer/web3signer-eth2.html.
type client struct {
// httpSignerClient defines the interface for interacting with a remote web3signer.
type httpSignerClient interface {
Sign(ctx context.Context, pubKey string, request *SignRequest) (bls.Signature, error)
GetPublicKeys(ctx context.Context, url string) ([][48]byte, error)
}
// apiClient a wrapper object around web3signer APIs. API docs found here https://consensys.github.io/web3signer/web3signer-eth2.html.
type apiClient struct {
BasePath string
restClient *http.Client
}
// newClient method instantiates a new client object.
//nolint:unused,deadcode
func newClient(endpoint string) (*client, error) {
u, err := url.Parse(endpoint)
// newApiClient method instantiates a new apiClient object.
func newApiClient(baseEndpoint string) (*apiClient, error) {
u, err := url.Parse(baseEndpoint)
if err != nil {
return nil, errors.Wrap(err, "invalid format, unable to parse url")
}
return &client{
return &apiClient{
BasePath: u.Host,
restClient: &http.Client{
Timeout: maxTimeout,
@@ -73,7 +79,7 @@ type signResponse struct {
}
// Sign is a wrapper method around the web3signer sign api.
func (client *client) Sign(pubKey string, request *SignRequest) (bls.Signature, error) {
func (client *apiClient) Sign(_ context.Context, pubKey string, request *SignRequest) (bls.Signature, error) {
requestPath := ethApiNamespace + pubKey
jsonRequest, err := json.Marshal(request)
if err != nil {
@@ -93,17 +99,16 @@ func (client *client) Sign(pubKey string, request *SignRequest) (bls.Signature,
if err := client.unmarshalResponse(resp.Body, &signResp); err != nil {
return nil, err
}
decoded, err := decodeHex(signResp.Signature)
decoded, err := hexutil.Decode(signResp.Signature)
if err != nil {
return nil, err
return nil, errors.Wrap(err, "failed to decode signature")
}
return bls.SignatureFromBytes(decoded)
}
// GetPublicKeys is a wrapper method around the web3signer publickeys api (this may be removed in the future or moved to another location due to its usage).
func (client *client) GetPublicKeys() ([][]byte, error) {
const requestPath = "/publicKeys"
resp, err := client.doRequest(http.MethodGet, client.BasePath+requestPath, nil)
func (client *apiClient) GetPublicKeys(_ context.Context, url string) ([][48]byte, error) {
resp, err := client.doRequest(http.MethodGet, url, nil /* no body needed on get request */)
if err != nil {
return nil, err
}
@@ -111,15 +116,15 @@ func (client *client) GetPublicKeys() ([][]byte, error) {
if err := client.unmarshalResponse(resp.Body, &publicKeys); err != nil {
return nil, err
}
decodedKeys := make([][]byte, len(publicKeys))
decodedKeys := make([][48]byte, len(publicKeys))
var errorKeyPositions string
for i, value := range publicKeys {
decodedKey, err := decodeHex(value)
decodedKey, err := hexutil.Decode(value)
if err != nil {
errorKeyPositions += fmt.Sprintf("%v, ", i)
continue
}
decodedKeys[i] = decodedKey
decodedKeys[i] = bytesutil.ToBytes48(decodedKey)
}
if errorKeyPositions != "" {
return nil, errors.New("failed to decode from Hex from the following public key index locations: " + errorKeyPositions)
@@ -128,7 +133,7 @@ func (client *client) GetPublicKeys() ([][]byte, error) {
}
// ReloadSignerKeys is a wrapper method around the web3signer reload api.
func (client *client) ReloadSignerKeys() error {
func (client *apiClient) ReloadSignerKeys(_ context.Context) error {
const requestPath = "/reload"
if _, err := client.doRequest(http.MethodPost, client.BasePath+requestPath, nil); err != nil {
return err
@@ -137,9 +142,9 @@ func (client *client) ReloadSignerKeys() error {
}
// GetServerStatus is a wrapper method around the web3signer upcheck api
func (client *client) GetServerStatus() (string, error) {
func (client *apiClient) GetServerStatus(_ context.Context) (string, error) {
const requestPath = "/upcheck"
resp, err := client.doRequest(http.MethodGet, client.BasePath+requestPath, nil)
resp, err := client.doRequest(http.MethodGet, client.BasePath+requestPath, nil /* no body needed on get request */)
if err != nil {
return "", err
}
@@ -151,7 +156,7 @@ func (client *client) GetServerStatus() (string, error) {
}
// doRequest is a utility method for requests.
func (client *client) doRequest(httpMethod, fullPath string, body io.Reader) (*http.Response, error) {
func (client *apiClient) doRequest(httpMethod, fullPath string, body io.Reader) (*http.Response, error) {
req, err := http.NewRequest(httpMethod, fullPath, body)
if err != nil {
return nil, errors.Wrap(err, "invalid format, failed to create new Post Request Object")
@@ -169,7 +174,7 @@ func (client *client) doRequest(httpMethod, fullPath string, body io.Reader) (*h
}
// unmarshalResponse is a utility method for unmarshalling responses.
func (*client) unmarshalResponse(responseBody io.ReadCloser, unmarshalledResponseObject interface{}) error {
func (*apiClient) unmarshalResponse(responseBody io.ReadCloser, unmarshalledResponseObject interface{}) error {
defer closeBody(responseBody)
if err := json.NewDecoder(responseBody).Decode(&unmarshalledResponseObject); err != nil {
return errors.Wrap(err, "invalid format, unable to read response body as array of strings")
@@ -177,15 +182,6 @@ func (*client) unmarshalResponse(responseBody io.ReadCloser, unmarshalledRespons
return nil
}
// decodeHex a utility method for decoding hex strings may be a duplicate in which case will be removed in the future.
func decodeHex(signature string) ([]byte, error) {
decoded, err := hex.DecodeString(strings.TrimPrefix(signature, "0x"))
if err != nil {
return nil, errors.Wrap(err, "invalid format, failed to unmarshal json response")
}
return decoded, nil
}
// closeBody a utility method to wrap an error for closing
func closeBody(body io.Closer) {
if err := body.Close(); err != nil {

View File

@@ -2,6 +2,7 @@ package remote_web3signer
import (
"bytes"
"context"
"fmt"
"io/ioutil"
"net/http"
@@ -20,17 +21,7 @@ func (m *mockTransport) RoundTrip(*http.Request) (*http.Response, error) {
return m.mockResponse, nil
}
func TestClient_Sign_HappyPath(t *testing.T) {
json := `{
"signature": "0xb3baa751d0a9132cfe93e4e3d5ff9075111100e3789dca219ade5a24d27e19d16b3353149da1833e9b691bb38634e8dc04469be7032132906c927d7e1a49b414730612877bc6b2810c8f202daf793d1ab0d6b5cb21d52f9e52e883859887a5d9"
}`
// create a new reader with that JSON
r := ioutil.NopCloser(bytes.NewReader([]byte(json)))
mock := &mockTransport{mockResponse: &http.Response{
StatusCode: 200,
Body: r,
}}
cl := client{BasePath: "example.com", restClient: &http.Client{Transport: mock}}
func getClientMockSignRequest() *SignRequest {
forkData := &Fork{
PreviousVersion: "",
CurrentVersion: "",
@@ -49,7 +40,22 @@ func TestClient_Sign_HappyPath(t *testing.T) {
SigningRoot: "0xfasd0fjsa0dfjas0dfjasdf",
AggregationSlot: AggregationSlotData,
}
resp, err := cl.Sign("a2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820", &web3SignerRequest)
return &web3SignerRequest
}
func TestClient_Sign_HappyPath(t *testing.T) {
json := `{
"signature": "0xb3baa751d0a9132cfe93e4e3d5ff9075111100e3789dca219ade5a24d27e19d16b3353149da1833e9b691bb38634e8dc04469be7032132906c927d7e1a49b414730612877bc6b2810c8f202daf793d1ab0d6b5cb21d52f9e52e883859887a5d9"
}`
// create a new reader with that JSON
r := ioutil.NopCloser(bytes.NewReader([]byte(json)))
mock := &mockTransport{mockResponse: &http.Response{
StatusCode: 200,
Body: r,
}}
cl := apiClient{BasePath: "example.com", restClient: &http.Client{Transport: mock}}
resp, err := cl.Sign(context.Background(), "a2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820", getClientMockSignRequest())
assert.NotNil(t, resp)
assert.Nil(t, err)
assert.EqualValues(t, "0xb3baa751d0a9132cfe93e4e3d5ff9075111100e3789dca219ade5a24d27e19d16b3353149da1833e9b691bb38634e8dc04469be7032132906c927d7e1a49b414730612877bc6b2810c8f202daf793d1ab0d6b5cb21d52f9e52e883859887a5d9", fmt.Sprintf("%#x", resp.Marshal()))
@@ -57,31 +63,33 @@ func TestClient_Sign_HappyPath(t *testing.T) {
func TestClient_GetPublicKeys_HappyPath(t *testing.T) {
// public keys are returned hex encoded with 0x
json := `["0x613262356161616439633665666566653762623962313234336130343334303466333336323933376366623662333138333339323938333331373366343736363330656132636665623064396464663135663937636138363835393438383230","0x613262356161616439633665666566653762623962313234336130343334303466333336323933376366623662333138333339323938333331373366343736363330656132636665623064396464663135663937636138363835393438383230"]`
json := `["0xa2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820"]`
// create a new reader with that JSON
r := ioutil.NopCloser(bytes.NewReader([]byte(json)))
mock := &mockTransport{mockResponse: &http.Response{
StatusCode: 200,
Body: r,
}}
cl := client{BasePath: "example.com", restClient: &http.Client{Transport: mock}}
resp, err := cl.GetPublicKeys()
cl := apiClient{BasePath: "example.com", restClient: &http.Client{Transport: mock}}
resp, err := cl.GetPublicKeys(context.Background(), "example.com/api/publickeys")
assert.NotNil(t, resp)
assert.Nil(t, err)
// we would like them as 48byte base64 without 0x
assert.EqualValues(t, "a2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820", string(resp[0]))
assert.EqualValues(t, "[162 181 170 173 156 110 254 254 123 185 177 36 58 4 52 4 243 54 41 55 207 182 179 24 51 146 152 51 23 63 71 102 48 234 44 254 176 217 221 241 95 151 202 134 133 148 136 32]", fmt.Sprintf("%v", resp[0][:]))
}
// TODO: not really in use, should be revisited
func TestClient_ReloadSignerKeys_HappyPath(t *testing.T) {
mock := &mockTransport{mockResponse: &http.Response{
StatusCode: 200,
Body: ioutil.NopCloser(bytes.NewReader(nil)),
}}
cl := client{BasePath: "example.com", restClient: &http.Client{Transport: mock}}
err := cl.ReloadSignerKeys()
cl := apiClient{BasePath: "example.com", restClient: &http.Client{Transport: mock}}
err := cl.ReloadSignerKeys(context.Background())
assert.Nil(t, err)
}
// TODO: not really in use, should be revisited
func TestClient_GetServerStatus_HappyPath(t *testing.T) {
json := `"some server status, not sure what it looks like, need to find some sample data"`
r := ioutil.NopCloser(bytes.NewReader([]byte(json)))
@@ -89,8 +97,8 @@ func TestClient_GetServerStatus_HappyPath(t *testing.T) {
StatusCode: 200,
Body: r,
}}
cl := client{BasePath: "example.com", restClient: &http.Client{Transport: mock}}
resp, err := cl.GetServerStatus()
cl := apiClient{BasePath: "example.com", restClient: &http.Client{Transport: mock}}
resp, err := cl.GetServerStatus(context.Background())
assert.NotNil(t, resp)
assert.Nil(t, err)
}

View File

@@ -0,0 +1,154 @@
package remote_web3signer
import (
"context"
"fmt"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/async/event"
"github.com/prysmaticlabs/prysm/crypto/bls"
validatorpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/validator-client"
)
// SetupConfig includes configuration values for initializing.
// a keymanager, such as passwords, the wallet, and more.
// Web3Signer contains one public keys option. Either through a URL or a static key list.
type SetupConfig struct {
BaseEndpoint string
GenesisValidatorsRoot []byte
// Either URL or keylist must be set.
// If the URL is set, the keymanager will fetch the public keys from the URL.
// caution: this option is susceptible to slashing if the web3signer's validator keys are shared across validators
PublicKeysURL string
// Either URL or keylist must be set.
// a static list of public keys to be passed by the user to determine what accounts should sign.
// This will provide a layer of safety against slashing if the web3signer is shared across validators.
ProvidedPublicKeys [][48]byte
}
// Keymanager defines the web3signer keymanager.
type Keymanager struct {
client httpSignerClient
genesisValidatorsRoot []byte
publicKeysURL string
providedPublicKeys [][48]byte
accountsChangedFeed *event.Feed
}
// NewKeymanager instantiates a new web3signer key manager.
func NewKeymanager(_ context.Context, cfg *SetupConfig) (*Keymanager, error) {
if cfg.BaseEndpoint == "" || len(cfg.GenesisValidatorsRoot) == 0 {
return nil, fmt.Errorf("invalid setup config, one or more configs are empty: BaseEndpoint: %v, GenesisValidatorsRoot: %v", cfg.BaseEndpoint, cfg.GenesisValidatorsRoot)
}
if cfg.PublicKeysURL != "" && len(cfg.ProvidedPublicKeys) != 0 {
return nil, errors.New("Either a provided list of public keys or a URL to a list of public keys must be provided, but not both")
}
if cfg.PublicKeysURL == "" && len(cfg.ProvidedPublicKeys) == 0 {
return nil, errors.New("no valid public key options provided")
}
client, err := newApiClient(cfg.BaseEndpoint)
if err != nil {
return nil, errors.Wrap(err, "could not create apiClient")
}
return &Keymanager{
client: httpSignerClient(client),
genesisValidatorsRoot: cfg.GenesisValidatorsRoot,
accountsChangedFeed: new(event.Feed),
publicKeysURL: cfg.PublicKeysURL,
providedPublicKeys: cfg.ProvidedPublicKeys,
}, nil
}
// FetchValidatingPublicKeys fetches the validating public keys from the remote server or from the provided keys.
func (km *Keymanager) FetchValidatingPublicKeys(ctx context.Context) ([][48]byte, error) {
if km.publicKeysURL != "" {
providedPublicKeys, err := km.client.GetPublicKeys(ctx, km.publicKeysURL)
if err != nil {
return nil, err
}
km.providedPublicKeys = providedPublicKeys
}
return km.providedPublicKeys, nil
}
// Sign signs the message by using a remote web3signer server.
func (km *Keymanager) Sign(ctx context.Context, request *validatorpb.SignRequest) (bls.Signature, error) {
if request.Fork == nil {
return nil, errors.New("invalid sign request: Fork is nil")
}
signRequestType, err := getSignRequestType(request)
if err != nil {
return nil, err
}
forkData := &Fork{
PreviousVersion: hexutil.Encode(request.Fork.PreviousVersion),
CurrentVersion: hexutil.Encode(request.Fork.CurrentVersion),
Epoch: fmt.Sprint(request.Fork.Epoch),
}
forkInfoData := &ForkInfo{
Fork: forkData,
GenesisValidatorsRoot: hexutil.Encode(km.genesisValidatorsRoot),
}
aggregationSlotData := &AggregationSlot{Slot: fmt.Sprint(request.AggregationSlot)}
web3SignerRequest := SignRequest{
Type: signRequestType,
ForkInfo: forkInfoData,
SigningRoot: hexutil.Encode(request.SigningRoot),
AggregationSlot: aggregationSlotData,
}
return km.client.Sign(ctx, hexutil.Encode(request.PublicKey), &web3SignerRequest)
}
// getSignRequestType returns the type of the sign request.
func getSignRequestType(request *validatorpb.SignRequest) (string, error) {
switch request.Object.(type) {
case *validatorpb.SignRequest_Block:
return "BLOCK", nil
case *validatorpb.SignRequest_AttestationData:
return "ATTESTATION", nil
case *validatorpb.SignRequest_AggregateAttestationAndProof:
return "AGGREGATE_AND_PROOF", nil
case *validatorpb.SignRequest_Slot:
return "AGGREGATION_SLOT", nil
case *validatorpb.SignRequest_BlockV2:
return "BLOCK_V2", nil
// TODO(#10053): Need to add support for merge blocks.
/*
case *validatorpb.SignRequest_BlockV3:
return "BLOCK_V3", nil
*/
// We do not support "DEPOSIT" type.
/*
case *validatorpb.:
return "DEPOSIT", nil
*/
case *validatorpb.SignRequest_Epoch:
return "RANDAO_REVEAL", nil
case *validatorpb.SignRequest_Exit:
return "VOLUNTARY_EXIT", nil
case *validatorpb.SignRequest_SyncMessageBlockRoot:
return "SYNC_COMMITTEE_MESSAGE", nil
case *validatorpb.SignRequest_SyncAggregatorSelectionData:
return "SYNC_COMMITTEE_SELECTION_PROOF", nil
case *validatorpb.SignRequest_ContributionAndProof:
return "SYNC_COMMITTEE_CONTRIBUTION_AND_PROOF", nil
default:
return "", errors.New(fmt.Sprintf("Web3signer sign request type: %T not found", request.Object))
}
}
// SubscribeAccountChanges returns the event subscription for changes to public keys.
func (*Keymanager) SubscribeAccountChanges(_ chan [][48]byte) event.Subscription {
// Not used right now.
// Returns a stub for the time being as there is a danger of being slashed if the apiClient reloads keys dynamically.
// Because there is no way to dynamically reload keys, add or remove remote keys we are returning a stub without any event updates for the time being.
return event.NewSubscription(func(i <-chan struct{}) error {
return nil
})
}

View File

@@ -0,0 +1,143 @@
package remote_web3signer
import (
"context"
"encoding/hex"
"fmt"
"strings"
"testing"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/prysmaticlabs/prysm/crypto/bls"
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
v1alpha1 "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
validatorpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/validator-client"
"github.com/stretchr/testify/assert"
)
type MockClient struct {
Signature string
PublicKeys []string
}
func (mc *MockClient) Sign(_ context.Context, _ string, _ *SignRequest) (bls.Signature, error) {
decoded, err := hex.DecodeString(strings.TrimPrefix(mc.Signature, "0x"))
if err != nil {
return nil, err
}
return bls.SignatureFromBytes(decoded)
}
func (mc *MockClient) GetPublicKeys(_ context.Context, _ string) ([][48]byte, error) {
var keys [][48]byte
for _, pk := range mc.PublicKeys {
decoded, err := hex.DecodeString(strings.TrimPrefix(pk, "0x"))
if err != nil {
return nil, err
}
keys = append(keys, bytesutil.ToBytes48(decoded))
}
return keys, nil
}
func getMockSignRequest() *validatorpb.SignRequest {
return &validatorpb.SignRequest{
Object: &validatorpb.SignRequest_Block{},
Fork: &v1alpha1.Fork{Epoch: 0},
AggregationSlot: 9999,
}
}
func TestKeymanager_Sign_HappyPath(t *testing.T) {
client := &MockClient{
Signature: "0xb3baa751d0a9132cfe93e4e3d5ff9075111100e3789dca219ade5a24d27e19d16b3353149da1833e9b691bb38634e8dc04469be7032132906c927d7e1a49b414730612877bc6b2810c8f202daf793d1ab0d6b5cb21d52f9e52e883859887a5d9",
}
ctx := context.Background()
root, err := hexutil.Decode("0x270d43e74ce340de4bca2b1936beca0f4f5408d9e78aec4850920baf659d5b69")
if err != nil {
fmt.Printf("error: %v", err)
}
config := &SetupConfig{
BaseEndpoint: "example.com",
GenesisValidatorsRoot: root,
PublicKeysURL: "example2.com/api/v1/eth2/publicKeys",
}
km, err := NewKeymanager(ctx, config)
if err != nil {
fmt.Printf("error: %v", err)
}
km.client = client
resp, err := km.Sign(ctx, getMockSignRequest())
if err != nil {
fmt.Printf("error: %v", err)
}
assert.NotNil(t, resp)
assert.Nil(t, err)
assert.EqualValues(t, "0xb3baa751d0a9132cfe93e4e3d5ff9075111100e3789dca219ade5a24d27e19d16b3353149da1833e9b691bb38634e8dc04469be7032132906c927d7e1a49b414730612877bc6b2810c8f202daf793d1ab0d6b5cb21d52f9e52e883859887a5d9", fmt.Sprintf("%#x", resp.Marshal()))
}
func TestKeymanager_FetchValidatingPublicKeys_HappyPath_WithKeyList(t *testing.T) {
ctx := context.Background()
decodedKey, err := hexutil.Decode("0xa2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820")
if err != nil {
fmt.Printf("error: %v", err)
}
keys := [][48]byte{
bytesutil.ToBytes48(decodedKey),
}
root, err := hexutil.Decode("0x270d43e74ce340de4bca2b1936beca0f4f5408d9e78aec4850920baf659d5b69")
if err != nil {
fmt.Printf("error: %v", err)
}
config := &SetupConfig{
BaseEndpoint: "example.com",
GenesisValidatorsRoot: root,
ProvidedPublicKeys: keys,
}
km, err := NewKeymanager(ctx, config)
if err != nil {
fmt.Printf("error: %v", err)
}
resp, err := km.FetchValidatingPublicKeys(ctx)
if err != nil {
fmt.Printf("error: %v", err)
}
assert.NotNil(t, resp)
assert.Nil(t, err)
assert.EqualValues(t, resp, keys)
}
func TestKeymanager_FetchValidatingPublicKeys_HappyPath_WithExternalURL(t *testing.T) {
ctx := context.Background()
client := &MockClient{
PublicKeys: []string{"0xa2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820"},
}
decodedKey, err := hexutil.Decode("0xa2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820")
if err != nil {
fmt.Printf("error: %v", err)
}
keys := [][48]byte{
bytesutil.ToBytes48(decodedKey),
}
root, err := hexutil.Decode("0x270d43e74ce340de4bca2b1936beca0f4f5408d9e78aec4850920baf659d5b69")
if err != nil {
fmt.Printf("error: %v", err)
}
config := &SetupConfig{
BaseEndpoint: "example.com",
GenesisValidatorsRoot: root,
PublicKeysURL: "example2.com/api/v1/eth2/publicKeys",
}
km, err := NewKeymanager(ctx, config)
if err != nil {
fmt.Printf("error: %v", err)
}
km.client = client
resp, err := km.FetchValidatingPublicKeys(ctx)
if err != nil {
fmt.Printf("error: %v", err)
}
assert.NotNil(t, resp)
assert.Nil(t, err)
assert.EqualValues(t, resp, keys)
}