Skip to content

Commit f23fc78

Browse files
author
mace
authored
Replace subkey with go port (#114)
* Move to go.mod * Replace subkey with go port * Update README file * Refactor fixed array type conversion * Fix linter error * Expand tests * Update changes with new version of subkey go port * Fix linter errors
1 parent 3b7101e commit f23fc78

8 files changed

Lines changed: 313 additions & 122 deletions

File tree

Dockerfile

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,11 @@
11
# Note: We don't use Alpine and its packaged Rust/Cargo because they're too often out of date,
22
# preventing them from being used to build Substrate/Polkadot.
33

4-
# First Phase - Load Subkey
5-
FROM parity/subkey:2.0.0 as subkey
6-
RUN subkey --version
7-
8-
## Second Phase - Build context for tests
4+
## First Phase - Build context for tests
95
FROM parity/substrate:v2.0.0
106

117
USER root
128

13-
COPY --from=subkey /usr/local/bin/subkey /usr/local/bin/subkey
14-
159
# gcc for cgo
1610
RUN apt-get update && apt-get install -y --no-install-recommends \
1711
g++ \
@@ -65,9 +59,6 @@ RUN mkdir -p $GOPATH/src/github.com/centrifuge/go-substrate-rpc-client
6559
WORKDIR $GOPATH/src/github.com/centrifuge/go-substrate-rpc-client
6660
COPY . .
6761

68-
# Ensuring Subkey is available
69-
RUN subkey --version
70-
7162
RUN make install
7263

7364
# Reset parent entrypoint

README.md

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,6 @@ This client is modelled after [polkadot-js/api](https://github.com/polkadot-js/a
1313

1414
This package is feature complete, but it is relatively new and might still contain bugs. We advice to use it with caution in production. It comes without any warranties, please refer to LICENCE for details.
1515

16-
## Requirements
17-
Substrate Key Management requires `subkey` to be present in your PATH: https://substrate.dev/docs/en/knowledgebase/integrate/subkey
18-
19-
The `subkey` recommended version: https://github.com/paritytech/substrate/releases/tag/v2.0.0-rc6
20-
2116
## Documentation & Usage Examples
2217

2318
Please refer to https://godoc.org/github.com/centrifuge/go-substrate-rpc-client

go.mod

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,14 @@ require (
77
github.com/btcsuite/btcutil v1.0.2
88
github.com/davecgh/go-spew v1.1.1
99
github.com/deckarep/golang-set v1.7.1
10-
github.com/ethereum/go-ethereum v1.9.3
11-
github.com/go-stack/stack v1.8.0 // indirect
10+
github.com/ethereum/go-ethereum v1.9.25
1211
github.com/gorilla/websocket v1.4.1
1312
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
1413
github.com/pierrec/xxHash v0.1.5
1514
github.com/rs/cors v1.6.0
16-
github.com/stretchr/testify v1.3.0
17-
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d
18-
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 // indirect
15+
github.com/stretchr/testify v1.6.1
16+
github.com/vedhavyas/go-subkey v1.0.2
17+
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
1918
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
2019
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce
2120
)

go.sum

Lines changed: 207 additions & 4 deletions
Large diffs are not rendered by default.

signature/signature.go

Lines changed: 35 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,16 @@
1717
package signature
1818

1919
import (
20-
"encoding/hex"
21-
"encoding/json"
20+
"errors"
2221
"fmt"
2322
"os"
24-
"os/exec"
25-
"strings"
23+
"strconv"
2624

25+
"github.com/vedhavyas/go-subkey"
26+
"github.com/vedhavyas/go-subkey/sr25519"
2727
"golang.org/x/crypto/blake2b"
2828
)
2929

30-
const subkeyCmd = "subkey"
31-
3230
type KeyringPair struct {
3331
// URI is the derivation path for the private key in subkey
3432
URI string
@@ -38,52 +36,25 @@ type KeyringPair struct {
3836
PublicKey []byte
3937
}
4038

41-
// InspectKeyInfo type is used as target from `subkey` inspect JSON output
42-
type InspectKeyInfo struct {
43-
AccountID string `json:"accountId"`
44-
PublicKey string `json:"publicKey"`
45-
SecretPhrase string `json:"secretPhrase"`
46-
SecretSeed string `json:"secretSeed"`
47-
SS58Address string `json:"ss58Address"`
48-
}
49-
5039
// KeyringPairFromSecret creates KeyPair based on seed/phrase and network
5140
// Leave network empty for default behavior
52-
func KeyringPairFromSecret(seedOrPhrase, network string) (KeyringPair, error) {
53-
var args []string
54-
if network != "" {
55-
args = []string{"--network", network}
56-
}
57-
args = append([]string{"inspect", "--output-type", "Json", seedOrPhrase}, args...)
58-
59-
// use "subkey" command for creation of public key and address
60-
cmd := exec.Command(subkeyCmd, args...)
61-
62-
// execute the command, get the output
63-
out, err := cmd.Output()
41+
func KeyringPairFromSecret(seedOrPhrase string, network uint8) (KeyringPair, error) {
42+
scheme := sr25519.Scheme{}
43+
kyr, err := subkey.DeriveKeyPair(scheme, seedOrPhrase)
6444
if err != nil {
65-
return KeyringPair{}, fmt.Errorf("failed to generate keyring pair from secret: %v", err.Error())
45+
return KeyringPair{}, err
6646
}
6747

68-
if string(out) == "Invalid phrase/URI given" {
69-
return KeyringPair{}, fmt.Errorf("failed to generate keyring pair from secret: invalid phrase/URI given")
70-
}
71-
72-
var keyInfo InspectKeyInfo
73-
err = json.Unmarshal(out, &keyInfo)
48+
ss58Address, err := kyr.SS58Address(network)
7449
if err != nil {
75-
return KeyringPair{}, fmt.Errorf("failed to deserialize key info JSON output: %v", err.Error())
50+
return KeyringPair{}, err
7651
}
7752

78-
pk, err := hex.DecodeString(strings.Replace(keyInfo.PublicKey, "0x", "", 1))
79-
if err != nil {
80-
return KeyringPair{}, fmt.Errorf("failed to generate keyring pair from secret, could not hex decode pubkey: "+
81-
"%v with error: %v", keyInfo.PublicKey, err.Error())
82-
}
53+
var pk = kyr.Public()
8354

8455
return KeyringPair{
8556
URI: seedOrPhrase,
86-
Address: keyInfo.SS58Address,
57+
Address: ss58Address,
8758
PublicKey: pk,
8859
}, nil
8960
}
@@ -103,31 +74,18 @@ func Sign(data []byte, privateKeyURI string) ([]byte, error) {
10374
data = h[:]
10475
}
10576

106-
// use "subkey" command for signature
107-
cmd := exec.Command(subkeyCmd, "sign", "--suri", privateKeyURI, "--hex")
108-
109-
// data to stdin
110-
dataHex := hex.EncodeToString(data)
111-
cmd.Stdin = strings.NewReader(dataHex)
112-
113-
// log.Printf("echo -n \"%v\" | %v sign %v --hex", dataHex, subkeyCmd, privateKeyURI)
114-
115-
// execute the command, get the output
116-
out, err := cmd.Output()
77+
scheme := sr25519.Scheme{}
78+
kyr, err := subkey.DeriveKeyPair(scheme, privateKeyURI)
11779
if err != nil {
118-
return nil, fmt.Errorf("failed to sign with subkey: %v", err.Error())
80+
return nil, err
11981
}
12082

121-
// remove line feed
122-
if len(out) > 0 && out[len(out)-1] == 10 {
123-
out = out[:len(out)-1]
83+
signature, err := kyr.Sign(data)
84+
if err != nil {
85+
return nil, err
12486
}
12587

126-
outStr := string(out)
127-
128-
dec, err := hex.DecodeString(outStr)
129-
130-
return dec, err
88+
return signature, nil
13189
}
13290

13391
// Verify verifies data using the provided signature and the key under the derivation path. Requires the subkey
@@ -139,32 +97,19 @@ func Verify(data []byte, sig []byte, privateKeyURI string) (bool, error) {
13997
data = h[:]
14098
}
14199

142-
// hexify the sig
143-
sigHex := hex.EncodeToString(sig)
144-
145-
// use "subkey" command for signature
146-
cmd := exec.Command(subkeyCmd, "verify", "--hex", sigHex, privateKeyURI)
147-
148-
// data to stdin
149-
dataHex := hex.EncodeToString(data)
150-
cmd.Stdin = strings.NewReader(dataHex)
151-
152-
//log.Printf("echo -n \"%v\" | %v verify --hex %v %v", dataHex, subkeyCmd, sigHex, privateKeyURI)
153-
154-
// execute the command, get the output
155-
out, err := cmd.Output()
100+
scheme := sr25519.Scheme{}
101+
kyr, err := subkey.DeriveKeyPair(scheme, privateKeyURI)
156102
if err != nil {
157-
return false, fmt.Errorf("failed to verify with subkey: %v", err.Error())
103+
return false, err
158104
}
159105

160-
// remove line feed
161-
if len(out) > 0 && out[len(out)-1] == 10 {
162-
out = out[:len(out)-1]
106+
if len(sig) != 64 {
107+
return false, errors.New("wrong signature length")
163108
}
164109

165-
outStr := string(out)
166-
valid := outStr == "Signature verifies correctly."
167-
return valid, nil
110+
v := kyr.Verify(data, sig)
111+
112+
return v, nil
168113
}
169114

170115
// LoadKeyringPairFromEnv looks up whether the env variable TEST_PRIV_KEY is set and is not empty and tries to use its
@@ -173,12 +118,18 @@ func Verify(data []byte, sig []byte, privateKeyURI string) (bool, error) {
173118
// Loads Network from TEST_NETWORK variable
174119
// Leave TEST_NETWORK empty or unset for default
175120
func LoadKeyringPairFromEnv() (kp KeyringPair, ok bool) {
176-
network := os.Getenv("TEST_NETWORK")
121+
networkString := os.Getenv("TEST_NETWORK")
122+
network, err := strconv.ParseInt(networkString, 10, 8)
123+
if err != nil {
124+
// defaults to generic substrate address
125+
// https://github.com/paritytech/substrate/wiki/External-Address-Format-(SS58)#checksum-types
126+
network = 42
127+
}
177128
priv, ok := os.LookupEnv("TEST_PRIV_KEY")
178129
if !ok || priv == "" {
179130
return kp, false
180131
}
181-
kp, err := KeyringPairFromSecret(priv, network)
132+
kp, err = KeyringPairFromSecret(priv, uint8(network))
182133
if err != nil {
183134
panic(fmt.Errorf("cannot load keyring pair from env or use fallback: %v", err))
184135
}

signature/signature_test.go

Lines changed: 64 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,36 +30,64 @@ var testSecretSeed = "0x167d9a020688544ea246b056799d6a771e97c9da057e4d0b87024537
3030
var testPubKey = "0xdc64bef918ddda3126a39a11113767741ddfdf91399f055e1d963f2ae1ec2535"
3131
var testAddressSS58 = "5H3gKVQU7DfNFfNGkgTrD7p715jjg7QXtat8X3UxiSyw7APW"
3232
var testKusamaAddressSS58 = "HZHyokLjagJ1KBiXPGu75B79g1yUnDiLxisuhkvCFCRrWBk"
33+
var testPolkadotAddressSS58 = "15yyTpfXxzvqhCNniKWrMGeFrhjPNQxfy5ccgLUKGY1THbTW"
3334

34-
func TestKeyRingPairFromSecretPhrase(t *testing.T) {
35-
p, err := KeyringPairFromSecret(testSecretPhrase, "")
35+
func TestKeyRingPairFromSecretPhrase_SubstrateAddress(t *testing.T) {
36+
p, err := KeyringPairFromSecret(testSecretPhrase, 42)
3637
assert.NoError(t, err)
3738

3839
assert.Equal(t, KeyringPair{
39-
URI: testSecretPhrase,
40-
Address: testAddressSS58,
40+
URI: testSecretPhrase,
41+
Address: testAddressSS58,
4142
PublicKey: types.MustHexDecodeString(testPubKey),
4243
}, p)
4344
}
4445

46+
func TestKeyRingPairFromSecretPhrase_PolkadotAddress(t *testing.T) {
47+
p, err := KeyringPairFromSecret(testSecretPhrase, 0)
48+
assert.NoError(t, err)
49+
50+
assert.Equal(t, KeyringPair{
51+
URI: testSecretPhrase,
52+
Address: testPolkadotAddressSS58,
53+
PublicKey: types.MustHexDecodeString(testPubKey),
54+
}, p)
55+
}
56+
57+
func TestKeyRingPairFromSecretPhrase_KusamaAddress(t *testing.T) {
58+
p, err := KeyringPairFromSecret(testSecretPhrase, 2)
59+
assert.NoError(t, err)
60+
61+
assert.Equal(t, KeyringPair{
62+
URI: testSecretPhrase,
63+
Address: testKusamaAddressSS58,
64+
PublicKey: types.MustHexDecodeString(testPubKey),
65+
}, p)
66+
}
67+
68+
func TestKeyRingPairFromSecretPhrase_InvalidSecretPhrase(t *testing.T) {
69+
_, err := KeyringPairFromSecret("foo", 42)
70+
assert.Error(t, err)
71+
}
72+
4573
func TestKeyringPairFromSecretSeed(t *testing.T) {
46-
p, err := KeyringPairFromSecret(testSecretSeed, "")
74+
p, err := KeyringPairFromSecret(testSecretSeed, 42)
4775
assert.NoError(t, err)
4876

4977
assert.Equal(t, KeyringPair{
50-
URI: testSecretSeed,
51-
Address: testAddressSS58,
78+
URI: testSecretSeed,
79+
Address: testAddressSS58,
5280
PublicKey: types.MustHexDecodeString(testPubKey),
5381
}, p)
5482
}
5583

5684
func TestKeyringPairFromSecretSeedAndNetwork(t *testing.T) {
57-
p, err := KeyringPairFromSecret(testSecretSeed, "kusama")
85+
p, err := KeyringPairFromSecret(testSecretSeed, 42)
5886
assert.NoError(t, err)
5987

6088
assert.Equal(t, KeyringPair{
61-
URI: testSecretSeed,
62-
Address: testKusamaAddressSS58,
89+
URI: testSecretSeed,
90+
Address: testAddressSS58,
6391
PublicKey: types.MustHexDecodeString(testPubKey),
6492
}, p)
6593
}
@@ -70,12 +98,36 @@ func TestSignAndVerify(t *testing.T) {
7098
sig, err := Sign(data, TestKeyringPairAlice.URI)
7199
assert.NoError(t, err)
72100

73-
ok, err := Verify(data, sig, types.HexEncodeToString(TestKeyringPairAlice.PublicKey))
101+
ok, err := Verify(data, sig, TestKeyringPairAlice.URI)
74102
assert.NoError(t, err)
75103

76104
assert.True(t, ok)
77105
}
78106

107+
func TestSign_InvalidSecretPhrase(t *testing.T) {
108+
data := []byte("hello!")
109+
110+
_, err := Sign(data, "foo")
111+
assert.Error(t, err)
112+
}
113+
114+
func TestSignAndVerify_InvalidSecretPhraseOnVerify(t *testing.T) {
115+
data := []byte("hello!")
116+
117+
sig, err := Sign(data, TestKeyringPairAlice.URI)
118+
assert.NoError(t, err)
119+
120+
_, err = Verify(data, sig, "foo")
121+
assert.Error(t, err)
122+
}
123+
124+
func TestVerify_InvalidSignatureLength(t *testing.T) {
125+
data := []byte("hello!")
126+
127+
_, err := Verify(data, []byte{'f', 'o', 'o'}, TestKeyringPairAlice.URI)
128+
assert.Error(t, err)
129+
}
130+
79131
func TestSignAndVerifyLong(t *testing.T) {
80132
data := make([]byte, 258)
81133
_, err := rand.Read(data)
@@ -84,7 +136,7 @@ func TestSignAndVerifyLong(t *testing.T) {
84136
sig, err := Sign(data, TestKeyringPairAlice.URI)
85137
assert.NoError(t, err)
86138

87-
ok, err := Verify(data, sig, types.HexEncodeToString(TestKeyringPairAlice.PublicKey))
139+
ok, err := Verify(data, sig, TestKeyringPairAlice.URI)
88140
assert.NoError(t, err)
89141

90142
assert.True(t, ok)

types/extrinsic_payload_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ func TestExtrinsicPayload_Sign(t *testing.T) {
7575
// verify sig
7676
b, err := EncodeToBytes(examplaryExtrinsicPayload)
7777
assert.NoError(t, err)
78-
ok, err := signature.Verify(b, sig[:], HexEncodeToString(signature.TestKeyringPairAlice.PublicKey))
78+
ok, err := signature.Verify(b, sig[:], signature.TestKeyringPairAlice.URI)
7979
assert.NoError(t, err)
8080
assert.True(t, ok)
8181
}

types/extrinsic_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ func TestExtrinsic_Sign(t *testing.T) {
136136
// verify sig
137137
b, err := EncodeToBytes(verifyPayload)
138138
assert.NoError(t, err)
139-
ok, err := signature.Verify(b, extDec.Signature.Signature.AsSr25519[:], HexEncodeToString(signature.TestKeyringPairAlice.PublicKey))
139+
ok, err := signature.Verify(b, extDec.Signature.Signature.AsSr25519[:], signature.TestKeyringPairAlice.URI)
140140
assert.NoError(t, err)
141141
assert.True(t, ok)
142142
}

0 commit comments

Comments
 (0)