From 3b268558d8d9de79dbfd1e01505a3dc84149740c Mon Sep 17 00:00:00 2001 From: WeebDataHoarder <57538841+WeebDataHoarder@users.noreply.github.com> Date: Tue, 23 May 2023 15:33:54 +0200 Subject: [PATCH] Split and cleanup merkle tree for transactions --- monero/block/block.go | 75 +++-------------------------------------- monero/crypto/merkle.go | 57 +++++++++++++++++++++++++++++++ utils/number.go | 8 +++++ utils/number_test.go | 20 ++++++++++- 4 files changed, 88 insertions(+), 72 deletions(-) create mode 100644 monero/crypto/merkle.go diff --git a/monero/block/block.go b/monero/block/block.go index 37cbf9b..5b1ce15 100644 --- a/monero/block/block.go +++ b/monero/block/block.go @@ -10,7 +10,6 @@ import ( "git.gammaspectra.live/P2Pool/p2pool-observer/monero/randomx" "git.gammaspectra.live/P2Pool/p2pool-observer/monero/transaction" "git.gammaspectra.live/P2Pool/p2pool-observer/types" - "git.gammaspectra.live/P2Pool/sha3" "io" ) @@ -244,7 +243,10 @@ func (b *Block) HashingBlobBufferLength() int { func (b *Block) HashingBlob(preAllocatedBuf []byte) []byte { buf := b.HeaderBlob(preAllocatedBuf) - txTreeHash := b.TxTreeHash() + merkleTree := make(crypto.BinaryTreeHash, len(b.Transactions)+1) + merkleTree[0] = b.Coinbase.Id() + copy(merkleTree[1:], b.Transactions) + txTreeHash := merkleTree.RootHash() buf = append(buf, txTreeHash[:]...) buf = binary.AppendUvarint(buf, uint64(len(b.Transactions)+1)) @@ -252,69 +254,6 @@ func (b *Block) HashingBlob(preAllocatedBuf []byte) []byte { return buf } -func (b *Block) TxTreeHash() (rootHash types.Hash) { - //TODO: cache - //transaction hashes - h := make([]byte, 0, types.HashSize*len(b.Transactions)+types.HashSize) - coinbaseTxId := b.Coinbase.Id() - - h = append(h, coinbaseTxId[:]...) - for _, txId := range b.Transactions { - h = append(h, txId[:]...) - } - - count := len(b.Transactions) + 1 - if count == 1 { - rootHash = types.HashFromBytes(h) - } else if count == 2 { - rootHash = crypto.PooledKeccak256(h) - } else { - hashInstance := crypto.GetKeccak256Hasher() - defer crypto.PutKeccak256Hasher(hashInstance) - var cnt int - - { - //TODO: expand this loop properly - //find closest low power of two - for cnt = 1; cnt <= count; cnt <<= 1 { - } - cnt >>= 1 - } - - ints := make([]byte, cnt*types.HashSize) - copy(ints, h[:(cnt*2-count)*types.HashSize]) - - { - i := cnt*2 - count - j := cnt*2 - count - for j < cnt { - keccakl(hashInstance, ints[j*types.HashSize:], h[i*types.HashSize:], types.HashSize*2) - i += 2 - j++ - } - } - - for cnt > 2 { - cnt >>= 1 - { - i := 0 - j := 0 - - for j < cnt { - keccakl(hashInstance, ints[j*types.HashSize:], ints[i*types.HashSize:], types.HashSize*2) - - i += 2 - j++ - } - } - } - - keccakl(hashInstance, rootHash[:], ints, types.HashSize*2) - } - - return -} - func (b *Block) Difficulty(f GetDifficultyByHeightFunc) types.Difficulty { //cached by sidechain.Share return f(b.Coinbase.GenHeight) @@ -335,9 +274,3 @@ func (b *Block) Id() types.Hash { buf := b.HashingBlob(make([]byte, 0, b.HashingBlobBufferLength())) return crypto.PooledKeccak256(varIntBuf[:binary.PutUvarint(varIntBuf[:], uint64(len(buf)))], buf) } - -func keccakl(hasher *sha3.HasherState, dst []byte, data []byte, len int) { - hasher.Reset() - _, _ = hasher.Write(data[:len]) - crypto.HashFastSum(hasher, dst) -} diff --git a/monero/crypto/merkle.go b/monero/crypto/merkle.go new file mode 100644 index 0000000..3ad7e31 --- /dev/null +++ b/monero/crypto/merkle.go @@ -0,0 +1,57 @@ +package crypto + +import ( + "git.gammaspectra.live/P2Pool/p2pool-observer/types" + "git.gammaspectra.live/P2Pool/p2pool-observer/utils" + "git.gammaspectra.live/P2Pool/sha3" +) + +type BinaryTreeHash []types.Hash + +func (t BinaryTreeHash) leafHash(hasher *sha3.HasherState) (rootHash types.Hash) { + switch len(t) { + case 0: + panic("unsupported length") + case 1: + return t[0] + case 2: + hasher.Reset() + _, _ = hasher.Write(t[0][:]) + _, _ = hasher.Write(t[1][:]) + HashFastSum(hasher, rootHash[:]) + return rootHash + default: + panic("unsupported length") + } +} + +func (t BinaryTreeHash) RootHash() (rootHash types.Hash) { + + hasher := GetKeccak256Hasher() + defer PutKeccak256Hasher(hasher) + count := len(t) + if count <= 2 { + return t.leafHash(hasher) + } + + cnt := utils.PreviousPowerOfTwo(uint64(len(t))) + + temporaryTree := make(BinaryTreeHash, cnt) + copy(temporaryTree, t[:cnt*2-count]) + + offset := cnt*2 - count + for i := 0; (i + offset) < cnt; i++ { + temporaryTree[offset+i] = t[offset+i*2 : offset+i*2+2].leafHash(hasher) + } + + for cnt > 2 { + cnt >>= 1 + for i := 0; i < cnt; i++ { + temporaryTree[i] = temporaryTree[i*2 : i*2+2].leafHash(hasher) + } + } + + rootHash = temporaryTree[:2].leafHash(hasher) + + return +} diff --git a/utils/number.go b/utils/number.go index 5a3adfd..4c9b2fe 100644 --- a/utils/number.go +++ b/utils/number.go @@ -4,6 +4,7 @@ import ( "encoding/hex" "github.com/jxskiss/base62" "golang.org/x/exp/constraints" + "math/bits" "strconv" "strings" ) @@ -83,3 +84,10 @@ func Max[T constraints.Ordered](v0 T, values ...T) (result T) { } return } + +func PreviousPowerOfTwo(x uint64) int { + if x == 0 { + return 0 + } + return 1 << (64 - bits.LeadingZeros64(x) - 1) +} diff --git a/utils/number_test.go b/utils/number_test.go index dc77c26..07c3804 100644 --- a/utils/number_test.go +++ b/utils/number_test.go @@ -1,6 +1,8 @@ package utils -import "testing" +import ( + "testing" +) func TestNumber(t *testing.T) { s := "S" @@ -10,3 +12,19 @@ func TestNumber(t *testing.T) { t.Fail() } } + +func TestPreviousPowerOfTwo(t *testing.T) { + loopPath := func(x uint64) int { + //find closest low power of two + var cnt uint64 + for cnt = 1; cnt <= x; cnt <<= 1 { + } + cnt >>= 1 + return int(cnt) + } + for i := uint64(1); i < 65536; i++ { + if PreviousPowerOfTwo(i) != loopPath(i) { + t.Fatalf("expected %d, got %d for iteration %d", loopPath(i), PreviousPowerOfTwo(i), i) + } + } +}