Skip to content

Commit 4ea5871

Browse files
tac0turtletac0turtleCopilot
authored
feat: add import and export of signing key (#2345)
<!-- Please read and fill out this form before submitting your PR. Please make sure you have reviewed our contributors guide before submitting your first PR. NOTE: PR titles should follow semantic commits: https://www.conventionalcommits.org/en/v1.0.0/ --> ## Overview This pr adds export and import of the signing key to be stored externally <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced new CLI commands for exporting and importing signing keys, allowing users to manage private keys directly from the command line. - Added a keys management command to multiple CLI applications, providing enhanced control over key storage and transfer. - **Tests** - Added comprehensive tests covering key import/export functionality, including error handling and edge cases, to ensure reliability and security. - Enhanced tests for header signing and genesis creation to improve validation and coverage. - **Chores** - Updated coverage configuration to exclude files named `metrics.go` from coverage analysis. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: tac0turtle <you@example.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent b9f64fd commit 4ea5871

9 files changed

Lines changed: 682 additions & 6 deletions

File tree

apps/evm/based/main.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,12 @@ func main() {
2424
ctx := context.Background()
2525
rootCmd.AddCommand(
2626
cmd.NewExtendedRunNodeCmd(ctx),
27-
rollcmd.VersionCmd,
2827
cmd.InitCmd(),
28+
29+
rollcmd.VersionCmd,
2930
rollcmd.NetInfoCmd,
31+
rollcmd.StoreUnsafeCleanCmd,
32+
rollcmd.KeysCmd(),
3033
)
3134

3235
if err := rootCmd.Execute(); err != nil {

apps/evm/single/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ func main() {
2727
rollcmd.VersionCmd,
2828
rollcmd.NetInfoCmd,
2929
rollcmd.StoreUnsafeCleanCmd,
30+
rollcmd.KeysCmd(),
3031
)
3132

3233
if err := rootCmd.Execute(); err != nil {

apps/testapp/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ func main() {
1919
rollcmd.VersionCmd,
2020
rollcmd.NetInfoCmd,
2121
rollcmd.StoreUnsafeCleanCmd,
22+
rollcmd.KeysCmd(),
2223
initCmd,
2324
)
2425

codecov.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ ignore:
1515
- "da/internal/mocks"
1616
- "da/cmd"
1717
- "execution/evm" # EVM is covered in e2e/integration tests
18+
- "**/metrics.go"

pkg/cmd/keys.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package cmd
2+
3+
import (
4+
"encoding/hex"
5+
"fmt"
6+
"os"
7+
"path/filepath"
8+
9+
"github.com/spf13/cobra"
10+
11+
rollconf "github.com/rollkit/rollkit/pkg/config"
12+
"github.com/rollkit/rollkit/pkg/signer/file"
13+
)
14+
15+
// KeysCmd returns a command for managing keys.
16+
func KeysCmd() *cobra.Command {
17+
cmd := &cobra.Command{
18+
Use: "keys",
19+
Short: "Manage signing keys",
20+
}
21+
22+
cmd.AddCommand(exportKeyCmd())
23+
cmd.AddCommand(importKeyCmd())
24+
25+
return cmd
26+
}
27+
28+
func exportKeyCmd() *cobra.Command {
29+
cmd := &cobra.Command{
30+
Use: "export",
31+
Short: "Export the private key to plain text",
32+
Long: `Export the locally saved signing key to plain text.
33+
This allows the key to be stored in a password manager or other secure storage.
34+
35+
WARNING: The exported key is not encrypted. Handle it with extreme care.
36+
Anyone with access to the exported key can sign messages on your behalf.
37+
`,
38+
RunE: func(cmd *cobra.Command, args []string) error {
39+
nodeConfig, err := rollconf.Load(cmd)
40+
if err != nil {
41+
return fmt.Errorf("failed to load node config: %w", err)
42+
}
43+
44+
passphrase, err := cmd.Flags().GetString(rollconf.FlagSignerPassphrase)
45+
if err != nil {
46+
return err
47+
}
48+
if passphrase == "" {
49+
return fmt.Errorf("passphrase is required. Please provide it using the --%s flag", rollconf.FlagSignerPassphrase)
50+
}
51+
52+
keyPath := filepath.Join(nodeConfig.RootDir, "config")
53+
54+
// Print a strong warning to stderr
55+
cmd.PrintErrln("WARNING: EXPORTING PRIVATE KEY. HANDLE WITH EXTREME CARE.")
56+
cmd.PrintErrln("ANYONE WITH ACCESS TO THIS KEY CAN SIGN MESSAGES ON YOUR BEHALF.")
57+
58+
privKeyBytes, err := file.ExportPrivateKey(keyPath, []byte(passphrase))
59+
if err != nil {
60+
return fmt.Errorf("failed to export private key: %w", err)
61+
}
62+
63+
hexKey := hex.EncodeToString(privKeyBytes)
64+
cmd.Println(hexKey)
65+
66+
return nil
67+
},
68+
}
69+
cmd.Flags().String(rollconf.FlagSignerPassphrase, "", "Passphrase for the signer key")
70+
return cmd
71+
}
72+
73+
func importKeyCmd() *cobra.Command {
74+
cmd := &cobra.Command{
75+
Use: "import [hex-private-key]",
76+
Short: "Import a private key from plain text",
77+
Long: `Import a decrypted private key and generate the encrypted file locally.
78+
This allows the system to use it for signing.
79+
80+
The private key should be provided as a hex-encoded string.
81+
82+
WARNING: This command will overwrite any existing key.
83+
If a 'signer.json' file exists in your home directory, you must use the --force flag.
84+
`,
85+
Args: cobra.ExactArgs(1),
86+
RunE: func(cmd *cobra.Command, args []string) error {
87+
nodeConfig, err := rollconf.Load(cmd)
88+
if err != nil {
89+
return fmt.Errorf("failed to load node config: %w", err)
90+
}
91+
92+
passphrase, err := cmd.Flags().GetString(rollconf.FlagSignerPassphrase)
93+
if err != nil {
94+
return err
95+
}
96+
if passphrase == "" {
97+
return fmt.Errorf("passphrase is required to encrypt the imported key. Please provide it using the --%s flag", rollconf.FlagSignerPassphrase)
98+
}
99+
100+
hexKey := args[0]
101+
privKeyBytes, err := hex.DecodeString(hexKey)
102+
if err != nil {
103+
return fmt.Errorf("failed to decode hex private key: %w", err)
104+
}
105+
106+
keyPath := filepath.Join(nodeConfig.RootDir, "config")
107+
filePath := filepath.Join(keyPath, "signer.json")
108+
109+
// Check if file exists and if --force is used
110+
force, _ := cmd.Flags().GetBool("force")
111+
if _, err := os.Stat(filePath); err == nil && !force {
112+
return fmt.Errorf("key file already exists at %s. Use --force to overwrite", filePath)
113+
}
114+
115+
if err := file.ImportPrivateKey(keyPath, privKeyBytes, []byte(passphrase)); err != nil {
116+
return fmt.Errorf("failed to import private key: %w", err)
117+
}
118+
119+
cmd.Printf("Successfully imported key and saved to %s\n", filePath)
120+
return nil
121+
},
122+
}
123+
124+
cmd.Flags().Bool("force", false, "Overwrite existing key file if it exists")
125+
cmd.Flags().String(rollconf.FlagSignerPassphrase, "", "Passphrase to encrypt the imported key")
126+
return cmd
127+
}

0 commit comments

Comments
 (0)