8f9f53a618
This commit converts the wallet to use the new secure hierarchical deterministic wallet address manager package as well as the walletdb package. The following is an overview of modified functionality: - The wallet must now be created before starting the executable - A new flag --create has been added to create the new wallet using wizard style question and answer prompts - Starting the process without an existing wallet will instruct now display a message to run it with --create - Providing the --create flag with an existing wallet will simply show an error and return In addition the snacl package has been modified to return the memory after performing scrypt operations to the OS. Previously a runtime.GC was being invoked which forced it to release the memory as far as the garbage collector is concerned, but the memory was not released back to the OS immediatley. This modification allows the memory to be released immedately since it won't be needed again until the next wallet unlock.
249 lines
6.3 KiB
Go
249 lines
6.3 KiB
Go
package snacl
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"crypto/subtle"
|
|
"encoding/binary"
|
|
"errors"
|
|
"io"
|
|
"runtime/debug"
|
|
|
|
"golang.org/x/crypto/nacl/secretbox"
|
|
"golang.org/x/crypto/scrypt"
|
|
|
|
"github.com/btcsuite/fastsha256"
|
|
)
|
|
|
|
var (
|
|
prng = rand.Reader
|
|
)
|
|
|
|
var (
|
|
ErrInvalidPassword = errors.New("invalid password")
|
|
ErrMalformed = errors.New("malformed data")
|
|
ErrDecryptFailed = errors.New("unable to decrypt")
|
|
)
|
|
|
|
// Zero out a byte slice.
|
|
func zero(b []byte) {
|
|
for i := range b {
|
|
b[i] ^= b[i]
|
|
}
|
|
}
|
|
|
|
const (
|
|
// Expose secretbox's Overhead const here for convenience.
|
|
Overhead = secretbox.Overhead
|
|
KeySize = 32
|
|
NonceSize = 24
|
|
DefaultN = 16384 // 2^14
|
|
DefaultR = 8
|
|
DefaultP = 1
|
|
)
|
|
|
|
// CryptoKey represents a secret key which can be used to encrypt and decrypt
|
|
// data.
|
|
type CryptoKey [KeySize]byte
|
|
|
|
// Encrypt encrypts the passed data.
|
|
func (ck *CryptoKey) Encrypt(in []byte) ([]byte, error) {
|
|
var nonce [NonceSize]byte
|
|
_, err := io.ReadFull(prng, nonce[:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
blob := secretbox.Seal(nil, in, &nonce, (*[KeySize]byte)(ck))
|
|
return append(nonce[:], blob...), nil
|
|
}
|
|
|
|
// Decrypt decrypts the passed data. The must be the output of the Encrypt
|
|
// function.
|
|
func (ck *CryptoKey) Decrypt(in []byte) ([]byte, error) {
|
|
if len(in) < NonceSize {
|
|
return nil, ErrMalformed
|
|
}
|
|
|
|
var nonce [NonceSize]byte
|
|
copy(nonce[:], in[:NonceSize])
|
|
blob := in[NonceSize:]
|
|
|
|
opened, ok := secretbox.Open(nil, blob, &nonce, (*[KeySize]byte)(ck))
|
|
if !ok {
|
|
return nil, ErrDecryptFailed
|
|
}
|
|
|
|
return opened, nil
|
|
}
|
|
|
|
// Zero clears the key by manually zeroing all memory. This is for security
|
|
// conscience application which wish to zero the memory after they've used it
|
|
// rather than waiting until it's reclaimed by the garbage collector. The
|
|
// key is no longer usable after this call.
|
|
func (ck *CryptoKey) Zero() {
|
|
zero(ck[:])
|
|
}
|
|
|
|
// GenerateCryptoKey generates a new crypotgraphically random key.
|
|
func GenerateCryptoKey() (*CryptoKey, error) {
|
|
var key CryptoKey
|
|
_, err := io.ReadFull(prng, key[:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &key, nil
|
|
}
|
|
|
|
// Parameters are not secret and can be stored in plain text.
|
|
type Parameters struct {
|
|
Salt [KeySize]byte
|
|
Digest [fastsha256.Size]byte
|
|
N int
|
|
R int
|
|
P int
|
|
}
|
|
|
|
// SecretKey houses a crypto key and the parameters needed to derive it from a
|
|
// passphrase. It should only be used in memory.
|
|
type SecretKey struct {
|
|
Key *CryptoKey
|
|
Parameters Parameters
|
|
}
|
|
|
|
// deriveKey fills out the Key field.
|
|
func (sk *SecretKey) deriveKey(password *[]byte) error {
|
|
key, err := scrypt.Key(*password, sk.Parameters.Salt[:],
|
|
sk.Parameters.N,
|
|
sk.Parameters.R,
|
|
sk.Parameters.P,
|
|
len(sk.Key))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
copy(sk.Key[:], key)
|
|
zero(key)
|
|
|
|
// I'm not a fan of forced garbage collections, but scrypt allocates a
|
|
// ton of memory and calling it back to back without a GC cycle in
|
|
// between means you end up needing twice the amount of memory. For
|
|
// example, if your scrypt parameters are such that you require 1GB and
|
|
// you call it twice in a row, without this you end up allocating 2GB
|
|
// since the first GB probably hasn't been released yet.
|
|
debug.FreeOSMemory()
|
|
|
|
return nil
|
|
}
|
|
|
|
// Marshal returns the Parameters field marshalled into a format suitable for
|
|
// storage. This result of this can be stored in clear text.
|
|
func (sk *SecretKey) Marshal() []byte {
|
|
params := &sk.Parameters
|
|
|
|
// The marshalled format for the the params is as follows:
|
|
// <salt><digest><N><R><P>
|
|
//
|
|
// KeySize + fastsha256.Size + N (8 bytes) + R (8 bytes) + P (8 bytes)
|
|
marshalled := make([]byte, KeySize+fastsha256.Size+24)
|
|
|
|
b := marshalled
|
|
copy(b[:KeySize], params.Salt[:])
|
|
b = b[KeySize:]
|
|
copy(b[:fastsha256.Size], params.Digest[:])
|
|
b = b[fastsha256.Size:]
|
|
binary.LittleEndian.PutUint64(b[:8], uint64(params.N))
|
|
b = b[8:]
|
|
binary.LittleEndian.PutUint64(b[:8], uint64(params.R))
|
|
b = b[8:]
|
|
binary.LittleEndian.PutUint64(b[:8], uint64(params.P))
|
|
|
|
return marshalled
|
|
}
|
|
|
|
// Unmarshal unmarshalls the parameters needed to derive the secret key from a
|
|
// passphrase into sk.
|
|
func (sk *SecretKey) Unmarshal(marshalled []byte) error {
|
|
if sk.Key == nil {
|
|
sk.Key = (*CryptoKey)(&[KeySize]byte{})
|
|
}
|
|
|
|
// The marshalled format for the the params is as follows:
|
|
// <salt><digest><N><R><P>
|
|
//
|
|
// KeySize + fastsha256.Size + N (8 bytes) + R (8 bytes) + P (8 bytes)
|
|
if len(marshalled) != KeySize+fastsha256.Size+24 {
|
|
return ErrMalformed
|
|
}
|
|
|
|
params := &sk.Parameters
|
|
copy(params.Salt[:], marshalled[:KeySize])
|
|
marshalled = marshalled[KeySize:]
|
|
copy(params.Digest[:], marshalled[:fastsha256.Size])
|
|
marshalled = marshalled[fastsha256.Size:]
|
|
params.N = int(binary.LittleEndian.Uint64(marshalled[:8]))
|
|
marshalled = marshalled[8:]
|
|
params.R = int(binary.LittleEndian.Uint64(marshalled[:8]))
|
|
marshalled = marshalled[8:]
|
|
params.P = int(binary.LittleEndian.Uint64(marshalled[:8]))
|
|
|
|
return nil
|
|
}
|
|
|
|
// Zero zeroes the underlying secret key while leaving the parameters intact.
|
|
// This effectively makes the key unusable until it is derived again via the
|
|
// DeriveKey function.
|
|
func (sk *SecretKey) Zero() {
|
|
sk.Key.Zero()
|
|
}
|
|
|
|
// DeriveKey derives the underlying secret key and ensures it matches the
|
|
// expected digest. This should only be called after previously calling the
|
|
// Zero function or on an initial Unmarshal.
|
|
func (sk *SecretKey) DeriveKey(password *[]byte) error {
|
|
if err := sk.deriveKey(password); err != nil {
|
|
return err
|
|
}
|
|
|
|
// verify password
|
|
digest := fastsha256.Sum256(sk.Key[:])
|
|
if subtle.ConstantTimeCompare(digest[:], sk.Parameters.Digest[:]) != 1 {
|
|
return ErrInvalidPassword
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Encrypt encrypts in bytes and returns a JSON blob.
|
|
func (sk *SecretKey) Encrypt(in []byte) ([]byte, error) {
|
|
return sk.Key.Encrypt(in)
|
|
}
|
|
|
|
// Decrypt takes in a JSON blob and returns it's decrypted form.
|
|
func (sk *SecretKey) Decrypt(in []byte) ([]byte, error) {
|
|
return sk.Key.Decrypt(in)
|
|
}
|
|
|
|
// NewSecretKey returns a SecretKey structure based on the passed parameters.
|
|
func NewSecretKey(password *[]byte, N, r, p int) (*SecretKey, error) {
|
|
sk := SecretKey{
|
|
Key: (*CryptoKey)(&[KeySize]byte{}),
|
|
}
|
|
// setup parameters
|
|
sk.Parameters.N = N
|
|
sk.Parameters.R = r
|
|
sk.Parameters.P = p
|
|
_, err := io.ReadFull(prng, sk.Parameters.Salt[:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// derive key
|
|
err = sk.deriveKey(password)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// store digest
|
|
sk.Parameters.Digest = fastsha256.Sum256(sk.Key[:])
|
|
|
|
return &sk, nil
|
|
}
|