@marcuspuchalla/nachos 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +64 -0
- package/LICENSE +674 -0
- package/README.md +345 -0
- package/dist/chunk-2FUTHZQQ.cjs +755 -0
- package/dist/chunk-2FUTHZQQ.cjs.map +1 -0
- package/dist/chunk-2HBCILJS.cjs +2034 -0
- package/dist/chunk-2HBCILJS.cjs.map +1 -0
- package/dist/chunk-7CFYWHS6.js +742 -0
- package/dist/chunk-7CFYWHS6.js.map +1 -0
- package/dist/chunk-PD72MVTX.cjs +160 -0
- package/dist/chunk-PD72MVTX.cjs.map +1 -0
- package/dist/chunk-ZDZ2B5PE.js +149 -0
- package/dist/chunk-ZDZ2B5PE.js.map +1 -0
- package/dist/chunk-ZRPJUEIZ.js +2020 -0
- package/dist/chunk-ZRPJUEIZ.js.map +1 -0
- package/dist/encoder/index.cjs +57 -0
- package/dist/encoder/index.cjs.map +1 -0
- package/dist/encoder/index.d.cts +72 -0
- package/dist/encoder/index.d.ts +72 -0
- package/dist/encoder/index.js +4 -0
- package/dist/encoder/index.js.map +1 -0
- package/dist/index.cjs +606 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +494 -0
- package/dist/index.d.ts +494 -0
- package/dist/index.js +523 -0
- package/dist/index.js.map +1 -0
- package/dist/metafile-cjs.json +1 -0
- package/dist/metafile-esm.json +1 -0
- package/dist/parser/index.cjs +85 -0
- package/dist/parser/index.cjs.map +1 -0
- package/dist/parser/index.d.cts +72 -0
- package/dist/parser/index.d.ts +72 -0
- package/dist/parser/index.js +4 -0
- package/dist/parser/index.js.map +1 -0
- package/dist/types-DvNlfbKB.d.cts +301 -0
- package/dist/types-DvNlfbKB.d.ts +301 -0
- package/dist/useCborSimpleEncoder-ButVU988.d.cts +268 -0
- package/dist/useCborSimpleEncoder-TVxzNJ_9.d.ts +268 -0
- package/dist/useCborTag-B_iaShG6.d.ts +142 -0
- package/dist/useCborTag-BfTIV8HM.d.cts +142 -0
- package/package.json +102 -0
- package/src/__tests__/public-api.test.ts +326 -0
- package/src/encoder/__tests__/cbor-collection-encoder.test.ts +331 -0
- package/src/encoder/__tests__/cbor-integer-encoder.test.ts +283 -0
- package/src/encoder/__tests__/cbor-simple-encoder.test.ts +224 -0
- package/src/encoder/__tests__/cbor-string-encoder.test.ts +345 -0
- package/src/encoder/__tests__/cbor-tag-encoder.test.ts +565 -0
- package/src/encoder/composables/#useCborTagEncoder.ts# +158 -0
- package/src/encoder/composables/useCborCollectionEncoder.ts +424 -0
- package/src/encoder/composables/useCborEncoder.ts +203 -0
- package/src/encoder/composables/useCborIntegerEncoder.ts +188 -0
- package/src/encoder/composables/useCborSimpleEncoder.ts +266 -0
- package/src/encoder/composables/useCborStringEncoder.ts +266 -0
- package/src/encoder/composables/useCborTagEncoder.ts +158 -0
- package/src/encoder/index.ts +35 -0
- package/src/encoder/types.ts +88 -0
- package/src/encoder/utils.ts +80 -0
- package/src/index.ts +434 -0
- package/src/parser/__tests__/ast-tree-structure.test.ts +311 -0
- package/src/parser/__tests__/cbor-collection-errors.test.ts +296 -0
- package/src/parser/__tests__/cbor-collection.test.ts +369 -0
- package/src/parser/__tests__/cbor-deterministic-encoding.test.ts +432 -0
- package/src/parser/__tests__/cbor-diagnostic.test.ts +333 -0
- package/src/parser/__tests__/cbor-duplicate-keys.test.ts +235 -0
- package/src/parser/__tests__/cbor-float-errors.test.ts +222 -0
- package/src/parser/__tests__/cbor-float.test.ts +502 -0
- package/src/parser/__tests__/cbor-integer-errors.test.ts +139 -0
- package/src/parser/__tests__/cbor-integer.test.ts +200 -0
- package/src/parser/__tests__/cbor-map-duplicate-keys.test.ts +403 -0
- package/src/parser/__tests__/cbor-parser-errors.test.ts +126 -0
- package/src/parser/__tests__/cbor-security-dos-protection.test.ts +503 -0
- package/src/parser/__tests__/cbor-sequences.test.ts +150 -0
- package/src/parser/__tests__/cbor-source-map.test.ts +321 -0
- package/src/parser/__tests__/cbor-standard-tags.test.ts +340 -0
- package/src/parser/__tests__/cbor-string-errors.test.ts +227 -0
- package/src/parser/__tests__/cbor-string.test.ts +224 -0
- package/src/parser/__tests__/cbor-tag-advanced.test.ts +500 -0
- package/src/parser/__tests__/cbor-tag-errors.test.ts +447 -0
- package/src/parser/__tests__/cbor-tag-source-map.test.ts +360 -0
- package/src/parser/__tests__/cbor-tag.test.ts +684 -0
- package/src/parser/__tests__/extreme-edge-cases.test.ts +146 -0
- package/src/parser/__tests__/pathBuilder.test.ts +256 -0
- package/src/parser/__tests__/rfc-test-vectors.test.ts +607 -0
- package/src/parser/__tests__/security-limits.test.ts +248 -0
- package/src/parser/__tests__/utils-errors.test.ts +127 -0
- package/src/parser/composables/useCborCollection.ts +509 -0
- package/src/parser/composables/useCborDiagnostic.ts +381 -0
- package/src/parser/composables/useCborFloat.ts +256 -0
- package/src/parser/composables/useCborInteger.ts +114 -0
- package/src/parser/composables/useCborParser.ts +951 -0
- package/src/parser/composables/useCborString.ts +330 -0
- package/src/parser/composables/useCborStringTypes.ts +129 -0
- package/src/parser/composables/useCborTag.ts +739 -0
- package/src/parser/index.ts +56 -0
- package/src/parser/types.ts +371 -0
- package/src/parser/utils/pathBuilder.ts +259 -0
- package/src/parser/utils.ts +398 -0
- package/src/utils/__tests__/logger.test.ts +186 -0
- package/src/utils/logger.ts +96 -0
|
@@ -0,0 +1,565 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CBOR Tag Encoder Tests
|
|
3
|
+
* Tests encoding of tagged values (Major Type 6)
|
|
4
|
+
*
|
|
5
|
+
* Tests cover:
|
|
6
|
+
* - Standard CBOR tags (0-24)
|
|
7
|
+
* - Plutus constructor tags (121-127, 1280-1400, 102)
|
|
8
|
+
* - Custom application tags
|
|
9
|
+
* - Nested tagged values
|
|
10
|
+
* - Tagged values in collections
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { describe, it, expect } from 'vitest'
|
|
14
|
+
import { useCborEncoder } from '../composables/useCborEncoder'
|
|
15
|
+
import { useCborParser } from '../../parser/composables/useCborParser'
|
|
16
|
+
|
|
17
|
+
describe('useCborEncoder - Tag Encoding', () => {
|
|
18
|
+
const { encode } = useCborEncoder()
|
|
19
|
+
|
|
20
|
+
describe('Standard CBOR Tags', () => {
|
|
21
|
+
it('should encode tag 0 (datetime string)', () => {
|
|
22
|
+
const tagged = {
|
|
23
|
+
tag: 0,
|
|
24
|
+
value: '2013-03-21T20:04:00Z'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const result = encode(tagged)
|
|
28
|
+
|
|
29
|
+
// Tag 0 (c0) + text string
|
|
30
|
+
expect(result.hex).toMatch(/^c0/)
|
|
31
|
+
expect(result.hex).toContain('323031332d30332d32315432303a30343a30305a')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('should encode tag 1 (epoch datetime)', () => {
|
|
35
|
+
const tagged = {
|
|
36
|
+
tag: 1,
|
|
37
|
+
value: 1363896240
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const result = encode(tagged)
|
|
41
|
+
|
|
42
|
+
// Tag 1 (c1) + integer
|
|
43
|
+
expect(result.hex).toMatch(/^c1/)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('should encode tag 2 (bignum)', () => {
|
|
47
|
+
const tagged = {
|
|
48
|
+
tag: 2,
|
|
49
|
+
value: new Uint8Array([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const result = encode(tagged)
|
|
53
|
+
|
|
54
|
+
// Tag 2 (c2) + byte string
|
|
55
|
+
expect(result.hex).toMatch(/^c2/)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('should encode tag 24 (encoded CBOR data item)', () => {
|
|
59
|
+
const tagged = {
|
|
60
|
+
tag: 24,
|
|
61
|
+
value: new Uint8Array([0x64, 0x49, 0x45, 0x54, 0x46]) // CBOR for "IETF"
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const result = encode(tagged)
|
|
65
|
+
|
|
66
|
+
// Tag 24 (d8 18) + byte string
|
|
67
|
+
expect(result.hex).toMatch(/^d818/)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('should encode tag 32 (URI)', () => {
|
|
71
|
+
const tagged = {
|
|
72
|
+
tag: 32,
|
|
73
|
+
value: 'https://example.com'
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const result = encode(tagged)
|
|
77
|
+
|
|
78
|
+
// Tag 32 (d8 20) + text string
|
|
79
|
+
expect(result.hex).toMatch(/^d820/)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('should encode tag 258 (set)', () => {
|
|
83
|
+
const tagged = {
|
|
84
|
+
tag: 258,
|
|
85
|
+
value: [1, 2, 3]
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const result = encode(tagged)
|
|
89
|
+
|
|
90
|
+
// Tag 258 (d9 01 02) + array
|
|
91
|
+
expect(result.hex).toMatch(/^d90102/)
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
describe('Plutus Constructor Tags (Compact)', () => {
|
|
96
|
+
it('should encode tag 121 (Plutus constructor 0)', () => {
|
|
97
|
+
const tagged = {
|
|
98
|
+
tag: 121,
|
|
99
|
+
value: []
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const result = encode(tagged)
|
|
103
|
+
|
|
104
|
+
// Tag 121 (d8 79) + empty array (80)
|
|
105
|
+
expect(result.hex).toBe('d87980')
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('should encode tag 122 (Plutus constructor 1)', () => {
|
|
109
|
+
const tagged = {
|
|
110
|
+
tag: 122,
|
|
111
|
+
value: [42]
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const result = encode(tagged)
|
|
115
|
+
|
|
116
|
+
// Tag 122 (d8 7a) + array [42]
|
|
117
|
+
expect(result.hex).toMatch(/^d87a/)
|
|
118
|
+
expect(result.hex).toContain('182a') // 42
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('should encode tag 123 (Plutus constructor 2)', () => {
|
|
122
|
+
const tagged = {
|
|
123
|
+
tag: 123,
|
|
124
|
+
value: [1, 2]
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const result = encode(tagged)
|
|
128
|
+
|
|
129
|
+
// Tag 123 (d8 7b) + array [1, 2]
|
|
130
|
+
expect(result.hex).toBe('d87b820102')
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('should encode tag 127 (Plutus constructor 6)', () => {
|
|
134
|
+
const tagged = {
|
|
135
|
+
tag: 127,
|
|
136
|
+
value: [1, 2, 3, 4, 5, 6]
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const result = encode(tagged)
|
|
140
|
+
|
|
141
|
+
// Tag 127 (d8 7f) + array
|
|
142
|
+
expect(result.hex).toMatch(/^d87f/)
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
describe('Plutus Constructor Tags (Extended)', () => {
|
|
147
|
+
it('should encode tag 1280 (Plutus constructor 7)', () => {
|
|
148
|
+
const tagged = {
|
|
149
|
+
tag: 1280,
|
|
150
|
+
value: [7, 7, 7]
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const result = encode(tagged)
|
|
154
|
+
|
|
155
|
+
// Tag 1280 (d9 05 00) + array
|
|
156
|
+
expect(result.hex).toMatch(/^d90500/)
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it('should encode tag 1283 (Plutus constructor 10)', () => {
|
|
160
|
+
const tagged = {
|
|
161
|
+
tag: 1283,
|
|
162
|
+
value: [1, 2, 3]
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const result = encode(tagged)
|
|
166
|
+
|
|
167
|
+
// Tag 1283 (d9 05 03)
|
|
168
|
+
expect(result.hex).toBe('d9050383010203')
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it('should encode tag 1400 (Plutus constructor 127)', () => {
|
|
172
|
+
const tagged = {
|
|
173
|
+
tag: 1400,
|
|
174
|
+
value: []
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const result = encode(tagged)
|
|
178
|
+
|
|
179
|
+
// Tag 1400 (d9 05 78) + empty array
|
|
180
|
+
expect(result.hex).toBe('d9057880')
|
|
181
|
+
})
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
describe('Plutus Alternative Constructor', () => {
|
|
185
|
+
it('should encode tag 102 (alternative constructor)', () => {
|
|
186
|
+
const tagged = {
|
|
187
|
+
tag: 102,
|
|
188
|
+
value: [200, [99]]
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const result = encode(tagged)
|
|
192
|
+
|
|
193
|
+
// Tag 102 (d8 66) + array [200, [99]]
|
|
194
|
+
expect(result.hex).toBe('d8668218c8811863')
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
it('should encode tag 102 with complex value', () => {
|
|
198
|
+
const tagged = {
|
|
199
|
+
tag: 102,
|
|
200
|
+
value: [0, ['constructor_variant', 123]]
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const result = encode(tagged)
|
|
204
|
+
|
|
205
|
+
// Tag 102 + array [0, ["constructor_variant", 123]]
|
|
206
|
+
expect(result.hex).toMatch(/^d866/)
|
|
207
|
+
})
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
describe('Nested Tagged Values', () => {
|
|
211
|
+
it('should encode tag within tag', () => {
|
|
212
|
+
const nested = {
|
|
213
|
+
tag: 121,
|
|
214
|
+
value: [{
|
|
215
|
+
tag: 121,
|
|
216
|
+
value: []
|
|
217
|
+
}]
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const result = encode(nested)
|
|
221
|
+
|
|
222
|
+
// Outer tag 121 + array + inner tag 121 + empty array
|
|
223
|
+
// d8 79 81 d8 79 80
|
|
224
|
+
expect(result.hex).toBe('d87981d87980')
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
it('should encode deeply nested tags (3 levels)', () => {
|
|
228
|
+
const deeply = {
|
|
229
|
+
tag: 121,
|
|
230
|
+
value: [{
|
|
231
|
+
tag: 121,
|
|
232
|
+
value: [{
|
|
233
|
+
tag: 121,
|
|
234
|
+
value: []
|
|
235
|
+
}]
|
|
236
|
+
}]
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const result = encode(deeply)
|
|
240
|
+
|
|
241
|
+
// d8 79 81 d8 79 81 d8 79 80
|
|
242
|
+
expect(result.hex).toBe('d87981d87981d87980')
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
it('should encode tag with mixed content', () => {
|
|
246
|
+
const mixed = {
|
|
247
|
+
tag: 121,
|
|
248
|
+
value: [
|
|
249
|
+
1,
|
|
250
|
+
{
|
|
251
|
+
tag: 122,
|
|
252
|
+
value: [2]
|
|
253
|
+
},
|
|
254
|
+
3
|
|
255
|
+
]
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const result = encode(mixed)
|
|
259
|
+
|
|
260
|
+
// Tag 121 + array [1, tag 122 [2], 3]
|
|
261
|
+
expect(result.hex).toMatch(/^d879/)
|
|
262
|
+
expect(result.hex).toContain('d87a') // inner tag 122
|
|
263
|
+
})
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
describe('Tags in Collections', () => {
|
|
267
|
+
it('should encode array of tagged values', () => {
|
|
268
|
+
const array = [
|
|
269
|
+
{ tag: 121, value: [] },
|
|
270
|
+
{ tag: 121, value: [] }
|
|
271
|
+
]
|
|
272
|
+
|
|
273
|
+
const result = encode(array)
|
|
274
|
+
|
|
275
|
+
// Array of 2 + tag 121 [] + tag 121 []
|
|
276
|
+
expect(result.hex).toBe('82d87980d87980')
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
it('should encode map with tagged values', () => {
|
|
280
|
+
const map = {
|
|
281
|
+
'key': {
|
|
282
|
+
tag: 121,
|
|
283
|
+
value: [42]
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const result = encode(map)
|
|
288
|
+
|
|
289
|
+
// Map + "key" (636b6579) + tag 121 [42]
|
|
290
|
+
expect(result.hex).toMatch(/a1636b6579d87981182a/)
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
it('should encode tag containing map', () => {
|
|
294
|
+
const tagged = {
|
|
295
|
+
tag: 121,
|
|
296
|
+
value: [{ amount: 1000000 }]
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const result = encode(tagged)
|
|
300
|
+
|
|
301
|
+
// Tag 121 + array + map
|
|
302
|
+
expect(result.hex).toMatch(/^d87981/)
|
|
303
|
+
expect(result.hex).toContain('616d6f756e74') // "amount"
|
|
304
|
+
})
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
describe('Real-World Cardano Examples', () => {
|
|
308
|
+
it('should encode Cardano redeemer structure', () => {
|
|
309
|
+
const redeemer = {
|
|
310
|
+
tag: 121,
|
|
311
|
+
value: [
|
|
312
|
+
'stream_mgxpv1qd_5ke7nkj',
|
|
313
|
+
new Uint8Array(Buffer.from('2fc2a082557dc6a74dfc42d204a6d3ff1a241c103c0bbdd2f3525ce6', 'hex')),
|
|
314
|
+
new Uint8Array(Buffer.from('d8b6a54c95aac8970bcfbf625bb694336c43baa40aa1fc50952563f4', 'hex')),
|
|
315
|
+
55155648,
|
|
316
|
+
104622300,
|
|
317
|
+
104628456,
|
|
318
|
+
107085404,
|
|
319
|
+
0,
|
|
320
|
+
{ tag: 121, value: [] }
|
|
321
|
+
]
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const result = encode(redeemer)
|
|
325
|
+
|
|
326
|
+
// Should encode full redeemer structure
|
|
327
|
+
expect(result.hex).toMatch(/^d879/)
|
|
328
|
+
expect(result.hex).toContain('d87980') // nested tag 121 []
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
it('should encode Plutus Nothing (tag 121, empty array)', () => {
|
|
332
|
+
const nothing = {
|
|
333
|
+
tag: 121,
|
|
334
|
+
value: []
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const result = encode(nothing)
|
|
338
|
+
|
|
339
|
+
expect(result.hex).toBe('d87980')
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
it('should encode Plutus Just (tag 122, [value])', () => {
|
|
343
|
+
const just = {
|
|
344
|
+
tag: 122,
|
|
345
|
+
value: [42]
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const result = encode(just)
|
|
349
|
+
|
|
350
|
+
// Tag 122 + array [42]
|
|
351
|
+
expect(result.hex).toMatch(/^d87a81182a/)
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
it('should encode Plutus pair (tag 121, [a, b])', () => {
|
|
355
|
+
const pair = {
|
|
356
|
+
tag: 121,
|
|
357
|
+
value: [100, 200]
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const result = encode(pair)
|
|
361
|
+
|
|
362
|
+
// Tag 121 (d879) + array [100, 200] (821864 18c8)
|
|
363
|
+
expect(result.hex).toBe('d87982186418c8')
|
|
364
|
+
})
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
describe('Canonical Encoding for Tags', () => {
|
|
368
|
+
it('should use smallest tag encoding', () => {
|
|
369
|
+
// Tag 0-23: single byte (c0-d7)
|
|
370
|
+
const tag10 = { tag: 10, value: 0 }
|
|
371
|
+
const result10 = encode(tag10, { canonical: true })
|
|
372
|
+
expect(result10.bytes[0]).toBe(0xca) // ca = tag 10
|
|
373
|
+
|
|
374
|
+
// Tag 24-255: d8 + 1 byte
|
|
375
|
+
const tag100 = { tag: 100, value: 0 }
|
|
376
|
+
const result100 = encode(tag100, { canonical: true })
|
|
377
|
+
expect(result100.bytes[0]).toBe(0xd8)
|
|
378
|
+
expect(result100.bytes[1]).toBe(100)
|
|
379
|
+
|
|
380
|
+
// Tag 256+: d9 + 2 bytes
|
|
381
|
+
const tag1000 = { tag: 1000, value: 0 }
|
|
382
|
+
const result1000 = encode(tag1000, { canonical: true })
|
|
383
|
+
expect(result1000.bytes[0]).toBe(0xd9)
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
it('should use canonical encoding for tagged value content', () => {
|
|
387
|
+
const tagged = {
|
|
388
|
+
tag: 121,
|
|
389
|
+
value: [1, 2, 3]
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const result = encode(tagged, { canonical: true })
|
|
393
|
+
|
|
394
|
+
// Array should be definite-length (83), not indefinite (9f)
|
|
395
|
+
expect(result.bytes[2]).toBe(0x83) // array of 3
|
|
396
|
+
expect(result.bytes[2]).not.toBe(0x9f) // not indefinite
|
|
397
|
+
})
|
|
398
|
+
})
|
|
399
|
+
|
|
400
|
+
describe('Edge Cases', () => {
|
|
401
|
+
it('should encode tag with empty byte string', () => {
|
|
402
|
+
const tagged = {
|
|
403
|
+
tag: 2,
|
|
404
|
+
value: new Uint8Array([])
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const result = encode(tagged)
|
|
408
|
+
|
|
409
|
+
// Tag 2 (c2) + empty byte string (40)
|
|
410
|
+
expect(result.hex).toBe('c240')
|
|
411
|
+
})
|
|
412
|
+
|
|
413
|
+
it('should encode tag with null value', () => {
|
|
414
|
+
const tagged = {
|
|
415
|
+
tag: 121,
|
|
416
|
+
value: [null]
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const result = encode(tagged)
|
|
420
|
+
|
|
421
|
+
// Tag 121 + array [null]
|
|
422
|
+
expect(result.hex).toBe('d87981f6')
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
it('should encode tag with boolean values', () => {
|
|
426
|
+
const tagged = {
|
|
427
|
+
tag: 121,
|
|
428
|
+
value: [true, false]
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const result = encode(tagged)
|
|
432
|
+
|
|
433
|
+
// Tag 121 + array [true, false]
|
|
434
|
+
expect(result.hex).toBe('d87982f5f4')
|
|
435
|
+
})
|
|
436
|
+
|
|
437
|
+
it('should encode tag with large integer', () => {
|
|
438
|
+
const tagged = {
|
|
439
|
+
tag: 121,
|
|
440
|
+
value: [4294967296] // 2^32
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const result = encode(tagged)
|
|
444
|
+
|
|
445
|
+
// Tag 121 + array [2^32]
|
|
446
|
+
expect(result.hex).toMatch(/^d87981/)
|
|
447
|
+
expect(result.hex).toContain('1b0000000100000000') // large int encoding
|
|
448
|
+
})
|
|
449
|
+
|
|
450
|
+
it('should encode tag with negative integer', () => {
|
|
451
|
+
const tagged = {
|
|
452
|
+
tag: 121,
|
|
453
|
+
value: [-1000]
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const result = encode(tagged)
|
|
457
|
+
|
|
458
|
+
// Tag 121 + array [-1000]
|
|
459
|
+
expect(result.hex).toBe('d879813903e7')
|
|
460
|
+
})
|
|
461
|
+
})
|
|
462
|
+
|
|
463
|
+
describe('Error Handling', () => {
|
|
464
|
+
it('should handle invalid tag structure gracefully', () => {
|
|
465
|
+
// Missing tag property - should encode as regular object
|
|
466
|
+
const invalid = {
|
|
467
|
+
value: [1, 2, 3]
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const result = encode(invalid)
|
|
471
|
+
|
|
472
|
+
// Should encode as map, not tag
|
|
473
|
+
expect(result.hex).toMatch(/^a1/) // map, not tag
|
|
474
|
+
})
|
|
475
|
+
|
|
476
|
+
it('should encode very large tag numbers', () => {
|
|
477
|
+
const largeTag = {
|
|
478
|
+
tag: 65535,
|
|
479
|
+
value: 0
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const result = encode(largeTag)
|
|
483
|
+
|
|
484
|
+
// Tag 65535: d9 ff ff
|
|
485
|
+
expect(result.hex).toMatch(/^d9ffff/)
|
|
486
|
+
})
|
|
487
|
+
|
|
488
|
+
it('should encode tag with complex nested structure', () => {
|
|
489
|
+
const complex = {
|
|
490
|
+
tag: 121,
|
|
491
|
+
value: [
|
|
492
|
+
[1, 2, [3, 4, [5]]],
|
|
493
|
+
{ a: 1, b: { c: 2 } },
|
|
494
|
+
'text',
|
|
495
|
+
new Uint8Array([0xff, 0x00])
|
|
496
|
+
]
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const result = encode(complex)
|
|
500
|
+
|
|
501
|
+
// Should handle deeply nested structures
|
|
502
|
+
expect(result.hex).toMatch(/^d879/)
|
|
503
|
+
expect(result.bytes.length).toBeGreaterThan(10)
|
|
504
|
+
})
|
|
505
|
+
})
|
|
506
|
+
|
|
507
|
+
describe('Roundtrip Compatibility', () => {
|
|
508
|
+
it('should encode tags that can be decoded back', () => {
|
|
509
|
+
const { parseWithSourceMap } = useCborParser()
|
|
510
|
+
|
|
511
|
+
const original = {
|
|
512
|
+
tag: 121,
|
|
513
|
+
value: [1, 2, 3]
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const encoded = encode(original, { canonical: true })
|
|
517
|
+
const decoded = parseWithSourceMap(encoded.hex, { validatePlutusSemantics: false })
|
|
518
|
+
|
|
519
|
+
// Remove plutus field if present for comparison
|
|
520
|
+
if ('plutus' in decoded.value) {
|
|
521
|
+
const { plutus, ...rest } = decoded.value as any
|
|
522
|
+
expect(rest).toEqual(original)
|
|
523
|
+
} else {
|
|
524
|
+
expect(decoded.value).toEqual(original)
|
|
525
|
+
}
|
|
526
|
+
})
|
|
527
|
+
|
|
528
|
+
it('should roundtrip complex Plutus structures', () => {
|
|
529
|
+
const { parseWithSourceMap } = useCborParser()
|
|
530
|
+
|
|
531
|
+
const complex = {
|
|
532
|
+
tag: 121,
|
|
533
|
+
value: [
|
|
534
|
+
{ tag: 122, value: [42] },
|
|
535
|
+
{ tag: 121, value: [] },
|
|
536
|
+
{ tag: 123, value: [1, 2] }
|
|
537
|
+
]
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const encoded = encode(complex, { canonical: true })
|
|
541
|
+
const decoded = parseWithSourceMap(encoded.hex, { validatePlutusSemantics: false })
|
|
542
|
+
|
|
543
|
+
// Remove plutus field if present for comparison
|
|
544
|
+
const removeP = (obj: any): any => {
|
|
545
|
+
if (obj && typeof obj === 'object') {
|
|
546
|
+
if ('plutus' in obj) {
|
|
547
|
+
const { plutus, ...rest } = obj
|
|
548
|
+
return removeP(rest)
|
|
549
|
+
}
|
|
550
|
+
if (Array.isArray(obj)) {
|
|
551
|
+
return obj.map(removeP)
|
|
552
|
+
}
|
|
553
|
+
const result: any = {}
|
|
554
|
+
for (const key in obj) {
|
|
555
|
+
result[key] = removeP(obj[key])
|
|
556
|
+
}
|
|
557
|
+
return result
|
|
558
|
+
}
|
|
559
|
+
return obj
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
expect(removeP(decoded.value)).toEqual(complex)
|
|
563
|
+
})
|
|
564
|
+
})
|
|
565
|
+
})
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CBOR Tag Encoder Composable
|
|
3
|
+
* Handles Major Type 6 (Semantic Tags)
|
|
4
|
+
* Following RFC 8949 specification
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { EncodeResult, TaggedValue, EncodableValue } from '../types'
|
|
8
|
+
import { bytesToHex, writeUint, writeBigUint } from '../utils'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* CBOR Tag Encoder Composable
|
|
12
|
+
*
|
|
13
|
+
* Provides functions to encode tagged values to CBOR format:
|
|
14
|
+
* - Major Type 6: Semantic tags (0 to 2^64-1)
|
|
15
|
+
*
|
|
16
|
+
* Tags provide semantic meaning to CBOR values:
|
|
17
|
+
* - Tag 0: Date/time string (RFC 3339)
|
|
18
|
+
* - Tag 1: Epoch timestamp
|
|
19
|
+
* - Tag 2: Positive bignum
|
|
20
|
+
* - Tag 3: Negative bignum
|
|
21
|
+
* - Tag 258: Cardano set (CIP-0005)
|
|
22
|
+
* - And many more...
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* const { encodeTag } = useCborTagEncoder()
|
|
27
|
+
*
|
|
28
|
+
* // Encode date/time string (tag 0)
|
|
29
|
+
* const result1 = encodeTag(0, '2013-03-21T20:04:00Z', encode)
|
|
30
|
+
* // result1.hex: 'c074323031332d30332d32315432303a30343a30305a'
|
|
31
|
+
*
|
|
32
|
+
* // Encode positive bignum (tag 2)
|
|
33
|
+
* const result2 = encodeTag(2, new Uint8Array([0x01, 0xff]), encode)
|
|
34
|
+
* // result2.hex: 'c24201ff'
|
|
35
|
+
*
|
|
36
|
+
* // Encode Cardano set (tag 258)
|
|
37
|
+
* const result3 = encodeTag(258, [1, 2, 3], encode)
|
|
38
|
+
* // result3.hex: 'd9010283010203'
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export function useCborTagEncoder() {
|
|
42
|
+
/**
|
|
43
|
+
* Encode tag number (Major Type 6 header)
|
|
44
|
+
*
|
|
45
|
+
* Tag numbers use the same encoding rules as unsigned integers:
|
|
46
|
+
* - 0-23: Direct encoding in initial byte (0xc0-0xd7)
|
|
47
|
+
* - 24-255: 0xd8 + 1 byte
|
|
48
|
+
* - 256-65535: 0xd9 + 2 bytes
|
|
49
|
+
* - 65536-4294967295: 0xda + 4 bytes
|
|
50
|
+
* - 4294967296-2^64-1: 0xdb + 8 bytes
|
|
51
|
+
*
|
|
52
|
+
* @param tagNumber - Tag number (0 to 2^64-1)
|
|
53
|
+
* @returns Encoded tag header bytes
|
|
54
|
+
* @throws Error if tag number is negative or >= 2^64
|
|
55
|
+
*/
|
|
56
|
+
const encodeTagNumber = (tagNumber: number | bigint): Uint8Array => {
|
|
57
|
+
// Convert to BigInt for consistent handling
|
|
58
|
+
const bigTag = typeof tagNumber === 'bigint' ? tagNumber : BigInt(tagNumber)
|
|
59
|
+
|
|
60
|
+
// Validate tag is non-negative
|
|
61
|
+
if (bigTag < 0n) {
|
|
62
|
+
throw new Error('Tag number cannot be negative')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Validate tag doesn't exceed 2^64-1
|
|
66
|
+
const MAX_UINT64 = 18446744073709551615n // 2^64 - 1
|
|
67
|
+
if (bigTag > MAX_UINT64) {
|
|
68
|
+
throw new Error('Tag number exceeds maximum (2^64-1)')
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let bytes: Uint8Array
|
|
72
|
+
|
|
73
|
+
// Direct encoding (0-23) - Major type 6 (0xc0) + tag number
|
|
74
|
+
if (bigTag <= 23n) {
|
|
75
|
+
bytes = new Uint8Array([0xc0 + Number(bigTag)])
|
|
76
|
+
}
|
|
77
|
+
// 1-byte encoding (24-255) - 0xd8 + 1 byte
|
|
78
|
+
else if (bigTag <= 255n) {
|
|
79
|
+
bytes = new Uint8Array([0xd8, Number(bigTag)])
|
|
80
|
+
}
|
|
81
|
+
// 2-byte encoding (256-65535) - 0xd9 + 2 bytes
|
|
82
|
+
else if (bigTag <= 65535n) {
|
|
83
|
+
const valueBytes = writeUint(Number(bigTag), 2)
|
|
84
|
+
bytes = new Uint8Array([0xd9, ...valueBytes])
|
|
85
|
+
}
|
|
86
|
+
// 4-byte encoding (65536-4294967295) - 0xda + 4 bytes
|
|
87
|
+
else if (bigTag <= 4294967295n) {
|
|
88
|
+
const valueBytes = writeUint(Number(bigTag), 4)
|
|
89
|
+
bytes = new Uint8Array([0xda, ...valueBytes])
|
|
90
|
+
}
|
|
91
|
+
// 8-byte encoding (> 4294967295) - 0xdb + 8 bytes
|
|
92
|
+
else {
|
|
93
|
+
const valueBytes = writeBigUint(bigTag, 8)
|
|
94
|
+
bytes = new Uint8Array([0xdb, ...valueBytes])
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return bytes
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Encode tagged value (tag + content)
|
|
102
|
+
*
|
|
103
|
+
* A tagged value consists of:
|
|
104
|
+
* 1. Tag number (Major Type 6 header)
|
|
105
|
+
* 2. Tagged content (recursively encoded value)
|
|
106
|
+
*
|
|
107
|
+
* The encode function is passed as a parameter to avoid circular dependencies.
|
|
108
|
+
*
|
|
109
|
+
* @param tagNumber - Tag number
|
|
110
|
+
* @param value - Value to tag
|
|
111
|
+
* @param encode - Encoder function for the tagged value
|
|
112
|
+
* @returns Encoded CBOR bytes and hex string
|
|
113
|
+
*/
|
|
114
|
+
const encodeTag = (
|
|
115
|
+
tagNumber: number | bigint,
|
|
116
|
+
value: EncodableValue,
|
|
117
|
+
encode: (value: EncodableValue) => EncodeResult
|
|
118
|
+
): EncodeResult => {
|
|
119
|
+
// Encode tag number
|
|
120
|
+
const tagBytes = encodeTagNumber(tagNumber)
|
|
121
|
+
|
|
122
|
+
// Recursively encode the tagged value
|
|
123
|
+
const valueResult = encode(value)
|
|
124
|
+
|
|
125
|
+
// Concatenate tag header + value bytes
|
|
126
|
+
const totalLength = tagBytes.length + valueResult.bytes.length
|
|
127
|
+
const bytes = new Uint8Array(totalLength)
|
|
128
|
+
bytes.set(tagBytes, 0)
|
|
129
|
+
bytes.set(valueResult.bytes, tagBytes.length)
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
bytes,
|
|
133
|
+
hex: bytesToHex(bytes)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Encode TaggedValue object
|
|
139
|
+
*
|
|
140
|
+
* Convenience function for encoding { tag, value } objects.
|
|
141
|
+
*
|
|
142
|
+
* @param taggedValue - Object with tag and value properties
|
|
143
|
+
* @param encode - Encoder function for the tagged value
|
|
144
|
+
* @returns Encoded CBOR bytes and hex string
|
|
145
|
+
*/
|
|
146
|
+
const encodeTaggedValue = (
|
|
147
|
+
taggedValue: TaggedValue,
|
|
148
|
+
encode: (value: EncodableValue) => EncodeResult
|
|
149
|
+
): EncodeResult => {
|
|
150
|
+
return encodeTag(taggedValue.tag, taggedValue.value, encode)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
encodeTagNumber,
|
|
155
|
+
encodeTag,
|
|
156
|
+
encodeTaggedValue
|
|
157
|
+
}
|
|
158
|
+
}
|