From 9c02c8ce933cc67b3e7241f6d6d44c9bcfff3485 Mon Sep 17 00:00:00 2001 From: Alessio Treglia Date: Wed, 4 Apr 2018 23:25:14 +0100 Subject: [PATCH 01/22] Add import/export of public keys #79 --- keys/keybase.go | 47 +++++++++++++++++++++++++++++++++++ keys/keybase_test.go | 59 ++++++++++++++++++++++++++++++++++++++++++-- keys/mintkey.go | 26 +++++++++++++++---- keys/types.go | 2 ++ 4 files changed, 127 insertions(+), 7 deletions(-) diff --git a/keys/keybase.go b/keys/keybase.go index 88a12000c..5c7b64723 100644 --- a/keys/keybase.go +++ b/keys/keybase.go @@ -110,6 +110,10 @@ func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig crypto.Signat if err != nil { return } + if info.PrivKeyArmor == "" { + err = fmt.Errorf("private key not available") + return + } priv, err := unarmorDecryptPrivKey(info.PrivKeyArmor, passphrase) if err != nil { return @@ -127,6 +131,22 @@ func (kb dbKeybase) Export(name string) (armor string, err error) { return armorInfoBytes(bz), nil } +// ExportPubKey returns public keys in ASCII armored format. +func (kb dbKeybase) ExportPubKey(name string) (armor string, err error) { + bz := kb.db.Get(infoKey(name)) + if bz == nil { + return "", errors.New("No key to export with name " + name) + } + info, err := readInfo(bz) + if err != nil { + return + } + return armorPubKeyBytes(info.PubKey.Bytes()), nil +} + +// ExportPubKey imports ASCII-armored public keys. +// Store a new Info object holding a public key only, i.e. it will +// not be possible to sign with it as it lacks the secret key. func (kb dbKeybase) Import(name string, armor string) (err error) { bz := kb.db.Get(infoKey(name)) if len(bz) > 0 { @@ -140,6 +160,23 @@ func (kb dbKeybase) Import(name string, armor string) (err error) { return nil } +func (kb dbKeybase) ImportPubKey(name string, armor string) (err error) { + bz := kb.db.Get(infoKey(name)) + if len(bz) > 0 { + return errors.New("Cannot overwrite data for name " + name) + } + pubBytes, err := unarmorPubKeyBytes(armor) + if err != nil { + return + } + pubKey, err := crypto.PubKeyFromBytes(pubBytes) + if err != nil { + return + } + kb.writePubKey(pubKey, name) + return +} + // Delete removes key forever, but we must present the // proper passphrase before deleting it (for security). func (kb dbKeybase) Delete(name, passphrase string) error { @@ -174,6 +211,16 @@ func (kb dbKeybase) Update(name, oldpass, newpass string) error { kb.writeKey(key, name, newpass) return nil } + +func (kb dbKeybase) writePubKey(pub crypto.PubKey, name string) Info { + // make Info + info := newInfo(name, pub, "") + + // write them both + kb.db.SetSync(infoKey(name), info.bytes()) + return info +} + func (kb dbKeybase) writeKey(priv crypto.PrivKey, name, passphrase string) Info { // generate the encrypted privkey privArmor := encryptArmorPrivKey(priv, passphrase) diff --git a/keys/keybase_test.go b/keys/keybase_test.go index 84c81c81f..aaf3b92fb 100644 --- a/keys/keybase_test.go +++ b/keys/keybase_test.go @@ -91,8 +91,8 @@ func TestSignVerify(t *testing.T) { ) algo := keys.AlgoSecp256k1 - n1, n2 := "some dude", "a dudette" - p1, p2 := "1234", "foobar" + n1, n2, n3 := "some dude", "a dudette", "dude-ish" + p1, p2, p3 := "1234", "foobar", "foobar" // create two users and get their info i1, _, err := cstore.Create(n1, p1, algo) @@ -101,9 +101,18 @@ func TestSignVerify(t *testing.T) { i2, _, err := cstore.Create(n2, p2, algo) require.Nil(t, err) + // Import a public key + armor, err := cstore.ExportPubKey(n2) + require.Nil(t, err) + cstore.ImportPubKey(n3, armor) + i3, err := cstore.Get(n3) + require.Nil(t, err) + require.Equal(t, i3.PrivKeyArmor, "") + // let's try to sign some messages d1 := []byte("my first message") d2 := []byte("some other important info!") + d3 := []byte("feels like I forgot something...") // try signing both data with both keys... s11, pub1, err := cstore.Sign(n1, p1, d1) @@ -145,6 +154,10 @@ func TestSignVerify(t *testing.T) { valid := tc.key.VerifyBytes(tc.data, tc.sig) assert.Equal(t, tc.valid, valid, "%d", i) } + + // Now try to sign data with a secret-less key + _, _, err = cstore.Sign(n3, p3, d3) + assert.NotNil(t, err) } /* @@ -243,6 +256,48 @@ func TestExportImport(t *testing.T) { assert.Equal(t, john, john2) } +func TestExportImportPubKey(t *testing.T) { + // make the storage with reasonable defaults + db := dbm.NewMemDB() + cstore := keys.New( + db, + words.MustLoadCodec("english"), + ) + + // Create a private-public key pair and ensure consistency + info, _, err := cstore.Create("john", "passphrase", keys.AlgoEd25519) + assert.Nil(t, err) + assert.NotEqual(t, info.PrivKeyArmor, "") + assert.Equal(t, info.Name, "john") + addr := info.PubKey.Address() + john, err := cstore.Get("john") + assert.Nil(t, err) + assert.Equal(t, john.Name, "john") + assert.Equal(t, john.PubKey.Address(), addr) + + // Export the public key only + armor, err := cstore.ExportPubKey("john") + assert.Nil(t, err) + // Import it under a different name + err = cstore.ImportPubKey("john-pubkey-only", armor) + assert.Nil(t, err) + // Ensure consistency + john2, err := cstore.Get("john-pubkey-only") + assert.Nil(t, err) + assert.Equal(t, john2.PrivKeyArmor, "") + // Compare the public keys + assert.True(t, john.PubKey.Equals(john2.PubKey)) + // Ensure the original key hasn't changed + john, err = cstore.Get("john") + assert.Nil(t, err) + assert.Equal(t, john.PubKey.Address(), addr) + assert.Equal(t, john.Name, "john") + + // Ensure keys cannot be overwritten + err = cstore.ImportPubKey("john-pubkey-only", armor) + assert.NotNil(t, err) +} + // TestAdvancedKeyManagement verifies update, import, export functionality func TestAdvancedKeyManagement(t *testing.T) { diff --git a/keys/mintkey.go b/keys/mintkey.go index 73263b83a..264a9747d 100644 --- a/keys/mintkey.go +++ b/keys/mintkey.go @@ -13,24 +13,40 @@ import ( const ( blockTypePrivKey = "TENDERMINT PRIVATE KEY" blockTypeKeyInfo = "TENDERMINT KEY INFO" + blockTypePubKey = "TENDERMINT PUBLIC KEY" ) func armorInfoBytes(bz []byte) string { + return armorBytes(bz, blockTypeKeyInfo) +} + +func armorPubKeyBytes(bz []byte) string { + return armorBytes(bz, blockTypePubKey) +} + +func armorBytes(bz []byte, blockType string) string { header := map[string]string{ "type": "Info", "version": "0.0.0", } - armorStr := crypto.EncodeArmor(blockTypeKeyInfo, header, bz) - return armorStr + return crypto.EncodeArmor(blockType, header, bz) } func unarmorInfoBytes(armorStr string) (bz []byte, err error) { - blockType, header, bz, err := crypto.DecodeArmor(armorStr) + return unarmorBytes(armorStr, blockTypeKeyInfo) +} + +func unarmorPubKeyBytes(armorStr string) (bz []byte, err error) { + return unarmorBytes(armorStr, blockTypePubKey) +} + +func unarmorBytes(armorStr, blockType string) (bz []byte, err error) { + bType, header, bz, err := crypto.DecodeArmor(armorStr) if err != nil { return } - if blockType != blockTypeKeyInfo { - err = fmt.Errorf("Unrecognized armor type: %v", blockType) + if bType != blockType { + err = fmt.Errorf("Unrecognized armor type %q, expected: %q", bType, blockType) return } if header["version"] != "0.0.0" { diff --git a/keys/types.go b/keys/types.go index 40751ad77..e1df644ab 100644 --- a/keys/types.go +++ b/keys/types.go @@ -18,7 +18,9 @@ type Keybase interface { Delete(name, passphrase string) error Import(name string, armor string) (err error) + ImportPubKey(name string, armor string) (err error) Export(name string) (armor string, err error) + ExportPubKey(name string) (armor string, err error) } // Info is the public information about a key From 105847b7dd804b297a03959d356dba458f608932 Mon Sep 17 00:00:00 2001 From: Alessio Treglia Date: Thu, 5 Apr 2018 08:13:13 +0100 Subject: [PATCH 02/22] Fix comments --- keys/keybase.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/keys/keybase.go b/keys/keybase.go index 5c7b64723..876eef8f5 100644 --- a/keys/keybase.go +++ b/keys/keybase.go @@ -132,6 +132,8 @@ func (kb dbKeybase) Export(name string) (armor string, err error) { } // ExportPubKey returns public keys in ASCII armored format. +// Retrieve a Info object by its name and return the public key in +// a portable format. func (kb dbKeybase) ExportPubKey(name string) (armor string, err error) { bz := kb.db.Get(infoKey(name)) if bz == nil { @@ -144,9 +146,6 @@ func (kb dbKeybase) ExportPubKey(name string) (armor string, err error) { return armorPubKeyBytes(info.PubKey.Bytes()), nil } -// ExportPubKey imports ASCII-armored public keys. -// Store a new Info object holding a public key only, i.e. it will -// not be possible to sign with it as it lacks the secret key. func (kb dbKeybase) Import(name string, armor string) (err error) { bz := kb.db.Get(infoKey(name)) if len(bz) > 0 { @@ -160,6 +159,9 @@ func (kb dbKeybase) Import(name string, armor string) (err error) { return nil } +// ExportPubKey imports ASCII-armored public keys. +// Store a new Info object holding a public key only, i.e. it will +// not be possible to sign with it as it lacks the secret key. func (kb dbKeybase) ImportPubKey(name string, armor string) (err error) { bz := kb.db.Get(infoKey(name)) if len(bz) > 0 { From ad837a8183fcfe84248cc3a92157fb463b2d7b09 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 5 May 2018 19:17:21 -0400 Subject: [PATCH 03/22] fix ed25519 Generate --- priv_key.go | 7 ++++--- priv_key_test.go | 16 +++++++++++----- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/priv_key.go b/priv_key.go index 61d373f60..82e1cfced 100644 --- a/priv_key.go +++ b/priv_key.go @@ -83,9 +83,10 @@ func (privKey PrivKeyEd25519) Generate(index int) PrivKeyEd25519 { panic(err) } newBytes := Sha256(bz) - var newKey [64]byte - copy(newKey[:], newBytes) - return PrivKeyEd25519(newKey) + newKey := new([64]byte) + copy(newKey[:32], newBytes) + ed25519.MakePublicKey(newKey) + return PrivKeyEd25519(*newKey) } func GenPrivKeyEd25519() PrivKeyEd25519 { diff --git a/priv_key_test.go b/priv_key_test.go index 3fbfba34d..6abe36ac5 100644 --- a/priv_key_test.go +++ b/priv_key_test.go @@ -1,15 +1,21 @@ -package crypto - -/* +package crypto_test import ( - "fmt" "testing" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + crypto "github.com/tendermint/go-crypto" ) +func TestGeneratePrivKey(t *testing.T) { + testPriv := crypto.GenPrivKeyEd25519() + testGenerate := testPriv.Generate(1) + signBytes := []byte("something to sign") + assert.True(t, testGenerate.PubKey().VerifyBytes(signBytes, testGenerate.Sign(signBytes))) +} + +/* + type BadKey struct { PrivKeyEd25519 } From 94ce56d243e02cbfab1dfc1868a06a79cb07a0e4 Mon Sep 17 00:00:00 2001 From: Liamsi Date: Wed, 9 May 2018 11:48:46 +0100 Subject: [PATCH 04/22] Use constant-time comparator (sublte.ConstantTimeCompare) to compare signatures prevents potential signature forgery resolves #91 --- signature.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/signature.go b/signature.go index cfe927137..4f55420c5 100644 --- a/signature.go +++ b/signature.go @@ -1,9 +1,10 @@ package crypto import ( - "bytes" "fmt" + "crypto/subtle" + . "github.com/tendermint/tmlibs/common" ) @@ -41,7 +42,7 @@ func (sig SignatureEd25519) String() string { return fmt.Sprintf("/%X.../", Fing func (sig SignatureEd25519) Equals(other Signature) bool { if otherEd, ok := other.(SignatureEd25519); ok { - return bytes.Equal(sig[:], otherEd[:]) + return subtle.ConstantTimeCompare(sig[:], otherEd[:]) == 1 } else { return false } @@ -74,7 +75,7 @@ func (sig SignatureSecp256k1) String() string { return fmt.Sprintf("/%X.../", Fi func (sig SignatureSecp256k1) Equals(other Signature) bool { if otherSecp, ok := other.(SignatureSecp256k1); ok { - return bytes.Equal(sig[:], otherSecp[:]) + return subtle.ConstantTimeCompare(sig[:], otherSecp[:]) == 1 } else { return false } From 3477dd7a9042e04517d79d0eb153f8fe715c3786 Mon Sep 17 00:00:00 2001 From: Liamsi Date: Wed, 9 May 2018 14:30:17 +0100 Subject: [PATCH 05/22] safer PRNG seeding: hash concatenation of fresh seedBytes with current seedBytes --- random.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/random.go b/random.go index 46754219d..66da035a9 100644 --- a/random.go +++ b/random.go @@ -4,6 +4,7 @@ import ( "crypto/aes" "crypto/cipher" crand "crypto/rand" + "crypto/sha256" "encoding/hex" "io" "sync" @@ -72,8 +73,12 @@ type randInfo struct { func (ri *randInfo) MixEntropy(seedBytes []byte) { ri.mtx.Lock() defer ri.mtx.Unlock() - // Make new ri.seedBytes - hashBytes := Sha256(seedBytes) + // Make new ri.seedBytes using passed seedBytes and current ri.seedBytes: + // ri.seedBytes = sha256( seedBytes || ri.seedBytes ) + h := sha256.New() + h.Write(seedBytes) + h.Write(ri.seedBytes[:]) + hashBytes := h.Sum(nil) hashBytes32 := [32]byte{} copy(hashBytes32[:], hashBytes) ri.seedBytes = xorBytes32(ri.seedBytes, hashBytes32) From 35cf21c6ebdad9632414d3f244824302b2717e56 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Wed, 9 May 2018 13:48:21 -0700 Subject: [PATCH 06/22] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 785e887b8..4a524c014 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,10 @@ go-crypto is the cryptographic package adapted for Tendermint's uses ## Binary encoding +For Binary encoding, please refer to the [Tendermint encoding spec](https://github.com/tendermint/tendermint/blob/develop/docs/specification/new-spec/encoding.md). + +## JSON Encoding + go-crypto `.Bytes()` uses Amino:binary encoding, but Amino:JSON is also supported. ```go From 1c8dffaa282ba9ebfe597e38f9849bc70f40efe5 Mon Sep 17 00:00:00 2001 From: Zaki Manian Date: Sun, 13 May 2018 11:06:20 -0400 Subject: [PATCH 07/22] Initial implementation of xchacha20poly1035 aead --- xchacha20poly1035/xchachapoly.go | 237 +++++++++++++++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 xchacha20poly1035/xchachapoly.go diff --git a/xchacha20poly1035/xchachapoly.go b/xchacha20poly1035/xchachapoly.go new file mode 100644 index 000000000..b9d823414 --- /dev/null +++ b/xchacha20poly1035/xchachapoly.go @@ -0,0 +1,237 @@ +package xchacha20poly1305 + +import ( + "crypto/cipher" + "encoding/binary" + "errors" + + "golang.org/x/crypto/chacha20poly1305" +) + +var sigma = [4]uint32{0x61707865, 0x3320646e, 0x79622d32, 0x6b206574} + +type xchacha20poly1305 struct { + key [KeySize]byte +} + +const ( + // KeySize is the size of the key used by this AEAD, in bytes. + KeySize = 32 + // NonceSize is the size of the nonce used with this AEAD, in bytes. + NonceSize = 24 +) + +//New xChaChapoly1305 AEAD with 24 byte nonces +func New(key []byte) (cipher.AEAD, error) { + if len(key) != KeySize { + return nil, errors.New("chacha20poly1305: bad key length") + } + ret := new(xchacha20poly1305) + copy(ret.key[:], key) + return ret, nil + +} +func (c *xchacha20poly1305) NonceSize() int { + return NonceSize +} + +func (c *xchacha20poly1305) Overhead() int { + return 16 +} + +func (c *xchacha20poly1305) Seal(dst, nonce, plaintext, additionalData []byte) []byte { + if len(nonce) != NonceSize { + panic("chacha20poly1305: bad nonce length passed to Seal") + } + + if uint64(len(plaintext)) > (1<<38)-64 { + panic("chacha20poly1305: plaintext too large") + } + + var subKey [KeySize]byte + var hNonce [16]byte + var subNonce [chacha20poly1305.NonceSize]byte + copy(hNonce[:], nonce[:16]) + + HChaCha20(&subKey, &hNonce, &c.key) + + chacha20poly1305, _ := chacha20poly1305.New(subKey[:]) + + copy(subNonce[4:], nonce[16:]) + + return chacha20poly1305.Seal(dst, subNonce[:], plaintext, additionalData) +} + +func (c *xchacha20poly1305) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) { + if len(nonce) != NonceSize { + panic("chacha20poly1305: bad nonce length passed to Open") + } + if uint64(len(ciphertext)) > (1<<38)-48 { + panic("chacha20poly1305: ciphertext too large") + } + var subKey [KeySize]byte + var hNonce [16]byte + var subNonce [chacha20poly1305.NonceSize]byte + copy(hNonce[:], nonce[:16]) + + HChaCha20(&subKey, &hNonce, &c.key) + + chacha20poly1305, _ := chacha20poly1305.New(subKey[:]) + + copy(subNonce[4:], nonce[16:]) + + return chacha20poly1305.Open(dst, subNonce[:], ciphertext, additionalData) +} + +// The MIT License (MIT) + +// Copyright (c) 2016 Andreas Auernhammer + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// HChaCha20 generates 32 pseudo-random bytes from a 128 bit nonce and a 256 bit secret key. +// It can be used as a key-derivation-function (KDF). +func HChaCha20(out *[32]byte, nonce *[16]byte, key *[32]byte) { hChaCha20Generic(out, nonce, key) } + +func hChaCha20Generic(out *[32]byte, nonce *[16]byte, key *[32]byte) { + v00 := sigma[0] + v01 := sigma[1] + v02 := sigma[2] + v03 := sigma[3] + v04 := binary.LittleEndian.Uint32(key[0:]) + v05 := binary.LittleEndian.Uint32(key[4:]) + v06 := binary.LittleEndian.Uint32(key[8:]) + v07 := binary.LittleEndian.Uint32(key[12:]) + v08 := binary.LittleEndian.Uint32(key[16:]) + v09 := binary.LittleEndian.Uint32(key[20:]) + v10 := binary.LittleEndian.Uint32(key[24:]) + v11 := binary.LittleEndian.Uint32(key[28:]) + v12 := binary.LittleEndian.Uint32(nonce[0:]) + v13 := binary.LittleEndian.Uint32(nonce[4:]) + v14 := binary.LittleEndian.Uint32(nonce[8:]) + v15 := binary.LittleEndian.Uint32(nonce[12:]) + + for i := 0; i < 20; i += 2 { + v00 += v04 + v12 ^= v00 + v12 = (v12 << 16) | (v12 >> 16) + v08 += v12 + v04 ^= v08 + v04 = (v04 << 12) | (v04 >> 20) + v00 += v04 + v12 ^= v00 + v12 = (v12 << 8) | (v12 >> 24) + v08 += v12 + v04 ^= v08 + v04 = (v04 << 7) | (v04 >> 25) + v01 += v05 + v13 ^= v01 + v13 = (v13 << 16) | (v13 >> 16) + v09 += v13 + v05 ^= v09 + v05 = (v05 << 12) | (v05 >> 20) + v01 += v05 + v13 ^= v01 + v13 = (v13 << 8) | (v13 >> 24) + v09 += v13 + v05 ^= v09 + v05 = (v05 << 7) | (v05 >> 25) + v02 += v06 + v14 ^= v02 + v14 = (v14 << 16) | (v14 >> 16) + v10 += v14 + v06 ^= v10 + v06 = (v06 << 12) | (v06 >> 20) + v02 += v06 + v14 ^= v02 + v14 = (v14 << 8) | (v14 >> 24) + v10 += v14 + v06 ^= v10 + v06 = (v06 << 7) | (v06 >> 25) + v03 += v07 + v15 ^= v03 + v15 = (v15 << 16) | (v15 >> 16) + v11 += v15 + v07 ^= v11 + v07 = (v07 << 12) | (v07 >> 20) + v03 += v07 + v15 ^= v03 + v15 = (v15 << 8) | (v15 >> 24) + v11 += v15 + v07 ^= v11 + v07 = (v07 << 7) | (v07 >> 25) + v00 += v05 + v15 ^= v00 + v15 = (v15 << 16) | (v15 >> 16) + v10 += v15 + v05 ^= v10 + v05 = (v05 << 12) | (v05 >> 20) + v00 += v05 + v15 ^= v00 + v15 = (v15 << 8) | (v15 >> 24) + v10 += v15 + v05 ^= v10 + v05 = (v05 << 7) | (v05 >> 25) + v01 += v06 + v12 ^= v01 + v12 = (v12 << 16) | (v12 >> 16) + v11 += v12 + v06 ^= v11 + v06 = (v06 << 12) | (v06 >> 20) + v01 += v06 + v12 ^= v01 + v12 = (v12 << 8) | (v12 >> 24) + v11 += v12 + v06 ^= v11 + v06 = (v06 << 7) | (v06 >> 25) + v02 += v07 + v13 ^= v02 + v13 = (v13 << 16) | (v13 >> 16) + v08 += v13 + v07 ^= v08 + v07 = (v07 << 12) | (v07 >> 20) + v02 += v07 + v13 ^= v02 + v13 = (v13 << 8) | (v13 >> 24) + v08 += v13 + v07 ^= v08 + v07 = (v07 << 7) | (v07 >> 25) + v03 += v04 + v14 ^= v03 + v14 = (v14 << 16) | (v14 >> 16) + v09 += v14 + v04 ^= v09 + v04 = (v04 << 12) | (v04 >> 20) + v03 += v04 + v14 ^= v03 + v14 = (v14 << 8) | (v14 >> 24) + v09 += v14 + v04 ^= v09 + v04 = (v04 << 7) | (v04 >> 25) + } + + binary.LittleEndian.PutUint32(out[0:], v00) + binary.LittleEndian.PutUint32(out[4:], v01) + binary.LittleEndian.PutUint32(out[8:], v02) + binary.LittleEndian.PutUint32(out[12:], v03) + binary.LittleEndian.PutUint32(out[16:], v12) + binary.LittleEndian.PutUint32(out[20:], v13) + binary.LittleEndian.PutUint32(out[24:], v14) + binary.LittleEndian.PutUint32(out[28:], v15) +} From aa2b6b546f85a625fd06f511421d36b2f579fa3e Mon Sep 17 00:00:00 2001 From: Liamsi Date: Tue, 15 May 2018 11:39:48 +0100 Subject: [PATCH 08/22] Remove outdated non-building code in _nano --- _nano/keys.go | 294 --------------------------------------------- _nano/keys_test.go | 142 ---------------------- _nano/sign.go | 63 ---------- _nano/sign_test.go | 160 ------------------------ 4 files changed, 659 deletions(-) delete mode 100644 _nano/keys.go delete mode 100644 _nano/keys_test.go delete mode 100644 _nano/sign.go delete mode 100644 _nano/sign_test.go diff --git a/_nano/keys.go b/_nano/keys.go deleted file mode 100644 index 8cf1c3721..000000000 --- a/_nano/keys.go +++ /dev/null @@ -1,294 +0,0 @@ -package nano - -import ( - "bytes" - "encoding/hex" - - "github.com/pkg/errors" - - ledger "github.com/ethanfrey/ledger" - - crypto "github.com/tendermint/go-crypto" - amino "github.com/tendermint/go-amino" -) - -//nolint -const ( - NameLedgerEd25519 = "ledger-ed25519" - TypeLedgerEd25519 = 0x10 - - // Timeout is the number of seconds to wait for a response from the ledger - // if eg. waiting for user confirmation on button push - Timeout = 20 -) - -var device *ledger.Ledger - -// getLedger gets a copy of the device, and caches it -func getLedger() (*ledger.Ledger, error) { - var err error - if device == nil { - device, err = ledger.FindLedger() - } - return device, err -} - -func signLedger(device *ledger.Ledger, msg []byte) (pub crypto.PubKey, sig crypto.Signature, err error) { - var resp []byte - - packets := generateSignRequests(msg) - for _, pack := range packets { - resp, err = device.Exchange(pack, Timeout) - if err != nil { - return pub, sig, err - } - } - - // the last call is the result we want and needs to be parsed - key, bsig, err := parseDigest(resp) - if err != nil { - return pub, sig, err - } - - var b [32]byte - copy(b[:], key) - return PubKeyLedgerEd25519FromBytes(b), crypto.SignatureEd25519FromBytes(bsig), nil -} - -// PrivKeyLedgerEd25519 implements PrivKey, calling the ledger nano -// we cache the PubKey from the first call to use it later -type PrivKeyLedgerEd25519 struct { - // PubKey should be private, but we want to encode it via go-amino - // so we can view the address later, even without having the ledger - // attached - CachedPubKey crypto.PubKey -} - -// NewPrivKeyLedgerEd25519 will generate a new key and store the -// public key for later use. -func NewPrivKeyLedgerEd25519() (crypto.PrivKey, error) { - var pk PrivKeyLedgerEd25519 - // getPubKey will cache the pubkey for later use, - // this allows us to return an error early if the ledger - // is not plugged in - _, err := pk.getPubKey() - return pk.Wrap(), err -} - -// ValidateKey allows us to verify the sanity of a key -// after loading it from disk -func (pk *PrivKeyLedgerEd25519) ValidateKey() error { - // getPubKey will return an error if the ledger is not - // properly set up... - pub, err := pk.forceGetPubKey() - if err != nil { - return err - } - // verify this matches cached address - if !pub.Equals(pk.CachedPubKey) { - return errors.New("ledger doesn't match cached key") - } - return nil -} - -// AssertIsPrivKeyInner fulfils PrivKey Interface -func (pk *PrivKeyLedgerEd25519) AssertIsPrivKeyInner() {} - -// Bytes fulfils PrivKey Interface - but it stores the cached pubkey so we can verify -// the same key when we reconnect to a ledger -func (pk *PrivKeyLedgerEd25519) Bytes() []byte { - return amino.BinaryBytes(pk.Wrap()) -} - -// Sign calls the ledger and stores the PubKey for future use -// -// XXX/TODO: panics if there is an error communicating with the ledger. -// -// Communication is checked on NewPrivKeyLedger and PrivKeyFromBytes, -// returning an error, so this should only trigger if the privkey is held -// in memory for a while before use. -func (pk *PrivKeyLedgerEd25519) Sign(msg []byte) crypto.Signature { - // oh, I wish there was better error handling - dev, err := getLedger() - if err != nil { - panic(err) - } - - pub, sig, err := signLedger(dev, msg) - if err != nil { - panic(err) - } - - // if we have no pubkey yet, store it for future queries - if pk.CachedPubKey.Empty() { - pk.CachedPubKey = pub - } else if !pk.CachedPubKey.Equals(pub) { - panic("signed with a different key than stored") - } - return sig -} - -// PubKey returns the stored PubKey -// TODO: query the ledger if not there, once it is not volatile -func (pk *PrivKeyLedgerEd25519) PubKey() crypto.PubKey { - key, err := pk.getPubKey() - if err != nil { - panic(err) - } - return key -} - -// getPubKey reads the pubkey from cache or from the ledger itself -// since this involves IO, it may return an error, which is not exposed -// in the PubKey interface, so this function allows better error handling -func (pk *PrivKeyLedgerEd25519) getPubKey() (key crypto.PubKey, err error) { - // if we have no pubkey, set it - if pk.CachedPubKey.Empty() { - pk.CachedPubKey, err = pk.forceGetPubKey() - } - return pk.CachedPubKey, err -} - -// forceGetPubKey is like getPubKey but ignores any cached key -// and ensures we get it from the ledger itself. -func (pk *PrivKeyLedgerEd25519) forceGetPubKey() (key crypto.PubKey, err error) { - dev, err := getLedger() - if err != nil { - return key, errors.New("Can't connect to ledger device") - } - key, _, err = signLedger(dev, []byte{0}) - if err != nil { - return key, errors.New("Please open cosmos app on the ledger") - } - return key, err -} - -// Equals fulfils PrivKey Interface - makes sure both keys refer to the -// same -func (pk *PrivKeyLedgerEd25519) Equals(other crypto.PrivKey) bool { - if ledger, ok := other.Unwrap().(*PrivKeyLedgerEd25519); ok { - return pk.CachedPubKey.Equals(ledger.CachedPubKey) - } - return false -} - -// MockPrivKeyLedgerEd25519 behaves as the ledger, but stores a pre-packaged call-response -// for use in test cases -type MockPrivKeyLedgerEd25519 struct { - Msg []byte - Pub [KeyLength]byte - Sig [SigLength]byte -} - -// NewMockKey returns -func NewMockKey(msg, pubkey, sig string) (pk MockPrivKeyLedgerEd25519) { - var err error - pk.Msg, err = hex.DecodeString(msg) - if err != nil { - panic(err) - } - - bpk, err := hex.DecodeString(pubkey) - if err != nil { - panic(err) - } - bsig, err := hex.DecodeString(sig) - if err != nil { - panic(err) - } - - copy(pk.Pub[:], bpk) - copy(pk.Sig[:], bsig) - return pk -} - -var _ crypto.PrivKeyInner = MockPrivKeyLedgerEd25519{} - -// AssertIsPrivKeyInner fulfils PrivKey Interface -func (pk MockPrivKeyLedgerEd25519) AssertIsPrivKeyInner() {} - -// Bytes fulfils PrivKey Interface - not supported -func (pk MockPrivKeyLedgerEd25519) Bytes() []byte { - return nil -} - -// Sign returns a real SignatureLedger, if the msg matches what we expect -func (pk MockPrivKeyLedgerEd25519) Sign(msg []byte) crypto.Signature { - if !bytes.Equal(pk.Msg, msg) { - panic("Mock key is for different msg") - } - return crypto.SignatureEd25519(pk.Sig).Wrap() -} - -// PubKey returns a real PubKeyLedgerEd25519, that will verify this signature -func (pk MockPrivKeyLedgerEd25519) PubKey() crypto.PubKey { - return PubKeyLedgerEd25519FromBytes(pk.Pub) -} - -// Equals compares that two Mocks have the same data -func (pk MockPrivKeyLedgerEd25519) Equals(other crypto.PrivKey) bool { - if mock, ok := other.Unwrap().(MockPrivKeyLedgerEd25519); ok { - return bytes.Equal(mock.Pub[:], pk.Pub[:]) && - bytes.Equal(mock.Sig[:], pk.Sig[:]) && - bytes.Equal(mock.Msg, pk.Msg) - } - return false -} - -//////////////////////////////////////////// -// pubkey - -// PubKeyLedgerEd25519 works like a normal Ed25519 except a hash before the verify bytes -type PubKeyLedgerEd25519 struct { - crypto.PubKeyEd25519 -} - -// PubKeyLedgerEd25519FromBytes creates a PubKey from the raw bytes -func PubKeyLedgerEd25519FromBytes(key [32]byte) crypto.PubKey { - return PubKeyLedgerEd25519{crypto.PubKeyEd25519(key)}.Wrap() -} - -// Bytes fulfils pk Interface - no data, just type info -func (pk PubKeyLedgerEd25519) Bytes() []byte { - return amino.BinaryBytes(pk.Wrap()) -} - -// VerifyBytes uses the normal Ed25519 algorithm but a sha512 hash beforehand -func (pk PubKeyLedgerEd25519) VerifyBytes(msg []byte, sig crypto.Signature) bool { - hmsg := hashMsg(msg) - return pk.PubKeyEd25519.VerifyBytes(hmsg, sig) -} - -// Equals implements PubKey interface -func (pk PubKeyLedgerEd25519) Equals(other crypto.PubKey) bool { - if ledger, ok := other.Unwrap().(PubKeyLedgerEd25519); ok { - return pk.PubKeyEd25519.Equals(ledger.PubKeyEd25519.Wrap()) - } - return false -} - -/*** registration with go-data ***/ - -func init() { - crypto.PrivKeyMapper. - RegisterImplementation(&PrivKeyLedgerEd25519{}, NameLedgerEd25519, TypeLedgerEd25519). - RegisterImplementation(MockPrivKeyLedgerEd25519{}, "mock-ledger", 0x11) - - crypto.PubKeyMapper. - RegisterImplementation(PubKeyLedgerEd25519{}, NameLedgerEd25519, TypeLedgerEd25519) -} - -// Wrap fulfils interface for PrivKey struct -func (pk *PrivKeyLedgerEd25519) Wrap() crypto.PrivKey { - return crypto.PrivKey{PrivKeyInner: pk} -} - -// Wrap fulfils interface for PrivKey struct -func (pk MockPrivKeyLedgerEd25519) Wrap() crypto.PrivKey { - return crypto.PrivKey{PrivKeyInner: pk} -} - -// Wrap fulfils interface for PubKey struct -func (pk PubKeyLedgerEd25519) Wrap() crypto.PubKey { - return crypto.PubKey{PubKeyInner: pk} -} diff --git a/_nano/keys_test.go b/_nano/keys_test.go deleted file mode 100644 index fda096e29..000000000 --- a/_nano/keys_test.go +++ /dev/null @@ -1,142 +0,0 @@ -package nano - -import ( - "encoding/hex" - "os" - "testing" - - asrt "github.com/stretchr/testify/assert" - rqr "github.com/stretchr/testify/require" - - crypto "github.com/tendermint/go-crypto" -) - -func TestLedgerKeys(t *testing.T) { - assert, require := asrt.New(t), rqr.New(t) - - cases := []struct { - msg, pubkey, sig string - valid bool - }{ - 0: { - msg: "F00D", - pubkey: "8E8754F012C2FDB492183D41437FD837CB81D8BBE731924E2E0DAF43FD3F2C93", - sig: "787DC03E9E4EE05983E30BAE0DEFB8DB0671DBC2F5874AC93F8D8CA4018F7A42D6F9A9BCEADB422AC8E27CEE9CA205A0B88D22CD686F0A43EB806E8190A3C400", - valid: true, - }, - 1: { - msg: "DEADBEEF", - pubkey: "0C45ADC887A5463F668533443C829ED13EA8E2E890C778957DC28DB9D2AD5A6C", - sig: "00ED74EED8FDAC7988A14BF6BC222120CBAC249D569AF4C2ADABFC86B792F97DF73C4919BE4B6B0ACB53547273BF29FBF0A9E0992FFAB6CB6C9B09311FC86A00", - valid: true, - }, - 2: { - msg: "1234567890AA", - pubkey: "598FC1F0C76363D14D7480736DEEF390D85863360F075792A6975EFA149FD7EA", - sig: "59AAB7D7BDC4F936B6415DE672A8B77FA6B8B3451CD95B3A631F31F9A05DAEEE5E7E4F89B64DDEBB5F63DC042CA13B8FCB8185F82AD7FD5636FFDA6B0DC9570B", - valid: true, - }, - 3: { - msg: "1234432112344321", - pubkey: "359E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120", - sig: "616B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208", - valid: true, - }, - 4: { - msg: "12344321123443", - pubkey: "359E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120", - sig: "616B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208", - valid: false, - }, - 5: { - msg: "1234432112344321", - pubkey: "459E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120", - sig: "616B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208", - valid: false, - }, - 6: { - msg: "1234432112344321", - pubkey: "359E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120", - sig: "716B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208", - valid: false, - }, - } - - for i, tc := range cases { - bmsg, err := hex.DecodeString(tc.msg) - require.NoError(err, "%d", i) - - priv := NewMockKey(tc.msg, tc.pubkey, tc.sig) - pub := priv.PubKey() - sig := priv.Sign(bmsg) - - valid := pub.VerifyBytes(bmsg, sig) - assert.Equal(tc.valid, valid, "%d", i) - } -} - -func TestRealLedger(t *testing.T) { - assert, require := asrt.New(t), rqr.New(t) - - if os.Getenv("WITH_LEDGER") == "" { - t.Skip("Set WITH_LEDGER to run code on real ledger") - } - msg := []byte("kuhehfeohg") - - priv, err := NewPrivKeyLedgerEd25519() - require.Nil(err, "%+v", err) - pub := priv.PubKey() - sig := priv.Sign(msg) - - valid := pub.VerifyBytes(msg, sig) - assert.True(valid) - - // now, let's serialize the key and make sure it still works - bs := priv.Bytes() - priv2, err := crypto.PrivKeyFromBytes(bs) - require.Nil(err, "%+v", err) - - // make sure we get the same pubkey when we load from disk - pub2 := priv2.PubKey() - require.Equal(pub, pub2) - - // signing with the loaded key should match the original pubkey - sig = priv2.Sign(msg) - valid = pub.VerifyBytes(msg, sig) - assert.True(valid) - - // make sure pubkeys serialize properly as well - bs = pub.Bytes() - bpub, err := crypto.PubKeyFromBytes(bs) - require.NoError(err) - assert.Equal(pub, bpub) -} - -// TestRealLedgerErrorHandling calls. These tests assume -// the ledger is not plugged in.... -func TestRealLedgerErrorHandling(t *testing.T) { - require := rqr.New(t) - - if os.Getenv("WITH_LEDGER") != "" { - t.Skip("Skipping on WITH_LEDGER as it tests unplugged cases") - } - - // first, try to generate a key, must return an error - // (no panic) - _, err := NewPrivKeyLedgerEd25519() - require.Error(err) - - led := PrivKeyLedgerEd25519{} // empty - // or with some pub key - ed := crypto.GenPrivKeyEd25519() - led2 := PrivKeyLedgerEd25519{CachedPubKey: ed.PubKey()} - - // loading these should return errors - bs := led.Bytes() - _, err = crypto.PrivKeyFromBytes(bs) - require.Error(err) - - bs = led2.Bytes() - _, err = crypto.PrivKeyFromBytes(bs) - require.Error(err) -} diff --git a/_nano/sign.go b/_nano/sign.go deleted file mode 100644 index c40801583..000000000 --- a/_nano/sign.go +++ /dev/null @@ -1,63 +0,0 @@ -package nano - -import ( - "bytes" - "crypto/sha512" - - "github.com/pkg/errors" -) - -const ( - App = 0x80 - Init = 0x00 - Update = 0x01 - Digest = 0x02 - MaxChunk = 253 - KeyLength = 32 - SigLength = 64 -) - -var separator = []byte{0, 0xCA, 0xFE, 0} - -func generateSignRequests(payload []byte) [][]byte { - // nice one-shot - digest := []byte{App, Digest} - if len(payload) < MaxChunk { - return [][]byte{append(digest, payload...)} - } - - // large payload is multi-chunk - result := [][]byte{{App, Init}} - update := []byte{App, Update} - for len(payload) > MaxChunk { - msg := append(update, payload[:MaxChunk]...) - payload = payload[MaxChunk:] - result = append(result, msg) - } - result = append(result, append(update, payload...)) - result = append(result, digest) - return result -} - -func parseDigest(resp []byte) (key, sig []byte, err error) { - if resp[0] != App || resp[1] != Digest { - return nil, nil, errors.New("Invalid header") - } - resp = resp[2:] - if len(resp) != KeyLength+SigLength+len(separator) { - return nil, nil, errors.Errorf("Incorrect length: %d", len(resp)) - } - - key, resp = resp[:KeyLength], resp[KeyLength:] - if !bytes.Equal(separator, resp[:len(separator)]) { - return nil, nil, errors.New("Cannot find 0xCAFE") - } - - sig = resp[len(separator):] - return key, sig, nil -} - -func hashMsg(data []byte) []byte { - res := sha512.Sum512(data) - return res[:] -} diff --git a/_nano/sign_test.go b/_nano/sign_test.go deleted file mode 100644 index 18e4e0d0b..000000000 --- a/_nano/sign_test.go +++ /dev/null @@ -1,160 +0,0 @@ -package nano - -import ( - "encoding/hex" - "testing" - - "github.com/pkg/errors" - asrt "github.com/stretchr/testify/assert" - rqr "github.com/stretchr/testify/require" - - crypto "github.com/tendermint/go-crypto" -) - -func parseEdKey(data []byte) (key crypto.PubKey, err error) { - ed := crypto.PubKeyEd25519{} - if len(data) < len(ed) { - return key, errors.Errorf("Key length too short: %d", len(data)) - } - copy(ed[:], data) - return ed.Wrap(), nil -} - -func parseSig(data []byte) (key crypto.Signature, err error) { - ed := crypto.SignatureEd25519{} - if len(data) < len(ed) { - return key, errors.Errorf("Sig length too short: %d", len(data)) - } - copy(ed[:], data) - return ed.Wrap(), nil -} - -func TestParseDigest(t *testing.T) { - assert, require := asrt.New(t), rqr.New(t) - - cases := []struct { - output string - key string - sig string - valid bool - }{ - { - output: "80028E8754F012C2FDB492183D41437FD837CB81D8BBE731924E2E0DAF43FD3F2C9300CAFE00787DC03E9E4EE05983E30BAE0DEFB8DB0671DBC2F5874AC93F8D8CA4018F7A42D6F9A9BCEADB422AC8E27CEE9CA205A0B88D22CD686F0A43EB806E8190A3C400", - key: "8E8754F012C2FDB492183D41437FD837CB81D8BBE731924E2E0DAF43FD3F2C93", - sig: "787DC03E9E4EE05983E30BAE0DEFB8DB0671DBC2F5874AC93F8D8CA4018F7A42D6F9A9BCEADB422AC8E27CEE9CA205A0B88D22CD686F0A43EB806E8190A3C400", - valid: true, - }, - { - output: "800235467890876543525437890796574535467890", - key: "", - sig: "", - valid: false, - }, - } - - for i, tc := range cases { - msg, err := hex.DecodeString(tc.output) - require.Nil(err, "%d: %+v", i, err) - - lKey, lSig, err := parseDigest(msg) - if !tc.valid { - assert.NotNil(err, "%d", i) - } else if assert.Nil(err, "%d: %+v", i, err) { - key, err := hex.DecodeString(tc.key) - require.Nil(err, "%d: %+v", i, err) - sig, err := hex.DecodeString(tc.sig) - require.Nil(err, "%d: %+v", i, err) - - assert.Equal(key, lKey, "%d", i) - assert.Equal(sig, lSig, "%d", i) - } - } -} - -type cryptoCase struct { - msg string - key string - sig string - valid bool -} - -func toBytes(c cryptoCase) (msg, key, sig []byte, err error) { - msg, err = hex.DecodeString(c.msg) - if err != nil { - return - } - key, err = hex.DecodeString(c.key) - if err != nil { - return - } - sig, err = hex.DecodeString(c.sig) - return -} - -func TestCryptoConvert(t *testing.T) { - assert, require := asrt.New(t), rqr.New(t) - - cases := []cryptoCase{ - 0: { - msg: "F00D", - key: "8E8754F012C2FDB492183D41437FD837CB81D8BBE731924E2E0DAF43FD3F2C93", - sig: "787DC03E9E4EE05983E30BAE0DEFB8DB0671DBC2F5874AC93F8D8CA4018F7A42D6F9A9BCEADB422AC8E27CEE9CA205A0B88D22CD686F0A43EB806E8190A3C400", - valid: true, - }, - 1: { - msg: "DEADBEEF", - key: "0C45ADC887A5463F668533443C829ED13EA8E2E890C778957DC28DB9D2AD5A6C", - sig: "00ED74EED8FDAC7988A14BF6BC222120CBAC249D569AF4C2ADABFC86B792F97DF73C4919BE4B6B0ACB53547273BF29FBF0A9E0992FFAB6CB6C9B09311FC86A00", - valid: true, - }, - 2: { - msg: "1234567890AA", - key: "598FC1F0C76363D14D7480736DEEF390D85863360F075792A6975EFA149FD7EA", - sig: "59AAB7D7BDC4F936B6415DE672A8B77FA6B8B3451CD95B3A631F31F9A05DAEEE5E7E4F89B64DDEBB5F63DC042CA13B8FCB8185F82AD7FD5636FFDA6B0DC9570B", - valid: true, - }, - 3: { - msg: "1234432112344321", - key: "359E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120", - sig: "616B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208", - valid: true, - }, - 4: { - msg: "12344321123443", - key: "359E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120", - sig: "616B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208", - valid: false, - }, - 5: { - msg: "1234432112344321", - key: "459E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120", - sig: "616B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208", - valid: false, - }, - 6: { - msg: "1234432112344321", - key: "359E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120", - sig: "716B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208", - valid: false, - }, - } - - for i, tc := range cases { - msg, key, sig, err := toBytes(tc) - require.Nil(err, "%d: %+v", i, err) - - pk, err := parseEdKey(key) - require.Nil(err, "%d: %+v", i, err) - psig, err := parseSig(sig) - require.Nil(err, "%d: %+v", i, err) - - // it is not the signature of the message itself - valid := pk.VerifyBytes(msg, psig) - assert.False(valid, "%d", i) - - // but rather of the hash of the msg - hmsg := hashMsg(msg) - valid = pk.VerifyBytes(hmsg, psig) - assert.Equal(tc.valid, valid, "%d", i) - } -} From 91361407192ed45b390c9d111e87fe5b54c6281b Mon Sep 17 00:00:00 2001 From: Liamsi Date: Tue, 15 May 2018 12:07:05 +0100 Subject: [PATCH 09/22] get rid of go-bindata dependency in Makefile; hardcode its output instead --- Makefile | 18 +++++++++--------- keys/words/wordlist/wordlist.go | 8 ++++---- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index 9ae475983..68fb8344c 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,8 @@ GOTOOLS = \ github.com/golang/dep/cmd/dep \ - github.com/jteeuwen/go-bindata/go-bindata # gopkg.in/alecthomas/gometalinter.v2 \ - # -GOTOOLS_CHECK = dep go-bindata #gometalinter.v2 + +GOTOOLS_CHECK = dep #gometalinter.v2 all: check get_vendor_deps build test install @@ -13,12 +12,13 @@ check: check_tools ######################################## ### Build -wordlist: - # Generating wordlist.go... - go-bindata -ignore ".*\.go" -o keys/words/wordlist/wordlist.go -pkg "wordlist" keys/words/wordlist/... +# Command to generate the workd list (kept here for documentation purposes only): +# wordlist: + # Generating wordlist.go ... + # go-bindata -ignore ".*\.go" -o keys/words/wordlist/wordlist.go -pkg "wordlist" keys/words/wordlist/... -build: wordlist - # Nothing else to build! +#build: wordlist +# # Nothing else to build! install: # Nothing to install! @@ -96,4 +96,4 @@ metalinter_all: # To avoid unintended conflicts with file names, always add to .PHONY # unless there is a reason not to. # https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html -.PHONEY: check wordlist build install check_tools get_tools update_tools get_vendor_deps test fmt metalinter metalinter_all +.PHONEY: check build install check_tools get_tools update_tools get_vendor_deps test fmt metalinter metalinter_all diff --git a/keys/words/wordlist/wordlist.go b/keys/words/wordlist/wordlist.go index 4efff82cb..9ffbb1539 100644 --- a/keys/words/wordlist/wordlist.go +++ b/keys/words/wordlist/wordlist.go @@ -86,7 +86,7 @@ func keysWordsWordlistChinese_simplifiedTxt() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "keys/words/wordlist/chinese_simplified.txt", size: 8192, mode: os.FileMode(420), modTime: time.Unix(1514928181, 0)} + info := bindataFileInfo{name: "keys/words/wordlist/chinese_simplified.txt", size: 8192, mode: os.FileMode(420), modTime: time.Unix(1523115814, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -106,7 +106,7 @@ func keysWordsWordlistEnglishTxt() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "keys/words/wordlist/english.txt", size: 13116, mode: os.FileMode(420), modTime: time.Unix(1514928181, 0)} + info := bindataFileInfo{name: "keys/words/wordlist/english.txt", size: 13116, mode: os.FileMode(420), modTime: time.Unix(1523115814, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -126,7 +126,7 @@ func keysWordsWordlistJapaneseTxt() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "keys/words/wordlist/japanese.txt", size: 24287, mode: os.FileMode(420), modTime: time.Unix(1514928181, 0)} + info := bindataFileInfo{name: "keys/words/wordlist/japanese.txt", size: 24287, mode: os.FileMode(420), modTime: time.Unix(1523115814, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -146,7 +146,7 @@ func keysWordsWordlistSpanishTxt() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "keys/words/wordlist/spanish.txt", size: 13659, mode: os.FileMode(420), modTime: time.Unix(1514928181, 0)} + info := bindataFileInfo{name: "keys/words/wordlist/spanish.txt", size: 13659, mode: os.FileMode(420), modTime: time.Unix(1523115814, 0)} a := &asset{bytes: bytes, info: info} return a, nil } From 3fe985e289f12955161effd2fa4cb0988dc1445c Mon Sep 17 00:00:00 2001 From: Liamsi Date: Tue, 15 May 2018 12:23:33 +0100 Subject: [PATCH 10/22] fix makefile --- Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 68fb8344c..74af29941 100644 --- a/Makefile +++ b/Makefile @@ -13,12 +13,12 @@ check: check_tools ### Build # Command to generate the workd list (kept here for documentation purposes only): -# wordlist: - # Generating wordlist.go ... +wordlist: + # To re-generate wordlist.go run: # go-bindata -ignore ".*\.go" -o keys/words/wordlist/wordlist.go -pkg "wordlist" keys/words/wordlist/... -#build: wordlist -# # Nothing else to build! +build: wordlist + # Nothing else to build! install: # Nothing to install! From 8423f6ef5a7fd29bd76636cc16702911d246b4b1 Mon Sep 17 00:00:00 2001 From: Zaki Manian Date: Mon, 21 May 2018 20:45:36 -0700 Subject: [PATCH 11/22] Add the libsodium test vector --- xchacha20poly1035/xchachapoly.go | 8 +-- xchacha20poly1035/xchachapoly_test.go | 91 +++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 xchacha20poly1035/xchachapoly_test.go diff --git a/xchacha20poly1035/xchachapoly.go b/xchacha20poly1035/xchachapoly.go index b9d823414..96413b8c1 100644 --- a/xchacha20poly1035/xchachapoly.go +++ b/xchacha20poly1035/xchachapoly.go @@ -41,11 +41,11 @@ func (c *xchacha20poly1305) Overhead() int { func (c *xchacha20poly1305) Seal(dst, nonce, plaintext, additionalData []byte) []byte { if len(nonce) != NonceSize { - panic("chacha20poly1305: bad nonce length passed to Seal") + panic("xchacha20poly1305: bad nonce length passed to Seal") } if uint64(len(plaintext)) > (1<<38)-64 { - panic("chacha20poly1305: plaintext too large") + panic("xchacha20poly1305: plaintext too large") } var subKey [KeySize]byte @@ -64,10 +64,10 @@ func (c *xchacha20poly1305) Seal(dst, nonce, plaintext, additionalData []byte) [ func (c *xchacha20poly1305) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) { if len(nonce) != NonceSize { - panic("chacha20poly1305: bad nonce length passed to Open") + panic("xchacha20poly1305: bad nonce length passed to Open") } if uint64(len(ciphertext)) > (1<<38)-48 { - panic("chacha20poly1305: ciphertext too large") + panic("xchacha20poly1305: ciphertext too large") } var subKey [KeySize]byte var hNonce [16]byte diff --git a/xchacha20poly1035/xchachapoly_test.go b/xchacha20poly1035/xchachapoly_test.go new file mode 100644 index 000000000..5e522e0b1 --- /dev/null +++ b/xchacha20poly1035/xchachapoly_test.go @@ -0,0 +1,91 @@ +package xchacha20poly1305 + +import ( + "bytes" + "encoding/hex" + "testing" +) + +func toHex(bits []byte) string { + return hex.EncodeToString(bits) +} + +func fromHex(bits string) []byte { + b, err := hex.DecodeString(bits) + if err != nil { + panic(err) + } + return b +} + +func TestHChaCha20(t *testing.T) { + for i, v := range hChaCha20Vectors { + var key [32]byte + var nonce [16]byte + copy(key[:], v.key) + copy(nonce[:], v.nonce) + + HChaCha20(&key, &nonce, &key) + if !bytes.Equal(key[:], v.keystream) { + t.Errorf("Test %d: keystream mismatch:\n \t got: %s\n \t want: %s", i, toHex(key[:]), toHex(v.keystream)) + } + } +} + +var hChaCha20Vectors = []struct { + key, nonce, keystream []byte +}{ + { + fromHex("0000000000000000000000000000000000000000000000000000000000000000"), + fromHex("000000000000000000000000000000000000000000000000"), + fromHex("1140704c328d1d5d0e30086cdf209dbd6a43b8f41518a11cc387b669b2ee6586"), + }, + { + fromHex("8000000000000000000000000000000000000000000000000000000000000000"), + fromHex("000000000000000000000000000000000000000000000000"), + fromHex("7d266a7fd808cae4c02a0a70dcbfbcc250dae65ce3eae7fc210f54cc8f77df86"), + }, + { + fromHex("0000000000000000000000000000000000000000000000000000000000000001"), + fromHex("000000000000000000000000000000000000000000000002"), + fromHex("e0c77ff931bb9163a5460c02ac281c2b53d792b1c43fea817e9ad275ae546963"), + }, + { + fromHex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"), + fromHex("000102030405060708090a0b0c0d0e0f1011121314151617"), + fromHex("51e3ff45a895675c4b33b46c64f4a9ace110d34df6a2ceab486372bacbd3eff6"), + }, +} + +func TestVectors(t *testing.T) { + for i, v := range vectors { + if len(v.plaintext) == 0 { + v.plaintext = make([]byte, len(v.ciphertext)) + } + + var nonce [24]byte + copy(nonce[:], v.nonce) + + aead, err := New(v.key) + if err != nil { + t.Error(err) + } + + dst := aead.Seal(nil, nonce[:], v.plaintext, v.ad) + if !bytes.Equal(dst, v.ciphertext) { + t.Errorf("Test %d: ciphertext mismatch:\n \t got: %s\n \t want: %s", i, toHex(dst), toHex(v.ciphertext)) + } + } +} + +var vectors = []struct { + key, nonce, ad, plaintext, ciphertext []byte +}{ + { + []byte{0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f}, + []byte{0x07, 0x00, 0x00, 0x00, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b}, + []byte{0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7}, + []byte("Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it."), + []byte{0x45, 0x3c, 0x06, 0x93, 0xa7, 0x40, 0x7f, 0x04, 0xff, 0x4c, 0x56, 0xae, 0xdb, 0x17, 0xa3, 0xc0, 0xa1, 0xaf, 0xff, 0x01, 0x17, 0x49, 0x30, 0xfc, 0x22, 0x28, 0x7c, 0x33, 0xdb, 0xcf, 0x0a, 0xc8, 0xb8, 0x9a, 0xd9, 0x29, 0x53, 0x0a, 0x1b, 0xb3, 0xab, 0x5e, 0x69, 0xf2, 0x4c, 0x7f, 0x60, 0x70, 0xc8, 0xf8, 0x40, 0xc9, 0xab, 0xb4, 0xf6, 0x9f, 0xbf, 0xc8, 0xa7, 0xff, 0x51, 0x26, 0xfa, 0xee, 0xbb, 0xb5, 0x58, 0x05, 0xee, 0x9c, 0x1c, 0xf2, 0xce, 0x5a, 0x57, 0x26, 0x32, 0x87, 0xae, 0xc5, 0x78, 0x0f, 0x04, 0xec, 0x32, 0x4c, 0x35, 0x14, 0x12, 0x2c, 0xfc, 0x32, 0x31, 0xfc, 0x1a, 0x8b, 0x71, 0x8a, 0x62, 0x86, 0x37, 0x30, 0xa2, 0x70, 0x2b, 0xb7, 0x63, 0x66, 0x11, 0x6b, 0xed, 0x09, 0xe0, 0xfd, 0x5c, 0x6d, 0x84, 0xb6, 0xb0, 0xc1, 0xab, 0xaf, 0x24, 0x9d, 0x5d, 0xd0, 0xf7, 0xf5, 0xa7, 0xea}, + }, +} From 134fdf716930c308ac9f6cd3a80f55ee57f40254 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 22 May 2018 12:43:44 -0400 Subject: [PATCH 12/22] bring in merkle from tmlibs --- merkle/README.md | 4 ++ merkle/simple_map.go | 84 ++++++++++++++++++++++ merkle/simple_map_test.go | 53 ++++++++++++++ merkle/simple_proof.go | 144 +++++++++++++++++++++++++++++++++++++ merkle/simple_tree.go | 91 +++++++++++++++++++++++ merkle/simple_tree_test.go | 87 ++++++++++++++++++++++ merkle/types.go | 47 ++++++++++++ 7 files changed, 510 insertions(+) create mode 100644 merkle/README.md create mode 100644 merkle/simple_map.go create mode 100644 merkle/simple_map_test.go create mode 100644 merkle/simple_proof.go create mode 100644 merkle/simple_tree.go create mode 100644 merkle/simple_tree_test.go create mode 100644 merkle/types.go diff --git a/merkle/README.md b/merkle/README.md new file mode 100644 index 000000000..c44978368 --- /dev/null +++ b/merkle/README.md @@ -0,0 +1,4 @@ +## Simple Merkle Tree + +For smaller static data structures that don't require immutable snapshots or mutability; +for instance the transactions and validation signatures of a block can be hashed using this simple merkle tree logic. diff --git a/merkle/simple_map.go b/merkle/simple_map.go new file mode 100644 index 000000000..cd38de761 --- /dev/null +++ b/merkle/simple_map.go @@ -0,0 +1,84 @@ +package merkle + +import ( + cmn "github.com/tendermint/tmlibs/common" + "golang.org/x/crypto/ripemd160" +) + +type SimpleMap struct { + kvs cmn.KVPairs + sorted bool +} + +func NewSimpleMap() *SimpleMap { + return &SimpleMap{ + kvs: nil, + sorted: false, + } +} + +func (sm *SimpleMap) Set(key string, value Hasher) { + sm.sorted = false + + // Hash the key to blind it... why not? + khash := SimpleHashFromBytes([]byte(key)) + + // And the value is hashed too, so you can + // check for equality with a cached value (say) + // and make a determination to fetch or not. + vhash := value.Hash() + + sm.kvs = append(sm.kvs, cmn.KVPair{ + Key: khash, + Value: vhash, + }) +} + +// Merkle root hash of items sorted by key +// (UNSTABLE: and by value too if duplicate key). +func (sm *SimpleMap) Hash() []byte { + sm.Sort() + return hashKVPairs(sm.kvs) +} + +func (sm *SimpleMap) Sort() { + if sm.sorted { + return + } + sm.kvs.Sort() + sm.sorted = true +} + +// Returns a copy of sorted KVPairs. +func (sm *SimpleMap) KVPairs() cmn.KVPairs { + sm.Sort() + kvs := make(cmn.KVPairs, len(sm.kvs)) + copy(kvs, sm.kvs) + return kvs +} + +//---------------------------------------- + +// A local extension to KVPair that can be hashed. +type KVPair cmn.KVPair + +func (kv KVPair) Hash() []byte { + hasher := ripemd160.New() + err := encodeByteSlice(hasher, kv.Key) + if err != nil { + panic(err) + } + err = encodeByteSlice(hasher, kv.Value) + if err != nil { + panic(err) + } + return hasher.Sum(nil) +} + +func hashKVPairs(kvs cmn.KVPairs) []byte { + kvsH := make([]Hasher, 0, len(kvs)) + for _, kvp := range kvs { + kvsH = append(kvsH, KVPair(kvp)) + } + return SimpleHashFromHashers(kvsH) +} diff --git a/merkle/simple_map_test.go b/merkle/simple_map_test.go new file mode 100644 index 000000000..c9c871354 --- /dev/null +++ b/merkle/simple_map_test.go @@ -0,0 +1,53 @@ +package merkle + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +type strHasher string + +func (str strHasher) Hash() []byte { + return SimpleHashFromBytes([]byte(str)) +} + +func TestSimpleMap(t *testing.T) { + { + db := NewSimpleMap() + db.Set("key1", strHasher("value1")) + assert.Equal(t, "acdb4f121bc6f25041eb263ab463f1cd79236a32", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") + } + { + db := NewSimpleMap() + db.Set("key1", strHasher("value2")) + assert.Equal(t, "b8cbf5adee8c524e14f531da9b49adbbbd66fffa", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") + } + { + db := NewSimpleMap() + db.Set("key1", strHasher("value1")) + db.Set("key2", strHasher("value2")) + assert.Equal(t, "1708aabc85bbe00242d3db8c299516aa54e48c38", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") + } + { + db := NewSimpleMap() + db.Set("key2", strHasher("value2")) // NOTE: out of order + db.Set("key1", strHasher("value1")) + assert.Equal(t, "1708aabc85bbe00242d3db8c299516aa54e48c38", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") + } + { + db := NewSimpleMap() + db.Set("key1", strHasher("value1")) + db.Set("key2", strHasher("value2")) + db.Set("key3", strHasher("value3")) + assert.Equal(t, "e728afe72ce351eed6aca65c5f78da19b9a6e214", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") + } + { + db := NewSimpleMap() + db.Set("key2", strHasher("value2")) // NOTE: out of order + db.Set("key1", strHasher("value1")) + db.Set("key3", strHasher("value3")) + assert.Equal(t, "e728afe72ce351eed6aca65c5f78da19b9a6e214", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") + } +} diff --git a/merkle/simple_proof.go b/merkle/simple_proof.go new file mode 100644 index 000000000..ca6ccf372 --- /dev/null +++ b/merkle/simple_proof.go @@ -0,0 +1,144 @@ +package merkle + +import ( + "bytes" + "fmt" +) + +type SimpleProof struct { + Aunts [][]byte `json:"aunts"` // Hashes from leaf's sibling to a root's child. +} + +// proofs[0] is the proof for items[0]. +func SimpleProofsFromHashers(items []Hasher) (rootHash []byte, proofs []*SimpleProof) { + trails, rootSPN := trailsFromHashers(items) + rootHash = rootSPN.Hash + proofs = make([]*SimpleProof, len(items)) + for i, trail := range trails { + proofs[i] = &SimpleProof{ + Aunts: trail.FlattenAunts(), + } + } + return +} + +func SimpleProofsFromMap(m map[string]Hasher) (rootHash []byte, proofs []*SimpleProof) { + sm := NewSimpleMap() + for k, v := range m { + sm.Set(k, v) + } + sm.Sort() + kvs := sm.kvs + kvsH := make([]Hasher, 0, len(kvs)) + for _, kvp := range kvs { + kvsH = append(kvsH, KVPair(kvp)) + } + return SimpleProofsFromHashers(kvsH) +} + +// Verify that leafHash is a leaf hash of the simple-merkle-tree +// which hashes to rootHash. +func (sp *SimpleProof) Verify(index int, total int, leafHash []byte, rootHash []byte) bool { + computedHash := computeHashFromAunts(index, total, leafHash, sp.Aunts) + return computedHash != nil && bytes.Equal(computedHash, rootHash) +} + +func (sp *SimpleProof) String() string { + return sp.StringIndented("") +} + +func (sp *SimpleProof) StringIndented(indent string) string { + return fmt.Sprintf(`SimpleProof{ +%s Aunts: %X +%s}`, + indent, sp.Aunts, + indent) +} + +// Use the leafHash and innerHashes to get the root merkle hash. +// If the length of the innerHashes slice isn't exactly correct, the result is nil. +// Recursive impl. +func computeHashFromAunts(index int, total int, leafHash []byte, innerHashes [][]byte) []byte { + if index >= total || index < 0 || total <= 0 { + return nil + } + switch total { + case 0: + panic("Cannot call computeHashFromAunts() with 0 total") + case 1: + if len(innerHashes) != 0 { + return nil + } + return leafHash + default: + if len(innerHashes) == 0 { + return nil + } + numLeft := (total + 1) / 2 + if index < numLeft { + leftHash := computeHashFromAunts(index, numLeft, leafHash, innerHashes[:len(innerHashes)-1]) + if leftHash == nil { + return nil + } + return SimpleHashFromTwoHashes(leftHash, innerHashes[len(innerHashes)-1]) + } + rightHash := computeHashFromAunts(index-numLeft, total-numLeft, leafHash, innerHashes[:len(innerHashes)-1]) + if rightHash == nil { + return nil + } + return SimpleHashFromTwoHashes(innerHashes[len(innerHashes)-1], rightHash) + } +} + +// Helper structure to construct merkle proof. +// The node and the tree is thrown away afterwards. +// Exactly one of node.Left and node.Right is nil, unless node is the root, in which case both are nil. +// node.Parent.Hash = hash(node.Hash, node.Right.Hash) or +// hash(node.Left.Hash, node.Hash), depending on whether node is a left/right child. +type SimpleProofNode struct { + Hash []byte + Parent *SimpleProofNode + Left *SimpleProofNode // Left sibling (only one of Left,Right is set) + Right *SimpleProofNode // Right sibling (only one of Left,Right is set) +} + +// Starting from a leaf SimpleProofNode, FlattenAunts() will return +// the inner hashes for the item corresponding to the leaf. +func (spn *SimpleProofNode) FlattenAunts() [][]byte { + // Nonrecursive impl. + innerHashes := [][]byte{} + for spn != nil { + if spn.Left != nil { + innerHashes = append(innerHashes, spn.Left.Hash) + } else if spn.Right != nil { + innerHashes = append(innerHashes, spn.Right.Hash) + } else { + break + } + spn = spn.Parent + } + return innerHashes +} + +// trails[0].Hash is the leaf hash for items[0]. +// trails[i].Parent.Parent....Parent == root for all i. +func trailsFromHashers(items []Hasher) (trails []*SimpleProofNode, root *SimpleProofNode) { + // Recursive impl. + switch len(items) { + case 0: + return nil, nil + case 1: + trail := &SimpleProofNode{items[0].Hash(), nil, nil, nil} + return []*SimpleProofNode{trail}, trail + default: + lefts, leftRoot := trailsFromHashers(items[:(len(items)+1)/2]) + rights, rightRoot := trailsFromHashers(items[(len(items)+1)/2:]) + rootHash := SimpleHashFromTwoHashes(leftRoot.Hash, rightRoot.Hash) + root := &SimpleProofNode{rootHash, nil, nil, nil} + leftRoot.Parent = root + leftRoot.Right = rightRoot + rightRoot.Parent = root + rightRoot.Left = leftRoot + return append(lefts, rights...), root + } +} diff --git a/merkle/simple_tree.go b/merkle/simple_tree.go new file mode 100644 index 000000000..9bdf52cb2 --- /dev/null +++ b/merkle/simple_tree.go @@ -0,0 +1,91 @@ +/* +Computes a deterministic minimal height merkle tree hash. +If the number of items is not a power of two, some leaves +will be at different levels. Tries to keep both sides of +the tree the same size, but the left may be one greater. + +Use this for short deterministic trees, such as the validator list. +For larger datasets, use IAVLTree. + + * + / \ + / \ + / \ + / \ + * * + / \ / \ + / \ / \ + / \ / \ + * * * h6 + / \ / \ / \ + h0 h1 h2 h3 h4 h5 + +*/ + +package merkle + +import ( + "golang.org/x/crypto/ripemd160" +) + +func SimpleHashFromTwoHashes(left []byte, right []byte) []byte { + var hasher = ripemd160.New() + err := encodeByteSlice(hasher, left) + if err != nil { + panic(err) + } + err = encodeByteSlice(hasher, right) + if err != nil { + panic(err) + } + return hasher.Sum(nil) +} + +func SimpleHashFromHashes(hashes [][]byte) []byte { + // Recursive impl. + switch len(hashes) { + case 0: + return nil + case 1: + return hashes[0] + default: + left := SimpleHashFromHashes(hashes[:(len(hashes)+1)/2]) + right := SimpleHashFromHashes(hashes[(len(hashes)+1)/2:]) + return SimpleHashFromTwoHashes(left, right) + } +} + +// NOTE: Do not implement this, use SimpleHashFromByteslices instead. +// type Byteser interface { Bytes() []byte } +// func SimpleHashFromBytesers(items []Byteser) []byte { ... } + +func SimpleHashFromByteslices(bzs [][]byte) []byte { + hashes := make([][]byte, len(bzs)) + for i, bz := range bzs { + hashes[i] = SimpleHashFromBytes(bz) + } + return SimpleHashFromHashes(hashes) +} + +func SimpleHashFromBytes(bz []byte) []byte { + hasher := ripemd160.New() + hasher.Write(bz) + return hasher.Sum(nil) +} + +func SimpleHashFromHashers(items []Hasher) []byte { + hashes := make([][]byte, len(items)) + for i, item := range items { + hash := item.Hash() + hashes[i] = hash + } + return SimpleHashFromHashes(hashes) +} + +func SimpleHashFromMap(m map[string]Hasher) []byte { + sm := NewSimpleMap() + for k, v := range m { + sm.Set(k, v) + } + return sm.Hash() +} diff --git a/merkle/simple_tree_test.go b/merkle/simple_tree_test.go new file mode 100644 index 000000000..8c4ed01f8 --- /dev/null +++ b/merkle/simple_tree_test.go @@ -0,0 +1,87 @@ +package merkle + +import ( + "bytes" + + cmn "github.com/tendermint/tmlibs/common" + . "github.com/tendermint/tmlibs/test" + + "testing" +) + +type testItem []byte + +func (tI testItem) Hash() []byte { + return []byte(tI) +} + +func TestSimpleProof(t *testing.T) { + + total := 100 + + items := make([]Hasher, total) + for i := 0; i < total; i++ { + items[i] = testItem(cmn.RandBytes(32)) + } + + rootHash := SimpleHashFromHashers(items) + + rootHash2, proofs := SimpleProofsFromHashers(items) + + if !bytes.Equal(rootHash, rootHash2) { + t.Errorf("Unmatched root hashes: %X vs %X", rootHash, rootHash2) + } + + // For each item, check the trail. + for i, item := range items { + itemHash := item.Hash() + proof := proofs[i] + + // Verify success + ok := proof.Verify(i, total, itemHash, rootHash) + if !ok { + t.Errorf("Verification failed for index %v.", i) + } + + // Wrong item index should make it fail + { + ok = proof.Verify((i+1)%total, total, itemHash, rootHash) + if ok { + t.Errorf("Expected verification to fail for wrong index %v.", i) + } + } + + // Trail too long should make it fail + origAunts := proof.Aunts + proof.Aunts = append(proof.Aunts, cmn.RandBytes(32)) + { + ok = proof.Verify(i, total, itemHash, rootHash) + if ok { + t.Errorf("Expected verification to fail for wrong trail length.") + } + } + proof.Aunts = origAunts + + // Trail too short should make it fail + proof.Aunts = proof.Aunts[0 : len(proof.Aunts)-1] + { + ok = proof.Verify(i, total, itemHash, rootHash) + if ok { + t.Errorf("Expected verification to fail for wrong trail length.") + } + } + proof.Aunts = origAunts + + // Mutating the itemHash should make it fail. + ok = proof.Verify(i, total, MutateByteSlice(itemHash), rootHash) + if ok { + t.Errorf("Expected verification to fail for mutated leaf hash") + } + + // Mutating the rootHash should make it fail. + ok = proof.Verify(i, total, itemHash, MutateByteSlice(rootHash)) + if ok { + t.Errorf("Expected verification to fail for mutated root hash") + } + } +} diff --git a/merkle/types.go b/merkle/types.go new file mode 100644 index 000000000..a0c491a7e --- /dev/null +++ b/merkle/types.go @@ -0,0 +1,47 @@ +package merkle + +import ( + "encoding/binary" + "io" +) + +type Tree interface { + Size() (size int) + Height() (height int8) + Has(key []byte) (has bool) + Proof(key []byte) (value []byte, proof []byte, exists bool) // TODO make it return an index + Get(key []byte) (index int, value []byte, exists bool) + GetByIndex(index int) (key []byte, value []byte) + Set(key []byte, value []byte) (updated bool) + Remove(key []byte) (value []byte, removed bool) + HashWithCount() (hash []byte, count int) + Hash() (hash []byte) + Save() (hash []byte) + Load(hash []byte) + Copy() Tree + Iterate(func(key []byte, value []byte) (stop bool)) (stopped bool) + IterateRange(start []byte, end []byte, ascending bool, fx func(key []byte, value []byte) (stop bool)) (stopped bool) +} + +type Hasher interface { + Hash() []byte +} + +//----------------------------------------------------------------------- +// NOTE: these are duplicated from go-amino so we dont need go-amino as a dep + +func encodeByteSlice(w io.Writer, bz []byte) (err error) { + err = encodeUvarint(w, uint64(len(bz))) + if err != nil { + return + } + _, err = w.Write(bz) + return +} + +func encodeUvarint(w io.Writer, i uint64) (err error) { + var buf [10]byte + n := binary.PutUvarint(buf[:], i) + _, err = w.Write(buf[0:n]) + return +} From 4663ffdf087b571e5ccbc0df5ec6a6b8ad36cce1 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 22 May 2018 12:46:15 -0400 Subject: [PATCH 13/22] add tmhash --- merkle/simple_map.go | 4 ++-- merkle/simple_map_test.go | 12 ++++++------ merkle/simple_tree.go | 6 +++--- tmhash/hash.go | 41 +++++++++++++++++++++++++++++++++++++++ tmhash/hash_test.go | 23 ++++++++++++++++++++++ 5 files changed, 75 insertions(+), 11 deletions(-) create mode 100644 tmhash/hash.go create mode 100644 tmhash/hash_test.go diff --git a/merkle/simple_map.go b/merkle/simple_map.go index cd38de761..41d1ccd56 100644 --- a/merkle/simple_map.go +++ b/merkle/simple_map.go @@ -2,7 +2,7 @@ package merkle import ( cmn "github.com/tendermint/tmlibs/common" - "golang.org/x/crypto/ripemd160" + "github.com/tendermint/go-crypto/tmhash" ) type SimpleMap struct { @@ -63,7 +63,7 @@ func (sm *SimpleMap) KVPairs() cmn.KVPairs { type KVPair cmn.KVPair func (kv KVPair) Hash() []byte { - hasher := ripemd160.New() + hasher := tmhash.New() err := encodeByteSlice(hasher, kv.Key) if err != nil { panic(err) diff --git a/merkle/simple_map_test.go b/merkle/simple_map_test.go index c9c871354..6e1004db2 100644 --- a/merkle/simple_map_test.go +++ b/merkle/simple_map_test.go @@ -17,37 +17,37 @@ func TestSimpleMap(t *testing.T) { { db := NewSimpleMap() db.Set("key1", strHasher("value1")) - assert.Equal(t, "acdb4f121bc6f25041eb263ab463f1cd79236a32", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") + assert.Equal(t, "3dafc06a52039d029be57c75c9d16356a4256ef4", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") } { db := NewSimpleMap() db.Set("key1", strHasher("value2")) - assert.Equal(t, "b8cbf5adee8c524e14f531da9b49adbbbd66fffa", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") + assert.Equal(t, "03eb5cfdff646bc4e80fec844e72fd248a1c6b2c", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") } { db := NewSimpleMap() db.Set("key1", strHasher("value1")) db.Set("key2", strHasher("value2")) - assert.Equal(t, "1708aabc85bbe00242d3db8c299516aa54e48c38", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") + assert.Equal(t, "acc3971eab8513171cc90ce8b74f368c38f9657d", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") } { db := NewSimpleMap() db.Set("key2", strHasher("value2")) // NOTE: out of order db.Set("key1", strHasher("value1")) - assert.Equal(t, "1708aabc85bbe00242d3db8c299516aa54e48c38", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") + assert.Equal(t, "acc3971eab8513171cc90ce8b74f368c38f9657d", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") } { db := NewSimpleMap() db.Set("key1", strHasher("value1")) db.Set("key2", strHasher("value2")) db.Set("key3", strHasher("value3")) - assert.Equal(t, "e728afe72ce351eed6aca65c5f78da19b9a6e214", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") + assert.Equal(t, "0cd117ad14e6cd22edcd9aa0d84d7063b54b862f", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") } { db := NewSimpleMap() db.Set("key2", strHasher("value2")) // NOTE: out of order db.Set("key1", strHasher("value1")) db.Set("key3", strHasher("value3")) - assert.Equal(t, "e728afe72ce351eed6aca65c5f78da19b9a6e214", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") + assert.Equal(t, "0cd117ad14e6cd22edcd9aa0d84d7063b54b862f", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") } } diff --git a/merkle/simple_tree.go b/merkle/simple_tree.go index 9bdf52cb2..3c31b1337 100644 --- a/merkle/simple_tree.go +++ b/merkle/simple_tree.go @@ -25,11 +25,11 @@ For larger datasets, use IAVLTree. package merkle import ( - "golang.org/x/crypto/ripemd160" + "github.com/tendermint/go-crypto/tmhash" ) func SimpleHashFromTwoHashes(left []byte, right []byte) []byte { - var hasher = ripemd160.New() + var hasher = tmhash.New() err := encodeByteSlice(hasher, left) if err != nil { panic(err) @@ -68,7 +68,7 @@ func SimpleHashFromByteslices(bzs [][]byte) []byte { } func SimpleHashFromBytes(bz []byte) []byte { - hasher := ripemd160.New() + hasher := tmhash.New() hasher.Write(bz) return hasher.Sum(nil) } diff --git a/tmhash/hash.go b/tmhash/hash.go new file mode 100644 index 000000000..de69c406f --- /dev/null +++ b/tmhash/hash.go @@ -0,0 +1,41 @@ +package tmhash + +import ( + "crypto/sha256" + "hash" +) + +var ( + Size = 20 + BlockSize = sha256.BlockSize +) + +type sha256trunc struct { + sha256 hash.Hash +} + +func (h sha256trunc) Write(p []byte) (n int, err error) { + return h.sha256.Write(p) +} +func (h sha256trunc) Sum(b []byte) []byte { + shasum := h.sha256.Sum(b) + return shasum[:Size] +} + +func (h sha256trunc) Reset() { + h.sha256.Reset() +} + +func (h sha256trunc) Size() int { + return Size +} + +func (h sha256trunc) BlockSize() int { + return h.sha256.BlockSize() +} + +func New() hash.Hash { + return sha256trunc{ + sha256: sha256.New(), + } +} diff --git a/tmhash/hash_test.go b/tmhash/hash_test.go new file mode 100644 index 000000000..abf0247ab --- /dev/null +++ b/tmhash/hash_test.go @@ -0,0 +1,23 @@ +package tmhash_test + +import ( + "crypto/sha256" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/tendermint/go-crypto/tmhash" +) + +func TestHash(t *testing.T) { + testVector := []byte("abc") + hasher := tmhash.New() + hasher.Write(testVector) + bz := hasher.Sum(nil) + + hasher = sha256.New() + hasher.Write(testVector) + bz2 := hasher.Sum(nil) + bz2 = bz2[:20] + + assert.Equal(t, bz, bz2) +} From c2636c3c6b95cc314e6a7ddc079daf236a9a1a5a Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 22 May 2018 13:04:42 -0400 Subject: [PATCH 14/22] tmhash: add Sum function --- tmhash/hash.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tmhash/hash.go b/tmhash/hash.go index de69c406f..9f684ce01 100644 --- a/tmhash/hash.go +++ b/tmhash/hash.go @@ -34,8 +34,15 @@ func (h sha256trunc) BlockSize() int { return h.sha256.BlockSize() } +// New returns a new hash.Hash. func New() hash.Hash { return sha256trunc{ sha256: sha256.New(), } } + +// Sum returns the first 20 bytes of SHA256 of the bz. +func Sum(bz []byte) []byte { + hash := sha256.Sum256(bz) + return hash[:Size] +} From 384f1e399afb23a30f01e629145c82afb3498664 Mon Sep 17 00:00:00 2001 From: Zaki Manian Date: Wed, 23 May 2018 23:54:26 +0200 Subject: [PATCH 15/22] Fixed the directory name and added a test vector --- {xchacha20poly1035 => xchacha20poly1305}/xchachapoly.go | 0 {xchacha20poly1035 => xchacha20poly1305}/xchachapoly_test.go | 5 +++++ 2 files changed, 5 insertions(+) rename {xchacha20poly1035 => xchacha20poly1305}/xchachapoly.go (100%) rename {xchacha20poly1035 => xchacha20poly1305}/xchachapoly_test.go (94%) diff --git a/xchacha20poly1035/xchachapoly.go b/xchacha20poly1305/xchachapoly.go similarity index 100% rename from xchacha20poly1035/xchachapoly.go rename to xchacha20poly1305/xchachapoly.go diff --git a/xchacha20poly1035/xchachapoly_test.go b/xchacha20poly1305/xchachapoly_test.go similarity index 94% rename from xchacha20poly1035/xchachapoly_test.go rename to xchacha20poly1305/xchachapoly_test.go index 5e522e0b1..460b90ab4 100644 --- a/xchacha20poly1035/xchachapoly_test.go +++ b/xchacha20poly1305/xchachapoly_test.go @@ -55,6 +55,11 @@ var hChaCha20Vectors = []struct { fromHex("000102030405060708090a0b0c0d0e0f1011121314151617"), fromHex("51e3ff45a895675c4b33b46c64f4a9ace110d34df6a2ceab486372bacbd3eff6"), }, + { + fromHex("24f11cce8a1b3d61e441561a696c1c1b7e173d084fd4812425435a8896a013dc"), + fromHex("d9660c5900ae19ddad28d6e06e45fe5e"), + fromHex("5966b3eec3bff1189f831f06afe4d4e3be97fa9235ec8c20d08acfbbb4e851e3"), + }, } func TestVectors(t *testing.T) { From 05a5294e528161ca7192295a9397d0d8e8adea63 Mon Sep 17 00:00:00 2001 From: Zaki Manian Date: Thu, 24 May 2018 00:12:54 +0200 Subject: [PATCH 16/22] Remove panic and check the round trip --- xchacha20poly1305/xchachapoly.go | 5 +++-- xchacha20poly1305/xchachapoly_test.go | 7 +++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/xchacha20poly1305/xchachapoly.go b/xchacha20poly1305/xchachapoly.go index 96413b8c1..9e0778579 100644 --- a/xchacha20poly1305/xchachapoly.go +++ b/xchacha20poly1305/xchachapoly.go @@ -4,6 +4,7 @@ import ( "crypto/cipher" "encoding/binary" "errors" + "fmt" "golang.org/x/crypto/chacha20poly1305" ) @@ -64,10 +65,10 @@ func (c *xchacha20poly1305) Seal(dst, nonce, plaintext, additionalData []byte) [ func (c *xchacha20poly1305) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) { if len(nonce) != NonceSize { - panic("xchacha20poly1305: bad nonce length passed to Open") + return nil, fmt.Errorf("xchacha20poly1305: bad nonce length passed to Open") } if uint64(len(ciphertext)) > (1<<38)-48 { - panic("xchacha20poly1305: ciphertext too large") + return nil, fmt.Errorf("xchacha20poly1305: ciphertext too large") } var subKey [KeySize]byte var hNonce [16]byte diff --git a/xchacha20poly1305/xchachapoly_test.go b/xchacha20poly1305/xchachapoly_test.go index 460b90ab4..3001217f4 100644 --- a/xchacha20poly1305/xchachapoly_test.go +++ b/xchacha20poly1305/xchachapoly_test.go @@ -80,6 +80,13 @@ func TestVectors(t *testing.T) { if !bytes.Equal(dst, v.ciphertext) { t.Errorf("Test %d: ciphertext mismatch:\n \t got: %s\n \t want: %s", i, toHex(dst), toHex(v.ciphertext)) } + open, err := aead.Open(nil, nonce[:], dst, v.ad) + if err != nil { + t.Error(err) + } + if !bytes.Equal(open, v.plaintext) { + t.Errorf("Test %d: plaintext mismatch:\n \t got: %s\n \t want: %s", i, string(open), string(v.plaintext)) + } } } From 707d27c11e34f87fda4c2d5623f6e0a6b15d2d14 Mon Sep 17 00:00:00 2001 From: vrde Date: Thu, 24 May 2018 16:57:37 +0200 Subject: [PATCH 17/22] Fix dead link in README.md (#106) * Fix dead link in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4a524c014..be087e045 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ go-crypto is the cryptographic package adapted for Tendermint's uses ## Binary encoding -For Binary encoding, please refer to the [Tendermint encoding spec](https://github.com/tendermint/tendermint/blob/develop/docs/specification/new-spec/encoding.md). +For Binary encoding, please refer to the [Tendermint encoding spec](https://github.com/tendermint/tendermint/blob/master/docs/spec/blockchain/encoding.md). ## JSON Encoding From 9f04935caa0ba104421c5c856bae92d5307e3869 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 22 May 2018 13:05:27 -0400 Subject: [PATCH 18/22] merkle: remove unused funcs. unexport simplemap. improv docs --- merkle/simple_map.go | 44 ++++++++++-------- merkle/simple_map_test.go | 17 ++++--- merkle/simple_proof.go | 18 ++++++-- merkle/simple_tree.go | 93 ++++++++++++-------------------------- merkle/simple_tree_test.go | 3 +- merkle/types.go | 2 + tmhash/hash.go | 2 +- 7 files changed, 84 insertions(+), 95 deletions(-) diff --git a/merkle/simple_map.go b/merkle/simple_map.go index 41d1ccd56..b073ecdd6 100644 --- a/merkle/simple_map.go +++ b/merkle/simple_map.go @@ -5,23 +5,27 @@ import ( "github.com/tendermint/go-crypto/tmhash" ) -type SimpleMap struct { +// Merkle tree from a map. +// Leaves are `hash(key) | hash(value)`. +// Leaves are sorted before Merkle hashing. +type simpleMap struct { kvs cmn.KVPairs sorted bool } -func NewSimpleMap() *SimpleMap { - return &SimpleMap{ +func newSimpleMap() *simpleMap { + return &simpleMap{ kvs: nil, sorted: false, } } -func (sm *SimpleMap) Set(key string, value Hasher) { +// Set hashes the key and value and appends it to the kv pairs. +func (sm *simpleMap) Set(key string, value Hasher) { sm.sorted = false // Hash the key to blind it... why not? - khash := SimpleHashFromBytes([]byte(key)) + khash := tmhash.Sum([]byte(key)) // And the value is hashed too, so you can // check for equality with a cached value (say) @@ -34,14 +38,14 @@ func (sm *SimpleMap) Set(key string, value Hasher) { }) } -// Merkle root hash of items sorted by key +// Hash Merkle root hash of items sorted by key // (UNSTABLE: and by value too if duplicate key). -func (sm *SimpleMap) Hash() []byte { +func (sm *simpleMap) Hash() []byte { sm.Sort() return hashKVPairs(sm.kvs) } -func (sm *SimpleMap) Sort() { +func (sm *simpleMap) Sort() { if sm.sorted { return } @@ -50,7 +54,8 @@ func (sm *SimpleMap) Sort() { } // Returns a copy of sorted KVPairs. -func (sm *SimpleMap) KVPairs() cmn.KVPairs { +// NOTE these contain the hashed key and value. +func (sm *simpleMap) KVPairs() cmn.KVPairs { sm.Sort() kvs := make(cmn.KVPairs, len(sm.kvs)) copy(kvs, sm.kvs) @@ -60,25 +65,28 @@ func (sm *SimpleMap) KVPairs() cmn.KVPairs { //---------------------------------------- // A local extension to KVPair that can be hashed. -type KVPair cmn.KVPair +// XXX: key and value do not need to already be hashed - +// the kvpair ("abc", "def") would not give the same result +// as ("ab", "cdef") as we're using length-prefixing. +type kvPair cmn.KVPair -func (kv KVPair) Hash() []byte { +func (kv kvPair) Hash() []byte { hasher := tmhash.New() err := encodeByteSlice(hasher, kv.Key) if err != nil { - panic(err) - } + panic(err) + } err = encodeByteSlice(hasher, kv.Value) if err != nil { - panic(err) - } + panic(err) + } return hasher.Sum(nil) } func hashKVPairs(kvs cmn.KVPairs) []byte { - kvsH := make([]Hasher, 0, len(kvs)) - for _, kvp := range kvs { - kvsH = append(kvsH, KVPair(kvp)) + kvsH := make([]Hasher, len(kvs)) + for i, kvp := range kvs { + kvsH[i] = kvPair(kvp) } return SimpleHashFromHashers(kvsH) } diff --git a/merkle/simple_map_test.go b/merkle/simple_map_test.go index 6e1004db2..9d0c25a2a 100644 --- a/merkle/simple_map_test.go +++ b/merkle/simple_map_test.go @@ -5,46 +5,49 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/tendermint/go-crypto/tmhash" ) type strHasher string func (str strHasher) Hash() []byte { - return SimpleHashFromBytes([]byte(str)) + h := tmhash.New() + h.Write([]byte(str)) + return h.Sum(nil) } func TestSimpleMap(t *testing.T) { { - db := NewSimpleMap() + db := newSimpleMap() db.Set("key1", strHasher("value1")) assert.Equal(t, "3dafc06a52039d029be57c75c9d16356a4256ef4", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") } { - db := NewSimpleMap() + db := newSimpleMap() db.Set("key1", strHasher("value2")) assert.Equal(t, "03eb5cfdff646bc4e80fec844e72fd248a1c6b2c", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") } { - db := NewSimpleMap() + db := newSimpleMap() db.Set("key1", strHasher("value1")) db.Set("key2", strHasher("value2")) assert.Equal(t, "acc3971eab8513171cc90ce8b74f368c38f9657d", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") } { - db := NewSimpleMap() + db := newSimpleMap() db.Set("key2", strHasher("value2")) // NOTE: out of order db.Set("key1", strHasher("value1")) assert.Equal(t, "acc3971eab8513171cc90ce8b74f368c38f9657d", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") } { - db := NewSimpleMap() + db := newSimpleMap() db.Set("key1", strHasher("value1")) db.Set("key2", strHasher("value2")) db.Set("key3", strHasher("value3")) assert.Equal(t, "0cd117ad14e6cd22edcd9aa0d84d7063b54b862f", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") } { - db := NewSimpleMap() + db := newSimpleMap() db.Set("key2", strHasher("value2")) // NOTE: out of order db.Set("key1", strHasher("value1")) db.Set("key3", strHasher("value3")) diff --git a/merkle/simple_proof.go b/merkle/simple_proof.go index ca6ccf372..f52d1ad9f 100644 --- a/merkle/simple_proof.go +++ b/merkle/simple_proof.go @@ -5,10 +5,12 @@ import ( "fmt" ) +// SimpleProof represents a simple merkle proof. type SimpleProof struct { Aunts [][]byte `json:"aunts"` // Hashes from leaf's sibling to a root's child. } +// SimpleProofsFromHashers computes inclusion proof for given items. // proofs[0] is the proof for items[0]. func SimpleProofsFromHashers(items []Hasher) (rootHash []byte, proofs []*SimpleProof) { trails, rootSPN := trailsFromHashers(items) @@ -22,8 +24,11 @@ func SimpleProofsFromHashers(items []Hasher) (rootHash []byte, proofs []*SimpleP return } +// SimpleProofsFromMap generates proofs from a map. The keys/values of the map will be used as the keys/values +// in the underlying key-value pairs. +// The keys are sorted before the proofs are computed. func SimpleProofsFromMap(m map[string]Hasher) (rootHash []byte, proofs []*SimpleProof) { - sm := NewSimpleMap() + sm := newSimpleMap() for k, v := range m { sm.Set(k, v) } @@ -31,7 +36,7 @@ func SimpleProofsFromMap(m map[string]Hasher) (rootHash []byte, proofs []*Simple kvs := sm.kvs kvsH := make([]Hasher, 0, len(kvs)) for _, kvp := range kvs { - kvsH = append(kvsH, KVPair(kvp)) + kvsH = append(kvsH, kvPair(kvp)) } return SimpleProofsFromHashers(kvsH) } @@ -43,10 +48,13 @@ func (sp *SimpleProof) Verify(index int, total int, leafHash []byte, rootHash [] return computedHash != nil && bytes.Equal(computedHash, rootHash) } +// String implements the stringer interface for SimpleProof. +// It is a wrapper around StringIndented. func (sp *SimpleProof) String() string { return sp.StringIndented("") } +// StringIndented generates a canonical string representation of a SimpleProof. func (sp *SimpleProof) StringIndented(indent string) string { return fmt.Sprintf(`SimpleProof{ %s Aunts: %X @@ -90,7 +98,7 @@ func computeHashFromAunts(index int, total int, leafHash []byte, innerHashes [][ } } -// Helper structure to construct merkle proof. +// SimpleProofNode is a helper structure to construct merkle proof. // The node and the tree is thrown away afterwards. // Exactly one of node.Left and node.Right is nil, unless node is the root, in which case both are nil. // node.Parent.Hash = hash(node.Hash, node.Right.Hash) or @@ -102,8 +110,8 @@ type SimpleProofNode struct { Right *SimpleProofNode // Right sibling (only one of Left,Right is set) } -// Starting from a leaf SimpleProofNode, FlattenAunts() will return -// the inner hashes for the item corresponding to the leaf. +// FlattenAunts will return the inner hashes for the item corresponding to the leaf, +// starting from a leaf SimpleProofNode. func (spn *SimpleProofNode) FlattenAunts() [][]byte { // Nonrecursive impl. innerHashes := [][]byte{} diff --git a/merkle/simple_tree.go b/merkle/simple_tree.go index 3c31b1337..c23f84264 100644 --- a/merkle/simple_tree.go +++ b/merkle/simple_tree.go @@ -1,91 +1,58 @@ -/* -Computes a deterministic minimal height merkle tree hash. -If the number of items is not a power of two, some leaves -will be at different levels. Tries to keep both sides of -the tree the same size, but the left may be one greater. - -Use this for short deterministic trees, such as the validator list. -For larger datasets, use IAVLTree. - - * - / \ - / \ - / \ - / \ - * * - / \ / \ - / \ / \ - / \ / \ - * * * h6 - / \ / \ / \ - h0 h1 h2 h3 h4 h5 - -*/ - package merkle import ( "github.com/tendermint/go-crypto/tmhash" ) -func SimpleHashFromTwoHashes(left []byte, right []byte) []byte { +// SimpleHashFromTwoHashes is the basic operation of the Merkle tree: Hash(left | right). +func SimpleHashFromTwoHashes(left, right []byte) []byte { var hasher = tmhash.New() err := encodeByteSlice(hasher, left) if err != nil { - panic(err) - } + panic(err) + } err = encodeByteSlice(hasher, right) if err != nil { - panic(err) - } - return hasher.Sum(nil) -} - -func SimpleHashFromHashes(hashes [][]byte) []byte { - // Recursive impl. - switch len(hashes) { - case 0: - return nil - case 1: - return hashes[0] - default: - left := SimpleHashFromHashes(hashes[:(len(hashes)+1)/2]) - right := SimpleHashFromHashes(hashes[(len(hashes)+1)/2:]) - return SimpleHashFromTwoHashes(left, right) - } -} - -// NOTE: Do not implement this, use SimpleHashFromByteslices instead. -// type Byteser interface { Bytes() []byte } -// func SimpleHashFromBytesers(items []Byteser) []byte { ... } - -func SimpleHashFromByteslices(bzs [][]byte) []byte { - hashes := make([][]byte, len(bzs)) - for i, bz := range bzs { - hashes[i] = SimpleHashFromBytes(bz) - } - return SimpleHashFromHashes(hashes) -} - -func SimpleHashFromBytes(bz []byte) []byte { - hasher := tmhash.New() - hasher.Write(bz) + panic(err) + } return hasher.Sum(nil) } +// SimpleHashFromHashers computes a Merkle tree from items that can be hashed. func SimpleHashFromHashers(items []Hasher) []byte { hashes := make([][]byte, len(items)) for i, item := range items { hash := item.Hash() hashes[i] = hash } - return SimpleHashFromHashes(hashes) + return simpleHashFromHashes(hashes) } +// SimpleHashFromMap computes a Merkle tree from sorted map. +// Like calling SimpleHashFromHashers with +// `item = []byte(Hash(key) | Hash(value))`, +// sorted by `item`. func SimpleHashFromMap(m map[string]Hasher) []byte { - sm := NewSimpleMap() + sm := newSimpleMap() for k, v := range m { sm.Set(k, v) } return sm.Hash() } + +//---------------------------------------------------------------- + +// Expects hashes! +func simpleHashFromHashes(hashes [][]byte) []byte { + // Recursive impl. + switch len(hashes) { + case 0: + return nil + case 1: + return hashes[0] + default: + left := simpleHashFromHashes(hashes[:(len(hashes)+1)/2]) + right := simpleHashFromHashes(hashes[(len(hashes)+1)/2:]) + return SimpleHashFromTwoHashes(left, right) + } +} diff --git a/merkle/simple_tree_test.go b/merkle/simple_tree_test.go index 8c4ed01f8..db8e3d7ff 100644 --- a/merkle/simple_tree_test.go +++ b/merkle/simple_tree_test.go @@ -7,6 +7,7 @@ import ( . "github.com/tendermint/tmlibs/test" "testing" + "github.com/tendermint/go-crypto/tmhash" ) type testItem []byte @@ -21,7 +22,7 @@ func TestSimpleProof(t *testing.T) { items := make([]Hasher, total) for i := 0; i < total; i++ { - items[i] = testItem(cmn.RandBytes(32)) + items[i] = testItem(cmn.RandBytes(tmhash.Size)) } rootHash := SimpleHashFromHashers(items) diff --git a/merkle/types.go b/merkle/types.go index a0c491a7e..ddc420659 100644 --- a/merkle/types.go +++ b/merkle/types.go @@ -5,6 +5,7 @@ import ( "io" ) +// Tree is a Merkle tree interface. type Tree interface { Size() (size int) Height() (height int8) @@ -23,6 +24,7 @@ type Tree interface { IterateRange(start []byte, end []byte, ascending bool, fx func(key []byte, value []byte) (stop bool)) (stopped bool) } +// Hasher represents a hashable piece of data which can be hashed in the Tree. type Hasher interface { Hash() []byte } diff --git a/tmhash/hash.go b/tmhash/hash.go index 9f684ce01..1b29d8680 100644 --- a/tmhash/hash.go +++ b/tmhash/hash.go @@ -5,7 +5,7 @@ import ( "hash" ) -var ( +const ( Size = 20 BlockSize = sha256.BlockSize ) From 862d3c342a03861c74cfbc649439cc81ceff5396 Mon Sep 17 00:00:00 2001 From: Liamsi Date: Wed, 30 May 2018 17:28:20 +0100 Subject: [PATCH 19/22] commit doc.go --- merkle/doc.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 merkle/doc.go diff --git a/merkle/doc.go b/merkle/doc.go new file mode 100644 index 000000000..da65dd858 --- /dev/null +++ b/merkle/doc.go @@ -0,0 +1,31 @@ +/* +Package merkle computes a deterministic minimal height Merkle tree hash. +If the number of items is not a power of two, some leaves +will be at different levels. Tries to keep both sides of +the tree the same size, but the left may be one greater. + +Use this for short deterministic trees, such as the validator list. +For larger datasets, use IAVLTree. + +Be aware that the current implementation by itself does not prevent +second pre-image attacks. Hence, use this library with caution. +Otherwise you might run into similar issues as, e.g., in early Bitcoin: +https://bitcointalk.org/?topic=102395 + + * + / \ + / \ + / \ + / \ + * * + / \ / \ + / \ / \ + / \ / \ + * * * h6 + / \ / \ / \ + h0 h1 h2 h3 h4 h5 + +TODO(ismail): add 2nd pre-image protection or clarify further on how we use this and why this secure. + +*/ +package merkle \ No newline at end of file From 52bd867fd918513e7cab9d57cf4fdf9c78396a03 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 30 May 2018 17:50:17 -0400 Subject: [PATCH 20/22] merkle: use amino for byteslice encoding --- merkle/simple_map.go | 15 +++++++-------- merkle/simple_map_test.go | 4 +--- merkle/types.go | 19 ++++--------------- 3 files changed, 12 insertions(+), 26 deletions(-) diff --git a/merkle/simple_map.go b/merkle/simple_map.go index b073ecdd6..cde5924f4 100644 --- a/merkle/simple_map.go +++ b/merkle/simple_map.go @@ -1,8 +1,8 @@ package merkle import ( - cmn "github.com/tendermint/tmlibs/common" "github.com/tendermint/go-crypto/tmhash" + cmn "github.com/tendermint/tmlibs/common" ) // Merkle tree from a map. @@ -65,21 +65,20 @@ func (sm *simpleMap) KVPairs() cmn.KVPairs { //---------------------------------------- // A local extension to KVPair that can be hashed. -// XXX: key and value do not need to already be hashed - -// the kvpair ("abc", "def") would not give the same result -// as ("ab", "cdef") as we're using length-prefixing. +// Key and value are length prefixed and concatenated, +// then hashed. type kvPair cmn.KVPair func (kv kvPair) Hash() []byte { hasher := tmhash.New() err := encodeByteSlice(hasher, kv.Key) if err != nil { - panic(err) - } + panic(err) + } err = encodeByteSlice(hasher, kv.Value) if err != nil { - panic(err) - } + panic(err) + } return hasher.Sum(nil) } diff --git a/merkle/simple_map_test.go b/merkle/simple_map_test.go index 9d0c25a2a..a89289a8f 100644 --- a/merkle/simple_map_test.go +++ b/merkle/simple_map_test.go @@ -11,9 +11,7 @@ import ( type strHasher string func (str strHasher) Hash() []byte { - h := tmhash.New() - h.Write([]byte(str)) - return h.Sum(nil) + return tmhash.Sum([]byte(str)) } func TestSimpleMap(t *testing.T) { diff --git a/merkle/types.go b/merkle/types.go index ddc420659..2fcb3f39d 100644 --- a/merkle/types.go +++ b/merkle/types.go @@ -1,8 +1,9 @@ package merkle import ( - "encoding/binary" "io" + + amino "github.com/tendermint/go-amino" ) // Tree is a Merkle tree interface. @@ -30,20 +31,8 @@ type Hasher interface { } //----------------------------------------------------------------------- -// NOTE: these are duplicated from go-amino so we dont need go-amino as a dep +// Uvarint length prefixed byteslice func encodeByteSlice(w io.Writer, bz []byte) (err error) { - err = encodeUvarint(w, uint64(len(bz))) - if err != nil { - return - } - _, err = w.Write(bz) - return -} - -func encodeUvarint(w io.Writer, i uint64) (err error) { - var buf [10]byte - n := binary.PutUvarint(buf[:], i) - _, err = w.Write(buf[0:n]) - return + return amino.EncodeByteSlice(w, bz) } From 20fdec6c0efd1d1387e4177ab82efea67b2dadf1 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 30 May 2018 18:34:11 -0400 Subject: [PATCH 21/22] fix comment --- keys/keybase.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keys/keybase.go b/keys/keybase.go index 876eef8f5..dd9726478 100644 --- a/keys/keybase.go +++ b/keys/keybase.go @@ -159,7 +159,7 @@ func (kb dbKeybase) Import(name string, armor string) (err error) { return nil } -// ExportPubKey imports ASCII-armored public keys. +// ImportPubKey imports ASCII-armored public keys. // Store a new Info object holding a public key only, i.e. it will // not be possible to sign with it as it lacks the secret key. func (kb dbKeybase) ImportPubKey(name string, armor string) (err error) { From e1ce3ffe0fd100816e597d842ee6e476dd320b9c Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 30 May 2018 20:48:52 -0400 Subject: [PATCH 22/22] changelog and version --- CHANGELOG.md | 30 ++++++++++++++++++++++++++++++ version.go | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91dcfab52..5db3a6109 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,35 @@ # Changelog +## 0.7.0 + +**May 30th, 2018** + +BREAKING CHANGES + +No breaking changes compared to 0.6.2, but making up for the version bump that +should have happened in 0.6.1. + +We also bring in the `tmlibs/merkle` package with breaking changes: + +- change the hash function from RIPEMD160 to tmhash (first 20-bytes of SHA256) +- remove unused funcs and unexport SimpleMap + +FEATURES + +- [xchacha20poly1305] New authenticated encryption module +- [merkle] Moved in from tmlibs +- [merkle/tmhash] New hash function: the first 20-bytes of SHA256 + +IMPROVEMENTS + +- Remove some dead code +- Use constant-time compare for signatures + +BUG FIXES + +- Fix MixEntropy weakness +- Fix PrivKeyEd25519.Generate() + ## 0.6.2 (April 9, 2018) IMPROVEMENTS diff --git a/version.go b/version.go index aac87c4f3..4cf5685a5 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package crypto -const Version = "0.6.2" +const Version = "0.7.0"