-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add bip32 key, go mod and masterkey object
- Loading branch information
Showing
11 changed files
with
1,389 additions
and
243 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,277 @@ | ||
package bip32 | ||
|
||
import ( | ||
"bytes" | ||
"crypto/hmac" | ||
"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 | ||
h := hmac.New(sha512.New, []byte("Bitcoin seed")) | ||
_, err := h.Write(seed) | ||
if err != nil { | ||
return nil, err | ||
} | ||
intermediary := h.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...) | ||
|
||
h := hmac.New(sha512.New, key.ChainCode) | ||
_, err := h.Write(data) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return h.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) | ||
} |
Oops, something went wrong.