-
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathbob.go
272 lines (236 loc) · 6.28 KB
/
bob.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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
package aip
import (
"encoding/hex"
"errors"
"strconv"
"strings"
ec "github.com/bitcoin-sv/go-sdk/primitives/ec"
"github.com/bitcoinschema/go-bpu"
)
// NewFromTape will create a new AIP object from a bob.Tape
// Using the FromTape() alone will prevent validation (data is needed via SetData to enable)
func NewFromTape(tape bpu.Tape) (a *Aip) {
a = new(Aip)
a.FromTape(tape)
return
}
// FromTape takes a BOB Tape and returns an Aip data structure.
// Using the FromTape() alone will prevent validation (data is needed via SetData to enable)
func (a *Aip) FromTape(tape bpu.Tape) {
// Not a valid tape?
if len(tape.Cell) < 4 {
return
}
// Loop to find start of AIP
var startIndex int
found := false
for i, cell := range tape.Cell {
if *cell.S == Prefix {
startIndex = i
found = true
break
}
}
if !found {
return
}
// Set the AIP fields
if tape.Cell[startIndex+1].S != nil {
a.Algorithm = Algorithm(*tape.Cell[startIndex+1].S)
}
if tape.Cell[startIndex+2].S != nil {
a.AlgorithmSigningComponent = *tape.Cell[startIndex+2].S
}
if tape.Cell[startIndex+3].B != nil {
a.Signature = *tape.Cell[startIndex+3].B
}
// Final index count
finalIndexCount := startIndex + 4
// Store the indices
if len(tape.Cell) > finalIndexCount {
// TODO: Consider OP_RETURN is included in sig when processing a tx using indices
// Loop over remaining indices if they exist and append to indices slice
a.Indices = make([]int, len(tape.Cell)-finalIndexCount)
for x := finalIndexCount - 1; x < len(tape.Cell); x++ {
if tape.Cell[x].S == nil {
continue
}
if index, err := strconv.Atoi(*tape.Cell[x].S); err == nil {
a.Indices = append(a.Indices, index)
}
}
}
}
// NewFromTapes will create a new AIP object from a []bob.Tape
// Using the FromTapes() alone will prevent validation (data is needed via SetData to enable)
func NewFromTapes(tapes []bpu.Tape) (a *Aip) {
// Loop tapes -> cells (only supporting 1 sig right now)
for _, t := range tapes {
for _, cell := range t.Cell {
if cell.S != nil && *cell.S == Prefix {
a = new(Aip)
a.FromTape(t)
a.SetDataFromTapes(tapes, 0)
return
}
}
}
return
}
// SetDataFromTapes sets the data the AIP signature is signing
func (a *Aip) SetDataFromTapes(tapes []bpu.Tape, instance int) {
// Set OP_RETURN to be consistent with BitcoinFiles SDK
// var data [][]byte
var data = []string{opReturn}
var foundAIP bool
var aipTapeIndex int
var aipCellIndex int
// First find the AIP tape and cell index
aipCount := 0
for i, tape := range tapes {
for j, cell := range tape.Cell {
if cell.S != nil && *cell.S == Prefix {
if aipCount == instance {
aipTapeIndex = i
aipCellIndex = j
foundAIP = true
break
}
aipCount++
}
}
if foundAIP {
break
}
}
// If we found AIP, collect data from all tapes up to the AIP tape
if foundAIP {
if len(a.Indices) == 0 {
// Walk over all output values and concatenate them until we hit the AIP prefix, then add in the separator
for i, tape := range tapes {
for j, cell := range tape.Cell {
if cell.S != nil && *cell.S == Prefix {
data = append(data, pipe)
a.Data = data
if i == aipTapeIndex && j >= aipCellIndex {
return
}
}
// Skip the OPS
// if cell.Ops != nil {
if cell.Op != nil && (*cell.Op == 0 || *cell.Op > 0x4e) {
continue
}
if cell.S != nil {
data = append(data, strings.TrimSpace(*cell.S))
}
}
}
} else {
var indexCt = 0
for _, tape := range tapes {
for _, cell := range tape.Cell {
if cell.S != nil && *cell.S != Prefix && contains(a.Indices, indexCt) {
data = append(data, *cell.S)
} else {
data = append(data, pipe)
}
indexCt++
}
}
}
a.Data = data
}
}
// SignBobOpReturnData appends a signature to a BOB Tx by adding a
// protocol separator followed by AIP information
func SignBobOpReturnData(privateKey *ec.PrivateKey, algorithm Algorithm, output bpu.Output) (*bpu.Output, *Aip, error) {
// Parse the data to sign
var dataToSign []string
for _, tape := range output.Tape {
for _, cell := range tape.Cell {
if cell.S != nil {
dataToSign = append(dataToSign, *cell.S)
} else {
// TODO: Review this case. Should we assume the b64 is signed?
// Should protocol doc for AIP mention this?
if cell.B != nil {
dataToSign = append(dataToSign, *cell.B)
}
// else if cell.Op != nil {
// dataToSign = append(dataToSign, string(*cell.Op))
// }
}
}
}
// Sign the data
a, err := Sign(privateKey, algorithm, strings.Join(dataToSign, ""))
if err != nil {
return nil, nil, err
}
algoHex := hex.EncodeToString([]byte(algorithm))
algoStr := string(algorithm)
hexAlgoSigningComponent := hex.EncodeToString([]byte(a.AlgorithmSigningComponent))
hexSig := hex.EncodeToString([]byte(a.Signature))
// Create the output tape
output.Tape = append(output.Tape, bpu.Tape{
Cell: []bpu.Cell{{
H: &hexPrefix,
S: &Prefix,
}, {
H: &algoHex,
S: &algoStr,
}, {
H: &hexAlgoSigningComponent,
S: &a.AlgorithmSigningComponent,
}, {
H: &hexSig,
S: &a.Signature,
}},
})
return &output, a, nil
}
// ValidateTapes validates the AIP signature for a given []bob.Tape
func ValidateTapes(tapes []bpu.Tape) (bool, error) {
// Loop tapes -> cells (only supporting 1 sig right now)
for _, tape := range tapes {
for _, cell := range tape.Cell {
// Once we hit AIP Prefix, stop
if cell.S != nil && *cell.S == Prefix {
a := NewFromTape(tape)
a.SetDataFromTapes(tapes, 0)
return a.Validate()
}
}
}
return false, errors.New("no AIP tape found")
}
// contains looks in a slice for a given value
func contains(s []int, e int) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}
// NewFromAllTapes will create all AIP objects from a []bob.Tape
func NewFromAllTapes(tapes []bpu.Tape) []*Aip {
var aips []*Aip
// Find all tapes that contain the AIP prefix
instance := 0
for i, t := range tapes {
for _, cell := range t.Cell {
if cell.S != nil && *cell.S == Prefix {
a := new(Aip)
a.FromTape(t)
// For all AIP entries, include all data from the start up to this entry
a.SetDataFromTapes(tapes[:i+1], instance)
instance++
aips = append(aips, a)
continue
}
}
}
return aips
}