Skip to content

Latest commit

 

History

History
165 lines (145 loc) · 5.34 KB

README.md

File metadata and controls

165 lines (145 loc) · 5.34 KB

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.