@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.
Files changed (100) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/LICENSE +674 -0
  3. package/README.md +345 -0
  4. package/dist/chunk-2FUTHZQQ.cjs +755 -0
  5. package/dist/chunk-2FUTHZQQ.cjs.map +1 -0
  6. package/dist/chunk-2HBCILJS.cjs +2034 -0
  7. package/dist/chunk-2HBCILJS.cjs.map +1 -0
  8. package/dist/chunk-7CFYWHS6.js +742 -0
  9. package/dist/chunk-7CFYWHS6.js.map +1 -0
  10. package/dist/chunk-PD72MVTX.cjs +160 -0
  11. package/dist/chunk-PD72MVTX.cjs.map +1 -0
  12. package/dist/chunk-ZDZ2B5PE.js +149 -0
  13. package/dist/chunk-ZDZ2B5PE.js.map +1 -0
  14. package/dist/chunk-ZRPJUEIZ.js +2020 -0
  15. package/dist/chunk-ZRPJUEIZ.js.map +1 -0
  16. package/dist/encoder/index.cjs +57 -0
  17. package/dist/encoder/index.cjs.map +1 -0
  18. package/dist/encoder/index.d.cts +72 -0
  19. package/dist/encoder/index.d.ts +72 -0
  20. package/dist/encoder/index.js +4 -0
  21. package/dist/encoder/index.js.map +1 -0
  22. package/dist/index.cjs +606 -0
  23. package/dist/index.cjs.map +1 -0
  24. package/dist/index.d.cts +494 -0
  25. package/dist/index.d.ts +494 -0
  26. package/dist/index.js +523 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/metafile-cjs.json +1 -0
  29. package/dist/metafile-esm.json +1 -0
  30. package/dist/parser/index.cjs +85 -0
  31. package/dist/parser/index.cjs.map +1 -0
  32. package/dist/parser/index.d.cts +72 -0
  33. package/dist/parser/index.d.ts +72 -0
  34. package/dist/parser/index.js +4 -0
  35. package/dist/parser/index.js.map +1 -0
  36. package/dist/types-DvNlfbKB.d.cts +301 -0
  37. package/dist/types-DvNlfbKB.d.ts +301 -0
  38. package/dist/useCborSimpleEncoder-ButVU988.d.cts +268 -0
  39. package/dist/useCborSimpleEncoder-TVxzNJ_9.d.ts +268 -0
  40. package/dist/useCborTag-B_iaShG6.d.ts +142 -0
  41. package/dist/useCborTag-BfTIV8HM.d.cts +142 -0
  42. package/package.json +102 -0
  43. package/src/__tests__/public-api.test.ts +326 -0
  44. package/src/encoder/__tests__/cbor-collection-encoder.test.ts +331 -0
  45. package/src/encoder/__tests__/cbor-integer-encoder.test.ts +283 -0
  46. package/src/encoder/__tests__/cbor-simple-encoder.test.ts +224 -0
  47. package/src/encoder/__tests__/cbor-string-encoder.test.ts +345 -0
  48. package/src/encoder/__tests__/cbor-tag-encoder.test.ts +565 -0
  49. package/src/encoder/composables/#useCborTagEncoder.ts# +158 -0
  50. package/src/encoder/composables/useCborCollectionEncoder.ts +424 -0
  51. package/src/encoder/composables/useCborEncoder.ts +203 -0
  52. package/src/encoder/composables/useCborIntegerEncoder.ts +188 -0
  53. package/src/encoder/composables/useCborSimpleEncoder.ts +266 -0
  54. package/src/encoder/composables/useCborStringEncoder.ts +266 -0
  55. package/src/encoder/composables/useCborTagEncoder.ts +158 -0
  56. package/src/encoder/index.ts +35 -0
  57. package/src/encoder/types.ts +88 -0
  58. package/src/encoder/utils.ts +80 -0
  59. package/src/index.ts +434 -0
  60. package/src/parser/__tests__/ast-tree-structure.test.ts +311 -0
  61. package/src/parser/__tests__/cbor-collection-errors.test.ts +296 -0
  62. package/src/parser/__tests__/cbor-collection.test.ts +369 -0
  63. package/src/parser/__tests__/cbor-deterministic-encoding.test.ts +432 -0
  64. package/src/parser/__tests__/cbor-diagnostic.test.ts +333 -0
  65. package/src/parser/__tests__/cbor-duplicate-keys.test.ts +235 -0
  66. package/src/parser/__tests__/cbor-float-errors.test.ts +222 -0
  67. package/src/parser/__tests__/cbor-float.test.ts +502 -0
  68. package/src/parser/__tests__/cbor-integer-errors.test.ts +139 -0
  69. package/src/parser/__tests__/cbor-integer.test.ts +200 -0
  70. package/src/parser/__tests__/cbor-map-duplicate-keys.test.ts +403 -0
  71. package/src/parser/__tests__/cbor-parser-errors.test.ts +126 -0
  72. package/src/parser/__tests__/cbor-security-dos-protection.test.ts +503 -0
  73. package/src/parser/__tests__/cbor-sequences.test.ts +150 -0
  74. package/src/parser/__tests__/cbor-source-map.test.ts +321 -0
  75. package/src/parser/__tests__/cbor-standard-tags.test.ts +340 -0
  76. package/src/parser/__tests__/cbor-string-errors.test.ts +227 -0
  77. package/src/parser/__tests__/cbor-string.test.ts +224 -0
  78. package/src/parser/__tests__/cbor-tag-advanced.test.ts +500 -0
  79. package/src/parser/__tests__/cbor-tag-errors.test.ts +447 -0
  80. package/src/parser/__tests__/cbor-tag-source-map.test.ts +360 -0
  81. package/src/parser/__tests__/cbor-tag.test.ts +684 -0
  82. package/src/parser/__tests__/extreme-edge-cases.test.ts +146 -0
  83. package/src/parser/__tests__/pathBuilder.test.ts +256 -0
  84. package/src/parser/__tests__/rfc-test-vectors.test.ts +607 -0
  85. package/src/parser/__tests__/security-limits.test.ts +248 -0
  86. package/src/parser/__tests__/utils-errors.test.ts +127 -0
  87. package/src/parser/composables/useCborCollection.ts +509 -0
  88. package/src/parser/composables/useCborDiagnostic.ts +381 -0
  89. package/src/parser/composables/useCborFloat.ts +256 -0
  90. package/src/parser/composables/useCborInteger.ts +114 -0
  91. package/src/parser/composables/useCborParser.ts +951 -0
  92. package/src/parser/composables/useCborString.ts +330 -0
  93. package/src/parser/composables/useCborStringTypes.ts +129 -0
  94. package/src/parser/composables/useCborTag.ts +739 -0
  95. package/src/parser/index.ts +56 -0
  96. package/src/parser/types.ts +371 -0
  97. package/src/parser/utils/pathBuilder.ts +259 -0
  98. package/src/parser/utils.ts +398 -0
  99. package/src/utils/__tests__/logger.test.ts +186 -0
  100. 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
+ }