From 6bb8110e397fc77c6591571041f0951a06358f50 Mon Sep 17 00:00:00 2001 From: wregulski Date: Thu, 17 Oct 2024 09:16:29 +0200 Subject: [PATCH 1/5] feat: add merkletreeparent util functions --- transaction/merkletreeparent.go | 54 ++++++++++++++++++++++++++++ transaction/merkletreeparent_test.go | 32 +++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 transaction/merkletreeparent.go create mode 100644 transaction/merkletreeparent_test.go diff --git a/transaction/merkletreeparent.go b/transaction/merkletreeparent.go new file mode 100644 index 0000000..6d495d4 --- /dev/null +++ b/transaction/merkletreeparent.go @@ -0,0 +1,54 @@ +package transaction + +import ( + "encoding/hex" + + "github.com/bitcoin-sv/go-sdk/chainhash" + crypto "github.com/bitcoin-sv/go-sdk/primitives/hash" + "github.com/bitcoin-sv/go-sdk/util" +) + +// MerkleTreeParentStr returns the Merkle Tree parent of two Merkle +// Tree children using hex strings instead of just bytes. +func MerkleTreeParentStr(leftNode, rightNode string) (string, error) { + l, err := hex.DecodeString(leftNode) + if err != nil { + return "", err + } + r, err := hex.DecodeString(rightNode) + if err != nil { + return "", err + } + + return hex.EncodeToString(MerkleTreeParent(l, r)), nil +} + +// MerkleTreeParent returns the Merkle Tree parent of two Merkle +// Tree children. +func MerkleTreeParent(leftNode, rightNode []byte) []byte { + // swap endianness before concatenating + l := util.ReverseBytes(leftNode) + r := util.ReverseBytes(rightNode) + + // concatenate leaves + concat := append(l, r...) + + // hash the concatenation + hash := crypto.Sha256d(concat) + + // swap endianness at the end and convert to hex string + return util.ReverseBytes(hash) +} + +// MerkleTreeParentBytes returns the Merkle Tree parent of two Merkle Tree children. +// The expectation is that the bytes are not reversed. +func MerkleTreeParentBytes(l *chainhash.Hash, r *chainhash.Hash) *chainhash.Hash { + lb := l.CloneBytes() + rb := r.CloneBytes() + concat := append(lb, rb...) + hash, err := chainhash.NewHash(crypto.Sha256d(concat)) + if err != nil { + return &chainhash.Hash{} + } + return hash +} diff --git a/transaction/merkletreeparent_test.go b/transaction/merkletreeparent_test.go new file mode 100644 index 0000000..d2857de --- /dev/null +++ b/transaction/merkletreeparent_test.go @@ -0,0 +1,32 @@ +package transaction_test + +import ( + "encoding/hex" + "testing" + + "github.com/bitcoin-sv/go-sdk/transaction" + "github.com/stretchr/testify/require" +) + +func TestGetMerkleTreeParentStr(t *testing.T) { + leftNode := "d6c79a6ef05572f0cb8e9a450c561fc40b0a8a7d48faad95e20d93ddeb08c231" + rightNode := "b1ed931b79056438b990d8981ba46fae97e5574b142445a74a44b978af284f98" + + expected := "b0d537b3ee52e472507f453df3d69561720346118a5a8c4d85ca0de73bc792be" + + parent, err := transaction.MerkleTreeParentStr(leftNode, rightNode) + + require.NoError(t, err) + require.Equal(t, expected, parent) +} + +func TestGetMerkleTreeParent(t *testing.T) { + leftNode, _ := hex.DecodeString("d6c79a6ef05572f0cb8e9a450c561fc40b0a8a7d48faad95e20d93ddeb08c231") + rightNode, _ := hex.DecodeString("b1ed931b79056438b990d8981ba46fae97e5574b142445a74a44b978af284f98") + + expected, _ := hex.DecodeString("b0d537b3ee52e472507f453df3d69561720346118a5a8c4d85ca0de73bc792be") + + parent := transaction.MerkleTreeParent(leftNode, rightNode) + + require.Equal(t, expected, parent) +} From c0cb4f3efbfa1d77628c4f2c34e617d3a11d0514 Mon Sep 17 00:00:00 2001 From: wregulski Date: Thu, 17 Oct 2024 10:43:13 +0200 Subject: [PATCH 2/5] fix: reformatted comments to be one liners --- transaction/merkletreeparent.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/transaction/merkletreeparent.go b/transaction/merkletreeparent.go index 6d495d4..632ff41 100644 --- a/transaction/merkletreeparent.go +++ b/transaction/merkletreeparent.go @@ -8,8 +8,7 @@ import ( "github.com/bitcoin-sv/go-sdk/util" ) -// MerkleTreeParentStr returns the Merkle Tree parent of two Merkle -// Tree children using hex strings instead of just bytes. +// MerkleTreeParentStr returns the Merkle Tree parent of two Merkle Tree children using hex strings instead of just bytes. func MerkleTreeParentStr(leftNode, rightNode string) (string, error) { l, err := hex.DecodeString(leftNode) if err != nil { @@ -23,8 +22,7 @@ func MerkleTreeParentStr(leftNode, rightNode string) (string, error) { return hex.EncodeToString(MerkleTreeParent(l, r)), nil } -// MerkleTreeParent returns the Merkle Tree parent of two Merkle -// Tree children. +// MerkleTreeParent returns the Merkle Tree parent of two MerkleTree children. func MerkleTreeParent(leftNode, rightNode []byte) []byte { // swap endianness before concatenating l := util.ReverseBytes(leftNode) From ef522dfcf3d6d4b410666cf2af4fa4257ce7765e Mon Sep 17 00:00:00 2001 From: wregulski Date: Thu, 17 Oct 2024 11:14:00 +0200 Subject: [PATCH 3/5] fix: performance upgrade for merkletree utils methods --- transaction/merkletreeparent.go | 29 +++++++++++++++++++---------- util/bytemanipulation.go | 7 +++++++ 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/transaction/merkletreeparent.go b/transaction/merkletreeparent.go index 632ff41..06d999f 100644 --- a/transaction/merkletreeparent.go +++ b/transaction/merkletreeparent.go @@ -8,7 +8,7 @@ import ( "github.com/bitcoin-sv/go-sdk/util" ) -// MerkleTreeParentStr returns the Merkle Tree parent of two Merkle Tree children using hex strings instead of just bytes. +// MerkleTreeParentStr returns the Merkle Tree parent of two MerkleTree children using hex strings instead of just bytes. func MerkleTreeParentStr(leftNode, rightNode string) (string, error) { l, err := hex.DecodeString(leftNode) if err != nil { @@ -24,18 +24,27 @@ func MerkleTreeParentStr(leftNode, rightNode string) (string, error) { // MerkleTreeParent returns the Merkle Tree parent of two MerkleTree children. func MerkleTreeParent(leftNode, rightNode []byte) []byte { - // swap endianness before concatenating - l := util.ReverseBytes(leftNode) - r := util.ReverseBytes(rightNode) + concatenated := flipTwoArrays(leftNode, rightNode) - // concatenate leaves - concat := append(l, r...) + hash := crypto.Sha256d(concatenated) - // hash the concatenation - hash := crypto.Sha256d(concat) + util.ReverseBytesInPlace(hash) - // swap endianness at the end and convert to hex string - return util.ReverseBytes(hash) + return hash +} + +// flipTwoArrays reverses two byte arrays individually and returns as one concatenated slice +// example: +// for a=[a, b, c], b=[d, e, f] the result is [c, b, a, f, e, d] +func flipTwoArrays(a, b []byte) []byte { + result := make([]byte, 0, len(a)+len(b)) + for i := len(a) - 1; i >= 0; i-- { + result = append(result, a[i]) + } + for i := len(b) - 1; i >= 0; i-- { + result = append(result, b[i]) + } + return result } // MerkleTreeParentBytes returns the Merkle Tree parent of two Merkle Tree children. diff --git a/util/bytemanipulation.go b/util/bytemanipulation.go index a652f9b..e3ec243 100644 --- a/util/bytemanipulation.go +++ b/util/bytemanipulation.go @@ -14,6 +14,13 @@ func ReverseBytes(a []byte) []byte { return tmp } +// ReverseBytesInPlace reverses the bytes (little endian/big endian) in place (no extra memory allocation). +func ReverseBytesInPlace(a []byte) { + for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 { + a[i], a[j] = a[j], a[i] + } +} + // LittleEndianBytes returns a byte array in little endian from an unsigned integer of 32 bytes. func LittleEndianBytes(v uint32, l uint32) []byte { buf := make([]byte, 4) From d44961e52905393437401e213bd950dc43b83965 Mon Sep 17 00:00:00 2001 From: wregulski Date: Thu, 17 Oct 2024 11:32:36 +0200 Subject: [PATCH 4/5] fix: resolve performance issues for MerkleTreeParentBytes --- transaction/merkletreeparent.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/transaction/merkletreeparent.go b/transaction/merkletreeparent.go index 06d999f..f7cd8c6 100644 --- a/transaction/merkletreeparent.go +++ b/transaction/merkletreeparent.go @@ -50,10 +50,10 @@ func flipTwoArrays(a, b []byte) []byte { // MerkleTreeParentBytes returns the Merkle Tree parent of two Merkle Tree children. // The expectation is that the bytes are not reversed. func MerkleTreeParentBytes(l *chainhash.Hash, r *chainhash.Hash) *chainhash.Hash { - lb := l.CloneBytes() - rb := r.CloneBytes() - concat := append(lb, rb...) - hash, err := chainhash.NewHash(crypto.Sha256d(concat)) + concatenated := make([]byte, len(l)+len(r)) + copy(concatenated, l[:]) + copy(concatenated[len(l):], r[:]) + hash, err := chainhash.NewHash(crypto.Sha256d(concatenated)) if err != nil { return &chainhash.Hash{} } From df67deb569b9d72103d2e463ee372123929a0292 Mon Sep 17 00:00:00 2001 From: wregulski Date: Thu, 17 Oct 2024 13:19:58 +0200 Subject: [PATCH 5/5] fix: shorten comment on MerkleTreeParentStr to pass linter --- transaction/merkletreeparent.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transaction/merkletreeparent.go b/transaction/merkletreeparent.go index f7cd8c6..38ef4d2 100644 --- a/transaction/merkletreeparent.go +++ b/transaction/merkletreeparent.go @@ -8,7 +8,7 @@ import ( "github.com/bitcoin-sv/go-sdk/util" ) -// MerkleTreeParentStr returns the Merkle Tree parent of two MerkleTree children using hex strings instead of just bytes. +// MerkleTreeParentStr returns the Merkle Tree parent of two MerkleTree children using hex strings instead of bytes. func MerkleTreeParentStr(leftNode, rightNode string) (string, error) { l, err := hex.DecodeString(leftNode) if err != nil {