-
Notifications
You must be signed in to change notification settings - Fork 10
/
merklepath.go
137 lines (115 loc) · 3.75 KB
/
merklepath.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package bc
import (
"encoding/hex"
"github.com/libsv/go-bt/v2"
)
// MerklePath data model json format according to BRC-58.
type MerklePath struct {
Index uint64 `json:"index"`
Path []string `json:"path"`
}
// NewMerklePathFromBytes creates a new MerklePath from a byte slice.
func NewMerklePathFromBytes(bytes []byte) (*MerklePath, error) {
mp := &MerklePath{}
mp.Path = make([]string, 0)
// start paring transaction index.
var offset int
index, size := bt.NewVarIntFromBytes(bytes[offset:])
offset += size
mp.Index = uint64(index)
// next value in the byte array is nLeaves (number of leaves in merkle path).
nLeaves, size := bt.NewVarIntFromBytes(bytes[offset:])
offset += size
// parse each leaf from the binary path
for k := 0; k < int(nLeaves); k++ {
leaf := bytes[offset : offset+32]
mp.Path = append(mp.Path, StringFromBytesReverse(leaf))
offset += 32
}
return mp, nil
}
// NewMerklePathFromStr creates a MerklePath from hex string.
func NewMerklePathFromStr(str string) (*MerklePath, error) {
bytes, err := hex.DecodeString(str)
if err != nil {
return nil, err
}
return NewMerklePathFromBytes(bytes)
}
// Bytes encodes a MerklePath as a slice of bytes. MerklePath Binary Format according to BRC-71 https://brc.dev/71
func (mp *MerklePath) Bytes() ([]byte, error) {
index := bt.VarInt(mp.Index)
nLeaves := bt.VarInt(len(mp.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 leaf into the binary path.
for _, leaf := range mp.Path {
// append leaf bytes into binary path, little endian.
bytes = append(bytes, BytesFromStringReverse(leaf)...)
}
return bytes, nil
}
// String encodes a MerklePath as a hex string.
func (mp *MerklePath) String() (string, error) {
bytes, err := mp.Bytes()
if err != nil {
return "", err
}
return hex.EncodeToString(bytes), nil
}
// CalculateRoot calculates the merkle root from a transaction ID and a MerklePath.
func (mp *MerklePath) CalculateRoot(txid string) (string, error) {
// start with txid
workingHash := BytesFromStringReverse(txid)
lsb := mp.Index
// hash with each path branch
for _, leaf := range mp.Path {
var digest []byte
leafBytes := BytesFromStringReverse(leaf)
// if the least significant bit is 1 then the working hash is on the right.
if lsb&1 > 0 {
digest = append(leafBytes, workingHash...)
} else {
digest = append(workingHash, leafBytes...)
}
workingHash = Sha256Sha256(digest)
lsb = lsb >> 1
}
return StringFromBytesReverse(workingHash), nil
}
// getPathElements traverses the tree and returns the path to Merkle root.
func getPathElements(txIndex int, hashes []string) []string {
// if our hash index is odd the next hash of the path is the previous element in the array otherwise the next element.
var path []string
var hash string
if txIndex%2 == 0 {
hash = hashes[txIndex+1]
} else {
hash = hashes[txIndex-1]
}
// when generating path if the neighbour is empty we append itself
if hash == "" {
path = append(path, hashes[txIndex])
} else {
path = append(path, hash)
}
// If we reach the Merkle root hash stop path calculation.
if len(hashes) == 3 {
return path
}
return append(path, getPathElements(txIndex/2, hashes[(len(hashes)+1)/2:])...)
}
// GetTxMerklePath with merkle tree we calculate the merkle path for a given transaction.
func GetTxMerklePath(txIndex int, merkleTree []string) *MerklePath {
merklePath := &MerklePath{
Index: uint64(txIndex),
Path: nil,
}
// if we have only one transaction in the block there is no merkle path to calculate
if len(merkleTree) != 1 {
merklePath.Path = getPathElements(txIndex, merkleTree)
}
return merklePath
}