- fundraiser compatibility for HD keys (BIP 39 & BIP 32 / BIP 44)pull/1782/head
@ -0,0 +1,62 @@ | |||||
package bip39 | |||||
import ( | |||||
"strings" | |||||
"github.com/bartekn/go-bip39" | |||||
) | |||||
// ValidSentenceLen defines the mnemonic sentence lengths supported by this BIP 39 library. | |||||
type ValidSentenceLen uint8 | |||||
const ( | |||||
// FundRaiser is the sentence length used during the cosmos fundraiser (12 words). | |||||
FundRaiser ValidSentenceLen = 12 | |||||
// FreshKey is the sentence length used for newly created keys (24 words). | |||||
FreshKey ValidSentenceLen = 24 | |||||
) | |||||
// NewMnemonic will return a string consisting of the mnemonic words for | |||||
// the given sentence length. | |||||
func NewMnemonic(len ValidSentenceLen) (words []string, err error) { | |||||
// len = (ENT + checksum) / 11 | |||||
var ENT int | |||||
switch len { | |||||
case FundRaiser: | |||||
ENT = 128 | |||||
case FreshKey: | |||||
ENT = 256 | |||||
} | |||||
var entropy []byte | |||||
entropy, err = bip39.NewEntropy(ENT) | |||||
if err != nil { | |||||
return | |||||
} | |||||
var mnemonic string | |||||
mnemonic, err = bip39.NewMnemonic(entropy) | |||||
if err != nil { | |||||
return | |||||
} | |||||
words = strings.Split(mnemonic, " ") | |||||
return | |||||
} | |||||
// MnemonicToSeed creates a BIP 39 seed from the passed mnemonic (with an empty BIP 39 password). | |||||
// This method does not validate the mnemonics checksum. | |||||
func MnemonicToSeed(mne string) (seed []byte) { | |||||
// we do not checksum here... | |||||
seed = bip39.NewSeed(mne, "") | |||||
return | |||||
} | |||||
// MnemonicToSeedWithErrChecking returns the same seed as MnemonicToSeed. | |||||
// It creates a BIP 39 seed from the passed mnemonic (with an empty BIP 39 password). | |||||
// | |||||
// Different from MnemonicToSeed it validates the checksum. | |||||
// For details on the checksum see the BIP 39 spec. | |||||
func MnemonicToSeedWithErrChecking(mne string) (seed []byte, err error) { | |||||
seed, err = bip39.NewSeedWithErrorChecking(mne, "") | |||||
return | |||||
} | |||||
@ -0,0 +1,15 @@ | |||||
package bip39 | |||||
import ( | |||||
"testing" | |||||
"github.com/stretchr/testify/assert" | |||||
) | |||||
func TestWordCodec_NewMnemonic(t *testing.T) { | |||||
_, err := NewMnemonic(FundRaiser) | |||||
assert.NoError(t, err, "unexpected error generating fundraiser mnemonic") | |||||
_, err = NewMnemonic(FreshKey) | |||||
assert.NoError(t, err, "unexpected error generating new 24-word mnemonic") | |||||
} |
@ -1,352 +0,0 @@ | |||||
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 | |||||
} |
@ -1,37 +0,0 @@ | |||||
package hd | |||||
/* | |||||
import ( | |||||
"encoding/hex" | |||||
"fmt" | |||||
"testing" | |||||
) | |||||
func TestManual(t *testing.T) { | |||||
bytes, _ := hex.DecodeString("dfac699f1618c9be4df2befe94dc5f313946ebafa386756bd4926a1ecfd7cf2438426ede521d1ee6512391bc200b7910bcbea593e68d52b874c29bdc5a308ed1") | |||||
fmt.Println(bytes) | |||||
puk, prk, ch, se := ComputeMastersFromSeed(string(bytes)) | |||||
fmt.Println(puk, ch, se) | |||||
pubBytes2 := DerivePublicKeyForPath( | |||||
HexDecode(puk), | |||||
HexDecode(ch), | |||||
//"44'/118'/0'/0/0", | |||||
"0/0", | |||||
) | |||||
fmt.Printf("PUB2 %X\n", pubBytes2) | |||||
privBytes := DerivePrivateKeyForPath( | |||||
HexDecode(prk), | |||||
HexDecode(ch), | |||||
//"44'/118'/0'/0/0", | |||||
//"0/0", | |||||
"44'/118'/0'/0/0", | |||||
) | |||||
fmt.Printf("PRIV %X\n", privBytes) | |||||
pubBytes := PubKeyBytesFromPrivKeyBytes(privBytes, true) | |||||
fmt.Printf("PUB %X\n", pubBytes) | |||||
} | |||||
*/ |
@ -0,0 +1,83 @@ | |||||
package hd | |||||
import ( | |||||
"encoding/hex" | |||||
"encoding/json" | |||||
"fmt" | |||||
"io/ioutil" | |||||
"testing" | |||||
"github.com/stretchr/testify/assert" | |||||
"github.com/tyler-smith/go-bip39" | |||||
"github.com/tendermint/go-crypto" | |||||
) | |||||
type addrData struct { | |||||
Mnemonic string | |||||
Master string | |||||
Seed string | |||||
Priv string | |||||
Pub string | |||||
Addr string | |||||
} | |||||
func initFundraiserTestVectors(t *testing.T) []addrData { | |||||
// NOTE: atom fundraiser address | |||||
// var hdPath string = "m/44'/118'/0'/0/0" | |||||
var hdToAddrTable []addrData | |||||
b, err := ioutil.ReadFile("test.json") | |||||
if err != nil { | |||||
t.Fatalf("could not read fundraiser test vector file (test.json): %s", err) | |||||
} | |||||
err = json.Unmarshal(b, &hdToAddrTable) | |||||
if err != nil { | |||||
t.Fatalf("could not decode test vectors (test.json): %s", err) | |||||
} | |||||
return hdToAddrTable | |||||
} | |||||
func TestFundraiserCompatibility(t *testing.T) { | |||||
hdToAddrTable := initFundraiserTestVectors(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, "") | |||||
t.Log("================================") | |||||
t.Logf("ROUND: %d MNEMONIC: %s", i, d.Mnemonic) | |||||
master, ch := ComputeMastersFromSeed(seed) | |||||
priv, err := DerivePrivateKeyForPath(master, ch, "44'/118'/0'/0/0") | |||||
assert.NoError(t, err) | |||||
pub := crypto.PrivKeySecp256k1(priv).PubKey() | |||||
t.Log("\tNODEJS GOLANG\n") | |||||
t.Logf("SEED \t%X %X\n", seedB, seed) | |||||
t.Logf("MSTR \t%X %X\n", masterB, master) | |||||
t.Logf("PRIV \t%X %X\n", privB, priv) | |||||
t.Logf("PUB \t%X %X\n", pubB, pub) | |||||
assert.Equal(t, seedB, seed) | |||||
assert.Equal(t, master[:], masterB, fmt.Sprintf("Expected masters to match for %d", i)) | |||||
assert.Equal(t, priv[:], privB, "Expected priv keys to match") | |||||
var pubBFixed [33]byte | |||||
copy(pubBFixed[:], pubB) | |||||
assert.Equal(t, pub, crypto.PubKeySecp256k1(pubBFixed), fmt.Sprintf("Expected pub keys to match for %d", i)) | |||||
addr := pub.Address() | |||||
t.Logf("ADDR \t%X %X\n", addrB, addr) | |||||
assert.Equal(t, addr, crypto.Address(addrB), fmt.Sprintf("Expected addresses to match %d", i)) | |||||
} | |||||
} | |||||
@ -1,238 +0,0 @@ | |||||
package hd | |||||
import ( | |||||
"bytes" | |||||
"encoding/hex" | |||||
"encoding/json" | |||||
"fmt" | |||||
"io/ioutil" | |||||
"os" | |||||
"testing" | |||||
"github.com/stretchr/testify/assert" | |||||
"github.com/tyler-smith/go-bip39" | |||||
"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, crypto.Address(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 { | |||||
_ = sink | |||||
} | |||||
} |
@ -0,0 +1,168 @@ | |||||
// Package hd provides basic functionality Hierarchical Deterministic Wallets. | |||||
// | |||||
// The user must understand the overall concept of the BIP 32 and the BIP 44 specs: | |||||
// https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki | |||||
// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki | |||||
// | |||||
// In combination with the bip39 package in go-crypto this package provides the functionality for deriving keys using a | |||||
// BIP 44 HD path, or, more general, by passing a BIP 32 path. | |||||
// | |||||
// In particular, this package (together with bip39) provides all necessary functionality to derive keys from | |||||
// mnemonics generated during the cosmos fundraiser. | |||||
package hd | |||||
import ( | |||||
"crypto/hmac" | |||||
"crypto/sha512" | |||||
"encoding/binary" | |||||
"errors" | |||||
"fmt" | |||||
"math/big" | |||||
"strconv" | |||||
"strings" | |||||
"github.com/btcsuite/btcd/btcec" | |||||
"github.com/tendermint/go-crypto" | |||||
) | |||||
// BIP44Prefix is the parts of the BIP32 HD path that are fixed by what we used during the fundraiser. | |||||
const ( | |||||
BIP44Prefix = "44'/118'/" | |||||
FullFundraiserPath = BIP44Prefix + "0'/0/0" | |||||
) | |||||
// BIP44Params wraps BIP 44 params (5 level BIP 32 path). | |||||
// To receive a canonical string representation ala | |||||
// m / purpose' / coin_type' / account' / change / address_index | |||||
// call String() on a BIP44Params instance. | |||||
type BIP44Params struct { | |||||
purpose uint32 | |||||
coinType uint32 | |||||
account uint32 | |||||
change bool | |||||
addressIdx uint32 | |||||
} | |||||
// NewParams creates a BIP 44 parameter object from the params: | |||||
// m / purpose' / coin_type' / account' / change / address_index | |||||
func NewParams(purpose, coinType, account uint32, change bool, addressIdx uint32) *BIP44Params { | |||||
return &BIP44Params{ | |||||
purpose: purpose, | |||||
coinType: coinType, | |||||
account: account, | |||||
change: change, | |||||
addressIdx: addressIdx, | |||||
} | |||||
} | |||||
// NewFundraiserParams creates a BIP 44 parameter object from the params: | |||||
// m / 44' / 118' / account' / 0 / address_index | |||||
// The fixed parameters (purpose', coin_type', and change) are determined by what was used in the fundraiser. | |||||
func NewFundraiserParams(account uint32, addressIdx uint32) *BIP44Params { | |||||
return NewParams(44, 118, account, false, addressIdx) | |||||
} | |||||
func (p BIP44Params) String() string { | |||||
var changeStr string | |||||
if p.change { | |||||
changeStr = "1" | |||||
} else { | |||||
changeStr = "0" | |||||
} | |||||
// m / purpose' / coin_type' / account' / change / address_index | |||||
return fmt.Sprintf("%d'/%d'/%d'/%s/%d", | |||||
p.purpose, | |||||
p.coinType, | |||||
p.account, | |||||
changeStr, | |||||
p.addressIdx) | |||||
} | |||||
// ComputeMastersFromSeed returns the master public key, master secret, and chain code in hex. | |||||
func ComputeMastersFromSeed(seed []byte) (secret [32]byte, chainCode [32]byte) { | |||||
masterSecret := []byte("Bitcoin seed") | |||||
secret, chainCode = i64(masterSecret, seed) | |||||
return | |||||
} | |||||
// DerivePrivateKeyForPath derives the private key by following the BIP 32/44 path from privKeyBytes, | |||||
// using the given chainCode. | |||||
func DerivePrivateKeyForPath(privKeyBytes [32]byte, chainCode [32]byte, path string) ([32]byte, error) { | |||||
data := privKeyBytes | |||||
parts := strings.Split(path, "/") | |||||
for _, part := range parts { | |||||
// do we have an apostrophe? | |||||
harden := part[len(part)-1:] == "'" | |||||
// harden == private derivation, else public derivation: | |||||
if harden { | |||||
part = part[:len(part)-1] | |||||
} | |||||
idx, err := strconv.Atoi(part) | |||||
if err != nil { | |||||
return [32]byte{}, fmt.Errorf("invalid BIP 32 path: %s", err) | |||||
} | |||||
if idx < 0 { | |||||
return [32]byte{}, errors.New("invalid BIP 32 path: index negative ot too large") | |||||
} | |||||
data, chainCode = derivePrivateKey(data, chainCode, uint32(idx), harden) | |||||
} | |||||
var derivedKey [32]byte | |||||
n := copy(derivedKey[:], data[:]) | |||||
if n != 32 || len(data) != 32 { | |||||
return [32]byte{}, fmt.Errorf("expected a (secp256k1) key of length 32, got length: %v", len(data)) | |||||
} | |||||
return derivedKey, nil | |||||
} | |||||
// derivePrivateKey derives the private key with index and chainCode. | |||||
// If harden is true, the derivation is 'hardened'. | |||||
// It returns the new private key and new chain code. | |||||
// For more information on hardened keys see: | |||||
// - https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki | |||||
func derivePrivateKey(privKeyBytes [32]byte, chainCode [32]byte, index uint32, harden bool) ([32]byte, [32]byte) { | |||||
var data []byte | |||||
if harden { | |||||
index = index | 0x80000000 | |||||
data = append([]byte{byte(0)}, privKeyBytes[:]...) | |||||
} else { | |||||
// this can't return an error: | |||||
pubkey := crypto.PrivKeySecp256k1(privKeyBytes).PubKey() | |||||
public := pubkey.(crypto.PubKeySecp256k1) | |||||
data = public[:] | |||||
} | |||||
data = append(data, uint32ToBytes(index)...) | |||||
data2, chainCode2 := i64(chainCode[:], data) | |||||
x := addScalars(privKeyBytes[:], data2[:]) | |||||
return x, chainCode2 | |||||
} | |||||
// modular big endian addition | |||||
func addScalars(a []byte, b []byte) [32]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[:] | |||||
} | |||||
// i64 returns the two halfs of the SHA512 HMAC of key and data. | |||||
func i64(key []byte, data []byte) (IL [32]byte, IR [32]byte) { | |||||
mac := hmac.New(sha512.New, key) | |||||
// sha512 does not err | |||||
_, _ = mac.Write(data) | |||||
I := mac.Sum(nil) | |||||
copy(IL[:], I[:32]) | |||||
copy(IR[:], I[32:]) | |||||
return | |||||
} |
@ -0,0 +1,73 @@ | |||||
package hd | |||||
import ( | |||||
"encoding/hex" | |||||
"fmt" | |||||
"github.com/tendermint/go-crypto/keys/bip39" | |||||
) | |||||
func ExampleStringifyPathParams() { | |||||
path := NewParams(44, 0, 0, false, 0) | |||||
fmt.Println(path.String()) | |||||
// Output: 44'/0'/0'/0/0 | |||||
} | |||||
func ExampleSomeBIP32TestVecs() { | |||||
seed := bip39.MnemonicToSeed("barrel original fuel morning among eternal " + | |||||
"filter ball stove pluck matrix mechanic") | |||||
master, ch := ComputeMastersFromSeed(seed) | |||||
fmt.Println("keys from fundraiser test-vector (cosmos, bitcoin, ether)") | |||||
fmt.Println() | |||||
// cosmos | |||||
priv, _ := DerivePrivateKeyForPath(master, ch, FullFundraiserPath) | |||||
fmt.Println(hex.EncodeToString(priv[:])) | |||||
// bitcoin | |||||
priv, _ = DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/0") | |||||
fmt.Println(hex.EncodeToString(priv[:])) | |||||
// ether | |||||
priv, _ = DerivePrivateKeyForPath(master, ch, "44'/60'/0'/0/0") | |||||
fmt.Println(hex.EncodeToString(priv[:])) | |||||
fmt.Println() | |||||
fmt.Println("keys generated via https://coinomi.com/recovery-phrase-tool.html") | |||||
fmt.Println() | |||||
seed = bip39.MnemonicToSeed( | |||||
"advice process birth april short trust crater change bacon monkey medal garment " + | |||||
"gorilla ranch hour rival razor call lunar mention taste vacant woman sister") | |||||
master, ch = ComputeMastersFromSeed(seed) | |||||
priv, _ = DerivePrivateKeyForPath(master, ch, "44'/1'/1'/0/4") | |||||
fmt.Println(hex.EncodeToString(priv[:])) | |||||
seed = bip39.MnemonicToSeed("idea naive region square margin day captain habit " + | |||||
"gun second farm pact pulse someone armed") | |||||
master, ch = ComputeMastersFromSeed(seed) | |||||
priv, _ = DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/420") | |||||
fmt.Println(hex.EncodeToString(priv[:])) | |||||
fmt.Println() | |||||
fmt.Println("BIP 32 example") | |||||
fmt.Println() | |||||
// bip32 path: m/0/7 | |||||
seed = bip39.MnemonicToSeed("monitor flock loyal sick object grunt duty ride develop assault harsh history") | |||||
master, ch = ComputeMastersFromSeed(seed) | |||||
priv, _ = DerivePrivateKeyForPath(master, ch, "0/7") | |||||
fmt.Println(hex.EncodeToString(priv[:])) | |||||
// Output: keys from fundraiser test-vector (cosmos, bitcoin, ether) | |||||
// | |||||
// bfcb217c058d8bbafd5e186eae936106ca3e943889b0b4a093ae13822fd3170c | |||||
// e77c3de76965ad89997451de97b95bb65ede23a6bf185a55d80363d92ee37c3d | |||||
// 7fc4d8a8146dea344ba04c593517d3f377fa6cded36cd55aee0a0bb968e651bc | |||||
// | |||||
// keys generated via https://coinomi.com/recovery-phrase-tool.html | |||||
// | |||||
// a61f10c5fecf40c084c94fa54273b6f5d7989386be4a37669e6d6f7b0169c163 | |||||
// 32c4599843de3ef161a629a461d12c60b009b676c35050be5f7ded3a3b23501f | |||||
// | |||||
// BIP 32 example | |||||
// | |||||
// c4c11d8c03625515905d7e89d25dfc66126fbc629ecca6db489a1a72fc4bda78 | |||||
} |
@ -1,32 +1,12 @@ | |||||
package keys | package keys | ||||
import "fmt" | |||||
type SignAlgo string | |||||
// SigningAlgo defines an algorithm to derive key-pairs which can be used for cryptographic signing. | |||||
type SigningAlgo string | |||||
const ( | const ( | ||||
AlgoEd25519 = SignAlgo("ed25519") | |||||
AlgoSecp256k1 = SignAlgo("secp256k1") | |||||
) | |||||
func cryptoAlgoToByte(key SignAlgo) byte { | |||||
switch key { | |||||
case AlgoEd25519: | |||||
return 0x01 | |||||
case AlgoSecp256k1: | |||||
return 0x02 | |||||
default: | |||||
panic(fmt.Sprintf("Unexpected type key %v", key)) | |||||
} | |||||
} | |||||
func byteToSignAlgo(b byte) SignAlgo { | |||||
switch b { | |||||
case 0x01: | |||||
return AlgoEd25519 | |||||
case 0x02: | |||||
return AlgoSecp256k1 | |||||
default: | |||||
panic(fmt.Sprintf("Unexpected type byte %X", b)) | |||||
} | |||||
} | |||||
// Secp256k1 uses the Bitcoin secp256k1 ECDSA parameters. | |||||
Secp256k1 = SigningAlgo("secp256k1") | |||||
// Ed25519 represents the Ed25519 signature system. | |||||
// It is currently not supported for end-user keys (wallets/ledgers). | |||||
Ed25519 = SigningAlgo("ed25519") | |||||
) |
@ -1,208 +0,0 @@ | |||||
package words | |||||
import ( | |||||
"encoding/binary" | |||||
"errors" | |||||
"hash/crc32" | |||||
"hash/crc64" | |||||
"github.com/howeyc/crc16" | |||||
) | |||||
// ECC is used for anything that calculates an error-correcting code | |||||
type ECC interface { | |||||
// AddECC calculates an error-correcting code for the input | |||||
// returns an output with the code appended | |||||
AddECC([]byte) []byte | |||||
// CheckECC verifies if the ECC is proper on the input and returns | |||||
// the data with the code removed, or an error | |||||
CheckECC([]byte) ([]byte, error) | |||||
} | |||||
var errInputTooShort = errors.New("input too short, no checksum present") | |||||
var errChecksumDoesntMatch = errors.New("checksum does not match") | |||||
// NoECC is a no-op placeholder, kind of useless... except for tests | |||||
type NoECC struct{} | |||||
var _ ECC = NoECC{} | |||||
func (_ NoECC) AddECC(input []byte) []byte { return input } | |||||
func (_ NoECC) CheckECC(input []byte) ([]byte, error) { return input, nil } | |||||
// CRC16 does the ieee crc16 polynomial check | |||||
type CRC16 struct { | |||||
Poly uint16 | |||||
table *crc16.Table | |||||
} | |||||
var _ ECC = (*CRC16)(nil) | |||||
const crc16ByteCount = 2 | |||||
func NewIBMCRC16() *CRC16 { | |||||
return &CRC16{Poly: crc16.IBM} | |||||
} | |||||
func NewSCSICRC16() *CRC16 { | |||||
return &CRC16{Poly: crc16.SCSI} | |||||
} | |||||
func NewCCITTCRC16() *CRC16 { | |||||
return &CRC16{Poly: crc16.CCITT} | |||||
} | |||||
func (c *CRC16) AddECC(input []byte) []byte { | |||||
table := c.getTable() | |||||
// get crc and convert to some bytes... | |||||
crc := crc16.Checksum(input, table) | |||||
check := make([]byte, crc16ByteCount) | |||||
binary.BigEndian.PutUint16(check, crc) | |||||
// append it to the input | |||||
output := append(input, check...) | |||||
return output | |||||
} | |||||
func (c *CRC16) CheckECC(input []byte) ([]byte, error) { | |||||
table := c.getTable() | |||||
if len(input) <= crc16ByteCount { | |||||
return nil, errInputTooShort | |||||
} | |||||
cut := len(input) - crc16ByteCount | |||||
data, check := input[:cut], input[cut:] | |||||
crc := binary.BigEndian.Uint16(check) | |||||
calc := crc16.Checksum(data, table) | |||||
if crc != calc { | |||||
return nil, errChecksumDoesntMatch | |||||
} | |||||
return data, nil | |||||
} | |||||
func (c *CRC16) getTable() *crc16.Table { | |||||
if c.table != nil { | |||||
return c.table | |||||
} | |||||
if c.Poly == 0 { | |||||
c.Poly = crc16.IBM | |||||
} | |||||
c.table = crc16.MakeTable(c.Poly) | |||||
return c.table | |||||
} | |||||
// CRC32 does the ieee crc32 polynomial check | |||||
type CRC32 struct { | |||||
Poly uint32 | |||||
table *crc32.Table | |||||
} | |||||
var _ ECC = (*CRC32)(nil) | |||||
func NewIEEECRC32() *CRC32 { | |||||
return &CRC32{Poly: crc32.IEEE} | |||||
} | |||||
func NewCastagnoliCRC32() *CRC32 { | |||||
return &CRC32{Poly: crc32.Castagnoli} | |||||
} | |||||
func NewKoopmanCRC32() *CRC32 { | |||||
return &CRC32{Poly: crc32.Koopman} | |||||
} | |||||
func (c *CRC32) AddECC(input []byte) []byte { | |||||
table := c.getTable() | |||||
// get crc and convert to some bytes... | |||||
crc := crc32.Checksum(input, table) | |||||
check := make([]byte, crc32.Size) | |||||
binary.BigEndian.PutUint32(check, crc) | |||||
// append it to the input | |||||
output := append(input, check...) | |||||
return output | |||||
} | |||||
func (c *CRC32) CheckECC(input []byte) ([]byte, error) { | |||||
table := c.getTable() | |||||
if len(input) <= crc32.Size { | |||||
return nil, errInputTooShort | |||||
} | |||||
cut := len(input) - crc32.Size | |||||
data, check := input[:cut], input[cut:] | |||||
crc := binary.BigEndian.Uint32(check) | |||||
calc := crc32.Checksum(data, table) | |||||
if crc != calc { | |||||
return nil, errChecksumDoesntMatch | |||||
} | |||||
return data, nil | |||||
} | |||||
func (c *CRC32) getTable() *crc32.Table { | |||||
if c.table == nil { | |||||
if c.Poly == 0 { | |||||
c.Poly = crc32.IEEE | |||||
} | |||||
c.table = crc32.MakeTable(c.Poly) | |||||
} | |||||
return c.table | |||||
} | |||||
// CRC64 does the ieee crc64 polynomial check | |||||
type CRC64 struct { | |||||
Poly uint64 | |||||
table *crc64.Table | |||||
} | |||||
var _ ECC = (*CRC64)(nil) | |||||
func NewISOCRC64() *CRC64 { | |||||
return &CRC64{Poly: crc64.ISO} | |||||
} | |||||
func NewECMACRC64() *CRC64 { | |||||
return &CRC64{Poly: crc64.ECMA} | |||||
} | |||||
func (c *CRC64) AddECC(input []byte) []byte { | |||||
table := c.getTable() | |||||
// get crc and convert to some bytes... | |||||
crc := crc64.Checksum(input, table) | |||||
check := make([]byte, crc64.Size) | |||||
binary.BigEndian.PutUint64(check, crc) | |||||
// append it to the input | |||||
output := append(input, check...) | |||||
return output | |||||
} | |||||
func (c *CRC64) CheckECC(input []byte) ([]byte, error) { | |||||
table := c.getTable() | |||||
if len(input) <= crc64.Size { | |||||
return nil, errInputTooShort | |||||
} | |||||
cut := len(input) - crc64.Size | |||||
data, check := input[:cut], input[cut:] | |||||
crc := binary.BigEndian.Uint64(check) | |||||
calc := crc64.Checksum(data, table) | |||||
if crc != calc { | |||||
return nil, errChecksumDoesntMatch | |||||
} | |||||
return data, nil | |||||
} | |||||
func (c *CRC64) getTable() *crc64.Table { | |||||
if c.table == nil { | |||||
if c.Poly == 0 { | |||||
c.Poly = crc64.ISO | |||||
} | |||||
c.table = crc64.MakeTable(c.Poly) | |||||
} | |||||
return c.table | |||||
} |
@ -1,62 +0,0 @@ | |||||
package words | |||||
import ( | |||||
"testing" | |||||
asrt "github.com/stretchr/testify/assert" | |||||
cmn "github.com/tendermint/tmlibs/common" | |||||
) | |||||
var codecs = []ECC{ | |||||
NewIBMCRC16(), | |||||
NewSCSICRC16(), | |||||
NewCCITTCRC16(), | |||||
NewIEEECRC32(), | |||||
NewCastagnoliCRC32(), | |||||
NewKoopmanCRC32(), | |||||
NewISOCRC64(), | |||||
NewECMACRC64(), | |||||
} | |||||
// TestECCPasses makes sure that the AddECC/CheckECC methods are symetric | |||||
func TestECCPasses(t *testing.T) { | |||||
assert := asrt.New(t) | |||||
checks := append(codecs, NoECC{}) | |||||
for _, check := range checks { | |||||
for i := 0; i < 2000; i++ { | |||||
numBytes := cmn.RandInt()%60 + 1 | |||||
data := cmn.RandBytes(numBytes) | |||||
checked := check.AddECC(data) | |||||
res, err := check.CheckECC(checked) | |||||
if assert.Nil(err, "%#v: %+v", check, err) { | |||||
assert.Equal(data, res, "%v", check) | |||||
} | |||||
} | |||||
} | |||||
} | |||||
// TestECCFails makes sure random data will (usually) fail the checksum | |||||
func TestECCFails(t *testing.T) { | |||||
assert := asrt.New(t) | |||||
checks := codecs | |||||
attempts := 2000 | |||||
for _, check := range checks { | |||||
failed := 0 | |||||
for i := 0; i < attempts; i++ { | |||||
numBytes := cmn.RandInt()%60 + 1 | |||||
data := cmn.RandBytes(numBytes) | |||||
_, err := check.CheckECC(data) | |||||
if err != nil { | |||||
failed += 1 | |||||
} | |||||
} | |||||
// we allow up to 1 falsely accepted checksums, as there are random matches | |||||
assert.InDelta(attempts, failed, 1, "%v", check) | |||||
} | |||||
} |
@ -1,200 +0,0 @@ | |||||
package words | |||||
import ( | |||||
"math/big" | |||||
"strings" | |||||
"github.com/pkg/errors" | |||||
"github.com/tendermint/go-crypto/keys/words/wordlist" | |||||
) | |||||
const BankSize = 2048 | |||||
// TODO: add error-checking codecs for invalid phrases | |||||
type Codec interface { | |||||
BytesToWords([]byte) ([]string, error) | |||||
WordsToBytes([]string) ([]byte, error) | |||||
} | |||||
type WordCodec struct { | |||||
words []string | |||||
bytes map[string]int | |||||
check ECC | |||||
} | |||||
var _ Codec = &WordCodec{} | |||||
func NewCodec(words []string) (codec *WordCodec, err error) { | |||||
if len(words) != BankSize { | |||||
return codec, errors.Errorf("Bank must have %d words, found %d", BankSize, len(words)) | |||||
} | |||||
res := &WordCodec{ | |||||
words: words, | |||||
// TODO: configure this outside??? | |||||
check: NewIEEECRC32(), | |||||
// check: NewIBMCRC16(), | |||||
} | |||||
return res, nil | |||||
} | |||||
// LoadCodec loads a pre-compiled language file | |||||
func LoadCodec(bank string) (codec *WordCodec, err error) { | |||||
words, err := loadBank(bank) | |||||
if err != nil { | |||||
return codec, err | |||||
} | |||||
return NewCodec(words) | |||||
} | |||||
// MustLoadCodec panics if word bank is missing, only for tests | |||||
func MustLoadCodec(bank string) *WordCodec { | |||||
codec, err := LoadCodec(bank) | |||||
if err != nil { | |||||
panic(err) | |||||
} | |||||
return codec | |||||
} | |||||
// loadBank opens a wordlist file and returns all words inside | |||||
func loadBank(bank string) ([]string, error) { | |||||
filename := "keys/words/wordlist/" + bank + ".txt" | |||||
words, err := wordlist.Asset(filename) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
wordsAll := strings.Split(strings.TrimSpace(string(words)), "\n") | |||||
return wordsAll, nil | |||||
} | |||||
// // TODO: read from go-bind assets | |||||
// func getData(filename string) (string, error) { | |||||
// f, err := os.Open(filename) | |||||
// if err != nil { | |||||
// return "", errors.WithStack(err) | |||||
// } | |||||
// defer f.Close() | |||||
// data, err := ioutil.ReadAll(f) | |||||
// if err != nil { | |||||
// return "", errors.WithStack(err) | |||||
// } | |||||
// return string(data), nil | |||||
// } | |||||
// given this many bytes, we will produce this many words | |||||
func wordlenFromBytes(numBytes int) int { | |||||
// 2048 words per bank, which is 2^11. | |||||
// 8 bits per byte, and we add +10 so it rounds up | |||||
return (8*numBytes + 10) / 11 | |||||
} | |||||
// given this many words, we will produce this many bytes. | |||||
// sometimes there are two possibilities. | |||||
// if maybeShorter is true, then represents len OR len-1 bytes | |||||
func bytelenFromWords(numWords int) (length int, maybeShorter bool) { | |||||
// calculate the max number of complete bytes we could store in this word | |||||
length = 11 * numWords / 8 | |||||
// if one less byte would also generate this length, set maybeShorter | |||||
if wordlenFromBytes(length-1) == numWords { | |||||
maybeShorter = true | |||||
} | |||||
return | |||||
} | |||||
// TODO: add checksum | |||||
func (c *WordCodec) BytesToWords(raw []byte) (words []string, err error) { | |||||
// always add a checksum to the data | |||||
data := c.check.AddECC(raw) | |||||
numWords := wordlenFromBytes(len(data)) | |||||
n2048 := big.NewInt(2048) | |||||
nData := big.NewInt(0).SetBytes(data) | |||||
nRem := big.NewInt(0) | |||||
// Alternative, use condition "nData.BitLen() > 0" | |||||
// to allow for shorter words when data has leading 0's | |||||
for i := 0; i < numWords; i++ { | |||||
nData.DivMod(nData, n2048, nRem) | |||||
rem := nRem.Int64() | |||||
w := c.words[rem] | |||||
// double-check bank on generation... | |||||
_, err := c.GetIndex(w) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
words = append(words, w) | |||||
} | |||||
return words, nil | |||||
} | |||||
func (c *WordCodec) WordsToBytes(words []string) ([]byte, error) { | |||||
l := len(words) | |||||
if l == 0 { | |||||
return nil, errors.New("Didn't provide any words") | |||||
} | |||||
n2048 := big.NewInt(2048) | |||||
nData := big.NewInt(0) | |||||
// since we output words based on the remainder, the first word has the lowest | |||||
// value... we must load them in reverse order | |||||
for i := 1; i <= l; i++ { | |||||
rem, err := c.GetIndex(words[l-i]) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
nRem := big.NewInt(int64(rem)) | |||||
nData.Mul(nData, n2048) | |||||
nData.Add(nData, nRem) | |||||
} | |||||
// we copy into a slice of the expected size, so it is not shorter if there | |||||
// are lots of leading 0s | |||||
dataBytes := nData.Bytes() | |||||
// copy into the container we have with the expected size | |||||
outLen, flex := bytelenFromWords(len(words)) | |||||
toCheck := make([]byte, outLen) | |||||
if len(dataBytes) > outLen { | |||||
return nil, errors.New("Invalid data, could not have been generated by this codec") | |||||
} | |||||
copy(toCheck[outLen-len(dataBytes):], dataBytes) | |||||
// validate the checksum... | |||||
output, err := c.check.CheckECC(toCheck) | |||||
if flex && err != nil { | |||||
// if flex, try again one shorter.... | |||||
toCheck = toCheck[1:] | |||||
output, err = c.check.CheckECC(toCheck) | |||||
} | |||||
return output, err | |||||
} | |||||
// GetIndex finds the index of the words to create bytes | |||||
// Generates a map the first time it is loaded, to avoid needless | |||||
// computation when list is not used. | |||||
func (c *WordCodec) GetIndex(word string) (int, error) { | |||||
// generate the first time | |||||
if c.bytes == nil { | |||||
b := map[string]int{} | |||||
for i, w := range c.words { | |||||
if _, ok := b[w]; ok { | |||||
return -1, errors.Errorf("Duplicate word in list: %s", w) | |||||
} | |||||
b[w] = i | |||||
} | |||||
c.bytes = b | |||||
} | |||||
// get the index, or an error | |||||
rem, ok := c.bytes[word] | |||||
if !ok { | |||||
return -1, errors.Errorf("Unrecognized word: %s", word) | |||||
} | |||||
return rem, nil | |||||
} |
@ -1,180 +0,0 @@ | |||||
package words | |||||
import ( | |||||
"testing" | |||||
asrt "github.com/stretchr/testify/assert" | |||||
rqr "github.com/stretchr/testify/require" | |||||
cmn "github.com/tendermint/tmlibs/common" | |||||
) | |||||
func TestLengthCalc(t *testing.T) { | |||||
assert := asrt.New(t) | |||||
cases := []struct { | |||||
bytes, words int | |||||
flexible bool | |||||
}{ | |||||
{1, 1, false}, | |||||
{2, 2, false}, | |||||
// bytes pairs with same word count | |||||
{3, 3, true}, | |||||
{4, 3, true}, | |||||
{5, 4, false}, | |||||
// bytes pairs with same word count | |||||
{10, 8, true}, | |||||
{11, 8, true}, | |||||
{12, 9, false}, | |||||
{13, 10, false}, | |||||
{20, 15, false}, | |||||
// bytes pairs with same word count | |||||
{21, 16, true}, | |||||
{32, 24, true}, | |||||
} | |||||
for _, tc := range cases { | |||||
wl := wordlenFromBytes(tc.bytes) | |||||
assert.Equal(tc.words, wl, "%d", tc.bytes) | |||||
bl, flex := bytelenFromWords(tc.words) | |||||
assert.Equal(tc.flexible, flex, "%d", tc.words) | |||||
if !flex { | |||||
assert.Equal(tc.bytes, bl, "%d", tc.words) | |||||
} else { | |||||
// check if it is either tc.bytes or tc.bytes +1 | |||||
choices := []int{tc.bytes, tc.bytes + 1} | |||||
assert.Contains(choices, bl, "%d", tc.words) | |||||
} | |||||
} | |||||
} | |||||
func TestEncodeDecode(t *testing.T) { | |||||
assert, require := asrt.New(t), rqr.New(t) | |||||
codec, err := LoadCodec("english") | |||||
require.Nil(err, "%+v", err) | |||||
cases := [][]byte{ | |||||
{7, 8, 9}, // TODO: 3 words -> 3 or 4 bytes | |||||
{12, 54, 99, 11}, // TODO: 3 words -> 3 or 4 bytes | |||||
{0, 54, 99, 11}, // TODO: 3 words -> 3 or 4 bytes, detect leading 0 | |||||
{1, 2, 3, 4, 5}, // normal | |||||
{0, 0, 0, 0, 122, 23, 82, 195}, // leading 0s (8 chars, unclear) | |||||
{0, 0, 0, 0, 5, 22, 123, 55, 22}, // leading 0s (9 chars, clear) | |||||
{22, 44, 55, 1, 13, 0, 0, 0, 0}, // trailing 0s (9 chars, clear) | |||||
{0, 5, 253, 2, 0}, // leading and trailing zeros | |||||
{255, 196, 172, 234, 192, 255}, // big numbers | |||||
{255, 196, 172, 1, 234, 192, 255}, // big numbers, two length choices | |||||
// others? | |||||
} | |||||
for i, tc := range cases { | |||||
w, err := codec.BytesToWords(tc) | |||||
if assert.Nil(err, "%d: %v", i, err) { | |||||
b, err := codec.WordsToBytes(w) | |||||
if assert.Nil(err, "%d: %v", i, err) { | |||||
assert.Equal(len(tc), len(b)) | |||||
assert.Equal(tc, b) | |||||
} | |||||
} | |||||
} | |||||
} | |||||
func TestCheckInvalidLists(t *testing.T) { | |||||
assert := asrt.New(t) | |||||
trivial := []string{"abc", "def"} | |||||
short := make([]string, 1234) | |||||
long := make([]string, BankSize+1) | |||||
right := make([]string, BankSize) | |||||
dups := make([]string, BankSize) | |||||
for _, list := range [][]string{short, long, right, dups} { | |||||
for i := range list { | |||||
list[i] = cmn.RandStr(8) | |||||
} | |||||
} | |||||
// create one single duplicate | |||||
dups[192] = dups[782] | |||||
cases := []struct { | |||||
words []string | |||||
loadable bool | |||||
valid bool | |||||
}{ | |||||
{trivial, false, false}, | |||||
{short, false, false}, | |||||
{long, false, false}, | |||||
{dups, true, false}, // we only check dups on first use... | |||||
{right, true, true}, | |||||
} | |||||
for i, tc := range cases { | |||||
codec, err := NewCodec(tc.words) | |||||
if !tc.loadable { | |||||
assert.NotNil(err, "%d", i) | |||||
} else if assert.Nil(err, "%d: %+v", i, err) { | |||||
data := cmn.RandBytes(32) | |||||
w, err := codec.BytesToWords(data) | |||||
if tc.valid { | |||||
assert.Nil(err, "%d: %+v", i, err) | |||||
b, err1 := codec.WordsToBytes(w) | |||||
assert.Nil(err1, "%d: %+v", i, err1) | |||||
assert.Equal(data, b) | |||||
} else { | |||||
assert.NotNil(err, "%d", i) | |||||
} | |||||
} | |||||
} | |||||
} | |||||
func getRandWord(c *WordCodec) string { | |||||
idx := cmn.RandInt() % BankSize | |||||
return c.words[idx] | |||||
} | |||||
func getDiffWord(c *WordCodec, not string) string { | |||||
w := getRandWord(c) | |||||
if w == not { | |||||
w = getRandWord(c) | |||||
} | |||||
return w | |||||
} | |||||
func TestCheckTypoDetection(t *testing.T) { | |||||
assert, require := asrt.New(t), rqr.New(t) | |||||
banks := []string{"english", "spanish", "japanese", "chinese_simplified"} | |||||
for _, bank := range banks { | |||||
codec, err := LoadCodec(bank) | |||||
require.Nil(err, "%s: %+v", bank, err) | |||||
for i := 0; i < 1000; i++ { | |||||
numBytes := cmn.RandInt()%60 + 4 | |||||
data := cmn.RandBytes(numBytes) | |||||
words, err := codec.BytesToWords(data) | |||||
assert.Nil(err, "%s: %+v", bank, err) | |||||
good, err := codec.WordsToBytes(words) | |||||
assert.Nil(err, "%s: %+v", bank, err) | |||||
assert.Equal(data, good, bank) | |||||
// now try some tweaks... | |||||
cut := words[1:] | |||||
_, err = codec.WordsToBytes(cut) | |||||
assert.NotNil(err, "%s: %s", bank, words) | |||||
// swap a word within the bank, should fails | |||||
words[3] = getDiffWord(codec, words[3]) | |||||
_, err = codec.WordsToBytes(words) | |||||
assert.NotNil(err, "%s: %s", bank, words) | |||||
// put a random word here, must fail | |||||
words[3] = cmn.RandStr(10) | |||||
_, err = codec.WordsToBytes(words) | |||||
assert.NotNil(err, "%s: %s", bank, words) | |||||
} | |||||
} | |||||
} |
@ -1,68 +0,0 @@ | |||||
package words | |||||
import ( | |||||
"testing" | |||||
cmn "github.com/tendermint/tmlibs/common" | |||||
) | |||||
func warmupCodec(bank string) *WordCodec { | |||||
codec, err := LoadCodec(bank) | |||||
if err != nil { | |||||
panic(err) | |||||
} | |||||
_, err = codec.GetIndex(codec.words[123]) | |||||
if err != nil { | |||||
panic(err) | |||||
} | |||||
return codec | |||||
} | |||||
func BenchmarkCodec(b *testing.B) { | |||||
banks := []string{"english", "spanish", "japanese", "chinese_simplified"} | |||||
for _, bank := range banks { | |||||
b.Run(bank, func(sub *testing.B) { | |||||
codec := warmupCodec(bank) | |||||
sub.ResetTimer() | |||||
benchSuite(sub, codec) | |||||
}) | |||||
} | |||||
} | |||||
func benchSuite(b *testing.B, codec *WordCodec) { | |||||
b.Run("to_words", func(sub *testing.B) { | |||||
benchMakeWords(sub, codec) | |||||
}) | |||||
b.Run("to_bytes", func(sub *testing.B) { | |||||
benchParseWords(sub, codec) | |||||
}) | |||||
} | |||||
func benchMakeWords(b *testing.B, codec *WordCodec) { | |||||
numBytes := 32 | |||||
data := cmn.RandBytes(numBytes) | |||||
for i := 1; i <= b.N; i++ { | |||||
_, err := codec.BytesToWords(data) | |||||
if err != nil { | |||||
panic(err) | |||||
} | |||||
} | |||||
} | |||||
func benchParseWords(b *testing.B, codec *WordCodec) { | |||||
// generate a valid test string to parse | |||||
numBytes := 32 | |||||
data := cmn.RandBytes(numBytes) | |||||
words, err := codec.BytesToWords(data) | |||||
if err != nil { | |||||
panic(err) | |||||
} | |||||
for i := 1; i <= b.N; i++ { | |||||
_, err := codec.WordsToBytes(words) | |||||
if err != nil { | |||||
panic(err) | |||||
} | |||||
} | |||||
} |