From 7c6761d4e1e8425523d407fcb7a143eb89c447bb Mon Sep 17 00:00:00 2001 From: ssilagadze Date: Thu, 7 Sep 2023 21:38:05 +0200 Subject: [PATCH] add building/decoding merkle path binary from/to merkle path json data --- merklepathbinary.go | 72 ++++++++++++++++++++++++++++++++++++++++ merklepathbinary_test.go | 59 ++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 merklepathbinary.go create mode 100644 merklepathbinary_test.go diff --git a/merklepathbinary.go b/merklepathbinary.go new file mode 100644 index 0000000..36c0971 --- /dev/null +++ b/merklepathbinary.go @@ -0,0 +1,72 @@ +package bc + +import ( + "encoding/hex" + + "github.com/libsv/go-bt/v2" +) + +// Merkle path data model json format according to BRC-58 +type MerklePathData struct { + Index uint64 `json:"index"` + Path []string `json:"path"` +} + +// Merkle Path Binary Format according to BRC-71 [index, nLeaves, [leaf0, leaf1, leaf2, ... leafnLeaves-1]] +type MerklePath string + +// Based on merkle path data model builds merkle path binary format +func BuildMerklePathBinary(merklePath *MerklePathData) (MerklePath, error) { + index := bt.VarInt(merklePath.Index) + nLeaves := bt.VarInt(len(merklePath.Path)) + + // first two arguments in merkle path bynary format are index of the transaction and number of leaves + bytes := []byte{} + bytes = append(bytes, index.Bytes()...) + bytes = append(bytes, nLeaves.Bytes()...) + + // now add each leave into the binary path + for _, leave := range merklePath.Path { + // decode hex leave into bytes + leaveBytes, err := hex.DecodeString(leave) + if err != nil { + return "", err + } + + // append leave bytes into binary path + bytes = append(bytes, leaveBytes...) + } + + return MerklePath(hex.EncodeToString(bytes)), nil +} + +// from merkle path binary decodes MerklePathData +func DecodeMerklePathBinary(merklePath MerklePath) (*MerklePathData, error) { + // convert hex to byte array + merklePathBinary, err := hex.DecodeString(string(merklePath)) + if err != nil { + return nil, err + } + + merklePathData := &MerklePathData{} + merklePathData.Path = make([]string, 0) + + // start paring transaction index + var offset int + index, size := bt.NewVarIntFromBytes(merklePathBinary[offset:]) + merklePathData.Index = uint64(index) + offset += size + + // next value in the byte array is nLeaves (number of leaves in merkle path) + nLeaves, size := bt.NewVarIntFromBytes(merklePathBinary[offset:]) + offset += size + + // parse each leaf from the binary path + for k := 0; k < int(nLeaves); k++ { + leaf := merklePathBinary[offset : offset+32] + merklePathData.Path = append(merklePathData.Path, hex.EncodeToString(leaf)) + offset += 32 + } + + return merklePathData, nil +} diff --git a/merklepathbinary_test.go b/merklepathbinary_test.go new file mode 100644 index 0000000..563da9c --- /dev/null +++ b/merklepathbinary_test.go @@ -0,0 +1,59 @@ +package bc_test + +import ( + "testing" + + "github.com/libsv/go-bc" + + "github.com/stretchr/testify/assert" +) + +func TestBuildingMerklePathBinary(t *testing.T) { + t.Parallel() + + // build example merkle path data + merklePathData := bc.MerklePathData{ + Index: 136, + Path: []string{"0c82025f47b31054e9ad52109ef25b00fd9aaae7153564619bab031d4112f56c", + "3b6ea708d7b84a078179b53cf2cb2f0636162ffd60a96f81815564bbc6c073cd", + "efac0f077fca2a10730400da62ebaebaba852bd5fc3fb7770e090a1919d9c8b4", + "1e81e396da7f63e3989a8bc9bdbefddf95c75da1eb3936944b6a55cf82d87034"}, + } + + // build binary path from it + merklePathBinary, err := bc.BuildMerklePathBinary(&merklePathData) + if err != nil { + t.Error(err) + return + } + + // assert binary path is expected + assert.Equal(t, bc.MerklePath("88040c82025f47b31054e9ad52109ef25b00fd9aaae7153564619bab031d4112f56c3b6ea708d7b84a078179b53cf2cb2f0636162ffd60a96f81815564bbc6c073cdefac0f077fca2a10730400da62ebaebaba852bd5fc3fb7770e090a1919d9c8b41e81e396da7f63e3989a8bc9bdbefddf95c75da1eb3936944b6a55cf82d87034"), merklePathBinary) +} + +func TestDecodingMerklePathBinary(t *testing.T) { + t.Parallel() + + merklePath := bc.MerklePath("88040c82025f47b31054e9ad52109ef25b00fd9aaae7153564619bab031d4112f56c3b6ea708d7b84a078179b53cf2cb2f0636162ffd60a96f81815564bbc6c073cdefac0f077fca2a10730400da62ebaebaba852bd5fc3fb7770e090a1919d9c8b41e81e396da7f63e3989a8bc9bdbefddf95c75da1eb3936944b6a55cf82d87034") + merklePathData, err := bc.DecodeMerklePathBinary(merklePath) + if err != nil { + t.Error(err) + return + } + + // merklePathData := bc.MerklePathData{ + // Index: 136, + // Path: []string{"0c82025f47b31054e9ad52109ef25b00fd9aaae7153564619bab031d4112f56c", + // "3b6ea708d7b84a078179b53cf2cb2f0636162ffd60a96f81815564bbc6c073cd", + // "efac0f077fca2a10730400da62ebaebaba852bd5fc3fb7770e090a1919d9c8b4", + // "1e81e396da7f63e3989a8bc9bdbefddf95c75da1eb3936944b6a55cf82d87034"}, + // } + + // assert binary path is expected + assert.Equal(t, uint64(136), merklePathData.Index) + assert.Equal(t, 4, len(merklePathData.Path)) + assert.Equal(t, "0c82025f47b31054e9ad52109ef25b00fd9aaae7153564619bab031d4112f56c", merklePathData.Path[0]) + assert.Equal(t, "3b6ea708d7b84a078179b53cf2cb2f0636162ffd60a96f81815564bbc6c073cd", merklePathData.Path[1]) + assert.Equal(t, "efac0f077fca2a10730400da62ebaebaba852bd5fc3fb7770e090a1919d9c8b4", merklePathData.Path[2]) + assert.Equal(t, "1e81e396da7f63e3989a8bc9bdbefddf95c75da1eb3936944b6a55cf82d87034", merklePathData.Path[3]) +}