tendermint/hd/hd_test.go
Emmanuel Odeke ae9c5b1ca0 hd: optimize ReverseBytes + add tests
* Optimized ReverseBytes to:
a) Minimally allocate --> 60.0% reduction in the number of allocations
b) Only walk halfway the length of the string thus performing
byte swaps from left to right. Improves the performance as well.
Complexity is O(n/2) instead of O(n) which is still O(n) but
benchmarks show the new time is in deed 1/2 of the original time.

* Added unit tests and some common cases to ensure correctness.

* Benchmark shoot out results:
```shell
name            old time/op    new time/op    delta
ReverseBytes-4     554ns ± 4%     242ns ± 3%  -56.20%  (p=0.000 n=10+10)

name            old alloc/op   new alloc/op   delta
ReverseBytes-4      208B ± 0%      114B ± 0%  -45.19%  (p=0.000 n=10+10)

name            old allocs/op  new allocs/op  delta
ReverseBytes-4      10.0 ± 0%       4.0 ± 0%  -60.00%  (p=0.000 n=10+10)
```
2017-07-28 12:21:41 -06:00

243 lines
5.4 KiB
Go

package hd
import (
"bytes"
"crypto/hmac"
"crypto/sha512"
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tyler-smith/go-bip39"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/mndrix/btcutil"
"github.com/tyler-smith/go-bip32"
"github.com/tendermint/go-crypto"
)
type addrData struct {
Mnemonic string
Master string
Seed string
Priv string
Pub string
Addr string
}
// NOTE: atom fundraiser address
var hdPath string = "m/44'/118'/0'/0/0"
var hdToAddrTable []addrData
func init() {
b, err := ioutil.ReadFile("test.json")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = json.Unmarshal(b, &hdToAddrTable)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func TestHDToAddr(t *testing.T) {
for i, d := range hdToAddrTable {
privB, _ := hex.DecodeString(d.Priv)
pubB, _ := hex.DecodeString(d.Pub)
addrB, _ := hex.DecodeString(d.Addr)
seedB, _ := hex.DecodeString(d.Seed)
masterB, _ := hex.DecodeString(d.Master)
seed := bip39.NewSeed(d.Mnemonic, "")
fmt.Println("================================")
fmt.Println("ROUND:", i, "MNEMONIC:", d.Mnemonic)
// master, priv, pub := tylerSmith(seed)
// master, priv, pub := btcsuite(seed)
master, priv, pub := gocrypto(seed)
fmt.Printf("\tNODEJS GOLANG\n")
fmt.Printf("SEED \t%X %X\n", seedB, seed)
fmt.Printf("MSTR \t%X %X\n", masterB, master)
fmt.Printf("PRIV \t%X %X\n", privB, priv)
fmt.Printf("PUB \t%X %X\n", pubB, pub)
_, _ = priv, privB
assert.Equal(t, master, masterB, fmt.Sprintf("Expected masters to match for %d", i))
assert.Equal(t, priv, privB, "Expected priv keys to match")
assert.Equal(t, pub, pubB, fmt.Sprintf("Expected pub keys to match for %d", i))
var pubT crypto.PubKeySecp256k1
copy(pubT[:], pub)
addr := pubT.Address()
fmt.Printf("ADDR \t%X %X\n", addrB, addr)
assert.Equal(t, addr, addrB, fmt.Sprintf("Expected addresses to match %d", i))
}
}
func TestReverseBytes(t *testing.T) {
tests := [...]struct {
v []byte
want []byte
}{
{[]byte(""), []byte("")},
{nil, nil},
{[]byte("Tendermint"), []byte("tnimredneT")},
{[]byte("T"), []byte("T")},
{[]byte("Te"), []byte("eT")},
}
for i, tt := range tests {
got := ReverseBytes(tt.v)
if !bytes.Equal(got, tt.want) {
t.Errorf("#%d:\ngot= (%x)\nwant=(%x)", i, got, tt.want)
}
}
}
func ifExit(err error, n int) {
if err != nil {
fmt.Println(n, err)
os.Exit(1)
}
}
func gocrypto(seed []byte) ([]byte, []byte, []byte) {
_, priv, ch, _ := ComputeMastersFromSeed(string(seed))
privBytes := DerivePrivateKeyForPath(
HexDecode(priv),
HexDecode(ch),
"44'/118'/0'/0/0",
)
pubBytes := PubKeyBytesFromPrivKeyBytes(privBytes, true)
return HexDecode(priv), privBytes, pubBytes
}
func btcsuite(seed []byte) ([]byte, []byte, []byte) {
fmt.Println("HD")
masterKey, err := hdkeychain.NewMaster(seed, &chaincfg.MainNetParams)
if err != nil {
hmac := hmac.New(sha512.New, []byte("Bitcoin seed"))
hmac.Write([]byte(seed))
intermediary := hmac.Sum(nil)
curve := btcutil.Secp256k1()
curveParams := curve.Params()
// Split it into our key and chain code
keyBytes := intermediary[:32]
fmt.Printf("\t%X\n", keyBytes)
fmt.Printf("\t%X\n", curveParams.N.Bytes())
keyInt, _ := binary.ReadVarint(bytes.NewBuffer(keyBytes))
fmt.Printf("\t%d\n", keyInt)
}
fh := hdkeychain.HardenedKeyStart
k, err := masterKey.Child(uint32(fh + 44))
ifExit(err, 44)
k, err = k.Child(uint32(fh + 118))
ifExit(err, 118)
k, err = k.Child(uint32(fh + 0))
ifExit(err, 1)
k, err = k.Child(uint32(0))
ifExit(err, 2)
k, err = k.Child(uint32(0))
ifExit(err, 3)
ecpriv, err := k.ECPrivKey()
ifExit(err, 10)
ecpub, err := k.ECPubKey()
ifExit(err, 11)
priv := ecpriv.Serialize()
pub := ecpub.SerializeCompressed()
mkey, _ := masterKey.ECPrivKey()
return mkey.Serialize(), priv, pub
}
// return priv and pub
func tylerSmith(seed []byte) ([]byte, []byte, []byte) {
masterKey, err := bip32.NewMasterKey(seed)
if err != nil {
hmac := hmac.New(sha512.New, []byte("Bitcoin seed"))
hmac.Write([]byte(seed))
intermediary := hmac.Sum(nil)
curve := btcutil.Secp256k1()
curveParams := curve.Params()
// Split it into our key and chain code
keyBytes := intermediary[:32]
fmt.Printf("\t%X\n", keyBytes)
fmt.Printf("\t%X\n", curveParams.N.Bytes())
keyInt, _ := binary.ReadVarint(bytes.NewBuffer(keyBytes))
fmt.Printf("\t%d\n", keyInt)
}
ifExit(err, 0)
fh := bip32.FirstHardenedChild
k, err := masterKey.NewChildKey(fh + 44)
ifExit(err, 44)
k, err = k.NewChildKey(fh + 118)
ifExit(err, 118)
k, err = k.NewChildKey(fh + 0)
ifExit(err, 1)
k, err = k.NewChildKey(0)
ifExit(err, 2)
k, err = k.NewChildKey(0)
ifExit(err, 3)
priv := k.Key
pub := k.PublicKey().Key
return masterKey.Key, priv, pub
}
// Benchmarks
var revBytesCases = [][]byte{
nil,
[]byte(""),
[]byte("12"),
// 16byte case
[]byte("abcdefghijklmnop"),
// 32byte case
[]byte("abcdefghijklmnopqrstuvwxyz123456"),
// 64byte case
[]byte("abcdefghijklmnopqrstuvwxyz123456abcdefghijklmnopqrstuvwxyz123456"),
}
func BenchmarkReverseBytes(b *testing.B) {
var sink []byte
for i := 0; i < b.N; i++ {
for _, tt := range revBytesCases {
sink = ReverseBytes(tt)
}
}
b.ReportAllocs()
// sink is necessary to ensure if the compiler tries
// to smart, that it won't optimize away the benchmarks.
if sink != nil {
}
}