diff --git a/hd/address.go b/hd/address.go index f59876739..9511ffad2 100644 --- a/hd/address.go +++ b/hd/address.go @@ -21,19 +21,30 @@ import ( "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcutil/base58" - "github.com/tendermint/go-crypto" "golang.org/x/crypto/ripemd160" ) -func ComputeAddress(pubKeyHex string, chainHex string, path string, index int32) string { +/* + + This file implements BIP32 HD wallets. + Note it only works for SECP256k1 keys. + It also includes some Bitcoin specific utility functions. + +*/ + +// ComputeBTCAddress returns the BTC address using the pubKeyHex and chainCodeHex +// for the given path and index. +func ComputeBTCAddress(pubKeyHex string, chainCodeHex string, path string, index int32) string { pubKeyBytes := DerivePublicKeyForPath( HexDecode(pubKeyHex), - HexDecode(chainHex), + HexDecode(chainCodeHex), fmt.Sprintf("%v/%v", path, index), ) - return AddrFromPubKeyBytes(pubKeyBytes) + return BTCAddrFromPubKeyBytes(pubKeyBytes) } +// ComputePrivateKey returns the private key using the master mprivHex and chainCodeHex +// for the given path and index. func ComputePrivateKey(mprivHex string, chainHex string, path string, index int32) string { privKeyBytes := DerivePrivateKeyForPath( HexDecode(mprivHex), @@ -43,12 +54,14 @@ func ComputePrivateKey(mprivHex string, chainHex string, path string, index int3 return HexEncode(privKeyBytes) } -func ComputeAddressForPrivKey(privKey string) string { +// ComputeBTCAddressForPrivKey returns the Bitcoin address for the given privKey. +func ComputeBTCAddressForPrivKey(privKey string) string { pubKeyBytes := PubKeyBytesFromPrivKeyBytes(HexDecode(privKey), true) - return AddrFromPubKeyBytes(pubKeyBytes) + return BTCAddrFromPubKeyBytes(pubKeyBytes) } -func SignMessage(privKey string, message string, compress bool) string { +// SignBTCMessage signs a "Bitcoin Signed Message". +func SignBTCMessage(privKey string, message string, compress bool) string { prefixBytes := []byte("Bitcoin Signed Message:\n") messageBytes := []byte(message) bytes := []byte{} @@ -67,25 +80,28 @@ func SignMessage(privKey string, message string, compress bool) string { PublicKey: ecdsaPubKey, D: new(big.Int).SetBytes(privKeyBytes), } - sigbytes, err := btcec.SignCompact(btcec.S256(), ecdsaPrivKey, crypto.Sha256(crypto.Sha256(bytes)), compress) + sigbytes, err := btcec.SignCompact(btcec.S256(), ecdsaPrivKey, CalcHash256(bytes), compress) if err != nil { panic(err) } return base64.StdEncoding.EncodeToString(sigbytes) } -// returns MPK, Chain, and master secret in hex. -func ComputeMastersFromSeed(seed string) (string, string, string, string) { - secret, chain := I64([]byte("Bitcoin seed"), []byte(seed)) +// ComputeMastersFromSeed returns the master public key, master secret, and chain code in hex. +func ComputeMastersFromSeed(seed string) (string, string, string) { + key, data := []byte("Bitcoin seed"), []byte(seed) + secret, chain := I64(key, data) pubKeyBytes := PubKeyBytesFromPrivKeyBytes(secret, true) - return HexEncode(pubKeyBytes), HexEncode(secret), HexEncode(chain), HexEncode(secret) + return HexEncode(pubKeyBytes), HexEncode(secret), HexEncode(chain) } +// ComputeWIF returns the privKey in Wallet Import Format. func ComputeWIF(privKey string, compress bool) string { return WIFFromPrivKeyBytes(HexDecode(privKey), compress) } -func ComputeTxId(rawTxHex string) string { +// ComputeBTCTxId returns the bitcoin transaction ID. +func ComputeBTCTxId(rawTxHex string) string { return HexEncode(ReverseBytes(CalcHash256(HexDecode(rawTxHex)))) } @@ -103,7 +119,11 @@ func printKeyInfo(privKeyBytes []byte, pubKeyBytes []byte, chain []byte) { } */ -func DerivePrivateKeyForPath(privKeyBytes []byte, chain []byte, path string) []byte { +//------------------------------------------------------------------- + +// DerivePrivateKeyForPath derives the private key by following the path from privKeyBytes, +// using the given chainCode. +func DerivePrivateKeyForPath(privKeyBytes []byte, chainCode []byte, path string) []byte { data := privKeyBytes parts := strings.Split(path, "/") for _, part := range parts { @@ -119,13 +139,15 @@ func DerivePrivateKeyForPath(privKeyBytes []byte, chain []byte, path string) []b if i < 0 { panic(errors.New("index too large.")) } - data, chain = DerivePrivateKey(data, chain, uint32(i), prime) + data, chainCode = DerivePrivateKey(data, chainCode, uint32(i), prime) //printKeyInfo(data, nil, chain) } return data } -func DerivePublicKeyForPath(pubKeyBytes []byte, chain []byte, path string) []byte { +// DerivePublicKeyForPath derives the public key by following the path from pubKeyBytes +// using the given chainCode. +func DerivePublicKeyForPath(pubKeyBytes []byte, chainCode []byte, path string) []byte { data := pubKeyBytes parts := strings.Split(path, "/") for _, part := range parts { @@ -140,36 +162,42 @@ func DerivePublicKeyForPath(pubKeyBytes []byte, chain []byte, path string) []byt if i < 0 { panic(errors.New("index too large.")) } - data, chain = DerivePublicKey(data, chain, uint32(i)) - //printKeyInfo(nil, data, chain) + data, chainCode = DerivePublicKey(data, chainCode, uint32(i)) + //printKeyInfo(nil, data, chainCode) } return data } -func DerivePrivateKey(privKeyBytes []byte, chain []byte, i uint32, prime bool) ([]byte, []byte) { +// DerivePrivateKey derives the private key with index and chainCode. +// If prime is true, the derivation is 'hardened'. +// It returns the new private key and new chain code. +func DerivePrivateKey(privKeyBytes []byte, chainCode []byte, index uint32, prime bool) ([]byte, []byte) { var data []byte if prime { - i = i | 0x80000000 + index = index | 0x80000000 data = append([]byte{byte(0)}, privKeyBytes...) } else { public := PubKeyBytesFromPrivKeyBytes(privKeyBytes, true) data = public } - data = append(data, uint32ToBytes(i)...) - data2, chain2 := I64(chain, data) + data = append(data, uint32ToBytes(index)...) + data2, chainCode2 := I64(chainCode, data) x := addScalars(privKeyBytes, data2) - return x, chain2 + return x, chainCode2 } -func DerivePublicKey(pubKeyBytes []byte, chain []byte, i uint32) ([]byte, []byte) { +// DerivePublicKey derives the public key with index and chainCode. +// It returns the new public key and new chain code. +func DerivePublicKey(pubKeyBytes []byte, chainCode []byte, index uint32) ([]byte, []byte) { data := []byte{} data = append(data, pubKeyBytes...) - data = append(data, uint32ToBytes(i)...) - data2, chain2 := I64(chain, data) + data = append(data, uint32ToBytes(index)...) + data2, chainCode2 := I64(chainCode, data) data2p := PubKeyBytesFromPrivKeyBytes(data2, true) - return addPoints(pubKeyBytes, data2p), chain2 + return addPoints(pubKeyBytes, data2p), chainCode2 } +// eliptic curve pubkey addition func addPoints(a []byte, b []byte) []byte { ap, err := btcec.ParsePubKey(a, btcec.S256()) if err != nil { @@ -188,6 +216,7 @@ func addPoints(a []byte, b []byte) []byte { return sum.SerializeCompressed() } +// modular big endian addition func addScalars(a []byte, b []byte) []byte { aInt := new(big.Int).SetBytes(a) bInt := new(big.Int).SetBytes(b) @@ -204,15 +233,21 @@ func uint32ToBytes(i uint32) []byte { return b[:] } +//------------------------------------------------------------------- + +// HexEncode encodes b in hex. func HexEncode(b []byte) string { return hex.EncodeToString(b) } +// HexDecode hex decodes the str. If str is not valid hex +// it will return an empty byte slice. func HexDecode(str string) []byte { b, _ := hex.DecodeString(str) return b } +// I64 returns the two halfs of the SHA512 HMAC of key and data. func I64(key []byte, data []byte) ([]byte, []byte) { mac := hmac.New(sha512.New, key) mac.Write(data) @@ -220,27 +255,36 @@ func I64(key []byte, data []byte) ([]byte, []byte) { return I[:32], I[32:] } -// This returns a Bitcoin-like address. -func AddrFromPubKeyBytes(pubKeyBytes []byte) string { - prefix := byte(0x00) // TODO Make const or configurable +//------------------------------------------------------------------- + +const ( + btcPrefixPubKeyHash = byte(0x00) + btcPrefixPrivKey = byte(0x80) +) + +// BTCAddrFromPubKeyBytes returns a B58 encoded Bitcoin mainnet address. +func BTCAddrFromPubKeyBytes(pubKeyBytes []byte) string { + versionPrefix := btcPrefixPubKeyHash // TODO Make const or configurable h160 := CalcHash160(pubKeyBytes) - h160 = append([]byte{prefix}, h160...) + h160 = append([]byte{versionPrefix}, h160...) checksum := CalcHash256(h160) b := append(h160, checksum[:4]...) return base58.Encode(b) } -func AddrBytesFromPubKeyBytes(pubKeyBytes []byte) (addrBytes []byte, checksum []byte) { - prefix := byte(0x00) // TODO Make const or configurable +// BTCAddrBytesFromPubKeyBytes returns a hex Bitcoin mainnet address and its checksum. +func BTCAddrBytesFromPubKeyBytes(pubKeyBytes []byte) (addrBytes []byte, checksum []byte) { + versionPrefix := btcPrefixPubKeyHash // TODO Make const or configurable h160 := CalcHash160(pubKeyBytes) - _h160 := append([]byte{prefix}, h160...) + _h160 := append([]byte{versionPrefix}, h160...) checksum = CalcHash256(_h160)[:4] return h160, checksum } +// WIFFromPrivKeyBytes returns the privKeyBytes in Wallet Import Format. func WIFFromPrivKeyBytes(privKeyBytes []byte, compress bool) string { - prefix := byte(0x80) // TODO Make const or configurable - bytes := append([]byte{prefix}, privKeyBytes...) + versionPrefix := btcPrefixPrivKey // TODO Make const or configurable + bytes := append([]byte{versionPrefix}, privKeyBytes...) if compress { bytes = append(bytes, byte(1)) } @@ -249,6 +293,7 @@ func WIFFromPrivKeyBytes(privKeyBytes []byte, compress bool) string { return base58.Encode(bytes) } +// PubKeyBytesFromPrivKeyBytes returns the optionally compressed public key bytes. func PubKeyBytesFromPrivKeyBytes(privKeyBytes []byte, compress bool) (pubKeyBytes []byte) { x, y := btcec.S256().ScalarBaseMult(privKeyBytes) pub := &btcec.PublicKey{ @@ -263,27 +308,30 @@ func PubKeyBytesFromPrivKeyBytes(privKeyBytes []byte, compress bool) (pubKeyByte return pub.SerializeUncompressed() } -// Calculate the hash of hasher over buf. -func CalcHash(buf []byte, hasher hash.Hash) []byte { - hasher.Write(buf) +//-------------------------------------------------------------- + +// CalcHash returns the hash of data using hasher. +func CalcHash(data []byte, hasher hash.Hash) []byte { + hasher.Write(data) return hasher.Sum(nil) } -// calculate hash160 which is ripemd160(sha256(data)) -func CalcHash160(buf []byte) []byte { - return CalcHash(CalcHash(buf, sha256.New()), ripemd160.New()) +// CalcHash160 returns the ripemd160(sha256(data)). +func CalcHash160(data []byte) []byte { + return CalcHash(CalcHash(data, sha256.New()), ripemd160.New()) } -// calculate hash256 which is sha256(sha256(data)) -func CalcHash256(buf []byte) []byte { - return CalcHash(CalcHash(buf, sha256.New()), sha256.New()) +// CalcHash256 returns the sha256(sha256(data)). +func CalcHash256(data []byte) []byte { + return CalcHash(CalcHash(data, sha256.New()), sha256.New()) } -// calculate sha512(data) -func CalcSha512(buf []byte) []byte { - return CalcHash(buf, sha512.New()) +// CalcSha512 returns the sha512(data). +func CalcSha512(data []byte) []byte { + return CalcHash(data, sha512.New()) } +// ReverseBytes returns the buf in the opposite order func ReverseBytes(buf []byte) []byte { var res []byte if len(buf) == 0 { diff --git a/hd/hd_test.go b/hd/hd_test.go index 0ec47f5c3..c9e540ad5 100644 --- a/hd/hd_test.go +++ b/hd/hd_test.go @@ -112,7 +112,7 @@ func ifExit(err error, n int) { func gocrypto(seed []byte) ([]byte, []byte, []byte) { - _, priv, ch, _ := ComputeMastersFromSeed(string(seed)) + _, priv, ch := ComputeMastersFromSeed(string(seed)) privBytes := DerivePrivateKeyForPath( HexDecode(priv),