@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,432 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CBOR Deterministic Encoding Tests (RFC 8949 Section 4.2)
|
|
3
|
+
*
|
|
4
|
+
* Core Deterministic Encoding Requirements:
|
|
5
|
+
* 1. Preferred serialization MUST be used (shortest form)
|
|
6
|
+
* 2. Indefinite-length items MUST NOT be used
|
|
7
|
+
* 3. Map keys MUST be sorted in bytewise lexicographic order
|
|
8
|
+
* 4. No duplicate map keys allowed
|
|
9
|
+
*
|
|
10
|
+
* Critical for Cardano transaction signing safety!
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { describe, it, expect } from 'vitest'
|
|
14
|
+
import { useCborParser } from '../composables/useCborParser'
|
|
15
|
+
import { useCborInteger } from '../composables/useCborInteger'
|
|
16
|
+
import { useCborCollection } from '../composables/useCborCollection'
|
|
17
|
+
|
|
18
|
+
describe('RFC 8949 Core Deterministic Encoding (Section 4.2)', () => {
|
|
19
|
+
describe('Requirement 1: Preferred Serialization (Shortest Form)', () => {
|
|
20
|
+
describe('Integers must use shortest encoding', () => {
|
|
21
|
+
it('should accept value 10 with direct encoding (AI=10)', () => {
|
|
22
|
+
const { parse } = useCborParser()
|
|
23
|
+
const result = parse('0a', { validateCanonical: true })
|
|
24
|
+
expect(result.value).toBe(10)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('should reject value 10 with wasteful 1-byte encoding', () => {
|
|
28
|
+
const { parse } = useCborParser()
|
|
29
|
+
// 0x180a = AI 24 + 0x0a (wasteful, should be direct 0x0a)
|
|
30
|
+
expect(() => parse('180a', { validateCanonical: true }))
|
|
31
|
+
.toThrow(/non-canonical.*integer/i)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('should accept value 24 with 1-byte encoding (AI=24)', () => {
|
|
35
|
+
const { parse } = useCborParser()
|
|
36
|
+
const result = parse('1818', { validateCanonical: true })
|
|
37
|
+
expect(result.value).toBe(24)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('should reject value 24 with wasteful 2-byte encoding', () => {
|
|
41
|
+
const { parse } = useCborParser()
|
|
42
|
+
// 0x190018 = AI 25 + 0x0018 (wasteful, should be AI 24)
|
|
43
|
+
expect(() => parse('190018', { validateCanonical: true }))
|
|
44
|
+
.toThrow(/non-canonical.*integer/i)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('should accept value 256 with 2-byte encoding (AI=25)', () => {
|
|
48
|
+
const { parse } = useCborParser()
|
|
49
|
+
const result = parse('190100', { validateCanonical: true })
|
|
50
|
+
expect(result.value).toBe(256)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('should reject value 256 with wasteful 4-byte encoding', () => {
|
|
54
|
+
const { parse } = useCborParser()
|
|
55
|
+
// 0x1a00000100 = AI 26 + 0x00000100 (wasteful)
|
|
56
|
+
expect(() => parse('1a00000100', { validateCanonical: true }))
|
|
57
|
+
.toThrow(/non-canonical.*integer/i)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('should accept value 65536 with 4-byte encoding (AI=26)', () => {
|
|
61
|
+
const { parse } = useCborParser()
|
|
62
|
+
const result = parse('1a00010000', { validateCanonical: true })
|
|
63
|
+
expect(result.value).toBe(65536)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('should reject value 65536 with wasteful 8-byte encoding', () => {
|
|
67
|
+
const { parse } = useCborParser()
|
|
68
|
+
// 0x1b0000000000010000 = AI 27 + 8 bytes (wasteful)
|
|
69
|
+
expect(() => parse('1b0000000000010000', { validateCanonical: true }))
|
|
70
|
+
.toThrow(/non-canonical.*integer/i)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('should accept value 4294967296 with 8-byte encoding (AI=27)', () => {
|
|
74
|
+
const { parse } = useCborParser()
|
|
75
|
+
const result = parse('1b0000000100000000', { validateCanonical: true })
|
|
76
|
+
// 4294967296 is within Number.MAX_SAFE_INTEGER, so it's a number, not BigInt
|
|
77
|
+
expect(result.value).toBe(4294967296)
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
describe('Negative integers must use shortest encoding', () => {
|
|
82
|
+
it('should accept -10 with direct encoding', () => {
|
|
83
|
+
const { parse } = useCborParser()
|
|
84
|
+
const result = parse('29', { validateCanonical: true }) // -1 - 9 = -10
|
|
85
|
+
expect(result.value).toBe(-10)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('should reject -10 with wasteful encoding', () => {
|
|
89
|
+
const { parse } = useCborParser()
|
|
90
|
+
// 0x3809 = negative with AI 24 (wasteful)
|
|
91
|
+
expect(() => parse('3809', { validateCanonical: true }))
|
|
92
|
+
.toThrow(/non-canonical.*integer/i)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('should accept -100 with 1-byte encoding', () => {
|
|
96
|
+
const { parse } = useCborParser()
|
|
97
|
+
const result = parse('3863', { validateCanonical: true }) // -1 - 99 = -100
|
|
98
|
+
expect(result.value).toBe(-100)
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
describe('String lengths must use shortest encoding', () => {
|
|
103
|
+
it('should accept 10-byte string with direct length', () => {
|
|
104
|
+
const { parse } = useCborParser()
|
|
105
|
+
// 0x6a = text string, length 10
|
|
106
|
+
const result = parse('6a48656c6c6f576f726c64', { validateCanonical: true }) // "HelloWorld" (10 bytes)
|
|
107
|
+
expect(result.value).toBe('HelloWorld')
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('should reject 10-byte string with wasteful 1-byte length', () => {
|
|
111
|
+
const { parse } = useCborParser()
|
|
112
|
+
// 0x780a = text string, AI 24, length 10 (wasteful)
|
|
113
|
+
expect(() => parse('780a48656c6c6f576f726c64', { validateCanonical: true })) // "HelloWorld"
|
|
114
|
+
.toThrow(/non-canonical/i)
|
|
115
|
+
})
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
describe('Array lengths must use shortest encoding', () => {
|
|
119
|
+
it('should accept array of 5 with direct length', () => {
|
|
120
|
+
const { parse } = useCborParser()
|
|
121
|
+
const result = parse('8501020304 05', { validateCanonical: true })
|
|
122
|
+
expect(result.value).toEqual([1, 2, 3, 4, 5])
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('should reject array of 5 with wasteful encoding', () => {
|
|
126
|
+
const { parse } = useCborParser()
|
|
127
|
+
// 0x9805 = array, AI 24, length 5 (wasteful)
|
|
128
|
+
expect(() => parse('98050102030405', { validateCanonical: true }))
|
|
129
|
+
.toThrow(/non-canonical/i)
|
|
130
|
+
})
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
describe('Map lengths must use shortest encoding', () => {
|
|
134
|
+
it('should accept map of 2 pairs with direct length', () => {
|
|
135
|
+
const { parse } = useCborParser()
|
|
136
|
+
const result = parse('a2616101616202', { validateCanonical: true })
|
|
137
|
+
expect(result.value).toEqual(new Map([['a', 1], ['b', 2]]))
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('should reject map of 2 pairs with wasteful encoding', () => {
|
|
141
|
+
const { parse } = useCborParser()
|
|
142
|
+
// 0xb802 = map, AI 24, length 2 (wasteful)
|
|
143
|
+
expect(() => parse('b802616101616202', { validateCanonical: true }))
|
|
144
|
+
.toThrow(/non-canonical/i)
|
|
145
|
+
})
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
describe('Requirement 2: No Indefinite-Length Encoding', () => {
|
|
150
|
+
it('should reject indefinite-length arrays in strict mode', () => {
|
|
151
|
+
const { parse } = useCborParser()
|
|
152
|
+
// 0x9f = indefinite array start
|
|
153
|
+
expect(() => parse('9f010203ff', { validateCanonical: true }))
|
|
154
|
+
.toThrow(/indefinite.*not allowed/i)
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('should reject indefinite-length maps in strict mode', () => {
|
|
158
|
+
const { parse } = useCborParser()
|
|
159
|
+
// 0xbf = indefinite map start
|
|
160
|
+
expect(() => parse('bf61610161 6202ff', { validateCanonical: true }))
|
|
161
|
+
.toThrow(/indefinite.*not allowed/i)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('should reject indefinite-length byte strings in strict mode', () => {
|
|
165
|
+
const { parse } = useCborParser()
|
|
166
|
+
// 0x5f = indefinite byte string start
|
|
167
|
+
expect(() => parse('5f42010243030405ff', { validateCanonical: true }))
|
|
168
|
+
.toThrow(/indefinite.*not allowed/i)
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it('should reject indefinite-length text strings in strict mode', () => {
|
|
172
|
+
const { parse } = useCborParser()
|
|
173
|
+
// 0x7f = indefinite text string start
|
|
174
|
+
expect(() => parse('7f657374726561646d696e67ff', { validateCanonical: true }))
|
|
175
|
+
.toThrow(/indefinite.*not allowed/i)
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
it('should accept definite-length arrays in strict mode', () => {
|
|
179
|
+
const { parse } = useCborParser()
|
|
180
|
+
const result = parse('83010203', { validateCanonical: true })
|
|
181
|
+
expect(result.value).toEqual([1, 2, 3])
|
|
182
|
+
})
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
describe('Requirement 3: Map Keys Must Be Sorted (Bytewise Lexicographic)', () => {
|
|
186
|
+
it('should accept map with correctly sorted integer keys', () => {
|
|
187
|
+
const { parse } = useCborParser()
|
|
188
|
+
// Keys: 1, 2, 3 (already sorted)
|
|
189
|
+
const result = parse('a3010a020b030c', { validateCanonical: true })
|
|
190
|
+
expect(result.value).toEqual(new Map([[1, 10], [2, 11], [3, 12]]))
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it('should reject map with unsorted integer keys', () => {
|
|
194
|
+
const { parse } = useCborParser()
|
|
195
|
+
// Keys: 2, 1, 3 (WRONG ORDER)
|
|
196
|
+
expect(() => parse('a3020b010a030c', { validateCanonical: true }))
|
|
197
|
+
.toThrow(/not in canonical order/i)
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
it('should accept map with correctly sorted string keys (alphabetical)', () => {
|
|
201
|
+
const { parse } = useCborParser()
|
|
202
|
+
// Keys: "a", "b", "c" (correct order)
|
|
203
|
+
const result = parse('a3616101616202616303', { validateCanonical: true })
|
|
204
|
+
expect(result.value).toEqual(new Map([['a', 1], ['b', 2], ['c', 3]]))
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
it('should reject map with unsorted string keys', () => {
|
|
208
|
+
const { parse } = useCborParser()
|
|
209
|
+
// Keys: "c", "a", "b" (WRONG ORDER)
|
|
210
|
+
expect(() => parse('a3616303616101616202', { validateCanonical: true }))
|
|
211
|
+
.toThrow(/not in canonical order/i)
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
it('should reject map with reversed string keys', () => {
|
|
215
|
+
const { parse } = useCborParser()
|
|
216
|
+
// Keys: "z", "a" (WRONG ORDER - should be "a", "z")
|
|
217
|
+
expect(() => parse('a2617a01616102', { validateCanonical: true }))
|
|
218
|
+
.toThrow(/not in canonical order/i)
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
it('should accept map with correctly sorted keys by length then content', () => {
|
|
222
|
+
const { parse } = useCborParser()
|
|
223
|
+
// Keys: "a" (1 byte), "aa" (2 bytes) - shorter keys come first
|
|
224
|
+
const result = parse('a26161016261 6102', { validateCanonical: true })
|
|
225
|
+
expect(result.value).toEqual(new Map([['a', 1], ['aa', 2]]))
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
it('should reject map with keys sorted by content but not length', () => {
|
|
229
|
+
const { parse } = useCborParser()
|
|
230
|
+
// Keys: "aa" (2 bytes), "a" (1 byte) - WRONG ORDER
|
|
231
|
+
expect(() => parse('a26261610261 6101', { validateCanonical: true }))
|
|
232
|
+
.toThrow(/not in canonical order/i)
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
it('should accept map with mixed-type keys sorted correctly', () => {
|
|
236
|
+
const { parse } = useCborParser()
|
|
237
|
+
// Integer keys sort before string keys bytewise
|
|
238
|
+
// Key 0 (0x00) < Key "a" (0x6161)
|
|
239
|
+
const result = parse('a2000a616101', { validateCanonical: true })
|
|
240
|
+
expect(result.value).toEqual(new Map([[0, 10], ['a', 1]]))
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
it('should reject map with mixed-type keys in wrong order', () => {
|
|
244
|
+
const { parse } = useCborParser()
|
|
245
|
+
// Key "a" (0x6161) should come AFTER Key 0 (0x00)
|
|
246
|
+
expect(() => parse('a26161010a00', { validateCanonical: true }))
|
|
247
|
+
.toThrow(/not in canonical order/i)
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
it('should accept empty map in canonical mode', () => {
|
|
251
|
+
const { parse } = useCborParser()
|
|
252
|
+
const result = parse('a0', { validateCanonical: true })
|
|
253
|
+
expect(result.value).toEqual(new Map())
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
it('should accept single-entry map in canonical mode', () => {
|
|
257
|
+
const { parse } = useCborParser()
|
|
258
|
+
const result = parse('a1616101', { validateCanonical: true })
|
|
259
|
+
expect(result.value).toEqual(new Map([['a', 1]]))
|
|
260
|
+
})
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
describe('Requirement 4: No Duplicate Map Keys (Deterministic)', () => {
|
|
264
|
+
it('should reject map with duplicate string keys in canonical mode', () => {
|
|
265
|
+
const { parse } = useCborParser()
|
|
266
|
+
// Map with duplicate "a" key (sorted: a, a, b)
|
|
267
|
+
// Keys must be sorted for canonical form, but duplicates still rejected
|
|
268
|
+
expect(() => parse('a3616101616103616202', { validateCanonical: true }))
|
|
269
|
+
.toThrow(/duplicate/i)
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
it('should reject map with duplicate integer keys in canonical mode', () => {
|
|
273
|
+
const { parse } = useCborParser()
|
|
274
|
+
// Map with duplicate key 1
|
|
275
|
+
expect(() => parse('a3010a010b020c', { validateCanonical: true }))
|
|
276
|
+
.toThrow(/duplicate/i)
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
it('should reject map with duplicate byte string keys', () => {
|
|
280
|
+
const { parse } = useCborParser()
|
|
281
|
+
// Map with duplicate h'0102' key
|
|
282
|
+
expect(() => parse('a34201020a42010 20b420304 0c', { validateCanonical: true }))
|
|
283
|
+
.toThrow(/duplicate/i)
|
|
284
|
+
})
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
describe('Cardano Transaction Signing Safety', () => {
|
|
288
|
+
it('should ensure deterministic encoding for transaction amounts', () => {
|
|
289
|
+
const { parse } = useCborParser()
|
|
290
|
+
|
|
291
|
+
// Simulated Cardano transaction with amount field
|
|
292
|
+
// Map { "amount": 1000000 } in canonical form
|
|
293
|
+
const canonicalTx = 'a166616d6f756e741a000f4240'
|
|
294
|
+
|
|
295
|
+
const result = parse(canonicalTx, {
|
|
296
|
+
validateCanonical: true,
|
|
297
|
+
dupMapKeyMode: 'reject'
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
expect(result.value).toEqual(new Map([['amount', 1000000]]))
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
it('should reject non-canonical transaction (duplicate amount field)', () => {
|
|
304
|
+
const { parse } = useCborParser()
|
|
305
|
+
|
|
306
|
+
// Malicious transaction with duplicate "amount" key
|
|
307
|
+
// First: 1000000, Second: 100000000
|
|
308
|
+
const maliciousTx = 'a266616d6f756e741a000f424066616d6f756e741a05f5e100'
|
|
309
|
+
|
|
310
|
+
expect(() => parse(maliciousTx, {
|
|
311
|
+
validateCanonical: true,
|
|
312
|
+
dupMapKeyMode: 'reject'
|
|
313
|
+
})).toThrow(/duplicate/i)
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
it('should reject non-canonical transaction (unsorted keys)', () => {
|
|
317
|
+
const { parse } = useCborParser()
|
|
318
|
+
|
|
319
|
+
// Transaction with unsorted keys: "b", "a" (should be "a", "b")
|
|
320
|
+
const maliciousTx = 'a2616201616101' // {"b": 1, "a": 1} - wrong order
|
|
321
|
+
|
|
322
|
+
expect(() => parse(maliciousTx, { validateCanonical: true }))
|
|
323
|
+
.toThrow(/not in canonical order/i)
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
it('should accept properly signed canonical Cardano UTXO', () => {
|
|
327
|
+
const { parse } = useCborParser()
|
|
328
|
+
|
|
329
|
+
// Canonical UTXO: sorted keys, shortest encoding
|
|
330
|
+
const canonicalUTXO = 'a26161 6472657761726482005820abcd...'
|
|
331
|
+
// Simplified test - in reality would be full UTXO structure
|
|
332
|
+
|
|
333
|
+
const simplifiedUTXO = 'a2616101616202' // {a: 1, b: 2}
|
|
334
|
+
const result = parse(simplifiedUTXO, { validateCanonical: true })
|
|
335
|
+
|
|
336
|
+
expect(result.value).toEqual(new Map([['a', 1], ['b', 2]]))
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
it('should enforce strict mode enables all validations', () => {
|
|
340
|
+
const { parse } = useCborParser()
|
|
341
|
+
|
|
342
|
+
// strict: true should enable validateCanonical, rejectDuplicateKeys, etc.
|
|
343
|
+
// Non-canonical integer
|
|
344
|
+
expect(() => parse('180a', { strict: true }))
|
|
345
|
+
.toThrow(/non-canonical/i)
|
|
346
|
+
|
|
347
|
+
// Indefinite length
|
|
348
|
+
expect(() => parse('9f0102 03ff', { strict: true }))
|
|
349
|
+
.toThrow(/indefinite.*not allowed/i)
|
|
350
|
+
})
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
describe('Complex Nested Structures (Deterministic)', () => {
|
|
354
|
+
it('should validate nested maps have sorted keys', () => {
|
|
355
|
+
const { parse } = useCborParser()
|
|
356
|
+
|
|
357
|
+
// Outer map with sorted keys, inner map with sorted keys
|
|
358
|
+
const nested = 'a2616101616 2a26163 01616402' // {a: 1, b: {c: 1, d: 2}}
|
|
359
|
+
|
|
360
|
+
const result = parse(nested, { validateCanonical: true })
|
|
361
|
+
expect(result.value).toEqual(new Map([['a', 1], ['b', new Map([['c', 1], ['d', 2]])]]))
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
it('should reject nested maps with unsorted inner keys', () => {
|
|
365
|
+
const { parse } = useCborParser()
|
|
366
|
+
|
|
367
|
+
// Outer sorted, but inner map has keys "d", "c" (WRONG ORDER)
|
|
368
|
+
const nested = 'a2616101616 2a2616402616301'
|
|
369
|
+
|
|
370
|
+
expect(() => parse(nested, { validateCanonical: true }))
|
|
371
|
+
.toThrow(/not in canonical order/i)
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
it('should validate arrays within maps in canonical form', () => {
|
|
375
|
+
const { parse } = useCborParser()
|
|
376
|
+
|
|
377
|
+
// Map with array value using shortest length encoding
|
|
378
|
+
const mapWithArray = 'a161618301 0203' // {a: [1, 2, 3]}
|
|
379
|
+
|
|
380
|
+
const result = parse(mapWithArray, { validateCanonical: true })
|
|
381
|
+
expect(result.value).toEqual(new Map([['a', [1, 2, 3]]]))
|
|
382
|
+
})
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
describe('Float Canonical Encoding (Shortest Form)', () => {
|
|
386
|
+
it('should accept float16 for values that fit', () => {
|
|
387
|
+
const { parse } = useCborParser()
|
|
388
|
+
// 0.0 as float16
|
|
389
|
+
const result = parse('f90000', { validateCanonical: true })
|
|
390
|
+
expect(result.value).toBe(0.0)
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
// Note: Float canonical form is complex - values like 5.5 should use float16
|
|
394
|
+
// if possible, but our current implementation may not validate this yet
|
|
395
|
+
// This is a lower priority than integer/map canonicalization
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
describe('Edge Cases in Deterministic Mode', () => {
|
|
399
|
+
it('should accept map with keys that differ only in length', () => {
|
|
400
|
+
const { parse } = useCborParser()
|
|
401
|
+
// Keys: "a", "aa", "aaa" (correctly sorted by length then content)
|
|
402
|
+
const result = parse('a3616101626161026361616103', { validateCanonical: true })
|
|
403
|
+
expect(result.value).toEqual(new Map([['a', 1], ['aa', 2], ['aaa', 3]]))
|
|
404
|
+
})
|
|
405
|
+
|
|
406
|
+
it('should accept map with byte string keys sorted correctly', () => {
|
|
407
|
+
const { parse } = useCborParser()
|
|
408
|
+
// Byte strings sort by bytewise comparison
|
|
409
|
+
const result = parse('a24200010142000202', { validateCanonical: true })
|
|
410
|
+
// Check that map has the byte string keys
|
|
411
|
+
expect(result.value.size).toBe(2)
|
|
412
|
+
// Keys are Uint8Arrays [0, 1] and [0, 2]
|
|
413
|
+
let foundFirst = false
|
|
414
|
+
let foundSecond = false
|
|
415
|
+
for (const [key] of result.value) {
|
|
416
|
+
if (key instanceof Uint8Array) {
|
|
417
|
+
if (key.length === 2 && key[0] === 0 && key[1] === 1) foundFirst = true
|
|
418
|
+
if (key.length === 2 && key[0] === 0 && key[1] === 2) foundSecond = true
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
expect(foundFirst).toBe(true)
|
|
422
|
+
expect(foundSecond).toBe(true)
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
it('should handle tags in canonical mode', () => {
|
|
426
|
+
const { parse } = useCborParser()
|
|
427
|
+
// Tag 1 (epoch time) with canonical integer
|
|
428
|
+
const result = parse('c11a514b67b0', { validateCanonical: true })
|
|
429
|
+
expect(result.value.tag).toBe(1)
|
|
430
|
+
})
|
|
431
|
+
})
|
|
432
|
+
})
|