Skip to content

Commit

Permalink
feat: adding working kms sign
Browse files Browse the repository at this point in the history
  • Loading branch information
praetoriansentry committed Dec 21, 2023
1 parent ca8abed commit f25dfcf
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 16 deletions.
185 changes: 169 additions & 16 deletions cmd/signer/signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,25 @@ import (
"cloud.google.com/go/kms/apiv1/kmspb"
"context"
"crypto/ecdsa"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/hex"
"encoding/json"
"encoding/pem"
"fmt"
accounts2 "github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/signer/core/apitypes"
"github.com/manifoldco/promptui"
"github.com/maticnetwork/polygon-cli/gethkeystore"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/wrapperspb"
"hash/crc32"
"math/big"
"os"
"strings"
Expand All @@ -38,9 +45,6 @@ type signerOpts struct {
gcpKeyRingID *string
}

type signedTx struct {
}

var inputSignerOpts = signerOpts{}

var SignerCmd = &cobra.Command{
Expand Down Expand Up @@ -108,9 +112,16 @@ var SignCmd = &cobra.Command{
return err
}
return sign(pk)

}
return nil
if *inputSignerOpts.kms == "GCP" {
tx, err := getTxDataToSign()
if err != nil {
return err
}
foo := GCPKMS{}
return foo.Sign(cmd.Context(), tx)
}
return fmt.Errorf("not implemented")
},
}

Expand Down Expand Up @@ -150,40 +161,59 @@ var CreateCmd = &cobra.Command{
}
if *inputSignerOpts.kms == "GCP" {
foo := GCPKMS{}
return foo.CreateKey(cmd.Context())
err := foo.CreateKeyRing(cmd.Context())
if err != nil {
return err
}
err = foo.CreateKey(cmd.Context())
if err != nil {
return err
}
}
return nil
},
}

func sign(pk *ecdsa.PrivateKey) error {
func getTxDataToSign() (*types.Transaction, error) {
dataToSign, err := os.ReadFile(*inputSignerOpts.dataFile)
if err != nil {
return err
return nil, err
}
var tx types.LegacyTx
// var tx types.DynamicFeeTx
// var tx types.LegacyTx
var tx apitypes.SendTxArgs
err = json.Unmarshal(dataToSign, &tx)
if err != nil {
// TODO in the future it might make sense to sign arbitrary data?
return nil, err
}
// tx.ChainID = new(big.Int).SetUint64(*inputSignerOpts.chainID)
return tx.ToTransaction(), nil

}
func sign(pk *ecdsa.PrivateKey) error {
tx, err := getTxDataToSign()
if err != nil {
return err
}
signer, err := getSigner()
if err != nil {
return err
}
signedTx, err := types.SignNewTx(pk, signer, &tx)
signedTx, err := types.SignTx(tx, signer, pk)
if err != nil {
return err
}
rlpData := make([]byte, 0)
buf := bytes.NewBuffer(rlpData)
err = signedTx.EncodeRLP(buf)
return outputSignedTx(signedTx)
}

func outputSignedTx(signedTx *types.Transaction) error {
rawTx, err := signedTx.MarshalBinary()
if err != nil {
return err
}
rawHexString := hex.EncodeToString(buf.Bytes())
rawHexString := hex.EncodeToString(rawTx)
out := make(map[string]any, 0)
out["txData"] = tx
out["signedTx"] = signedTx
out["rawSignedTx"] = rawHexString
outJSON, err := json.Marshal(out)
Expand Down Expand Up @@ -264,13 +294,136 @@ func (g *GCPKMS) CreateKey(ctx context.Context) error {
// Call the API.
result, err := client.CreateCryptoKey(ctx, req)
if err != nil {
if strings.Contains(err.Error(), "already exists") {
log.Info().Str("parent", parent).Str("id", id).Msg("key already exists")
return nil
}
return fmt.Errorf("failed to create key: %w", err)
}
log.Info().Str("name", result.Name).Msg("created key")
return nil

}

type publicKeyInfo struct {
Raw asn1.RawContent
Algorithm pkix.AlgorithmIdentifier
PublicKey asn1.BitString
}

func (g *GCPKMS) Sign(ctx context.Context, tx *types.Transaction) error {
// TODO we might need to set a version as a parameter
name := fmt.Sprintf("projects/%s/locations/%s/keyRings/%s/cryptoKeys/%s/cryptoKeyVersions/%d", *inputSignerOpts.gcpProjectID, *inputSignerOpts.gcpRegion, *inputSignerOpts.gcpKeyRingID, *inputSignerOpts.keyID, 1)

client, err := kms.NewKeyManagementClient(ctx)
if err != nil {
return fmt.Errorf("failed to create kms client: %w", err)
}
defer client.Close()

signer, err := getSigner()
if err != nil {
return err
}
digest := signer.Hash(tx)

// Optional but recommended: Compute digest's CRC32C.
crc32c := func(data []byte) uint32 {
t := crc32.MakeTable(crc32.Castagnoli)
return crc32.Checksum(data, t)

}
digestCRC32C := crc32c(digest.Bytes())

req := &kmspb.AsymmetricSignRequest{
Name: name,
Digest: &kmspb.Digest{
Digest: &kmspb.Digest_Sha256{
Sha256: digest.Bytes(),
},
},
DigestCrc32C: wrapperspb.Int64(int64(digestCRC32C)),
}

// Call the API.
result, err := client.AsymmetricSign(ctx, req)
if err != nil {
return fmt.Errorf("failed to sign digest: %w", err)
}

// Optional, but recommended: perform integrity verification on result.
// For more details on ensuring E2E in-transit integrity to and from Cloud KMS visit:
// https://cloud.google.com/kms/docs/data-integrity-guidelines
if result.VerifiedDigestCrc32C == false {
return fmt.Errorf("AsymmetricSign: request corrupted in-transit")
}
if result.Name != req.Name {
return fmt.Errorf("AsymmetricSign: request corrupted in-transit")
}
if int64(crc32c(result.Signature)) != result.SignatureCrc32C.Value {
return fmt.Errorf("AsymmetricSign: response corrupted in-transit")
}

pubKeyResponse, err := client.GetPublicKey(ctx, &kmspb.GetPublicKeyRequest{Name: name})
if err != nil {
return fmt.Errorf("failed to get public key: %w", err)
}

block, _ := pem.Decode([]byte(pubKeyResponse.Pem))
var gcpPubKey publicKeyInfo
if _, err := asn1.Unmarshal(block.Bytes, &gcpPubKey); err != nil {

Check failure on line 374 in cmd/signer/signer.go

View workflow job for this annotation

GitHub Actions / Lint

declaration of "err" shadows declaration at line 318
return err
}

// Verify Elliptic Curve signature.
var parsedSig struct{ R, S *big.Int }
if _, err = asn1.Unmarshal(result.Signature, &parsedSig); err != nil {
return fmt.Errorf("asn1.Unmarshal: %w", err)
}
ethSig := make([]byte, 0)

ethSig = append(ethSig, bigIntTo32Bytes(parsedSig.R)...)
ethSig = append(ethSig, bigIntTo32Bytes(parsedSig.S)...)
ethSig = append(ethSig, 0)

// Feels like a hack, but I cna't figure out a better way to determine the recovery ID than this since google isn't returning it. More research is required
pubKey, err := crypto.Ecrecover(digest.Bytes(), ethSig)
if err != nil || !bytes.Equal(pubKey, gcpPubKey.PublicKey.Bytes) {
ethSig[64] = 1
}
pubKey, err = crypto.Ecrecover(digest.Bytes(), ethSig)
if err != nil || !bytes.Equal(pubKey, gcpPubKey.PublicKey.Bytes) {
return fmt.Errorf("unable to determine recovery identifier value: %w", err)
}
pubKeyAddr := common.BytesToAddress(crypto.Keccak256(pubKey[1:])[12:])
log.Info().
Str("hexSignature", hex.EncodeToString(result.Signature)).
Str("ethSignature", hex.EncodeToString(ethSig)).
Msg("Got signature")

log.Info().
Str("recoveredPub", hex.EncodeToString(pubKey)).
Str("gcpPub", hex.EncodeToString(gcpPubKey.PublicKey.Bytes)).
Str("ethAddress", pubKeyAddr.String()).
Msg("Recovered pub key")

signedTx, err := tx.WithSignature(signer, ethSig)
if err != nil {
return err
}

return outputSignedTx(signedTx)
}

func bigIntTo32Bytes(num *big.Int) []byte {
// Convert big.Int to a 32-byte array
b := num.Bytes()
if len(b) < 32 {
// Left-pad with zeros if needed
b = append(make([]byte, 32-len(b)), b...)
}
return b
}
func getKeystorePassword() (string, error) {
if *inputSignerOpts.unsafePassword != "" {
return *inputSignerOpts.unsafePassword, nil
Expand Down Expand Up @@ -353,7 +506,7 @@ func init() {
inputSignerOpts.keyID = SignerCmd.PersistentFlags().String("key-id", "", "The id of the key to be used for signing")
inputSignerOpts.unsafePassword = SignerCmd.PersistentFlags().String("unsafe-password", "", "A non-interactively specified password for unlocking the keystore")

inputSignerOpts.signerType = SignerCmd.PersistentFlags().String("type", "latest", "The type of signer to use: latest, cancun, london, eip2930, eip155")
inputSignerOpts.signerType = SignerCmd.PersistentFlags().String("type", "london", "The type of signer to use: latest, cancun, london, eip2930, eip155")
inputSignerOpts.dataFile = SignerCmd.PersistentFlags().String("data-file", "", "File name holding data to be signed")

inputSignerOpts.chainID = SignerCmd.PersistentFlags().Uint64("chain-id", 0, "The chain id for the transactions.")
Expand Down
3 changes: 3 additions & 0 deletions cmd/signer/usage.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
```bash
polycli signer create
polycli signer create --keystore /tmp/keystore
polycli signer create --kms GCP --gcp-project-id prj-polygonlabs-devtools-dev --key-id jhilliard-trash
polycli signer sign --kms GCP --gcp-project-id prj-polygonlabs-devtools-dev --key-id jhilliard-trash --data-file foo.json
polycli signer sign --keystore /tmp/keystore --key-id 0x58ce4bE73Ee7D0dee75395Ef662e98F91AD2E740 --data-file foo.json
```
4 changes: 4 additions & 0 deletions hdwallet/hdwallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,10 @@ func toWIF(prvKey *bip32.Key) string {

func toETHAddress(prvKey *bip32.Key) common.Address {
concat := toUncompressedPubKey(prvKey)
return RawPubKeyToETHAddress(concat)

}
func RawPubKeyToETHAddress(concat []byte) common.Address {
h := sha3.NewLegacyKeccak256()
h.Write(concat)
b := h.Sum(nil)
Expand Down

0 comments on commit f25dfcf

Please sign in to comment.