forked from TrueCloudLab/neoneo-go
ec7e17ffa6
Simplifies a lot of code and removes some duplication. Unfortunately I had to move test_util random functions in same commit to avoid cycle dependencies. One of these random functions was also used in core/transaction testing, to simplify things I've just dropped it there and used a static string (which is nice to have for a test anyway). There is still sha256 left in wallet (but it needs to pass Hash structure into the signing function).
156 lines
3.4 KiB
Go
156 lines
3.4 KiB
Go
package wallet
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/CityOfZion/neo-go/pkg/crypto"
|
|
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
|
"golang.org/x/crypto/scrypt"
|
|
"golang.org/x/text/unicode/norm"
|
|
)
|
|
|
|
// NEP-2 standard implementation for encrypting and decrypting wallets.
|
|
|
|
// NEP-2 specified parameters used for cryptography.
|
|
const (
|
|
n = 16384
|
|
r = 8
|
|
p = 8
|
|
keyLen = 64
|
|
nepFlag = 0xe0
|
|
)
|
|
|
|
var nepHeader = []byte{0x01, 0x42}
|
|
|
|
type scryptParams struct {
|
|
N int `json:"n"`
|
|
R int `json:"r"`
|
|
P int `json:"p"`
|
|
}
|
|
|
|
func newScryptParams() scryptParams {
|
|
return scryptParams{
|
|
N: n,
|
|
R: r,
|
|
P: p,
|
|
}
|
|
}
|
|
|
|
// NEP2Encrypt encrypts a the PrivateKey using a given passphrase
|
|
// under the NEP-2 standard.
|
|
func NEP2Encrypt(priv *PrivateKey, passphrase string) (s string, err error) {
|
|
address, err := priv.Address()
|
|
if err != nil {
|
|
return s, err
|
|
}
|
|
|
|
addrHash := hash.Checksum([]byte(address))
|
|
// Normalize the passphrase according to the NFC standard.
|
|
phraseNorm := norm.NFC.Bytes([]byte(passphrase))
|
|
derivedKey, err := scrypt.Key(phraseNorm, addrHash, n, r, p, keyLen)
|
|
if err != nil {
|
|
return s, err
|
|
}
|
|
|
|
derivedKey1 := derivedKey[:32]
|
|
derivedKey2 := derivedKey[32:]
|
|
xr := xor(priv.Bytes(), derivedKey1)
|
|
|
|
encrypted, err := crypto.AESEncrypt(xr, derivedKey2)
|
|
if err != nil {
|
|
return s, err
|
|
}
|
|
|
|
buf := new(bytes.Buffer)
|
|
buf.Write(nepHeader)
|
|
buf.WriteByte(nepFlag)
|
|
buf.Write(addrHash)
|
|
buf.Write(encrypted)
|
|
|
|
if buf.Len() != 39 {
|
|
return s, fmt.Errorf("invalid buffer length: expecting 39 bytes got %d", buf.Len())
|
|
}
|
|
|
|
return crypto.Base58CheckEncode(buf.Bytes()), nil
|
|
}
|
|
|
|
// NEP2Decrypt decrypts an encrypted key using a given passphrase
|
|
// under the NEP-2 standard.
|
|
func NEP2Decrypt(key, passphrase string) (s string, err error) {
|
|
b, err := crypto.Base58CheckDecode(key)
|
|
if err != nil {
|
|
return s, nil
|
|
}
|
|
if err := validateNEP2Format(b); err != nil {
|
|
return s, err
|
|
}
|
|
|
|
addrHash := b[3:7]
|
|
// Normalize the passphrase according to the NFC standard.
|
|
phraseNorm := norm.NFC.Bytes([]byte(passphrase))
|
|
derivedKey, err := scrypt.Key(phraseNorm, addrHash, n, r, p, keyLen)
|
|
if err != nil {
|
|
return s, err
|
|
}
|
|
|
|
derivedKey1 := derivedKey[:32]
|
|
derivedKey2 := derivedKey[32:]
|
|
encryptedBytes := b[7:]
|
|
|
|
decrypted, err := crypto.AESDecrypt(encryptedBytes, derivedKey2)
|
|
if err != nil {
|
|
return s, err
|
|
}
|
|
|
|
privBytes := xor(decrypted, derivedKey1)
|
|
|
|
// Rebuild the private key.
|
|
privKey, err := NewPrivateKeyFromBytes(privBytes)
|
|
if err != nil {
|
|
return s, err
|
|
}
|
|
|
|
if !compareAddressHash(privKey, addrHash) {
|
|
return s, errors.New("password mismatch")
|
|
}
|
|
|
|
return privKey.WIF()
|
|
}
|
|
|
|
func compareAddressHash(priv *PrivateKey, inhash []byte) bool {
|
|
address, err := priv.Address()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
addrHash := hash.Checksum([]byte(address))
|
|
return bytes.Equal(addrHash, inhash)
|
|
}
|
|
|
|
func validateNEP2Format(b []byte) error {
|
|
if len(b) != 39 {
|
|
return fmt.Errorf("invalid length: expecting 39 got %d", len(b))
|
|
}
|
|
if b[0] != 0x01 {
|
|
return fmt.Errorf("invalid byte sequence: expecting 0x01 got 0x%02x", b[0])
|
|
}
|
|
if b[1] != 0x42 {
|
|
return fmt.Errorf("invalid byte sequence: expecting 0x42 got 0x%02x", b[1])
|
|
}
|
|
if b[2] != 0xe0 {
|
|
return fmt.Errorf("invalid byte sequence: expecting 0xe0 got 0x%02x", b[2])
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func xor(a, b []byte) []byte {
|
|
if len(a) != len(b) {
|
|
panic("cannot XOR non equal length arrays")
|
|
}
|
|
dst := make([]byte, len(a))
|
|
for i := 0; i < len(dst); i++ {
|
|
dst[i] = a[i] ^ b[i]
|
|
}
|
|
return dst
|
|
}
|