Skip to content

Commit 3bcb52c

Browse files
authored
feat: Support for importing unarmored key (secp256k1 & ed25519 algos) (#176)
1 parent 09cf7ba commit 3bcb52c

File tree

14 files changed

+333
-31
lines changed

14 files changed

+333
-31
lines changed

.github/workflows/labeler.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ jobs:
66
labeler:
77
runs-on: ubuntu-latest
88
steps:
9-
- uses: actions/labeler@main
9+
- name: Checkout
10+
uses: actions/checkout@v4
1011
with:
11-
repo-token: "${{ secrets.GITHUB_TOKEN }}"
12+
repo-token: "${{ secrets.GITHUB_TOKEN }}"
13+
- name: Label PR
14+
uses: actions/labeler@v5
15+
with:
16+
repo-token: "${{ secrets.GITHUB_TOKEN }}"

.github/workflows/release-sims.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
- name: install runsim
2323
run: |
2424
export GO111MODULE="on" && go install github.com/cosmos/tools/cmd/[email protected]
25-
- uses: actions/cache@v2.1.6
25+
- uses: actions/cache@v4
2626
with:
2727
path: ~/go/bin
2828
key: ${{ runner.os }}-go-runsim-binary
@@ -32,7 +32,7 @@ jobs:
3232
needs: [build, install-runsim]
3333
steps:
3434
- uses: actions/checkout@v2
35-
- uses: actions/cache@v2.1.6
35+
- uses: actions/cache@v4
3636
with:
3737
path: ~/go/bin
3838
key: ${{ runner.os }}-go-runsim-binary

.github/workflows/sims.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
run: go version
3232
- name: Install runsim
3333
run: export GO111MODULE="on" && go install github.com/cosmos/tools/cmd/[email protected]
34-
- uses: actions/cache@v2.1.6
34+
- uses: actions/cache@v4
3535
with:
3636
path: ~/go/bin
3737
key: ${{ runner.os }}-go-runsim-binary
@@ -52,7 +52,7 @@ jobs:
5252
**/**.go
5353
go.mod
5454
go.sum
55-
- uses: actions/cache@v2.1.6
55+
- uses: actions/cache@v4
5656
with:
5757
path: ~/go/bin
5858
key: ${{ runner.os }}-go-runsim-binary
@@ -80,7 +80,7 @@ jobs:
8080
go.sum
8181
SET_ENV_NAME_INSERTIONS: 1
8282
SET_ENV_NAME_LINES: 1
83-
- uses: actions/cache@v2.1.6
83+
- uses: actions/cache@v4
8484
with:
8585
path: ~/go/bin
8686
key: ${{ runner.os }}-go-runsim-binary
@@ -108,7 +108,7 @@ jobs:
108108
go.sum
109109
SET_ENV_NAME_INSERTIONS: 1
110110
SET_ENV_NAME_LINES: 1
111-
- uses: actions/cache@v2.1.6
111+
- uses: actions/cache@v4
112112
with:
113113
path: ~/go/bin
114114
key: ${{ runner.os }}-go-runsim-binary
@@ -136,7 +136,7 @@ jobs:
136136
go.sum
137137
SET_ENV_NAME_INSERTIONS: 1
138138
SET_ENV_NAME_LINES: 1
139-
- uses: actions/cache@v2.1.6
139+
- uses: actions/cache@v4
140140
with:
141141
path: ~/go/bin
142142
key: ${{ runner.os }}-go-runsim-binary

.github/workflows/test.yml

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
- name: install tparse
1919
run: |
2020
go install github.com/mfridman/[email protected]
21-
- uses: actions/cache@v2.1.6
21+
- uses: actions/cache@v4
2222
with:
2323
path: ~/go/bin
2424
key: ${{ runner.os }}-go-tparse-binary
@@ -77,19 +77,19 @@ jobs:
7777
- name: Split pkgs into 4 files
7878
run: split -d -n l/4 pkgs.txt pkgs.txt.part.
7979
# cache multiple
80-
- uses: actions/upload-artifact@v2
80+
- uses: actions/upload-artifact@v4
8181
with:
8282
name: "${{ github.sha }}-00"
8383
path: ./pkgs.txt.part.00
84-
- uses: actions/upload-artifact@v2
84+
- uses: actions/upload-artifact@v4
8585
with:
8686
name: "${{ github.sha }}-01"
8787
path: ./pkgs.txt.part.01
88-
- uses: actions/upload-artifact@v2
88+
- uses: actions/upload-artifact@v4
8989
with:
9090
name: "${{ github.sha }}-02"
9191
path: ./pkgs.txt.part.02
92-
- uses: actions/upload-artifact@v2
92+
- uses: actions/upload-artifact@v4
9393
with:
9494
name: "${{ github.sha }}-03"
9595
path: ./pkgs.txt.part.03
@@ -112,15 +112,15 @@ jobs:
112112
**/**.go
113113
go.mod
114114
go.sum
115-
- uses: actions/download-artifact@v2
115+
- uses: actions/download-artifact@v4
116116
with:
117117
name: "${{ github.sha }}-${{ matrix.part }}"
118118
if: env.GIT_DIFF
119119
- name: test & coverage report creation
120120
run: |
121121
cat pkgs.txt.part.${{ matrix.part }} | xargs go test -mod=readonly -timeout 30m -coverprofile=${{ matrix.part }}profile.out -covermode=atomic -tags='norace ledger test_ledger_mock'
122122
if: env.GIT_DIFF
123-
- uses: actions/upload-artifact@v2
123+
- uses: actions/upload-artifact@v4
124124
with:
125125
name: "${{ github.sha }}-${{ matrix.part }}-coverage"
126126
path: ./${{ matrix.part }}profile.out
@@ -136,19 +136,19 @@ jobs:
136136
**/**.go
137137
go.mod
138138
go.sum
139-
- uses: actions/download-artifact@v2
139+
- uses: actions/download-artifact@v4
140140
with:
141141
name: "${{ github.sha }}-00-coverage"
142142
if: env.GIT_DIFF
143-
- uses: actions/download-artifact@v2
143+
- uses: actions/download-artifact@v4
144144
with:
145145
name: "${{ github.sha }}-01-coverage"
146146
if: env.GIT_DIFF
147-
- uses: actions/download-artifact@v2
147+
- uses: actions/download-artifact@v4
148148
with:
149149
name: "${{ github.sha }}-02-coverage"
150150
if: env.GIT_DIFF
151-
- uses: actions/download-artifact@v2
151+
- uses: actions/download-artifact@v4
152152
with:
153153
name: "${{ github.sha }}-03-coverage"
154154
if: env.GIT_DIFF
@@ -190,15 +190,15 @@ jobs:
190190
**/**.go
191191
go.mod
192192
go.sum
193-
- uses: actions/download-artifact@v2
193+
- uses: actions/download-artifact@v4
194194
with:
195195
name: "${{ github.sha }}-${{ matrix.part }}"
196196
if: env.GIT_DIFF
197197
- name: test & coverage report creation
198198
run: |
199199
xargs --arg-file=pkgs.txt.part.${{ matrix.part }} go test -mod=readonly -timeout 30m -race -tags='cgo ledger test_ledger_mock'
200200
if: env.GIT_DIFF
201-
- uses: actions/upload-artifact@v2
201+
- uses: actions/upload-artifact@v4
202202
with:
203203
name: "${{ github.sha }}-${{ matrix.part }}-race-output"
204204
path: ./${{ matrix.part }}-race-output.txt

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ test-cover:
309309

310310
test-rosetta:
311311
docker build -t rosetta-ci:latest -f contrib/rosetta/node/Dockerfile .
312-
docker-compose -f contrib/rosetta/docker-compose.yaml up --abort-on-container-exit --exit-code-from test_rosetta --build
312+
docker compose -f contrib/rosetta/docker-compose.yaml up --abort-on-container-exit --exit-code-from test_rosetta --build
313313
.PHONY: test-rosetta
314314

315315
benchmark:

client/keys/export.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ import (
1212
)
1313

1414
const (
15-
flagUnarmoredHex = "unarmored-hex"
16-
flagUnsafe = "unsafe"
15+
flagUnarmoredHex = "unarmored-hex"
16+
flagUnarmoredKeyAlgo = "unarmored-key-algo"
17+
flagUnsafe = "unsafe"
1718
)
1819

1920
// ExportKeyCommand exports private keys from the key store.

client/keys/import.go

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@ package keys
22

33
import (
44
"bufio"
5+
"encoding/hex"
6+
"fmt"
57
"os"
8+
"strings"
9+
10+
"github.com/cosmos/cosmos-sdk/crypto/hd"
11+
"github.com/cosmos/cosmos-sdk/crypto/keyring"
612

713
"github.com/spf13/cobra"
814

@@ -38,3 +44,142 @@ func ImportKeyCommand() *cobra.Command {
3844
},
3945
}
4046
}
47+
48+
// ImportUnarmoredKeyCommand imports private keys from a keyfile.
49+
func ImportUnarmoredKeyCommand() *cobra.Command {
50+
cmd := &cobra.Command{
51+
Use: "import-unarmored <name> [keyfile]",
52+
Short: "Imports unarmored private key into the local keybase",
53+
Long: `Imports hex encoded raw unarmored private key into the local keybase
54+
55+
[keyfile] - Path to the file containing unarmored hex encoded private key.
56+
=> *IF* this non-mandatory 2nd positional argument has been
57+
*PROVIDED*, then private key will be read from that file.
58+
59+
=> *ELSE* If this positional argument has been *OMITTED*, then
60+
user will be prompted on terminal to provide the private key
61+
at SECURE PROMPT = passed in characters of the key hex value
62+
will *not* be displayed on the terminal.
63+
64+
File format: The only condition for the file format is, that
65+
the unarmored key must be on the first line (the file can also
66+
contain further lines, though they are ignored).
67+
68+
The 1st line must contain only hex encoded unarmored raw value,
69+
serialised *exactly* as it is expected by given cryptographic
70+
algorithm specified by the '--unarmored-key-algo <algo>' flag
71+
(see the description of that flag).
72+
Hex key value can be preceded & followed by any number of any
73+
whitespace characters, they will be ignored.
74+
75+
Key value:
76+
As mentioned above, key is expected to be hex encoded. Hex encoding can be
77+
lowercase, uppercase or mixed case, it does not matter, and it can (but
78+
does NOT need to) contain the '0x' or just 'x' prefix at the beginning of
79+
the hex encoded value.
80+
81+
Output:
82+
The command will print key info after the import, the same way as the
83+
'keys add ...' command does.
84+
This is quite useful, since user will immediately see the address (and pub
85+
key value) derived from the imported private key.`,
86+
Args: cobra.RangeArgs(1, 2),
87+
RunE: func(cmd *cobra.Command, args []string) error {
88+
clientCtx, err := client.GetClientQueryContext(cmd)
89+
if err != nil {
90+
return err
91+
}
92+
93+
buf := bufio.NewReader(clientCtx.Input)
94+
var privKeyHex string
95+
if len(args) == 1 {
96+
privKeyHex, err = input.GetPassword("Enter hex encoded private key:", buf)
97+
if err != nil {
98+
return err
99+
}
100+
} else {
101+
filename := args[1]
102+
f, err := os.OpenFile(filename, os.O_RDONLY, os.ModePerm)
103+
if err != nil {
104+
return fmt.Errorf("open file \"%s\" error: %w", filename, err)
105+
}
106+
defer f.Close()
107+
108+
sc := bufio.NewScanner(f)
109+
if sc.Scan() {
110+
firstLine := sc.Text()
111+
privKeyHex = strings.TrimSpace(firstLine)
112+
} else {
113+
return fmt.Errorf("unable to read 1st line from the \"%s\" file", filename)
114+
}
115+
116+
if err := sc.Err(); err != nil {
117+
return fmt.Errorf("error while scanning the \"%s\" file: %w", filename, err)
118+
}
119+
}
120+
121+
algo, _ := cmd.Flags().GetString(flagUnarmoredKeyAlgo)
122+
123+
privKeyHexLC := strings.ToLower(privKeyHex)
124+
acceptedHexValPrefixes := []string{"0x", "x"}
125+
for _, prefix := range acceptedHexValPrefixes {
126+
if strings.HasPrefix(privKeyHexLC, prefix) {
127+
privKeyHexLC = privKeyHexLC[len(prefix):]
128+
break
129+
}
130+
}
131+
132+
privKeyRaw, err := hex.DecodeString(privKeyHexLC)
133+
if err != nil {
134+
return fmt.Errorf("failed to decode provided hex value of private key: %w", err)
135+
}
136+
137+
info, err := clientCtx.Keyring.ImportUnarmoredPrivKey(args[0], privKeyRaw, algo)
138+
if err != nil {
139+
return fmt.Errorf("importing unarmored private key: %w", err)
140+
}
141+
142+
if err := printCreateUnarmored(cmd, info, clientCtx.OutputFormat); err != nil {
143+
return fmt.Errorf("printing private key info: %w", err)
144+
}
145+
146+
return nil
147+
},
148+
}
149+
150+
cmd.Flags().String(flagUnarmoredKeyAlgo, string(hd.Secp256k1Type), fmt.Sprintf(
151+
`Defines cryptographic scheme algorithm of the provided unarmored private key.
152+
At the moment *ONLY* the "%s" and "%s" algorithms are supported.
153+
Expected serialisation format of the raw unarmored key value:
154+
* for "%s": 32 bytes raw private key (hex encoded)
155+
* for "%s": 32 bytes raw public key immediately followed by 32 bytes
156+
private key = 64 bytes altogether (hex encoded)
157+
`, hd.Secp256k1Type, hd.Ed25519Type, hd.Secp256k1Type, hd.Ed25519Type))
158+
159+
return cmd
160+
}
161+
162+
func printCreateUnarmored(cmd *cobra.Command, info keyring.Info, outputFormat string) error {
163+
switch outputFormat {
164+
case OutputFormatText:
165+
cmd.PrintErrln()
166+
printKeyInfo(cmd.OutOrStdout(), info, keyring.MkAccKeyOutput, outputFormat)
167+
case OutputFormatJSON:
168+
out, err := keyring.MkAccKeyOutput(info)
169+
if err != nil {
170+
return err
171+
}
172+
173+
jsonString, err := KeysCdc.MarshalJSON(out)
174+
if err != nil {
175+
return err
176+
}
177+
178+
cmd.Println(string(jsonString))
179+
180+
default:
181+
return fmt.Errorf("invalid output format %s", outputFormat)
182+
}
183+
184+
return nil
185+
}

0 commit comments

Comments
 (0)