From a22c962e28053f9f51d71d3394e0c62bf551fb99 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 31 Oct 2018 12:42:05 -0400 Subject: [PATCH] TMHASH is 32 bytes. Closes #1990 (#2732) * tmhash is fully 32 bytes. closes #1990 * AddressSize * fix tests * fix max sizes --- crypto/crypto.go | 21 +++++++++++++------ crypto/ed25519/ed25519.go | 2 +- crypto/merkle/simple_map_test.go | 12 +++++------ crypto/tmhash/hash.go | 33 ++++++++++++++++++++++-------- crypto/tmhash/hash_test.go | 23 +++++++++++++++++++-- docs/spec/blockchain/blockchain.md | 2 +- docs/spec/blockchain/encoding.md | 7 +++---- docs/spec/blockchain/state.md | 4 ++-- p2p/key.go | 3 +-- state/validation.go | 4 ++-- types/block.go | 2 +- types/block_test.go | 17 +++++++-------- types/evidence.go | 2 +- types/validator.go | 8 ++++---- types/vote.go | 2 +- types/vote_test.go | 5 +++-- 16 files changed, 96 insertions(+), 51 deletions(-) diff --git a/crypto/crypto.go b/crypto/crypto.go index 09c12ff76..2462b0a98 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -1,21 +1,23 @@ package crypto import ( + "github.com/tendermint/tendermint/crypto/tmhash" cmn "github.com/tendermint/tendermint/libs/common" ) -type PrivKey interface { - Bytes() []byte - Sign(msg []byte) ([]byte, error) - PubKey() PubKey - Equals(PrivKey) bool -} +const ( + AddressSize = tmhash.TruncatedSize +) // An address is a []byte, but hex-encoded even in JSON. // []byte leaves us the option to change the address length. // Use an alias so Unmarshal methods (with ptr receivers) are available too. type Address = cmn.HexBytes +func AddressHash(bz []byte) Address { + return Address(tmhash.SumTruncated(bz)) +} + type PubKey interface { Address() Address Bytes() []byte @@ -23,6 +25,13 @@ type PubKey interface { Equals(PubKey) bool } +type PrivKey interface { + Bytes() []byte + Sign(msg []byte) ([]byte, error) + PubKey() PubKey + Equals(PrivKey) bool +} + type Symmetric interface { Keygen() []byte Encrypt(plaintext []byte, secret []byte) (ciphertext []byte) diff --git a/crypto/ed25519/ed25519.go b/crypto/ed25519/ed25519.go index 61872d98d..e077cbda4 100644 --- a/crypto/ed25519/ed25519.go +++ b/crypto/ed25519/ed25519.go @@ -136,7 +136,7 @@ type PubKeyEd25519 [PubKeyEd25519Size]byte // Address is the SHA256-20 of the raw pubkey bytes. func (pubKey PubKeyEd25519) Address() crypto.Address { - return crypto.Address(tmhash.Sum(pubKey[:])) + return crypto.Address(tmhash.SumTruncated(pubKey[:])) } // Bytes marshals the PubKey using amino encoding. diff --git a/crypto/merkle/simple_map_test.go b/crypto/merkle/simple_map_test.go index bc095c003..7abde119d 100644 --- a/crypto/merkle/simple_map_test.go +++ b/crypto/merkle/simple_map_test.go @@ -13,14 +13,14 @@ func TestSimpleMap(t *testing.T) { values []string // each string gets converted to []byte in test want string }{ - {[]string{"key1"}, []string{"value1"}, "fa9bc106ffd932d919bee935ceb6cf2b3dd72d8f"}, - {[]string{"key1"}, []string{"value2"}, "e00e7dcfe54e9fafef5111e813a587f01ba9c3e8"}, + {[]string{"key1"}, []string{"value1"}, "321d150de16dceb51c72981b432b115045383259b1a550adf8dc80f927508967"}, + {[]string{"key1"}, []string{"value2"}, "2a9e4baf321eac99f6eecc3406603c14bc5e85bb7b80483cbfc75b3382d24a2f"}, // swap order with 2 keys - {[]string{"key1", "key2"}, []string{"value1", "value2"}, "eff12d1c703a1022ab509287c0f196130123d786"}, - {[]string{"key2", "key1"}, []string{"value2", "value1"}, "eff12d1c703a1022ab509287c0f196130123d786"}, + {[]string{"key1", "key2"}, []string{"value1", "value2"}, "c4d8913ab543ba26aa970646d4c99a150fd641298e3367cf68ca45fb45a49881"}, + {[]string{"key2", "key1"}, []string{"value2", "value1"}, "c4d8913ab543ba26aa970646d4c99a150fd641298e3367cf68ca45fb45a49881"}, // swap order with 3 keys - {[]string{"key1", "key2", "key3"}, []string{"value1", "value2", "value3"}, "b2c62a277c08dbd2ad73ca53cd1d6bfdf5830d26"}, - {[]string{"key1", "key3", "key2"}, []string{"value1", "value3", "value2"}, "b2c62a277c08dbd2ad73ca53cd1d6bfdf5830d26"}, + {[]string{"key1", "key2", "key3"}, []string{"value1", "value2", "value3"}, "b23cef00eda5af4548a213a43793f2752d8d9013b3f2b64bc0523a4791196268"}, + {[]string{"key1", "key3", "key2"}, []string{"value1", "value3", "value2"}, "b23cef00eda5af4548a213a43793f2752d8d9013b3f2b64bc0523a4791196268"}, } for i, tc := range tests { db := newSimpleMap() diff --git a/crypto/tmhash/hash.go b/crypto/tmhash/hash.go index 1b29d8680..f9b958242 100644 --- a/crypto/tmhash/hash.go +++ b/crypto/tmhash/hash.go @@ -6,10 +6,27 @@ import ( ) const ( - Size = 20 + Size = sha256.Size BlockSize = sha256.BlockSize ) +// New returns a new hash.Hash. +func New() hash.Hash { + return sha256.New() +} + +// Sum returns the SHA256 of the bz. +func Sum(bz []byte) []byte { + h := sha256.Sum256(bz) + return h[:] +} + +//------------------------------------------------------------- + +const ( + TruncatedSize = 20 +) + type sha256trunc struct { sha256 hash.Hash } @@ -19,7 +36,7 @@ func (h sha256trunc) Write(p []byte) (n int, err error) { } func (h sha256trunc) Sum(b []byte) []byte { shasum := h.sha256.Sum(b) - return shasum[:Size] + return shasum[:TruncatedSize] } func (h sha256trunc) Reset() { @@ -27,22 +44,22 @@ func (h sha256trunc) Reset() { } func (h sha256trunc) Size() int { - return Size + return TruncatedSize } func (h sha256trunc) BlockSize() int { return h.sha256.BlockSize() } -// New returns a new hash.Hash. -func New() hash.Hash { +// NewTruncated returns a new hash.Hash. +func NewTruncated() hash.Hash { return sha256trunc{ sha256: sha256.New(), } } -// Sum returns the first 20 bytes of SHA256 of the bz. -func Sum(bz []byte) []byte { +// SumTruncated returns the first 20 bytes of SHA256 of the bz. +func SumTruncated(bz []byte) []byte { hash := sha256.Sum256(bz) - return hash[:Size] + return hash[:TruncatedSize] } diff --git a/crypto/tmhash/hash_test.go b/crypto/tmhash/hash_test.go index 27938039a..89a779801 100644 --- a/crypto/tmhash/hash_test.go +++ b/crypto/tmhash/hash_test.go @@ -14,10 +14,29 @@ func TestHash(t *testing.T) { hasher.Write(testVector) bz := hasher.Sum(nil) + bz2 := tmhash.Sum(testVector) + + hasher = sha256.New() + hasher.Write(testVector) + bz3 := hasher.Sum(nil) + + assert.Equal(t, bz, bz2) + assert.Equal(t, bz, bz3) +} + +func TestHashTruncated(t *testing.T) { + testVector := []byte("abc") + hasher := tmhash.NewTruncated() + hasher.Write(testVector) + bz := hasher.Sum(nil) + + bz2 := tmhash.SumTruncated(testVector) + hasher = sha256.New() hasher.Write(testVector) - bz2 := hasher.Sum(nil) - bz2 = bz2[:20] + bz3 := hasher.Sum(nil) + bz3 = bz3[:tmhash.TruncatedSize] assert.Equal(t, bz, bz2) + assert.Equal(t, bz, bz3) } diff --git a/docs/spec/blockchain/blockchain.md b/docs/spec/blockchain/blockchain.md index 061685378..d96a3c7b8 100644 --- a/docs/spec/blockchain/blockchain.md +++ b/docs/spec/blockchain/blockchain.md @@ -344,7 +344,7 @@ next validator sets Merkle root. ### ConsensusParamsHash ```go -block.ConsensusParamsHash == tmhash(amino(state.ConsensusParams)) +block.ConsensusParamsHash == TMHASH(amino(state.ConsensusParams)) ``` Hash of the amino-encoded consensus parameters. diff --git a/docs/spec/blockchain/encoding.md b/docs/spec/blockchain/encoding.md index 2f9fcdca1..f5120cdd4 100644 --- a/docs/spec/blockchain/encoding.md +++ b/docs/spec/blockchain/encoding.md @@ -176,13 +176,12 @@ greater, for example: h0 h1 h3 h4 h0 h1 h2 h3 h4 h5 ``` -Tendermint always uses the `TMHASH` hash function, which is the first 20-bytes -of the SHA256: +Tendermint always uses the `TMHASH` hash function, which is equivalent to +SHA256: ``` func TMHASH(bz []byte) []byte { - shasum := SHA256(bz) - return shasum[:20] + return SHA256(bz) } ``` diff --git a/docs/spec/blockchain/state.md b/docs/spec/blockchain/state.md index a0badd718..502f9d696 100644 --- a/docs/spec/blockchain/state.md +++ b/docs/spec/blockchain/state.md @@ -56,8 +56,8 @@ type Validator struct { } ``` -When hashing the Validator struct, the pubkey is not hashed, -because the address is already the hash of the pubkey. +When hashing the Validator struct, the address is not included, +because it is redundant with the pubkey. The `state.Validators`, `state.LastValidators`, and `state.NextValidators`, must always by sorted by validator address, so that there is a canonical order for computing the SimpleMerkleRoot. diff --git a/p2p/key.go b/p2p/key.go index 3f38b48a9..fc64f27bb 100644 --- a/p2p/key.go +++ b/p2p/key.go @@ -8,7 +8,6 @@ import ( crypto "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" - "github.com/tendermint/tendermint/crypto/tmhash" cmn "github.com/tendermint/tendermint/libs/common" ) @@ -17,7 +16,7 @@ type ID string // IDByteLength is the length of a crypto.Address. Currently only 20. // TODO: support other length addresses ? -const IDByteLength = tmhash.Size +const IDByteLength = crypto.AddressSize //------------------------------------------------------------------------------ // Persistent peer ID diff --git a/state/validation.go b/state/validation.go index a12919847..345224843 100644 --- a/state/validation.go +++ b/state/validation.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" - "github.com/tendermint/tendermint/crypto/tmhash" + "github.com/tendermint/tendermint/crypto" dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/types" ) @@ -158,7 +158,7 @@ func validateBlock(stateDB dbm.DB, state State, block *types.Block) error { // NOTE: We can't actually verify it's the right proposer because we dont // know what round the block was first proposed. So just check that it's // a legit address and a known validator. - if len(block.ProposerAddress) != tmhash.Size || + if len(block.ProposerAddress) != crypto.AddressSize || !state.Validators.HasAddress(block.ProposerAddress) { return fmt.Errorf( "Block.Header.ProposerAddress, %X, is not a validator", diff --git a/types/block.go b/types/block.go index 477e39997..46ad73a71 100644 --- a/types/block.go +++ b/types/block.go @@ -15,7 +15,7 @@ import ( const ( // MaxHeaderBytes is a maximum header size (including amino overhead). - MaxHeaderBytes int64 = 533 + MaxHeaderBytes int64 = 653 // MaxAminoOverheadForBlock - maximum amino overhead to encode a block (up to // MaxBlockSizeBytes in size) not including it's parts except Data. diff --git a/types/block_test.go b/types/block_test.go index 28e73f661..46881a099 100644 --- a/types/block_test.go +++ b/types/block_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/tmhash" cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/version" @@ -116,7 +117,7 @@ func TestBlockMakePartSetWithEvidence(t *testing.T) { partSet := MakeBlock(h, []Tx{Tx("Hello World")}, commit, evList).MakePartSet(1024) assert.NotNil(t, partSet) - assert.Equal(t, 2, partSet.Total()) + assert.Equal(t, 3, partSet.Total()) } func TestBlockHashesTo(t *testing.T) { @@ -262,7 +263,7 @@ func TestMaxHeaderBytes(t *testing.T) { AppHash: tmhash.Sum([]byte("app_hash")), LastResultsHash: tmhash.Sum([]byte("last_results_hash")), EvidenceHash: tmhash.Sum([]byte("evidence_hash")), - ProposerAddress: tmhash.Sum([]byte("proposer_address")), + ProposerAddress: crypto.AddressHash([]byte("proposer_address")), } bz, err := cdc.MarshalBinaryLengthPrefixed(h) @@ -292,9 +293,9 @@ func TestBlockMaxDataBytes(t *testing.T) { }{ 0: {-10, 1, 0, true, 0}, 1: {10, 1, 0, true, 0}, - 2: {742, 1, 0, true, 0}, - 3: {743, 1, 0, false, 0}, - 4: {744, 1, 0, false, 1}, + 2: {886, 1, 0, true, 0}, + 3: {887, 1, 0, false, 0}, + 4: {888, 1, 0, false, 1}, } for i, tc := range testCases { @@ -320,9 +321,9 @@ func TestBlockMaxDataBytesUnknownEvidence(t *testing.T) { }{ 0: {-10, 1, true, 0}, 1: {10, 1, true, 0}, - 2: {824, 1, true, 0}, - 3: {825, 1, false, 0}, - 4: {826, 1, false, 1}, + 2: {984, 1, true, 0}, + 3: {985, 1, false, 0}, + 4: {986, 1, false, 1}, } for i, tc := range testCases { diff --git a/types/evidence.go b/types/evidence.go index 7a808d57b..d1e15c819 100644 --- a/types/evidence.go +++ b/types/evidence.go @@ -14,7 +14,7 @@ import ( const ( // MaxEvidenceBytes is a maximum size of any evidence (including amino overhead). - MaxEvidenceBytes int64 = 436 + MaxEvidenceBytes int64 = 484 ) // ErrEvidenceInvalid wraps a piece of evidence and the error denoting how or why it is invalid. diff --git a/types/validator.go b/types/validator.go index af3471848..4bfd78a6d 100644 --- a/types/validator.go +++ b/types/validator.go @@ -77,15 +77,15 @@ func (v *Validator) Hash() []byte { } // Bytes computes the unique encoding of a validator with a given voting power. -// These are the bytes that gets hashed in consensus. It excludes pubkey -// as its redundant with the address. This also excludes accum which changes +// These are the bytes that gets hashed in consensus. It excludes address +// as its redundant with the pubkey. This also excludes accum which changes // every round. func (v *Validator) Bytes() []byte { return cdcEncode((struct { - Address Address + PubKey crypto.PubKey VotingPower int64 }{ - v.Address, + v.PubKey, v.VotingPower, })) } diff --git a/types/vote.go b/types/vote.go index 826330d5c..1d7e9cf6f 100644 --- a/types/vote.go +++ b/types/vote.go @@ -12,7 +12,7 @@ import ( const ( // MaxVoteBytes is a maximum vote size (including amino overhead). - MaxVoteBytes int64 = 199 + MaxVoteBytes int64 = 223 ) var ( diff --git a/types/vote_test.go b/types/vote_test.go index 572735858..cda54f898 100644 --- a/types/vote_test.go +++ b/types/vote_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/tmhash" ) @@ -37,7 +38,7 @@ func exampleVote(t byte) *Vote { Hash: tmhash.Sum([]byte("blockID_part_set_header_hash")), }, }, - ValidatorAddress: tmhash.Sum([]byte("validator_address")), + ValidatorAddress: crypto.AddressHash([]byte("validator_address")), ValidatorIndex: 56789, } } @@ -211,7 +212,7 @@ func TestMaxVoteBytes(t *testing.T) { timestamp := time.Date(math.MaxInt64, 0, 0, 0, 0, 0, math.MaxInt64, time.UTC) vote := &Vote{ - ValidatorAddress: tmhash.Sum([]byte("validator_address")), + ValidatorAddress: crypto.AddressHash([]byte("validator_address")), ValidatorIndex: math.MaxInt64, Height: math.MaxInt64, Round: math.MaxInt64,