mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-09 13:28:01 -05:00
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:
BIN
beacon-chain/p2p/metaData
Normal file
BIN
beacon-chain/p2p/metaData
Normal file
Binary file not shown.
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
154
validator/keymanager/remote-web3signer/keymanager.go
Normal file
154
validator/keymanager/remote-web3signer/keymanager.go
Normal 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
|
||||
})
|
||||
}
|
||||
143
validator/keymanager/remote-web3signer/keymanager_test.go
Normal file
143
validator/keymanager/remote-web3signer/keymanager_test.go
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user