neoneo-go/pkg/wallet/nep2.go

157 lines
3.4 KiB
Go
Raw Normal View History

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
}