diff --git a/chaincfg/chainhash/hash.go b/chaincfg/chainhash/hash.go index 2be585ed..4656decf 100644 --- a/chaincfg/chainhash/hash.go +++ b/chaincfg/chainhash/hash.go @@ -84,35 +84,45 @@ func NewHash(newHash []byte) (*Hash, error) { // the hexadecimal string of a byte-reversed hash, but any missing characters // result in zero padding at the end of the Hash. func NewHashFromStr(hash string) (*Hash, error) { - // Return error if hash string is too long. - if len(hash) > MaxHashStringSize { - return nil, ErrHashStrSize - } - - // Hex decoder expects the hash to be a multiple of two. - if len(hash)%2 != 0 { - hash = "0" + hash - } - - // Convert string hash to bytes. - buf, err := hex.DecodeString(hash) + ret := new(Hash) + err := Decode(ret, hash) if err != nil { return nil, err } - - // Un-reverse the decoded bytes, copying into in leading bytes of a - // Hash. There is no need to explicitly pad the result as any - // missing (when len(buf) < HashSize) bytes from the decoded hex string - // will remain zeros at the end of the Hash. - var ret Hash - blen := len(buf) - mid := blen / 2 - if blen%2 != 0 { - mid++ - } - blen-- - for i, b := range buf[:mid] { - ret[i], ret[blen-i] = buf[blen-i], b - } - return &ret, nil + return ret, nil +} + +// Decode decodes the byte-reversed hexadecimal string encoding of a Hash to a +// destination. +func Decode(dst *Hash, src string) error { + // Return error if hash string is too long. + if len(src) > MaxHashStringSize { + return ErrHashStrSize + } + + // Hex decoder expects the hash to be a multiple of two. When not, pad + // with a leading zero. + var srcBytes []byte + if len(src)%2 == 0 { + srcBytes = []byte(src) + } else { + srcBytes = make([]byte, 1+len(src)) + srcBytes[0] = '0' + copy(srcBytes[1:], src) + } + + // Hex decode the source bytes to a temporary destination. + var reversedHash Hash + _, err := hex.Decode(reversedHash[HashSize-hex.DecodedLen(len(srcBytes)):], srcBytes) + if err != nil { + return err + } + + // Reverse copy from the temporary hash to destination. Because the + // temporary was zeroed, the written result will be correctly padded. + for i, b := range reversedHash[:HashSize/2] { + dst[i], dst[HashSize-1-i] = reversedHash[HashSize-1-i], b + } + + return nil } diff --git a/chaincfg/chainhash/hash_test.go b/chaincfg/chainhash/hash_test.go new file mode 100644 index 00000000..d54ebde5 --- /dev/null +++ b/chaincfg/chainhash/hash_test.go @@ -0,0 +1,200 @@ +// Copyright (c) 2013-2016 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package chainhash + +import ( + "bytes" + "encoding/hex" + "testing" +) + +// Note: All test data is taken from the Bitcoin blockchain. This data is +// intentionally unmodified since it would be an unnecessary difference between +// the dcrd and btcd codebases. + +// mainNetGenesisHash is the hash of the first block in the block chain for the +// main network (genesis block). +var mainNetGenesisHash = Hash([HashSize]byte{ // Make go vet happy. + 0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72, + 0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f, + 0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c, + 0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, +}) + +// TestHash tests the Hash API. +func TestHash(t *testing.T) { + // Hash of block 234439. + blockHashStr := "14a0810ac680a3eb3f82edc878cea25ec41d6b790744e5daeef" + blockHash, err := NewHashFromStr(blockHashStr) + if err != nil { + t.Errorf("NewHashFromStr: %v", err) + } + + // Hash of block 234440 as byte slice. + buf := []byte{ + 0x79, 0xa6, 0x1a, 0xdb, 0xc6, 0xe5, 0xa2, 0xe1, + 0x39, 0xd2, 0x71, 0x3a, 0x54, 0x6e, 0xc7, 0xc8, + 0x75, 0x63, 0x2e, 0x75, 0xf1, 0xdf, 0x9c, 0x3f, + 0xa6, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + } + + hash, err := NewHash(buf) + if err != nil { + t.Errorf("NewHash: unexpected error %v", err) + } + + // Ensure proper size. + if len(hash) != HashSize { + t.Errorf("NewHash: hash length mismatch - got: %v, want: %v", + len(hash), HashSize) + } + + // Ensure contents match. + if !bytes.Equal(hash[:], buf) { + t.Errorf("NewHash: hash contents mismatch - got: %v, want: %v", + hash[:], buf) + } + + // Ensure contents of hash of block 234440 don't match 234439. + if hash.IsEqual(blockHash) { + t.Errorf("IsEqual: hash contents should not match - got: %v, want: %v", + hash, blockHash) + } + + // Set hash from byte slice and ensure contents match. + err = hash.SetBytes(blockHash.Bytes()) + if err != nil { + t.Errorf("SetBytes: %v", err) + } + if !hash.IsEqual(blockHash) { + t.Errorf("IsEqual: hash contents mismatch - got: %v, want: %v", + hash, blockHash) + } + + // Ensure nil hashes are handled properly. + if !(*Hash)(nil).IsEqual(nil) { + t.Error("IsEqual: nil hashes should match") + } + if hash.IsEqual(nil) { + t.Error("IsEqual: non-nil hash matches nil hash") + } + + // Invalid size for SetBytes. + err = hash.SetBytes([]byte{0x00}) + if err == nil { + t.Errorf("SetBytes: failed to received expected err - got: nil") + } + + // Invalid size for NewHash. + invalidHash := make([]byte, HashSize+1) + _, err = NewHash(invalidHash) + if err == nil { + t.Errorf("NewHash: failed to received expected err - got: nil") + } +} + +// TestHashString tests the stringized output for hashes. +func TestHashString(t *testing.T) { + // Block 100000 hash. + wantStr := "000000000003ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506" + hash := Hash([HashSize]byte{ // Make go vet happy. + 0x06, 0xe5, 0x33, 0xfd, 0x1a, 0xda, 0x86, 0x39, + 0x1f, 0x3f, 0x6c, 0x34, 0x32, 0x04, 0xb0, 0xd2, + 0x78, 0xd4, 0xaa, 0xec, 0x1c, 0x0b, 0x20, 0xaa, + 0x27, 0xba, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + }) + + hashStr := hash.String() + if hashStr != wantStr { + t.Errorf("String: wrong hash string - got %v, want %v", + hashStr, wantStr) + } +} + +// TestNewHashFromStr executes tests against the NewHashFromStr function. +func TestNewHashFromStr(t *testing.T) { + tests := []struct { + in string + want Hash + err error + }{ + // Genesis hash. + { + "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", + mainNetGenesisHash, + nil, + }, + + // Genesis hash with stripped leading zeros. + { + "19d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", + mainNetGenesisHash, + nil, + }, + + // Empty string. + { + "", + Hash{}, + nil, + }, + + // Single digit hash. + { + "1", + Hash([HashSize]byte{ // Make go vet happy. + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }), + nil, + }, + + // Block 203707 with stripped leading zeros. + { + "3264bc2ac36a60840790ba1d475d01367e7c723da941069e9dc", + Hash([HashSize]byte{ // Make go vet happy. + 0xdc, 0xe9, 0x69, 0x10, 0x94, 0xda, 0x23, 0xc7, + 0xe7, 0x67, 0x13, 0xd0, 0x75, 0xd4, 0xa1, 0x0b, + 0x79, 0x40, 0x08, 0xa6, 0x36, 0xac, 0xc2, 0x4b, + 0x26, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }), + nil, + }, + + // Hash string that is too long. + { + "01234567890123456789012345678901234567890123456789012345678912345", + Hash{}, + ErrHashStrSize, + }, + + // Hash string that is contains non-hex chars. + { + "abcdefg", + Hash{}, + hex.InvalidByteError('g'), + }, + } + + unexpectedErrStr := "NewHashFromStr #%d failed to detect expected error - got: %v want: %v" + unexpectedResultStr := "NewHashFromStr #%d got: %v want: %v" + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + result, err := NewHashFromStr(test.in) + if err != test.err { + t.Errorf(unexpectedErrStr, i, err, test.err) + continue + } else if err != nil { + // Got expected error. Move on to the next test. + continue + } + if !test.want.IsEqual(result) { + t.Errorf(unexpectedResultStr, i, result, &test.want) + continue + } + } +}