gcpkms

package
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 17, 2023 License: MIT Imports: 21 Imported by: 0

README

GCP KMS signer for go-ethereum

Despite having algorithms for asymmetric signing, Google Cloud Platform's Key Management Service (GCP KMS) does not support signing Ethereum transactions by default. While the Ethereum network (or EVM-compatible networks) uses ECDSA-SECP256K1-KECCAK256 for signing, the GCP KMS only supports ECDSA-SECP256K1-SHA256 to the nearest. This limits the ability of the GCP KMS to directly provide secure key management for blockchains, especially Ethereum.

Fortunately, with some tricks and extensions, we are able to convert a signature from the GCP KMS to an EVM-compatible signature. This package is dedicated to doing so.

Import

import "github.com/LampardNguyen234/evm-kms/gcpkms"

Prerequisites

Create a KMS Key

In order to sign Ethereum transactions using this package, you need to create a KMS key in GCP. To do this, please follow the instruction here. Remember to choose Purpose = Asymmetric sign and Algorithm = Elliptic Curve secp256k1 - SHA256 Digest in the Create key screen.

Download Credential (Optional)

Head over the IAM page of the GCP, create a service account to use to API and download the credential. The credential file looks like the following:

{
  "type": "service_account",
  "project_id": "__REDACTED__",
  "private_key_id": "__REDACTED__",
  "private_key": "-----BEGIN PRIVATE KEY-----\n__REDACTED__\n-----END PRIVATE KEY-----\n",
  "client_email": "__REDACTED__",
  "client_id": "__REDACTED__",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/__REDACTED__"
}

Save this file to your machine, you will need it later. Example location: /Users/SomeUser/.cred/gcp-credential.json.

Interact with the Code

Prepare the config

To create a new GoogleKMSClient, we need to know the detail of the key we are using. This can be initiated via a config of the following form:

// Config represents required information to create a Google Cloud KMS client.
type Config struct {
	// ProjectID is the ID of the working GCP project.
	ProjectID string `json:"ProjectID"`

	// LocationID is the region ID of the project.
	//
	// Example: us-west1.
	LocationID string `json:"LocationID"`

	// CredentialLocation is the absolute path of the credential file downloaded from the GCP.
	//
	// Example: "/Users/SomeUser/.cred/gcp-credential.json".
	// Leave this field empty if the environment varialbe `GOOGLE_APPLICATION_CREDENTIALS` has been set.
	CredentialLocation string `json:"CredentialLocation,omitempty"`

	// Key is the detail of the GCP KMS key.
	Key Key `json:"Key"`

	// ChainID is the ID of the target EVM chain.
	//
	// See https://chainlist.org.
	ChainID uint64 `json:"ChainID"`
}

Here is an example config:

{
  "ProjectID": "evm-kms",
  "LocationID": "us-west1",
  "CredentialLocation": "/Users/SomeUser/.cred/gcp-credential.json",
  "Key": {
    "Keyring": "my-keying-name",
    "Name": "evm-ecdsa",
    "Version": "1"
  },
  "ChainID": 1
}
Create the client

Then, create a client using the NewGoogleKMSClient function.

var err error
cfg = &Config{
    ProjectID:          "evm-kms",
    LocationID:         "us-west1",
    CredentialLocation: "/Users/SomeUser/.cred/gcp-credential.json",
    Key: Key{
        Keyring: "my-keying-name",
        Name:    "evm-ecdsa",
        Version: "1",
    },
    ChainID: 1,
}

c, err = NewGoogleKMSClient(context.Background(), *cfg)
if err != nil {
    panic(err)
}
Send ETH
Create a transaction
testTx := types.NewTx(&types.LegacyTx{
    To:       &common.HexToAddress("0x243e9517a24813a2d73e9a74cd2c1c699d0ff7a5"),
    Nonce:    9090,
    GasPrice: big.NewInt(1000000),
    Gas:      50000,
    Value:    big.NewInt(100),
    Data:     []byte{1, 2, 3},
})
Sign the transaction
signedTx, err := c.GetDefaultEVMTransactor().Signer(c.GetAddress(), testTx)
if err != nil {
    panic(err)
}
jsb, _ := json.Marshal(signedTx)
fmt.Println("signedTx", string(jsb))
Broadcast the transaction
err = evmClient.SendTransaction(ctx, signedTx)
if err != nil {
    panic(err)
}

See TestSendETH.

Send ERC20

The abigen tool generates .go binding files that are able to directly operate with the *bind.TransactOpts type. An example of this is here. The Transfer function takes as input a *bind.TransactOpts, which can be retrieved via the GetDefaultEVMTransactor function of the client, or can be constructed manually, as long as a bind.SignerFn is supplied.

transactor := &bind.TransactOpts{
    From:      c.GetAddress(),
    Nonce:     new(big.Int).SetUint64(90909),
    Signer:    c.GetEVMSignerFn(),
    GasPrice:  big.NewInt(1000000),
    GasLimit:  50000,
    Context:   ctx,
}

tx, err := erc20Instance.Transfer(transactor, common.HexToAddress("0x243e9517a24813a2d73e9a74cd2c1c699d0ff7a5"), new(big.Int).SetUint64(1000))
if err != nil {
    panic(err)
}

See TestSendERC20.

Documentation

Overview

Package gcpkms uses the Google Cloud Platform's Key Management Service to provide a signing interface for EVM-compatible transactions.

Rather than directly accessing a private key to sign a transaction, the client makes calls to the remote GCP KMS to do so and the private key never leaves the KMS.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Config

type Config struct {
	// ProjectID is the ID of the working GCP project.
	ProjectID string `json:"ProjectID"`

	// LocationID is the region ID of the project.
	//
	// Example: us-west1.
	LocationID string `json:"LocationID"`

	// CredentialLocation is the absolute path of the credential file downloaded from the GCP.
	//
	// Example: "/Users/SomeUser/.cred/gcp-credential.json".
	// Leave this field empty if the environment varialbe `GOOGLE_APPLICATION_CREDENTIALS` has been set.
	CredentialLocation string `json:"CredentialLocation,omitempty"`

	// Key is the detail of the GCP KMS key.
	Key Key `json:"Key"`

	// ChainID is the ID of the target EVM chain.
	//
	// See https://chainlist.org.
	ChainID uint64 `json:"ChainID"`
}

Config represents required information to create a Google Cloud KMS client.

func LoadConfigFromFile

func LoadConfigFromFile(filePath string) (*Config, error)

LoadConfigFromFile loads the config from the given config file.

func (Config) IsValid

func (cfg Config) IsValid() (bool, error)

IsValid checks if a Config is valid.

type GoogleKMSClient

type GoogleKMSClient struct {
	// contains filtered or unexported fields
}

GoogleKMSClient implements basic functionalities of a Google KMS client for signing transactions.

func NewGoogleKMSClient

func NewGoogleKMSClient(ctx context.Context, cfg Config, txSigner ...types.Signer) (*GoogleKMSClient, error)

NewGoogleKMSClient creates a new GCP KMS client with the given config.

If txSigner is not provided, the signer will be initiated as a types.NewLondonSigner(cfg.ChainID). Note that only the first value of txSigner is used.

func (GoogleKMSClient) GetAddress

func (c GoogleKMSClient) GetAddress() common.Address

GetAddress returns the EVM address of the current signer.

func (GoogleKMSClient) GetDefaultEVMTransactor

func (c GoogleKMSClient) GetDefaultEVMTransactor() *bind.TransactOpts

GetDefaultEVMTransactor returns the default KMS-backed instance of bind.TransactOpts. Only `Context`, `From`, and `Signer` fields are set.

func (GoogleKMSClient) GetEVMSignerFn

func (c GoogleKMSClient) GetEVMSignerFn() bind.SignerFn

GetEVMSignerFn returns the EVM signer using the GCP KMS.

func (GoogleKMSClient) GetPublicKey

func (c GoogleKMSClient) GetPublicKey() (*ecdsa.PublicKey, error)

GetPublicKey returns the public Key corresponding to the given keyId.

func (GoogleKMSClient) HasSignedTx

func (c GoogleKMSClient) HasSignedTx(tx *types.Transaction) (bool, error)

HasSignedTx checks if the given tx is signed by the current GoogleKMSClient.

func (GoogleKMSClient) SignHash

func (c GoogleKMSClient) SignHash(digest common.Hash) ([]byte, error)

SignHash calls the remote GCP KMS to sign a given digested message. Although the GCP KMS does not support keccak256 hash function (it uses SHA256 instead), it will not care about which hash function to use if you send the hash of message to the KMS.

func (*GoogleKMSClient) WithChainID added in v0.2.0

func (c *GoogleKMSClient) WithChainID(chainID *big.Int)

WithChainID assigns given chainID (and updates the corresponding signer) to the GoogleKMSClient.

func (*GoogleKMSClient) WithSigner added in v0.1.1

func (c *GoogleKMSClient) WithSigner(signer types.Signer)

WithSigner assigns the given signer to the GoogleKMSClient.

type Key

type Key struct {
	// Keyring is the name of your KMS keyring.
	Keyring string `json:"Keyring"`

	// Name is the name of the key in the Keyring.
	Name string `json:"Name"`

	// Version is the of the current key.
	Version string `json:"Version"`
}

Key consists of required information to retrieve the CGP KMS Key path.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL