forked from TrueCloudLab/neoneo-go
Merge pull request #589 from nspcc-dev/wallet-dump
I don't think open is actually of any use for our wallet, so I've renamed it to dump and implemented it fixing some things along the way.
This commit is contained in:
commit
c0523546c6
6 changed files with 103 additions and 46 deletions
|
@ -40,21 +40,48 @@ func NewCommands() []cli.Command {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "open",
|
Name: "dump",
|
||||||
Usage: "open a existing NEO wallet",
|
Usage: "check and dump an existing NEO wallet",
|
||||||
Action: openWallet,
|
Action: dumpWallet,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "path, p",
|
Name: "path, p",
|
||||||
Usage: "Target location of the wallet file.",
|
Usage: "Target location of the wallet file.",
|
||||||
},
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "decrypt, d",
|
||||||
|
Usage: "Decrypt encrypted keys.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func openWallet(ctx *cli.Context) error {
|
func dumpWallet(ctx *cli.Context) error {
|
||||||
|
path := ctx.String("path")
|
||||||
|
if len(path) == 0 {
|
||||||
|
return cli.NewExitError(errNoPath, 1)
|
||||||
|
}
|
||||||
|
wall, err := wallet.NewWalletFromFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
if ctx.Bool("decrypt") {
|
||||||
|
fmt.Print("Wallet password: ")
|
||||||
|
pass, err := terminal.ReadPassword(int(syscall.Stdin))
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
for i := range wall.Accounts {
|
||||||
|
// Just testing the decryption here.
|
||||||
|
err := wall.Accounts[i].Decrypt(string(pass))
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmtPrintWallet(wall)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,7 +104,7 @@ func createWallet(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dumpWallet(wall)
|
fmtPrintWallet(wall)
|
||||||
fmt.Printf("wallet successfully created, file location is %s\n", wall.Path())
|
fmt.Printf("wallet successfully created, file location is %s\n", wall.Path())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -110,7 +137,7 @@ func createAccount(ctx *cli.Context, wall *wallet.Wallet) error {
|
||||||
return wall.CreateAccount(name, phrase)
|
return wall.CreateAccount(name, phrase)
|
||||||
}
|
}
|
||||||
|
|
||||||
func dumpWallet(wall *wallet.Wallet) {
|
func fmtPrintWallet(wall *wallet.Wallet) {
|
||||||
b, _ := wall.JSON()
|
b, _ := wall.JSON()
|
||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
fmt.Println(string(b))
|
fmt.Println(string(b))
|
||||||
|
|
|
@ -14,7 +14,6 @@ import (
|
||||||
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
"github.com/CityOfZion/neo-go/pkg/vm/opcode"
|
"github.com/CityOfZion/neo-go/pkg/vm/opcode"
|
||||||
"github.com/CityOfZion/neo-go/pkg/wallet"
|
|
||||||
"github.com/nspcc-dev/dbft"
|
"github.com/nspcc-dev/dbft"
|
||||||
"github.com/nspcc-dev/dbft/block"
|
"github.com/nspcc-dev/dbft/block"
|
||||||
"github.com/nspcc-dev/dbft/crypto"
|
"github.com/nspcc-dev/dbft/crypto"
|
||||||
|
@ -178,16 +177,12 @@ func (s *service) validatePayload(p *Payload) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getKeyPair(cfg *config.WalletConfig) (crypto.PrivateKey, crypto.PublicKey) {
|
func getKeyPair(cfg *config.WalletConfig) (crypto.PrivateKey, crypto.PublicKey) {
|
||||||
acc, err := wallet.DecryptAccount(cfg.Path, cfg.Password)
|
// TODO: replace with wallet opening from the given path (#588)
|
||||||
|
key, err := keys.NEP2Decrypt(cfg.Path, cfg.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
key := acc.PrivateKey()
|
|
||||||
if key == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return &privateKey{PrivateKey: key}, &publicKey{PublicKey: key.PublicKey()}
|
return &privateKey{PrivateKey: key}, &publicKey{PublicKey: key.PublicKey()}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -77,13 +77,13 @@ func NEP2Encrypt(priv *PrivateKey, passphrase string) (s string, err error) {
|
||||||
|
|
||||||
// NEP2Decrypt decrypts an encrypted key using a given passphrase
|
// NEP2Decrypt decrypts an encrypted key using a given passphrase
|
||||||
// under the NEP-2 standard.
|
// under the NEP-2 standard.
|
||||||
func NEP2Decrypt(key, passphrase string) (s string, err error) {
|
func NEP2Decrypt(key, passphrase string) (*PrivateKey, error) {
|
||||||
b, err := base58.CheckDecode(key)
|
b, err := base58.CheckDecode(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return s, nil
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := validateNEP2Format(b); err != nil {
|
if err := validateNEP2Format(b); err != nil {
|
||||||
return s, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
addrHash := b[3:7]
|
addrHash := b[3:7]
|
||||||
|
@ -91,7 +91,7 @@ func NEP2Decrypt(key, passphrase string) (s string, err error) {
|
||||||
phraseNorm := norm.NFC.Bytes([]byte(passphrase))
|
phraseNorm := norm.NFC.Bytes([]byte(passphrase))
|
||||||
derivedKey, err := scrypt.Key(phraseNorm, addrHash, n, r, p, keyLen)
|
derivedKey, err := scrypt.Key(phraseNorm, addrHash, n, r, p, keyLen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return s, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
derivedKey1 := derivedKey[:32]
|
derivedKey1 := derivedKey[:32]
|
||||||
|
@ -100,7 +100,7 @@ func NEP2Decrypt(key, passphrase string) (s string, err error) {
|
||||||
|
|
||||||
decrypted, err := aesDecrypt(encryptedBytes, derivedKey2)
|
decrypted, err := aesDecrypt(encryptedBytes, derivedKey2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return s, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
privBytes := xor(decrypted, derivedKey1)
|
privBytes := xor(decrypted, derivedKey1)
|
||||||
|
@ -108,14 +108,14 @@ func NEP2Decrypt(key, passphrase string) (s string, err error) {
|
||||||
// Rebuild the private key.
|
// Rebuild the private key.
|
||||||
privKey, err := NewPrivateKeyFromBytes(privBytes)
|
privKey, err := NewPrivateKeyFromBytes(privBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return s, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !compareAddressHash(privKey, addrHash) {
|
if !compareAddressHash(privKey, addrHash) {
|
||||||
return s, errors.New("password mismatch")
|
return nil, errors.New("password mismatch")
|
||||||
}
|
}
|
||||||
|
|
||||||
return privKey.WIF(), nil
|
return privKey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func compareAddressHash(priv *PrivateKey, inhash []byte) bool {
|
func compareAddressHash(priv *PrivateKey, inhash []byte) bool {
|
||||||
|
|
|
@ -27,18 +27,13 @@ func TestNEP2Encrypt(t *testing.T) {
|
||||||
|
|
||||||
func TestNEP2Decrypt(t *testing.T) {
|
func TestNEP2Decrypt(t *testing.T) {
|
||||||
for _, testCase := range keytestcases.Arr {
|
for _, testCase := range keytestcases.Arr {
|
||||||
|
privKey, err := NEP2Decrypt(testCase.EncryptedWif, testCase.Passphrase)
|
||||||
privKeyString, err := NEP2Decrypt(testCase.EncryptedWif, testCase.Passphrase)
|
|
||||||
if testCase.Invalid {
|
if testCase.Invalid {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
privKey, err := NewPrivateKeyFromWIF(privKeyString)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, testCase.PrivateKey, privKey.String())
|
assert.Equal(t, testCase.PrivateKey, privKey.String())
|
||||||
|
|
||||||
wif := privKey.WIF()
|
wif := privKey.WIF()
|
||||||
|
@ -48,3 +43,39 @@ func TestNEP2Decrypt(t *testing.T) {
|
||||||
assert.Equal(t, testCase.Address, address)
|
assert.Equal(t, testCase.Address, address)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNEP2DecryptErrors(t *testing.T) {
|
||||||
|
p := "qwerty"
|
||||||
|
|
||||||
|
// Not a base58-encoded value
|
||||||
|
s := "qazwsx"
|
||||||
|
_, err := NEP2Decrypt(s, p)
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
// Valid base58, but not a NEP-2 format.
|
||||||
|
s = "KxhEDBQyyEFymvfJD96q8stMbJMbZUb6D1PmXqBWZDU2WvbvVs9o"
|
||||||
|
_, err = NEP2Decrypt(s, p)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateNEP2Format(t *testing.T) {
|
||||||
|
// Wrong length.
|
||||||
|
s := []byte("gobbledygook")
|
||||||
|
assert.Error(t, validateNEP2Format(s))
|
||||||
|
|
||||||
|
// Wrong header 1.
|
||||||
|
s = []byte("gobbledygookgobbledygookgobbledygookgob")
|
||||||
|
assert.Error(t, validateNEP2Format(s))
|
||||||
|
|
||||||
|
// Wrong header 2.
|
||||||
|
s[0] = 0x01
|
||||||
|
assert.Error(t, validateNEP2Format(s))
|
||||||
|
|
||||||
|
// Wrong header 3.
|
||||||
|
s[1] = 0x42
|
||||||
|
assert.Error(t, validateNEP2Format(s))
|
||||||
|
|
||||||
|
// OK
|
||||||
|
s[2] = 0xe0
|
||||||
|
assert.NoError(t, validateNEP2Format(s))
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package wallet
|
package wallet
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
)
|
)
|
||||||
|
@ -60,14 +62,16 @@ func NewAccount() (*Account, error) {
|
||||||
return newAccountFromPrivateKey(priv), nil
|
return newAccountFromPrivateKey(priv), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecryptAccount decrypts the encryptedWIF with the given passphrase and
|
// Decrypt decrypts the EncryptedWIF with the given passphrase returning error
|
||||||
// return the decrypted Account.
|
// if anything goes wrong.
|
||||||
func DecryptAccount(encryptedWIF, passphrase string) (*Account, error) {
|
func (a *Account) Decrypt(passphrase string) error {
|
||||||
wif, err := keys.NEP2Decrypt(encryptedWIF, passphrase)
|
var err error
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
if a.EncryptedWIF == "" {
|
||||||
|
return errors.New("no encrypted wif in the account")
|
||||||
}
|
}
|
||||||
return NewAccountFromWIF(wif)
|
a.privateKey, err = keys.NEP2Decrypt(a.EncryptedWIF, passphrase)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encrypt encrypts the wallet's PrivateKey with the given passphrase
|
// Encrypt encrypts the wallet's PrivateKey with the given passphrase
|
||||||
|
|
|
@ -6,32 +6,32 @@ import (
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/internal/keytestcases"
|
"github.com/CityOfZion/neo-go/pkg/internal/keytestcases"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewAccount(t *testing.T) {
|
func TestNewAccount(t *testing.T) {
|
||||||
for _, testCase := range keytestcases.Arr {
|
acc, err := NewAccount()
|
||||||
acc, err := NewAccountFromWIF(testCase.Wif)
|
require.NoError(t, err)
|
||||||
if testCase.Invalid {
|
require.NotNil(t, acc)
|
||||||
assert.Error(t, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.NoError(t, err)
|
|
||||||
compareFields(t, testCase, acc)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDecryptAccount(t *testing.T) {
|
func TestDecryptAccount(t *testing.T) {
|
||||||
for _, testCase := range keytestcases.Arr {
|
for _, testCase := range keytestcases.Arr {
|
||||||
acc, err := DecryptAccount(testCase.EncryptedWif, testCase.Passphrase)
|
acc := &Account{EncryptedWIF: testCase.EncryptedWif}
|
||||||
|
assert.Nil(t, acc.PrivateKey())
|
||||||
|
err := acc.Decrypt(testCase.Passphrase)
|
||||||
if testCase.Invalid {
|
if testCase.Invalid {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
compareFields(t, testCase, acc)
|
assert.NotNil(t, acc.PrivateKey())
|
||||||
|
assert.Equal(t, testCase.PrivateKey, acc.privateKey.String())
|
||||||
}
|
}
|
||||||
|
// No encrypted key.
|
||||||
|
acc := &Account{}
|
||||||
|
require.Error(t, acc.Decrypt("qwerty"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewFromWif(t *testing.T) {
|
func TestNewFromWif(t *testing.T) {
|
||||||
|
|
Loading…
Reference in a new issue