From 7152c408b122cc82f4ab45e91064b85b093f4fec Mon Sep 17 00:00:00 2001 From: Potuz Date: Mon, 1 May 2023 13:17:32 -0300 Subject: [PATCH 1/3] Expose a hashing function that takes byteslices --- hash.go | 40 ++++++++++++++++++++++++++++- hash_test.go | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 109 insertions(+), 2 deletions(-) diff --git a/hash.go b/hash.go index 797bc10..2da64c0 100644 --- a/hash.go +++ b/hash.go @@ -1,7 +1,7 @@ /* MIT License -Copyright (c) 2021 Prysmatic Labs +# Copyright (c) 2021 Prysmatic Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -25,9 +25,18 @@ package gohashtree import ( "fmt" + "reflect" + "unsafe" ) func _hash(digests *byte, p [][32]byte, count uint32) +func _hashByteSlice(digests *byte, p []byte, count uint32) { + header := *(*reflect.SliceHeader)(unsafe.Pointer(&p)) + header.Len /= 32 + header.Cap /= 32 + chunks := *(*[][32]byte)(unsafe.Pointer(&header)) + _hash(digests, chunks, count) +} func Hash(digests [][32]byte, chunks [][32]byte) error { if len(chunks) == 0 { @@ -51,3 +60,32 @@ func Hash(digests [][32]byte, chunks [][32]byte) error { func HashChunks(digests [][32]byte, chunks [][32]byte) { _hash(&digests[0][0], chunks, uint32(len(chunks)/2)) } + +func HashByteSlice(digests []byte, chunks []byte) error { + if len(chunks) == 0 { + return nil + } + if len(chunks)%64 != 0 { + return fmt.Errorf("chunks not multiple of 64 bytes") + } + if len(digests)%32 != 0 { + return fmt.Errorf("digests not multiple of 32 bytes") + } + if len(digests) < len(chunks)/2 { + return fmt.Errorf("not enough digest length, need at least %d, got %d", len(chunks)/2, len(digests)) + } + if supportedCPU { + _hashByteSlice(&digests[0], chunks, uint32(len(chunks)/64)) + } else { + chunkedDigest := make([][32]byte, len(digests)/32) + for i := 0; i < len(chunkedDigest); i++ { + copy(chunkedDigest[i][:], digests[32*i:32*i+32]) + } + chunkedChunks := make([][32]byte, len(chunks)/32) + for i := 0; i < len(chunkedChunks); i++ { + copy(chunkedChunks[i][:], chunks[32*i:32*i+32]) + } + sha256_1_generic(chunkedDigest, chunkedChunks) + } + return nil +} diff --git a/hash_test.go b/hash_test.go index ffaffe3..53dbb69 100644 --- a/hash_test.go +++ b/hash_test.go @@ -1,7 +1,7 @@ /* MIT License -Copyright (c) 2021 Prysmatic Labs +# Copyright (c) 2021 Prysmatic Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -199,6 +199,75 @@ func TestHash(t *testing.T) { } } +func TestHashByteSlice(t *testing.T) { + tests := []struct { + name string + count uint32 + }{ + { + name: "hash 1 block", + count: 1, + }, + { + name: "hash 4 blocks", + count: 4, + }, + { + name: "hash 8 blocks", + count: 8, + }, + { + name: "hash 16 blocks", + count: 16, + }, + { + name: "hash 18 blocks", + count: 18, + }, + { + name: "hash 24 blocks", + count: 24, + }, + { + name: "hash 32 blocks", + count: 32, + }, + { + name: "hash 31 blocks", + count: 31, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + digests := make([]byte, 32*tt.count) + chunks := make([]byte, 64*tt.count) + for i := 0; i < int(2*tt.count); i += 2 { + if n := copy(chunks[32*i:32*i+32], _test_32_block[i][:]); n != 32 { + t.Logf("copied wrong number of bytes") + t.Fail() + } + if n := copy(chunks[32*i+32:32*i+64], _test_32_block[i+1][:]); n != 32 { + t.Logf("copied wrong number of bytes") + t.Fail() + } + } + + err := gohashtree.HashByteSlice(digests, chunks) + if err != nil { + t.Log(err) + t.Fail() + } + for i := 0; i < int(tt.count); i++ { + if !reflect.DeepEqual(digests[32*i:32*i+32], _test_32_digests[i][:]) { + t.Logf("Digests are different\n Expected: %x\n Produced: %x\n", + _test_32_digests[i][:], digests[32*i:32*i+32]) + t.Fail() + } + } + }) + } +} + func TestOddChunks(t *testing.T) { digests := make([][32]byte, 1) chunks := make([][32]byte, 1) From aa3a313e72475b177316bc08f9b8fe6a5c748f22 Mon Sep 17 00:00:00 2001 From: Potuz Date: Mon, 1 May 2023 13:25:43 -0300 Subject: [PATCH 2/3] use unsafe pointers in body --- hash.go | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/hash.go b/hash.go index 2da64c0..e67b81a 100644 --- a/hash.go +++ b/hash.go @@ -30,13 +30,6 @@ import ( ) func _hash(digests *byte, p [][32]byte, count uint32) -func _hashByteSlice(digests *byte, p []byte, count uint32) { - header := *(*reflect.SliceHeader)(unsafe.Pointer(&p)) - header.Len /= 32 - header.Cap /= 32 - chunks := *(*[][32]byte)(unsafe.Pointer(&header)) - _hash(digests, chunks, count) -} func Hash(digests [][32]byte, chunks [][32]byte) error { if len(chunks) == 0 { @@ -74,17 +67,18 @@ func HashByteSlice(digests []byte, chunks []byte) error { if len(digests) < len(chunks)/2 { return fmt.Errorf("not enough digest length, need at least %d, got %d", len(chunks)/2, len(digests)) } + header := *(*reflect.SliceHeader)(unsafe.Pointer(&chunks)) + header.Len <<= 5 + header.Cap <<= 5 + chunkedChunks := *(*[][32]byte)(unsafe.Pointer(&header)) + if supportedCPU { - _hashByteSlice(&digests[0], chunks, uint32(len(chunks)/64)) + _hash(&digests[0], chunkedChunks, uint32(len(chunks)/64)) } else { - chunkedDigest := make([][32]byte, len(digests)/32) - for i := 0; i < len(chunkedDigest); i++ { - copy(chunkedDigest[i][:], digests[32*i:32*i+32]) - } - chunkedChunks := make([][32]byte, len(chunks)/32) - for i := 0; i < len(chunkedChunks); i++ { - copy(chunkedChunks[i][:], chunks[32*i:32*i+32]) - } + headerDigest := *(*reflect.SliceHeader)(unsafe.Pointer(&digests)) + headerDigest.Len <<= 5 + headerDigest.Cap <<= 5 + chunkedDigest := *(*[][32]byte)(unsafe.Pointer(&headerDigest)) sha256_1_generic(chunkedDigest, chunkedChunks) } return nil From d41b010d7c59bd1147ff079a019a52f2e4819839 Mon Sep 17 00:00:00 2001 From: Potuz Date: Mon, 1 May 2023 21:35:35 -0300 Subject: [PATCH 3/3] Preston's review --- hash.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hash.go b/hash.go index e67b81a..9223c27 100644 --- a/hash.go +++ b/hash.go @@ -67,6 +67,8 @@ func HashByteSlice(digests []byte, chunks []byte) error { if len(digests) < len(chunks)/2 { return fmt.Errorf("not enough digest length, need at least %d, got %d", len(chunks)/2, len(digests)) } + // We use an unsafe pointer to cast []byte to [][32]byte. The length and + // capacity of the slice need to be divided accordingly by 32. header := *(*reflect.SliceHeader)(unsafe.Pointer(&chunks)) header.Len <<= 5 header.Cap <<= 5