diff --git a/transaction/merkletreeparent.go b/transaction/merkletreeparent.go new file mode 100644 index 0000000..38ef4d2 --- /dev/null +++ b/transaction/merkletreeparent.go @@ -0,0 +1,61 @@ +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 MerkleTree children using hex strings instead of 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 MerkleTree children. +func MerkleTreeParent(leftNode, rightNode []byte) []byte { + concatenated := flipTwoArrays(leftNode, rightNode) + + hash := crypto.Sha256d(concatenated) + + util.ReverseBytesInPlace(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. +// The expectation is that the bytes are not reversed. +func MerkleTreeParentBytes(l *chainhash.Hash, r *chainhash.Hash) *chainhash.Hash { + 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{} + } + 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) +} 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)