package hd // XXX This package doesn't work with our address scheme, // XXX and it probably doesn't work for our other pubkey types. // XXX Fix it up to be more general but compatible. import ( "crypto/ecdsa" "crypto/hmac" "crypto/sha256" "crypto/sha512" "encoding/base64" "encoding/binary" "encoding/hex" "errors" "fmt" "hash" "math/big" "strconv" "strings" "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcutil/base58" "golang.org/x/crypto/ripemd160" ) /* 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(chainCodeHex), fmt.Sprintf("%v/%v", path, index), ) 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), HexDecode(chainHex), fmt.Sprintf("%v/%v", path, index), ) return HexEncode(privKeyBytes) } // ComputeBTCAddressForPrivKey returns the Bitcoin address for the given privKey. func ComputeBTCAddressForPrivKey(privKey string) string { pubKeyBytes := PubKeyBytesFromPrivKeyBytes(HexDecode(privKey), true) return BTCAddrFromPubKeyBytes(pubKeyBytes) } // 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{} bytes = append(bytes, byte(len(prefixBytes))) bytes = append(bytes, prefixBytes...) bytes = append(bytes, byte(len(messageBytes))) bytes = append(bytes, messageBytes...) privKeyBytes := HexDecode(privKey) x, y := btcec.S256().ScalarBaseMult(privKeyBytes) ecdsaPubKey := ecdsa.PublicKey{ Curve: btcec.S256(), X: x, Y: y, } ecdsaPrivKey := &btcec.PrivateKey{ PublicKey: ecdsaPubKey, D: new(big.Int).SetBytes(privKeyBytes), } sigbytes, err := btcec.SignCompact(btcec.S256(), ecdsaPrivKey, CalcHash256(bytes), compress) if err != nil { panic(err) } return base64.StdEncoding.EncodeToString(sigbytes) } // 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) } // ComputeWIF returns the privKey in Wallet Import Format. func ComputeWIF(privKey string, compress bool) string { return WIFFromPrivKeyBytes(HexDecode(privKey), compress) } // ComputeBTCTxId returns the bitcoin transaction ID. func ComputeBTCTxId(rawTxHex string) string { return HexEncode(ReverseBytes(CalcHash256(HexDecode(rawTxHex)))) } /* func printKeyInfo(privKeyBytes []byte, pubKeyBytes []byte, chain []byte) { if pubKeyBytes == nil { pubKeyBytes = PubKeyBytesFromPrivKeyBytes(privKeyBytes, true) } addr := AddrFromPubKeyBytes(pubKeyBytes) log.Println("\nprikey:\t%v\npubKeyBytes:\t%v\naddr:\t%v\nchain:\t%v", HexEncode(privKeyBytes), HexEncode(pubKeyBytes), addr, HexEncode(chain)) } */ //------------------------------------------------------------------- // 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 { prime := part[len(part)-1:] == "'" // prime == private derivation. Otherwise public. if prime { part = part[:len(part)-1] } i, err := strconv.Atoi(part) if err != nil { panic(err) } if i < 0 { panic(errors.New("index too large.")) } data, chainCode = DerivePrivateKey(data, chainCode, uint32(i), prime) //printKeyInfo(data, nil, chain) } return data } // 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 { prime := part[len(part)-1:] == "'" if prime { panic(errors.New("cannot do a prime derivation from public key")) } i, err := strconv.Atoi(part) if err != nil { panic(err) } if i < 0 { panic(errors.New("index too large.")) } data, chainCode = DerivePublicKey(data, chainCode, uint32(i)) //printKeyInfo(nil, data, chainCode) } return data } // 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 { index = index | 0x80000000 data = append([]byte{byte(0)}, privKeyBytes...) } else { public := PubKeyBytesFromPrivKeyBytes(privKeyBytes, true) data = public } data = append(data, uint32ToBytes(index)...) data2, chainCode2 := I64(chainCode, data) x := addScalars(privKeyBytes, data2) return x, chainCode2 } // 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(index)...) data2, chainCode2 := I64(chainCode, data) data2p := PubKeyBytesFromPrivKeyBytes(data2, true) 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 { panic(err) } bp, err := btcec.ParsePubKey(b, btcec.S256()) if err != nil { panic(err) } sumX, sumY := btcec.S256().Add(ap.X, ap.Y, bp.X, bp.Y) sum := &btcec.PublicKey{ Curve: btcec.S256(), X: sumX, Y: sumY, } 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) sInt := new(big.Int).Add(aInt, bInt) x := sInt.Mod(sInt, btcec.S256().N).Bytes() x2 := [32]byte{} copy(x2[32-len(x):], x) return x2[:] } func uint32ToBytes(i uint32) []byte { b := [4]byte{} binary.BigEndian.PutUint32(b[:], i) 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) I := mac.Sum(nil) return I[:32], I[32:] } //------------------------------------------------------------------- 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{versionPrefix}, h160...) checksum := CalcHash256(h160) b := append(h160, checksum[:4]...) return base58.Encode(b) } // 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{versionPrefix}, h160...) checksum = CalcHash256(_h160)[:4] return h160, checksum } // WIFFromPrivKeyBytes returns the privKeyBytes in Wallet Import Format. func WIFFromPrivKeyBytes(privKeyBytes []byte, compress bool) string { versionPrefix := btcPrefixPrivKey // TODO Make const or configurable bytes := append([]byte{versionPrefix}, privKeyBytes...) if compress { bytes = append(bytes, byte(1)) } checksum := CalcHash256(bytes) bytes = append(bytes, checksum[:4]...) 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{ Curve: btcec.S256(), X: x, Y: y, } if compress { return pub.SerializeCompressed() } return pub.SerializeUncompressed() } //-------------------------------------------------------------- // CalcHash returns the hash of data using hasher. func CalcHash(data []byte, hasher hash.Hash) []byte { hasher.Write(data) return hasher.Sum(nil) } // CalcHash160 returns the ripemd160(sha256(data)). func CalcHash160(data []byte) []byte { return CalcHash(CalcHash(data, sha256.New()), ripemd160.New()) } // CalcHash256 returns the sha256(sha256(data)). func CalcHash256(data []byte) []byte { return CalcHash(CalcHash(data, sha256.New()), sha256.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 { return res } // Walk till mid-way, swapping bytes from each end: // b[i] and b[len-i-1] blen := len(buf) res = make([]byte, blen) mid := blen / 2 for left := 0; left <= mid; left++ { right := blen - left - 1 res[left] = buf[right] res[right] = buf[left] } return res }