@@ -20,17 +20,32 @@ package ledger
20
20
21
21
import (
22
22
"bytes"
23
+ "encoding/binary"
23
24
"encoding/hex"
24
25
"fmt"
25
- "strconv"
26
-
27
26
"github.com/blinklabs-io/gouroboros/cbor"
27
+ _cbor "github.com/fxamacker/cbor/v2"
28
28
"golang.org/x/crypto/blake2b"
29
+ "math"
30
+ "reflect"
31
+ "strconv"
32
+ )
33
+
34
+ const (
35
+ maxAdditionalInformationWithoutArgument = 23
36
+ additionalInformationWith1ByteArgument = 24
37
+ additionalInformationWith2ByteArgument = 25
38
+ additionalInformationWith4ByteArgument = 26
39
+ additionalInformationWith8ByteArgument = 27
29
40
)
30
41
31
42
const (
32
43
LOVELACE_TOKEN = "lovelace"
33
44
BLOCK_BODY_HASH_ZERO_TX_HEX = "29571d16f081709b3c48651860077bebf9340abb3fc7133443c54f1f5a5edcf1"
45
+ MAX_LIST_LENGTH_CBOR = 23
46
+ CBOR_TYPE_MAP = 0xa0
47
+ CBOR_TYPE_MAP_INDEF = 0xbf
48
+ CBOR_BREAK_TAG = 0xff
34
49
)
35
50
36
51
func VerifyBlockBody (data string , blockBodyHash string ) (bool , error ) {
@@ -74,11 +89,204 @@ func VerifyBlockBody(data string, blockBodyHash string) (bool, error) {
74
89
return bytes .Equal (calculateBlockBodyHashByte [:32 ], blockBodyHashByte ), nil
75
90
}
76
91
92
+ func CustomTagSet () _cbor.TagSet {
93
+ customTagSet := _cbor .NewTagSet ()
94
+ tagOpts := _cbor.TagOptions {
95
+ EncTag : _cbor .EncTagRequired ,
96
+ DecTag : _cbor .DecTagRequired ,
97
+ }
98
+ // Wrapped CBOR
99
+ if err := customTagSet .Add (
100
+ tagOpts ,
101
+ reflect .TypeOf (cbor.WrappedCbor {}),
102
+ cbor .CborTagCbor ,
103
+ ); err != nil {
104
+ panic (err )
105
+ }
106
+ // Rational numbers
107
+ if err := customTagSet .Add (
108
+ tagOpts ,
109
+ reflect .TypeOf (cbor.Rat {}),
110
+ cbor .CborTagRational ,
111
+ ); err != nil {
112
+ panic (err )
113
+ }
114
+ // Sets
115
+ if err := customTagSet .Add (
116
+ tagOpts ,
117
+ reflect .TypeOf (cbor.Set {}),
118
+ cbor .CborTagSet ,
119
+ ); err != nil {
120
+ panic (err )
121
+ }
122
+ // Maps
123
+ if err := customTagSet .Add (
124
+ tagOpts ,
125
+ reflect .TypeOf (cbor.Map {}),
126
+ cbor .CborTagMap ,
127
+ ); err != nil {
128
+ panic (err )
129
+ }
130
+
131
+ return customTagSet
132
+ }
133
+
134
+ func GetEncMode () (_cbor.EncMode , error ) {
135
+ opts := _cbor.EncOptions {
136
+ Sort : _cbor .SortNone ,
137
+ }
138
+ customTagSet := CustomTagSet ()
139
+ em , err := opts .EncModeWithTags (customTagSet )
140
+ if err != nil {
141
+ return nil , err
142
+ }
143
+ return em , nil
144
+ }
145
+
146
+ func EncodeCborList (data []cbor.RawMessage ) ([]byte , error ) {
147
+ // Cardano base consider list more than 23 will be ListLenIndef
148
+ // https://github.com/IntersectMBO/cardano-base/blob/e86a25c54389ddd0f77fdbc3f3615c57bd91d543/cardano-binary/src/Cardano/Binary/ToCBOR.hs#L708C10-L708C28
149
+ if len (data ) <= MAX_LIST_LENGTH_CBOR {
150
+ return cbor .Encode (data )
151
+ }
152
+ buf := bytes .NewBuffer (nil )
153
+ em , err := GetEncMode ()
154
+ if err != nil {
155
+ return nil , err
156
+ }
157
+ enc := em .NewEncoder (buf )
158
+
159
+ if err := enc .StartIndefiniteArray (); err != nil {
160
+ return nil , err
161
+ }
162
+ for _ , item := range data {
163
+ err = enc .Encode (item )
164
+ if err != nil {
165
+ return nil , err
166
+ }
167
+ }
168
+ if err := enc .EndIndefinite (); err != nil {
169
+ return nil , err
170
+ }
171
+ return buf .Bytes (), err
172
+ }
173
+
174
+ func EncodeCborTxSeq (data []uint ) ([]byte , error ) {
175
+ // Cardano base consider list more than 23 will be ListLenIndef
176
+ // https://github.com/IntersectMBO/cardano-base/blob/e86a25c54389ddd0f77fdbc3f3615c57bd91d543/cardano-binary/src/Cardano/Binary/ToCBOR.hs#L708C10-L708C28
177
+
178
+ if len (data ) <= MAX_LIST_LENGTH_CBOR {
179
+ return cbor .Encode (data )
180
+ }
181
+ buf := bytes .NewBuffer (nil )
182
+ em , err := GetEncMode ()
183
+ if err != nil {
184
+ return nil , err
185
+ }
186
+ enc := em .NewEncoder (buf )
187
+
188
+ if err := enc .StartIndefiniteArray (); err != nil {
189
+ return nil , err
190
+ }
191
+ for _ , item := range data {
192
+ err = enc .Encode (item )
193
+ if err != nil {
194
+ return nil , err
195
+ }
196
+ }
197
+ if err := enc .EndIndefinite (); err != nil {
198
+ return nil , err
199
+ }
200
+ return buf .Bytes (), err
201
+ }
202
+
203
+ type AuxData struct {
204
+ index uint64
205
+ data []byte
206
+ }
207
+
208
+ // encodeHead writes CBOR head of specified type t and returns number of bytes written.
209
+ // copy from https://github.com/fxamacker/cbor/blob/46c3919161ecd1beff1b80867e08efb37d43f27c/encode.go#L1728
210
+ func encodeHead (e * bytes.Buffer , t byte , n uint64 ) int {
211
+ if n <= maxAdditionalInformationWithoutArgument {
212
+ const headSize = 1
213
+ e .WriteByte (t | byte (n ))
214
+ return headSize
215
+ }
216
+
217
+ if n <= math .MaxUint8 {
218
+ const headSize = 2
219
+ scratch := [headSize ]byte {
220
+ t | byte (additionalInformationWith1ByteArgument ),
221
+ byte (n ),
222
+ }
223
+ e .Write (scratch [:])
224
+ return headSize
225
+ }
226
+
227
+ if n <= math .MaxUint16 {
228
+ const headSize = 3
229
+ var scratch [headSize ]byte
230
+ scratch [0 ] = t | byte (additionalInformationWith2ByteArgument )
231
+ binary .BigEndian .PutUint16 (scratch [1 :], uint16 (n ))
232
+ e .Write (scratch [:])
233
+ return headSize
234
+ }
235
+
236
+ if n <= math .MaxUint32 {
237
+ const headSize = 5
238
+ var scratch [headSize ]byte
239
+ scratch [0 ] = t | byte (additionalInformationWith4ByteArgument )
240
+ binary .BigEndian .PutUint32 (scratch [1 :], uint32 (n ))
241
+ e .Write (scratch [:])
242
+ return headSize
243
+ }
244
+
245
+ const headSize = 9
246
+ var scratch [headSize ]byte
247
+ scratch [0 ] = t | byte (additionalInformationWith8ByteArgument )
248
+ binary .BigEndian .PutUint64 (scratch [1 :], n )
249
+ e .Write (scratch [:])
250
+ return headSize
251
+ }
252
+
253
+ // EncodeCborMap manual build aux bytes data
254
+ func EncodeCborMap (data []AuxData ) ([]byte , error ) {
255
+ dataLen := len (data )
256
+ if dataLen == 0 {
257
+ txSeqMetadata := make (map [uint64 ]interface {})
258
+ return cbor .Encode (txSeqMetadata )
259
+ }
260
+ var dataBuffer bytes.Buffer
261
+ var dataBytes []byte
262
+ if dataLen <= MAX_LIST_LENGTH_CBOR {
263
+ encodeHead (& dataBuffer , byte (CBOR_TYPE_MAP ), uint64 (dataLen ))
264
+ dataBytes = dataBuffer .Bytes ()
265
+ } else {
266
+ dataBytes = []byte {
267
+ uint8 (CBOR_TYPE_MAP_INDEF ),
268
+ }
269
+ }
270
+
271
+ for _ , aux := range data {
272
+ dataIndex := aux .index
273
+ dataValue := aux .data
274
+ indexBytes , _ := cbor .Encode (dataIndex )
275
+ dataBytes = append (dataBytes , indexBytes ... )
276
+ dataBytes = append (dataBytes , dataValue ... )
277
+ }
278
+ if dataLen > MAX_LIST_LENGTH_CBOR {
279
+ dataBytes = append (dataBytes , CBOR_BREAK_TAG )
280
+ }
281
+
282
+ return dataBytes , nil
283
+ }
284
+
77
285
func CalculateBlockBodyHash (txsRaw [][]string ) ([]byte , error ) {
78
- var txSeqBody []cbor.RawMessage
79
- var txSeqWit []cbor.RawMessage
80
- txSeqMetadata := make (map [ uint64 ] interface {}, len ( txsRaw ) )
81
- txSeqNonValid := []uint {}
286
+ txSeqBody := make ( []cbor.RawMessage , 0 )
287
+ txSeqWit := make ( []cbor.RawMessage , 0 )
288
+ auxRawData := make ([] AuxData , 0 )
289
+ txSeqNonValid := make ( []uint , 0 )
82
290
for index , tx := range txsRaw {
83
291
if len (tx ) != 3 {
84
292
return nil , fmt .Errorf (
@@ -120,22 +328,14 @@ func CalculateBlockBodyHash(txsRaw [][]string) ([]byte, error) {
120
328
auxBytesError .Error (),
121
329
)
122
330
}
123
-
124
- var auxInterface interface {}
125
- _ , auxDecodeError := cbor .Decode (auxBytes , & auxInterface )
126
- if auxDecodeError != nil {
127
- return nil , fmt .Errorf (
128
- "CalculateBlockBodyHash: decode aux tx[%v] error, %v" ,
129
- index ,
130
- auxDecodeError .Error (),
131
- )
132
- }
133
-
134
- txSeqMetadata [uint64 (index )] = auxInterface
331
+ auxRawData = append (auxRawData , AuxData {
332
+ index : uint64 (index ),
333
+ data : auxBytes ,
334
+ })
135
335
}
136
336
// TODO: should form nonValid TX here
137
337
}
138
- txSeqBodyBytes , txSeqBodyBytesError := cbor . Encode (txSeqBody )
338
+ txSeqBodyBytes , txSeqBodyBytesError := EncodeCborList (txSeqBody )
139
339
if txSeqBodyBytesError != nil {
140
340
return nil , fmt .Errorf (
141
341
"CalculateBlockBodyHash: encode txSeqBody error, %v" ,
@@ -146,7 +346,7 @@ func CalculateBlockBodyHash(txsRaw [][]string) ([]byte, error) {
146
346
txSeqBodySum32Bytes := blake2b .Sum256 (txSeqBodyBytes )
147
347
txSeqBodySumBytes := txSeqBodySum32Bytes [:]
148
348
149
- txSeqWitsBytes , txSeqWitsBytesError := cbor . Encode (txSeqWit )
349
+ txSeqWitsBytes , txSeqWitsBytesError := EncodeCborList (txSeqWit )
150
350
if txSeqWitsBytesError != nil {
151
351
return nil , fmt .Errorf (
152
352
"CalculateBlockBodyHash: encode txSeqWit error, %v" ,
@@ -156,7 +356,7 @@ func CalculateBlockBodyHash(txsRaw [][]string) ([]byte, error) {
156
356
txSeqWitsSum32Bytes := blake2b .Sum256 (txSeqWitsBytes )
157
357
txSeqWitsSumBytes := txSeqWitsSum32Bytes [:]
158
358
159
- txSeqMetadataBytes , txSeqMetadataBytesError := cbor . Encode ( txSeqMetadata )
359
+ txSeqMetadataBytes , txSeqMetadataBytesError := EncodeCborMap ( auxRawData )
160
360
if txSeqMetadataBytesError != nil {
161
361
return nil , fmt .Errorf (
162
362
"CalculateBlockBodyHash: encode txSeqMetadata error, %v" ,
@@ -166,7 +366,7 @@ func CalculateBlockBodyHash(txsRaw [][]string) ([]byte, error) {
166
366
txSeqMetadataSum32Bytes := blake2b .Sum256 (txSeqMetadataBytes )
167
367
txSeqMetadataSumBytes := txSeqMetadataSum32Bytes [:]
168
368
169
- txSeqNonValidBytes , txSeqNonValidBytesError := cbor . Encode (txSeqNonValid )
369
+ txSeqNonValidBytes , txSeqNonValidBytesError := EncodeCborTxSeq (txSeqNonValid )
170
370
if txSeqNonValidBytesError != nil {
171
371
return nil , fmt .Errorf (
172
372
"CalculateBlockBodyHash: encode txSeqNonValid error, %v" ,
0 commit comments