d0938d817f
This commit implements a new secure, scalable, hierarchical deterministic wallet address manager package. The following is an overview of features: - BIP0032 hierarchical deterministic keys - BIP0043/BIP0044 multi-account hierarchy - Strong focus on security: - Fully encrypted database including public information such as addresses as well as private information such as private keys and scripts needed to redeem pay-to-script-hash transactions - Hardened against memory scraping through the use of actively clearing private material from memory when locked - Different crypto keys used for public, private, and script data - Ability for different passphrases for public and private data - Scrypt-based key derivation - NaCl-based secretbox cryptography (XSalsa20 and Poly1305) - Multi-tier scalable key design to allow instant password changes regardless of the number of addresses stored - Import WIF keys - Import pay-to-script-hash scripts for things such as multi-signature transactions - Ability to export a watching-only version which does not contain any private key material - Programmatically detectable errors, including encapsulation of errors from packages it relies on - Address synchronization capabilities This commit only provides the implementation package. It does not include integration into to the existing wallet code base or conversion of existing addresses. That functionality will be provided by future commits.
238 lines
5.8 KiB
Go
238 lines
5.8 KiB
Go
package snacl
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"crypto/subtle"
|
|
"encoding/binary"
|
|
"errors"
|
|
"io"
|
|
|
|
"code.google.com/p/go.crypto/nacl/secretbox"
|
|
"code.google.com/p/go.crypto/scrypt"
|
|
|
|
"github.com/conformal/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 (
|
|
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)
|
|
|
|
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
|
|
}
|