Skip to content

Commit d40394c

Browse files
author
AJ ONeal
committed
feat: make work with arbitrary coin types
1 parent f5378b2 commit d40394c

File tree

4 files changed

+151
-37
lines changed

4 files changed

+151
-37
lines changed

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
Sign and Verify messages with Dash Private Keys
44

55
```bash
6-
dashmsg sign \
6+
dashmsg sign --cointype 0x4c \
77
'XK5DHnAiSj6HQNsNcDkawd9qdp8UFMdYftdVZFuRreTMJtbJhk8i' \
88
'dte2022-akerdemelidis|estoever|mmason'
99
```
@@ -28,18 +28,18 @@ dashmsg help
2828
```
2929

3030
```txt
31-
dashmsg v0.9.0 (gxxxxxx) 2022-03-12T01:33:52-0700
31+
dashmsg v0.9.1 (xxxxxxx) 2022-03-13T11:45:52-0700
3232
3333
Usage
34-
dashmsg <command> [flags] args...
34+
dashmsg <command> [flags] args...
3535
3636
See usage: dashmsg help <command>
3737
3838
Commands:
3939
version
40-
gen [name.wif]
41-
sign <key> <msg>
42-
inspect <key | address | signature>
40+
gen [--cointype '0xcc'] [name.wif]
41+
sign [--cointype '0x4c'] <key> <msg>
42+
inspect [--cointype '0x4c'] <key | address | signature>
4343
decode (alias of inspect)
4444
verify <payment address> <msg> <signature>
4545

cmd/dashmsg/main.go

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,15 @@ func usage() {
2626
fmt.Println(ver())
2727
fmt.Println()
2828
fmt.Println("Usage")
29-
fmt.Printf(" %s <command> [flags] args...\n", name)
29+
fmt.Printf(" %s <command> [flags] args...\n", name)
3030
fmt.Println("")
3131
fmt.Printf("See usage: %s help <command>\n", name)
3232
fmt.Println("")
3333
fmt.Println("Commands:")
3434
fmt.Println(" version")
35-
fmt.Println(" gen [name.wif]")
36-
fmt.Println(" sign <key> <msg>")
37-
fmt.Println(" inspect <key | address | signature>")
35+
fmt.Println(" gen [--cointype '0xcc'] [name.wif]")
36+
fmt.Println(" sign [--cointype '0x4c'] <key> <msg>")
37+
fmt.Println(" inspect [--cointype '0x4c'] <key | address | signature>")
3838
fmt.Println(" decode (alias of inspect)")
3939
fmt.Println(" verify <payment address> <msg> <signature>")
4040
fmt.Println("")
@@ -99,26 +99,42 @@ func main() {
9999
}
100100

101101
func gen(args []string) {
102-
wif := dashmsg.GenerateWIF()
102+
var cointype string
103103

104-
if len(args) == 1 {
104+
flags := flag.NewFlagSet("gen", flag.ExitOnError)
105+
flags.StringVar(&cointype, "cointype", "", "the magic version (hex) string of the private key")
106+
flags.Parse(args)
107+
108+
cointype = strings.TrimPrefix(cointype, "0x")
109+
110+
wif := dashmsg.GenerateWIF(cointype)
111+
112+
if len(flags.Args()) == 1 {
105113
b := []byte(wif)
106114
b = append(b, '\n')
107-
ioutil.WriteFile(args[0], b, 0644)
108-
fmt.Printf("wrote Private Key (as WIF) to %q\n", args[0])
115+
ioutil.WriteFile(flags.Args()[0], b, 0644)
116+
fmt.Printf("wrote Private Key (as WIF) to %q\n", flags.Args()[0])
109117
return
110118
}
111119

112120
fmt.Println(wif)
113121
}
114122

115123
func inspect(args []string) {
116-
if len(args) != 1 {
124+
var cointype string
125+
126+
flags := flag.NewFlagSet("inspect", flag.ExitOnError)
127+
flags.StringVar(&cointype, "cointype", "", "the magic version (hex) string of the private key")
128+
flags.Parse(args)
129+
130+
cointype = strings.TrimPrefix(cointype, "0x")
131+
132+
if len(flags.Args()) != 1 {
117133
fmt.Fprintf(os.Stderr, "usage: %s inspect <addr-or-key>\n", os.Args[0])
118134
os.Exit(1)
119135
return
120136
}
121-
input := args[0]
137+
input := flags.Args()[0]
122138

123139
var usererr error
124140
inputlen := len(input)
@@ -144,16 +160,19 @@ func inspect(args []string) {
144160
break
145161
}
146162

147-
priv, err := dashmsg.WIFToPrivateKey(wif)
163+
privCointype, priv, err := dashmsg.WIFToPrivateKey(wif)
148164
if nil != err {
149165
usererr = err
150166
break
151167
}
168+
if 0 == len(cointype) {
169+
cointype = privCointype
170+
}
152171

153172
pubBytes := dashmsg.MarshalPublicKey(priv.PublicKey)
154-
pkh := dashmsg.PublicKeyToAddress(priv.PublicKey)
173+
pkh := dashmsg.PublicKeyToAddress(cointype, priv.PublicKey)
155174

156-
fmt.Printf("PrivateKey (hex): %s (coin type)\n", hexstr[:2])
175+
fmt.Printf("PrivateKey (hex): %s (coin type)\n", privCointype)
157176
fmt.Printf(" : %s\n", hexstr[2:66])
158177
fmt.Printf(" : %s (compressed)\n", hexstr[66:])
159178
fmt.Println()
@@ -184,9 +203,14 @@ func inspect(args []string) {
184203
}
185204

186205
func sign(args []string) {
206+
var cointype string
207+
187208
flags := flag.NewFlagSet("sign", flag.ExitOnError)
209+
flags.StringVar(&cointype, "cointype", "", "the magic version (hex) string of the private key")
188210
flags.Parse(args)
189211

212+
cointype = strings.TrimPrefix(cointype, "0x")
213+
190214
if len(flags.Args()) <= 1 {
191215
fmt.Fprintf(os.Stderr, "usage: %s sign <addr-or-key> <msg>\n", os.Args[0])
192216
os.Exit(1)
@@ -195,7 +219,7 @@ func sign(args []string) {
195219
wifname := flags.Args()[0]
196220
payload := flags.Args()[1]
197221

198-
priv, err := readWif(wifname)
222+
_, priv, err := readWif(wifname)
199223
if nil != err {
200224
fmt.Fprintf(os.Stderr, "error: could not decode private key: %v\n", err)
201225
os.Exit(1)
@@ -252,7 +276,15 @@ func verify(args []string) {
252276
return
253277
}
254278

255-
if dashmsg.PublicKeyToAddress(*pub) == addr {
279+
cointype, err := dashmsg.AddressToCointype(addr)
280+
if nil != err {
281+
// Neither a valid file nor string. Blast!
282+
fmt.Printf("can't detect coin type of %q: %v\n", addr, err)
283+
os.Exit(1)
284+
return
285+
}
286+
287+
if dashmsg.PublicKeyToAddress(cointype, *pub) == addr {
256288
fmt.Println("Verified: true")
257289
return
258290
}
@@ -270,18 +302,18 @@ func readFileOrString(str string) []byte {
270302
return b
271303
}
272304

273-
func readWif(wifname string) (*ecdsa.PrivateKey, error) {
305+
func readWif(wifname string) (string, *ecdsa.PrivateKey, error) {
274306
// Read as file
275307
wif := readFileOrString(wifname)
276308

277-
priv, err := dashmsg.WIFToPrivateKey(string(wif))
309+
cointype, priv, err := dashmsg.WIFToPrivateKey(string(wif))
278310
if nil != err {
279311
// Neither a valid file nor string. Blast!
280-
return nil, fmt.Errorf(
312+
return "", nil, fmt.Errorf(
281313
"could not read private key as file (or parse as string) %q:\n%s",
282314
wifname, err,
283315
)
284316
}
285317

286-
return priv, nil
318+
return cointype, priv, nil
287319
}

dashmsg.go

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,38 +17,56 @@ import (
1717
)
1818

1919
var (
20-
// CheckVersion is the magic version byte signifying the payment address coin type (i.e. Dash, Public Key)
21-
CheckVersion = "4c" // Dash (vs 0x00 for BC)
20+
// CheckVersion is the Base58Check magic version (coin / network type) byte signifying the payment address coin type (i.e. Dash, Public Key, mainnet)
21+
CheckVersion = "4c" // Dash mainnet (vs 0x00 for BC)
22+
23+
// CheckVersionTest is CheckVersion, but for testnet
24+
CheckVersionTest = "8c" // Dash testnet
25+
26+
// WIFVersion is the Base58Check magic version (coin / network type) byte signifying the wallet type (i.e. Dash, Private Key, mainnet)
27+
WIFVersion = "cc" // Dash mainnet (vs 0x80 for BC)
28+
29+
// WIFVersionTest is WIFVersion, but for testnet
30+
WIFVersionTest = "ef" // Dash testnet
2231

23-
// WIFVersion is the magic version byte signifying the wallet type (i.e. Dash, Private Key)
24-
WIFVersion = "cc" // Dash (vs 0x80 for BC)
2532
// MagicBytes is the secure delimiter that scopes a message to a particular network
2633
MagicBytes = []byte("DarkCoin Signed Message:\n")
2734
)
2835

2936
var randReader io.Reader = rand.Reader
3037

3138
// GenerateWIF creates a new wallet private key as WIF
32-
func GenerateWIF() string {
39+
func GenerateWIF(magicVersion string) string {
40+
if "" == magicVersion {
41+
magicVersion = WIFVersion
42+
}
43+
if CheckVersion == magicVersion {
44+
magicVersion = WIFVersion
45+
}
46+
if CheckVersionTest == magicVersion {
47+
magicVersion = WIFVersionTest
48+
}
49+
3350
priv, _ := ecdsa.GenerateKey(secp256k1.S256(), randReader)
3451
b := priv.D.Bytes()
3552

3653
hexkey := hex.EncodeToString(b)
3754
compressed := "01"
38-
wif, _ := base58check.Encode(WIFVersion, hexkey+compressed)
55+
wif, _ := base58check.Encode(magicVersion, hexkey+compressed)
3956

4057
return wif
4158
}
4259

43-
// WIFToPrivateKey converts from base58check (WIF) to a standard(ish) ECDSA private key
44-
func WIFToPrivateKey(wif string) (*ecdsa.PrivateKey, error) {
60+
// WIFToPrivateKey decodes the base58check (WIF) into the network magicVersion and a standard(ish) ECDSA private key
61+
func WIFToPrivateKey(wif string) (string, *ecdsa.PrivateKey, error) {
4562
dHex, err := base58check.Decode(wif)
4663
if nil != err {
47-
return nil, err
64+
return "", nil, err
4865
}
4966
// remove the "version" and "compressed" bytes
5067
//fmt.Println("version:", dHex[0:2])
5168
//fmt.Println("compressed:", dHex[66:])
69+
magicVersion := dHex[0:2]
5270
dHex = dHex[2:66]
5371

5472
// can't get error here because base58check passed
@@ -72,7 +90,18 @@ func WIFToPrivateKey(wif string) (*ecdsa.PrivateKey, error) {
7290
//fmt.Println("PrivateKey:", hex.EncodeToString(di.Bytes()))
7391
//fmt.Println("PublicKey (x):", hex.EncodeToString(x.Bytes()))
7492
//fmt.Println("PublicKey (y):", hex.EncodeToString(y.Bytes()))
75-
return priv, nil
93+
return magicVersion, priv, nil
94+
}
95+
96+
// AddressToCointype reads the magic version (coin / network type) from the base58check address
97+
func AddressToCointype(addr string) (string, error) {
98+
dHex, err := base58check.Decode(addr)
99+
if nil != err {
100+
return "", err
101+
}
102+
103+
magicVersion := dHex[0:2]
104+
return magicVersion, nil
76105
}
77106

78107
/*
@@ -108,10 +137,22 @@ func SigToPub(magichash, dsig []byte) (*ecdsa.PublicKey, error) {
108137
}
109138

110139
// PublicKeyToAddress transforms a PublicKey into a PubKeyHash address is Base58Check format
111-
func PublicKeyToAddress(pub ecdsa.PublicKey) string {
140+
func PublicKeyToAddress(magicVersion string, pub ecdsa.PublicKey) string {
141+
if "" == magicVersion {
142+
magicVersion = CheckVersion
143+
}
144+
// just in case a private key was used to get the magic version
145+
// (the name says "public key" and all, but...)
146+
if WIFVersion == magicVersion {
147+
magicVersion = CheckVersion
148+
}
149+
if WIFVersionTest == magicVersion {
150+
magicVersion = CheckVersionTest
151+
}
152+
112153
pubKeyHash := secp256k1crypto.PubkeyToAddress(pub).Bytes()
113154
pubKeyHashHex := hex.EncodeToString(pubKeyHash)
114-
addr, _ := base58check.Encode(CheckVersion, pubKeyHashHex)
155+
addr, _ := base58check.Encode(magicVersion, pubKeyHashHex)
115156
//fmt.Println("PubKeyHash Bytes:", pubKeyHash)
116157
//fmt.Println("PubKeyHash:", addr)
117158

test.sh

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#!/bin/bash
2+
set -e
3+
set -u
4+
5+
my_priv="0x00"
6+
my_coin="0x80"
7+
my_message='silly message'
8+
echo "Message: ${my_message}"
9+
10+
my_wif="$(
11+
go run cmd/dashmsg/main.go gen --cointype "${my_priv}"
12+
)"
13+
14+
my_sig="$(
15+
go run cmd/dashmsg/main.go sign --cointype "${my_coin}" "${my_wif}" "${my_message}"
16+
)"
17+
18+
my_addr="$(
19+
go run cmd/dashmsg/main.go inspect --cointype "${my_coin}" "${my_wif}" |
20+
grep 'Address' | cut -d':' -f2 | cut -d' ' -f2
21+
)"
22+
23+
go run cmd/dashmsg/main.go verify "${my_addr}" "${my_message}" "${my_sig}"
24+
25+
# Inspect
26+
echo ""
27+
echo "Inspect..."
28+
echo "WIF: ${my_wif} (pub cointype: ${my_coin})"
29+
go run cmd/dashmsg/main.go inspect --cointype "${my_coin}" "${my_wif}"
30+
echo ''
31+
echo ''
32+
echo "Signature: ${my_sig}"
33+
go run cmd/dashmsg/main.go inspect "${my_sig}"
34+
echo ''
35+
echo ''
36+
echo "Address: ${my_addr}"
37+
go run cmd/dashmsg/main.go inspect "${my_addr}"
38+
39+
echo ''
40+
echo 'PASS'
41+
echo ''

0 commit comments

Comments
 (0)