mirror of
https://github.com/fluencelabs/tendermint
synced 2025-05-11 22:37:11 +00:00
* 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) ```
243 lines
5.4 KiB
Go
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 {
|
|
}
|
|
}
|