mirror of
https://github.com/luxfi/go-bip32.git
synced 2026-07-27 01:14:41 +00:00
287 lines
7.0 KiB
Go
287 lines
7.0 KiB
Go
package bip32
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/hmac"
|
|
"crypto/rand"
|
|
"crypto/sha512"
|
|
"encoding/hex"
|
|
"errors"
|
|
)
|
|
|
|
const (
|
|
// FirstHardenedChild is the index of the firxt "harded" child key as per the
|
|
// bip32 spec
|
|
FirstHardenedChild = uint32(0x80000000)
|
|
|
|
// PublicKeyCompressedLength is the byte count of a compressed public key
|
|
PublicKeyCompressedLength = 33
|
|
)
|
|
|
|
var (
|
|
// PrivateWalletVersion is the version flag for serialized private keys
|
|
PrivateWalletVersion, _ = hex.DecodeString("0488ADE4")
|
|
|
|
// PublicWalletVersion is the version flag for serialized private keys
|
|
PublicWalletVersion, _ = hex.DecodeString("0488B21E")
|
|
|
|
// ErrSerializedKeyWrongSize is returned when trying to deserialize a key that
|
|
// has an incorrect length
|
|
ErrSerializedKeyWrongSize = errors.New("Serialized keys should by exactly 82 bytes")
|
|
|
|
// ErrHardnedChildPublicKey is returned when trying to create a harded child
|
|
// of the public key
|
|
ErrHardnedChildPublicKey = errors.New("Can't create hardened child for public key")
|
|
|
|
// ErrInvalidChecksum is returned when deserializing a key with an incorrect
|
|
// checksum
|
|
ErrInvalidChecksum = errors.New("Checksum doesn't match")
|
|
|
|
// ErrInvalidPrivateKey is returned when a derived private key is invalid
|
|
ErrInvalidPrivateKey = errors.New("Invalid private key")
|
|
|
|
// ErrInvalidPublicKey is returned when a derived public key is invalid
|
|
ErrInvalidPublicKey = errors.New("Invalid public key")
|
|
)
|
|
|
|
// Key represents a bip32 extended key
|
|
type Key struct {
|
|
Key []byte // 33 bytes
|
|
Version []byte // 4 bytes
|
|
ChildNumber []byte // 4 bytes
|
|
FingerPrint []byte // 4 bytes
|
|
ChainCode []byte // 32 bytes
|
|
Depth byte // 1 bytes
|
|
IsPrivate bool // unserialized
|
|
}
|
|
|
|
// NewMasterKey creates a new master extended key from a seed
|
|
func NewMasterKey(seed []byte) (*Key, error) {
|
|
// Generate key and chaincode
|
|
hmac := hmac.New(sha512.New, []byte("Bitcoin seed"))
|
|
_, err := hmac.Write(seed)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
intermediary := hmac.Sum(nil)
|
|
|
|
// Split it into our key and chain code
|
|
keyBytes := intermediary[:32]
|
|
chainCode := intermediary[32:]
|
|
|
|
// Validate key
|
|
err = validatePrivateKey(keyBytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Create the key struct
|
|
key := &Key{
|
|
Version: PrivateWalletVersion,
|
|
ChainCode: chainCode,
|
|
Key: keyBytes,
|
|
Depth: 0x0,
|
|
ChildNumber: []byte{0x00, 0x00, 0x00, 0x00},
|
|
FingerPrint: []byte{0x00, 0x00, 0x00, 0x00},
|
|
IsPrivate: true,
|
|
}
|
|
|
|
return key, nil
|
|
}
|
|
|
|
// NewChildKey derives a child key from a given parent as outlined by bip32
|
|
func (key *Key) NewChildKey(childIdx uint32) (*Key, error) {
|
|
// Fail early if trying to create hardned child from public key
|
|
if !key.IsPrivate && childIdx >= FirstHardenedChild {
|
|
return nil, ErrHardnedChildPublicKey
|
|
}
|
|
|
|
intermediary, err := key.getIntermediary(childIdx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Create child Key with data common to all both scenarios
|
|
childKey := &Key{
|
|
ChildNumber: uint32Bytes(childIdx),
|
|
ChainCode: intermediary[32:],
|
|
Depth: key.Depth + 1,
|
|
IsPrivate: key.IsPrivate,
|
|
}
|
|
|
|
// Bip32 CKDpriv
|
|
if key.IsPrivate {
|
|
childKey.Version = PrivateWalletVersion
|
|
fingerprint, err := hash160(publicKeyForPrivateKey(key.Key))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
childKey.FingerPrint = fingerprint[:4]
|
|
childKey.Key = addPrivateKeys(intermediary[:32], key.Key)
|
|
|
|
// Validate key
|
|
err = validatePrivateKey(childKey.Key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Bip32 CKDpub
|
|
} else {
|
|
keyBytes := publicKeyForPrivateKey(intermediary[:32])
|
|
|
|
// Validate key
|
|
err := validateChildPublicKey(keyBytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
childKey.Version = PublicWalletVersion
|
|
fingerprint, err := hash160(key.Key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
childKey.FingerPrint = fingerprint[:4]
|
|
childKey.Key = addPublicKeys(keyBytes, key.Key)
|
|
}
|
|
|
|
return childKey, nil
|
|
}
|
|
|
|
func (key *Key) getIntermediary(childIdx uint32) ([]byte, error) {
|
|
// Get intermediary to create key and chaincode from
|
|
// Hardened children are based on the private key
|
|
// NonHardened children are based on the public key
|
|
childIndexBytes := uint32Bytes(childIdx)
|
|
|
|
var data []byte
|
|
if childIdx >= FirstHardenedChild {
|
|
data = append([]byte{0x0}, key.Key...)
|
|
} else {
|
|
if key.IsPrivate {
|
|
data = publicKeyForPrivateKey(key.Key)
|
|
} else {
|
|
data = key.Key
|
|
}
|
|
}
|
|
data = append(data, childIndexBytes...)
|
|
|
|
hmac := hmac.New(sha512.New, key.ChainCode)
|
|
_, err := hmac.Write(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return hmac.Sum(nil), nil
|
|
}
|
|
|
|
// PublicKey returns the public version of key or return a copy
|
|
// The 'Neuter' function from the bip32 spec
|
|
func (key *Key) PublicKey() *Key {
|
|
keyBytes := key.Key
|
|
|
|
if key.IsPrivate {
|
|
keyBytes = publicKeyForPrivateKey(keyBytes)
|
|
}
|
|
|
|
return &Key{
|
|
Version: PublicWalletVersion,
|
|
Key: keyBytes,
|
|
Depth: key.Depth,
|
|
ChildNumber: key.ChildNumber,
|
|
FingerPrint: key.FingerPrint,
|
|
ChainCode: key.ChainCode,
|
|
IsPrivate: false,
|
|
}
|
|
}
|
|
|
|
// Serialize a Key to a 78 byte byte slice
|
|
func (key *Key) Serialize() ([]byte, error) {
|
|
// Private keys should be prepended with a single null byte
|
|
keyBytes := key.Key
|
|
if key.IsPrivate {
|
|
keyBytes = append([]byte{0x0}, keyBytes...)
|
|
}
|
|
|
|
// Write fields to buffer in order
|
|
buffer := new(bytes.Buffer)
|
|
buffer.Write(key.Version)
|
|
buffer.WriteByte(key.Depth)
|
|
buffer.Write(key.FingerPrint)
|
|
buffer.Write(key.ChildNumber)
|
|
buffer.Write(key.ChainCode)
|
|
buffer.Write(keyBytes)
|
|
|
|
// Append the standard doublesha256 checksum
|
|
serializedKey, err := addChecksumToBytes(buffer.Bytes())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return serializedKey, nil
|
|
}
|
|
|
|
// B58Serialize encodes the Key in the standard Bitcoin base58 encoding
|
|
func (key *Key) B58Serialize() string {
|
|
serializedKey, err := key.Serialize()
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
return base58Encode(serializedKey)
|
|
}
|
|
|
|
// String encodes the Key in the standard Bitcoin base58 encoding
|
|
func (key *Key) String() string {
|
|
return key.B58Serialize()
|
|
}
|
|
|
|
// Deserialize a byte slice into a Key
|
|
func Deserialize(data []byte) (*Key, error) {
|
|
if len(data) != 82 {
|
|
return nil, ErrSerializedKeyWrongSize
|
|
}
|
|
var key = &Key{}
|
|
key.Version = data[0:4]
|
|
key.Depth = data[4]
|
|
key.FingerPrint = data[5:9]
|
|
key.ChildNumber = data[9:13]
|
|
key.ChainCode = data[13:45]
|
|
|
|
if data[45] == byte(0) {
|
|
key.IsPrivate = true
|
|
key.Key = data[46:78]
|
|
} else {
|
|
key.IsPrivate = false
|
|
key.Key = data[45:78]
|
|
}
|
|
|
|
// validate checksum
|
|
cs1, err := checksum(data[0 : len(data)-4])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cs2 := data[len(data)-4:]
|
|
for i := range cs1 {
|
|
if cs1[i] != cs2[i] {
|
|
return nil, ErrInvalidChecksum
|
|
}
|
|
}
|
|
return key, nil
|
|
}
|
|
|
|
// B58Deserialize deserializes a Key encoded in base58 encoding
|
|
func B58Deserialize(data string) (*Key, error) {
|
|
b, err := base58Decode(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return Deserialize(b)
|
|
}
|
|
|
|
// NewSeed returns a cryptographically secure seed
|
|
func NewSeed() ([]byte, error) {
|
|
// Well that easy, just make go read 256 random bytes into a slice
|
|
s := make([]byte, 256)
|
|
_, err := rand.Read(s)
|
|
return s, err
|
|
}
|