Skip to content

Commit

Permalink
feat: basic local signing functions
Browse files Browse the repository at this point in the history
  • Loading branch information
praetoriansentry committed Dec 20, 2023
1 parent 0b50d8d commit 8440c40
Show file tree
Hide file tree
Showing 8 changed files with 341 additions and 39 deletions.
14 changes: 7 additions & 7 deletions cmd/monitor/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ var MonitorCmd = &cobra.Command{
Short: "Monitor blocks using a JSON-RPC endpoint.",
Long: usage,
Args: cobra.NoArgs,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
// By default, hide logs from `polycli monitor`.
verbosityFlag := cmd.Flag("verbosity")
if verbosityFlag != nil && !verbosityFlag.Changed {
util.SetLogLevel(int(util.Silent))
}
},
// PersistentPreRun: func(cmd *cobra.Command, args []string) {
// // By default, hide logs from `polycli monitor`.
// verbosityFlag := cmd.Flag("verbosity")
// if verbosityFlag != nil && !verbosityFlag.Changed {
// util.SetLogLevel(int(util.Silent))
// }
// },
PreRunE: func(cmd *cobra.Command, args []string) error {
return checkFlags()
},
Expand Down
7 changes: 2 additions & 5 deletions cmd/parseethwallet/parseethwallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"github.com/maticnetwork/polygon-cli/gethkeystore"
"io"
"os"
"strings"
Expand All @@ -27,10 +28,6 @@ var (
inputKeyStoreDirectory *string
)

type plainKeyJSON struct {
Address string `json:"address"`
Crypto keystore.CryptoJSON `json:"crypto"`
}
type outKey struct {
Address string
PublicKey string
Expand Down Expand Up @@ -60,7 +57,7 @@ var ParseETHWalletCmd = &cobra.Command{
if err != nil {
return err
}
k := new(plainKeyJSON)
k := new(gethkeystore.RawKeystoreData)
err = json.Unmarshal(rawData, &k)
if err != nil {
return err
Expand Down
2 changes: 2 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cmd
import (
"fmt"
"github.com/maticnetwork/polygon-cli/cmd/dbbench"
"github.com/maticnetwork/polygon-cli/cmd/signer"
"os"

"github.com/maticnetwork/polygon-cli/cmd/fork"
Expand Down Expand Up @@ -120,6 +121,7 @@ func NewPolycliCommand() *cobra.Command {
parseethwallet.ParseETHWalletCmd,
rpc.RpcCmd,
rpcfuzz.RPCFuzzCmd,
signer.SignerCmd,
version.VersionCmd,
wallet.WalletCmd,
)
Expand Down
256 changes: 256 additions & 0 deletions cmd/signer/signer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
package signer

import (
"bytes"
"crypto/ecdsa"
"encoding/hex"
"encoding/json"
"fmt"
accounts2 "github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/manifoldco/promptui"
"github.com/maticnetwork/polygon-cli/gethkeystore"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"math/big"
"os"
)

type signerOpts struct {
keystore *string
privateKey *string
kms *string
keyID *string
unsafePassword *string
dataFile *string
signerType *string
chainID *uint64
}

type signedTx struct {

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

View workflow job for this annotation

GitHub Actions / Lint

type `signedTx` is unused (unused)
}

var inputSignerOpts = signerOpts{}

var SignerCmd = &cobra.Command{
Use: "signer",
Short: "Utilities for security signing transactions",
Long: "TODO",
Args: cobra.NoArgs,
}

var SignCmd = &cobra.Command{
Use: "sign",
Short: "Sign tx data",
Long: "TODO",
Args: cobra.NoArgs,
PreRunE: sanityCheck,
RunE: func(cmd *cobra.Command, args []string) error {
if *inputSignerOpts.keystore == "" && *inputSignerOpts.privateKey == "" && *inputSignerOpts.kms == "" {
return fmt.Errorf("no valid keystore was specified")
}

if *inputSignerOpts.keystore != "" {
ks := keystore.NewKeyStore(*inputSignerOpts.keystore, keystore.StandardScryptN, keystore.StandardScryptP)
accounts := ks.Accounts()
var accountToUnlock *accounts2.Account
for _, a := range accounts {
if a.Address.String() == *inputSignerOpts.keyID {
accountToUnlock = &a
break
}
}
if accountToUnlock == nil {
accountStrings := ""
for _, a := range accounts {
accountStrings += a.Address.String() + " "
}
return fmt.Errorf("the account with address <%s> could not be found in list [%s]", *inputSignerOpts.keyID, accountStrings)
}
password, err := getKeystorePassword()
if err != nil {
return err
}

err = ks.Unlock(*accountToUnlock, password)
if err != nil {
return err
}
// chainID := new(big.Int).SetUint64(*inputSignerOpts.chainID)

// ks.SignTx(*accountToUnlock, &tx, chainID)
log.Info().Str("path", accountToUnlock.URL.Path).Msg("Unlocked account")
encryptedKey, err := os.ReadFile(accountToUnlock.URL.Path)
if err != nil {
return err
}
privKey, err := gethkeystore.DecryptKeystoreFile(encryptedKey, password)
if err != nil {
return err
}
return sign(privKey)
}

if *inputSignerOpts.privateKey != "" {
pk, err := crypto.HexToECDSA(*inputSignerOpts.privateKey)
if err != nil {
return err
}
return sign(pk)

}
return nil
},
}

var CreateCmd = &cobra.Command{
Use: "create",
Short: "Create a new key",
Long: "TODO",
Args: cobra.NoArgs,
PreRunE: sanityCheck,
RunE: func(cmd *cobra.Command, args []string) error {
if *inputSignerOpts.keystore == "" && *inputSignerOpts.kms == "" {
log.Info().Msg("Generating new private hex key and writing to stdout")
pk, err := crypto.GenerateKey()
if err != nil {
return err
}
k := hex.EncodeToString(crypto.FromECDSA(pk))
fmt.Println(k)
return nil
}
if *inputSignerOpts.keystore != "" {
ks := keystore.NewKeyStore(*inputSignerOpts.keystore, keystore.StandardScryptN, keystore.StandardScryptP)
pk, err := crypto.GenerateKey()
if err != nil {
return err
}
password, err := getKeystorePassword()
if err != nil {
return err
}
acc, err := ks.ImportECDSA(pk, password)
if err != nil {
return err
}
log.Info().Str("address", acc.Address.String()).Msg("imported new account")
return nil
}
return nil
},
}

func sign(pk *ecdsa.PrivateKey) error {
dataToSign, err := os.ReadFile(*inputSignerOpts.dataFile)
if err != nil {
return err
}
var tx types.LegacyTx
err = json.Unmarshal(dataToSign, &tx)
if err != nil {
// TODO in the future it might make sense to sign arbitrary data?
return err
}
signer, err := getSigner()
if err != nil {
return err
}
signedTx, err := types.SignNewTx(pk, signer, &tx)
if err != nil {
return err
}
rlpData := make([]byte, 0)
buf := bytes.NewBuffer(rlpData)
err = signedTx.EncodeRLP(buf)
if err != nil {
return err
}
rawHexString := hex.EncodeToString(buf.Bytes())
out := make(map[string]any, 0)
out["txData"] = tx
out["signedTx"] = signedTx
out["rawSignedTx"] = rawHexString
outJSON, err := json.Marshal(out)
if err != nil {
return err
}
fmt.Println(string(outJSON))
return nil
}

func getKeystorePassword() (string, error) {
if *inputSignerOpts.unsafePassword != "" {
return *inputSignerOpts.unsafePassword, nil
}
return passwordPrompt.Run()
}

func sanityCheck(cmd *cobra.Command, args []string) error {
keyStoreMethods := 0
if *inputSignerOpts.kms != "" {
keyStoreMethods += 1
}
if *inputSignerOpts.privateKey != "" {
keyStoreMethods += 1
}
if *inputSignerOpts.keystore != "" {
keyStoreMethods += 1
}
if keyStoreMethods > 1 {
return fmt.Errorf("Multiple conflicting keystore mults were specified")
}
pwErr := passwordValidation(*inputSignerOpts.unsafePassword)
if *inputSignerOpts.unsafePassword != "" && pwErr != nil {
return pwErr
}
return nil
}

func passwordValidation(inputPw string) error {
if len(inputPw) < 6 {
return fmt.Errorf("Password only had %d character. 8 or more required", len(inputPw))
}
return nil
}

var passwordPrompt = promptui.Prompt{
Label: "Password",
Validate: passwordValidation,
Mask: '*',
}

func getSigner() (types.Signer, error) {
chainID := new(big.Int).SetUint64(*inputSignerOpts.chainID)
switch *inputSignerOpts.signerType {
case "latest":
return types.LatestSignerForChainID(chainID), nil
case "cancun":
return types.NewCancunSigner(chainID), nil
case "london":
return types.NewLondonSigner(chainID), nil
case "eip2930":
return types.NewEIP2930Signer(chainID), nil
case "eip155":
return types.NewEIP155Signer(chainID), nil
}
return nil, fmt.Errorf("signer %s is not recognized", *inputSignerOpts.signerType)
}

func init() {
inputSignerOpts.keystore = SignerCmd.PersistentFlags().String("keystore", "", "Use the keystore in the given folder or file")
inputSignerOpts.privateKey = SignerCmd.PersistentFlags().String("private-key", "", "Use the provided hex encoded private key")
inputSignerOpts.kms = SignerCmd.PersistentFlags().String("kms", "", "AWS or GCP if the key is stored in the cloud")
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.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.")

SignerCmd.AddCommand(SignCmd)
SignerCmd.AddCommand(CreateCmd)
}
4 changes: 4 additions & 0 deletions cmd/signer/usage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
```bash
polycli signer create
polycli signer create --keystore /tmp/keystore
```
35 changes: 35 additions & 0 deletions gethkeystore/gethkeystore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package gethkeystore

import (
"crypto/ecdsa"

"encoding/hex"
"encoding/json"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/crypto"
)

type RawKeystoreData struct {
Address string `json:"address"`
Crypto keystore.CryptoJSON `json:"crypto"`
}

func DecryptKeystoreFile(fileData []byte, password string) (*ecdsa.PrivateKey, error) {
var encryptedData RawKeystoreData
err := json.Unmarshal(fileData, &encryptedData)
if err != nil {
return nil, err
}
decryptedData, err := keystore.DecryptDataV3(encryptedData.Crypto, password)
if err != nil {
return nil, err
}

privKeyStr := hex.EncodeToString(decryptedData)

privKey, err := crypto.HexToECDSA(privKeyStr)
if err != nil {
return nil, err
}
return privKey, nil
}
Loading

0 comments on commit 8440c40

Please sign in to comment.