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:
Roman Khimov 2020-01-09 19:20:51 +03:00 committed by GitHub
commit c0523546c6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 103 additions and 46 deletions

View file

@ -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))

View file

@ -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()}
} }

View file

@ -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 {

View file

@ -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))
}

View file

@ -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

View file

@ -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) {