@marcuspuchalla/nachos 0.1.3 → 0.2.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 (68) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/dist/{chunk-PTWN7K3Y.cjs → chunk-3Z45RBZP.cjs} +469 -244
  3. package/dist/chunk-3Z45RBZP.cjs.map +1 -0
  4. package/dist/{chunk-2MTLSQ7E.js → chunk-EDXZTSIA.js} +224 -166
  5. package/dist/chunk-EDXZTSIA.js.map +1 -0
  6. package/dist/{chunk-R62CQQNI.cjs → chunk-HMUA5KLG.cjs} +239 -181
  7. package/dist/chunk-HMUA5KLG.cjs.map +1 -0
  8. package/dist/{chunk-ZDZ2B5PE.js → chunk-JESIF5IF.js} +7 -3
  9. package/dist/chunk-JESIF5IF.js.map +1 -0
  10. package/dist/{chunk-5A5T56JB.js → chunk-LWNWC2O7.js} +442 -217
  11. package/dist/chunk-LWNWC2O7.js.map +1 -0
  12. package/dist/{chunk-PD72MVTX.cjs → chunk-P6A2OOIY.cjs} +7 -3
  13. package/dist/chunk-P6A2OOIY.cjs.map +1 -0
  14. package/dist/encoder/index.cjs +14 -14
  15. package/dist/encoder/index.d.cts +5 -4
  16. package/dist/encoder/index.d.ts +5 -4
  17. package/dist/encoder/index.js +2 -2
  18. package/dist/index.cjs +58 -39
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.cts +40 -21
  21. package/dist/index.d.ts +40 -21
  22. package/dist/index.js +37 -17
  23. package/dist/index.js.map +1 -1
  24. package/dist/metafile-cjs.json +1 -1
  25. package/dist/metafile-esm.json +1 -1
  26. package/dist/parser/index.cjs +21 -21
  27. package/dist/parser/index.d.cts +4 -2
  28. package/dist/parser/index.d.ts +4 -2
  29. package/dist/parser/index.js +2 -2
  30. package/dist/{types-DvNlfbKB.d.cts → types-eG2qalpr.d.cts} +27 -1
  31. package/dist/{types-DvNlfbKB.d.ts → types-eG2qalpr.d.ts} +27 -1
  32. package/dist/{useCborSimpleEncoder-TVxzNJ_9.d.ts → useCborSimpleEncoder-CamvS-_N.d.ts} +7 -3
  33. package/dist/{useCborSimpleEncoder-ButVU988.d.cts → useCborSimpleEncoder-DXgPx62d.d.cts} +7 -3
  34. package/dist/{useCborTag-xV2Pz2VY.d.ts → useCborTag-D4d7xG3-.d.cts} +9 -4
  35. package/dist/{useCborTag-Cs1CZuXZ.d.cts → useCborTag-TYst1KR6.d.ts} +9 -4
  36. package/package.json +1 -1
  37. package/src/__tests__/audit-fixes.test.ts +141 -0
  38. package/src/__tests__/public-api.test.ts +153 -0
  39. package/src/__tests__/roundtrip.test.ts +5 -6
  40. package/src/encoder/__tests__/cbor-collection-encoder.test.ts +103 -5
  41. package/src/encoder/__tests__/cbor-encoder-errors.test.ts +40 -5
  42. package/src/encoder/__tests__/cbor-simple-encoder.test.ts +126 -0
  43. package/src/encoder/composables/useCborCollectionEncoder.ts +30 -26
  44. package/src/encoder/composables/useCborEncoder.ts +40 -0
  45. package/src/encoder/composables/useCborSimpleEncoder.ts +40 -9
  46. package/src/encoder/types.ts +9 -4
  47. package/src/encoder/utils.ts +33 -1
  48. package/src/index.ts +39 -20
  49. package/src/parser/__tests__/buffer-native-parsing.test.ts +338 -0
  50. package/src/parser/__tests__/cbor-map-duplicate-keys.test.ts +97 -7
  51. package/src/parser/__tests__/cbor-security-dos-protection.test.ts +164 -31
  52. package/src/parser/__tests__/cbor-standard-tags.test.ts +75 -7
  53. package/src/parser/__tests__/cbor-tag-reparse-fix.test.ts +268 -0
  54. package/src/parser/__tests__/utils-errors.test.ts +11 -3
  55. package/src/parser/composables/useCborCollection.ts +51 -45
  56. package/src/parser/composables/useCborDiagnostic.ts +28 -0
  57. package/src/parser/composables/useCborFloat.ts +2 -1
  58. package/src/parser/composables/useCborInteger.ts +24 -10
  59. package/src/parser/composables/useCborParser.ts +448 -208
  60. package/src/parser/composables/useCborTag.ts +53 -38
  61. package/src/parser/types.ts +32 -1
  62. package/src/parser/utils.ts +52 -0
  63. package/dist/chunk-2MTLSQ7E.js.map +0 -1
  64. package/dist/chunk-5A5T56JB.js.map +0 -1
  65. package/dist/chunk-PD72MVTX.cjs.map +0 -1
  66. package/dist/chunk-PTWN7K3Y.cjs.map +0 -1
  67. package/dist/chunk-R62CQQNI.cjs.map +0 -1
  68. package/dist/chunk-ZDZ2B5PE.js.map +0 -1
@@ -1,5 +1,5 @@
1
- export { c as useCborCollection, d as useCborFloat, a as useCborInteger, u as useCborParser, b as useCborString, e as useCborTag } from '../useCborTag-Cs1CZuXZ.cjs';
2
- export { o as CborAdditionalInfo, C as CborContext, n as CborMajorType, f as CborMap, p as CborSimpleValue, q as CborTag, e as CborValue, m as DEFAULT_LIMITS, D as DEFAULT_OPTIONS, r as DupMapKeyMode, c as ParseError, P as ParseOptions, a as ParseResult, b as ParseResultWithMap, d as ParserLimits, l as PlutusBytes, h as PlutusConstr, g as PlutusData, k as PlutusInt, j as PlutusList, i as PlutusMap, R as Result, S as SourceMapEntry, T as TaggedValue } from '../types-DvNlfbKB.cjs';
1
+ export { c as useCborCollection, d as useCborFloat, a as useCborInteger, u as useCborParser, b as useCborString, e as useCborTag } from '../useCborTag-D4d7xG3-.cjs';
2
+ export { o as CborAdditionalInfo, C as CborContext, n as CborMajorType, f as CborMap, p as CborSimpleValue, q as CborTag, e as CborValue, m as DEFAULT_LIMITS, D as DEFAULT_OPTIONS, r as DupMapKeyMode, c as ParseError, P as ParseOptions, a as ParseResult, b as ParseResultWithMap, d as ParserLimits, l as PlutusBytes, h as PlutusConstr, g as PlutusData, k as PlutusInt, j as PlutusList, i as PlutusMap, R as Result, S as SourceMapEntry, T as TaggedValue } from '../types-eG2qalpr.cjs';
3
3
 
4
4
  /**
5
5
  * Common utility functions for CBOR parsing
@@ -7,6 +7,8 @@ export { o as CborAdditionalInfo, C as CborContext, n as CborMajorType, f as Cbo
7
7
  /**
8
8
  * Converts hex string to Uint8Array
9
9
  *
10
+ * Validates even length and hex-only characters.
11
+ *
10
12
  * @param hex - Hex string (e.g., "1864")
11
13
  * @returns Byte array
12
14
  */
@@ -1,5 +1,5 @@
1
- export { c as useCborCollection, d as useCborFloat, a as useCborInteger, u as useCborParser, b as useCborString, e as useCborTag } from '../useCborTag-xV2Pz2VY.js';
2
- export { o as CborAdditionalInfo, C as CborContext, n as CborMajorType, f as CborMap, p as CborSimpleValue, q as CborTag, e as CborValue, m as DEFAULT_LIMITS, D as DEFAULT_OPTIONS, r as DupMapKeyMode, c as ParseError, P as ParseOptions, a as ParseResult, b as ParseResultWithMap, d as ParserLimits, l as PlutusBytes, h as PlutusConstr, g as PlutusData, k as PlutusInt, j as PlutusList, i as PlutusMap, R as Result, S as SourceMapEntry, T as TaggedValue } from '../types-DvNlfbKB.js';
1
+ export { c as useCborCollection, d as useCborFloat, a as useCborInteger, u as useCborParser, b as useCborString, e as useCborTag } from '../useCborTag-TYst1KR6.js';
2
+ export { o as CborAdditionalInfo, C as CborContext, n as CborMajorType, f as CborMap, p as CborSimpleValue, q as CborTag, e as CborValue, m as DEFAULT_LIMITS, D as DEFAULT_OPTIONS, r as DupMapKeyMode, c as ParseError, P as ParseOptions, a as ParseResult, b as ParseResultWithMap, d as ParserLimits, l as PlutusBytes, h as PlutusConstr, g as PlutusData, k as PlutusInt, j as PlutusList, i as PlutusMap, R as Result, S as SourceMapEntry, T as TaggedValue } from '../types-eG2qalpr.js';
3
3
 
4
4
  /**
5
5
  * Common utility functions for CBOR parsing
@@ -7,6 +7,8 @@ export { o as CborAdditionalInfo, C as CborContext, n as CborMajorType, f as Cbo
7
7
  /**
8
8
  * Converts hex string to Uint8Array
9
9
  *
10
+ * Validates even length and hex-only characters.
11
+ *
10
12
  * @param hex - Hex string (e.g., "1864")
11
13
  * @returns Byte array
12
14
  */
@@ -1,4 +1,4 @@
1
- export { bytesToHex, extractCborHeader, hexToBytes, readBigUint, readByte, readUint, useCborCollection, useCborFloat, useCborInteger, useCborParser, useCborString, useCborTag, validateUtf8Strict } from '../chunk-5A5T56JB.js';
2
- export { CborAdditionalInfo, CborMajorType, CborSimpleValue, CborTag, DEFAULT_LIMITS, DEFAULT_OPTIONS } from '../chunk-ZDZ2B5PE.js';
1
+ export { bytesToHex, extractCborHeader, hexToBytes, readBigUint, readByte, readUint, useCborCollection, useCborFloat, useCborInteger, useCborParser, useCborString, useCborTag, validateUtf8Strict } from '../chunk-LWNWC2O7.js';
2
+ export { CborAdditionalInfo, CborMajorType, CborSimpleValue, CborTag, DEFAULT_LIMITS, DEFAULT_OPTIONS } from '../chunk-JESIF5IF.js';
3
3
  //# sourceMappingURL=index.js.map
4
4
  //# sourceMappingURL=index.js.map
@@ -30,6 +30,19 @@ interface ParserLimits {
30
30
  * Following RFC 8949 Section 5.6 guidance
31
31
  */
32
32
  type DupMapKeyMode = 'allow' | 'warn' | 'reject';
33
+ /**
34
+ * Map key ordering for canonical/deterministic encoding and validation.
35
+ *
36
+ * - 'length-first': RFC 7049 Section 3.9 "Old Canonical CBOR" — shorter encoded
37
+ * keys sort first, ties broken bytewise. This is what Cardano CIP-21 mandates
38
+ * for transaction serialization, and is the default in this library.
39
+ * - 'bytewise': RFC 8949 Section 4.2.1 "Core Deterministic Encoding" — pure
40
+ * bytewise lexicographic order of the encoded keys (the modern generic default).
41
+ *
42
+ * @see https://cips.cardano.org/cip/CIP-21
43
+ * @see https://www.rfc-editor.org/rfc/rfc8949.html#section-4.2.1
44
+ */
45
+ type MapKeyOrder = 'length-first' | 'bytewise';
33
46
  /**
34
47
  * Parser options for controlling behavior
35
48
  */
@@ -55,6 +68,19 @@ interface ParseOptions {
55
68
  validateTagSemantics?: boolean;
56
69
  /** Validate Plutus constructor semantics (Tags 102, 121-127, 1280-1400) */
57
70
  validatePlutusSemantics?: boolean;
71
+ /**
72
+ * Map key ordering enforced when validateCanonical is set.
73
+ * Defaults to 'length-first' (Cardano CIP-21 / RFC 7049 Section 3.9).
74
+ * Use 'bytewise' for RFC 8949 Section 4.2.1 core deterministic order.
75
+ */
76
+ mapKeyOrder?: MapKeyOrder;
77
+ /**
78
+ * Reject trailing bytes after the top-level data item (well-formedness).
79
+ * Defaults to true for backward compatibility (decode returns bytesRead so
80
+ * callers can detect leftover data); automatically false-tightened, i.e. set
81
+ * to reject, in strict mode. Set explicitly to override.
82
+ */
83
+ allowTrailingData?: boolean;
58
84
  /** Resource limits */
59
85
  limits?: ParserLimits;
60
86
  }
@@ -298,4 +324,4 @@ type Result<T, E> = {
298
324
  error: E;
299
325
  };
300
326
 
301
- export { type CborContext as C, DEFAULT_OPTIONS as D, type ParseOptions as P, type Result as R, type SourceMapEntry as S, type TaggedValue as T, type ParseResult as a, type ParseResultWithMap as b, type ParseError as c, type ParserLimits as d, type CborValue as e, type CborMap as f, type PlutusData as g, type PlutusConstr as h, type PlutusMap as i, type PlutusList as j, type PlutusInt as k, type PlutusBytes as l, DEFAULT_LIMITS as m, CborMajorType as n, CborAdditionalInfo as o, CborSimpleValue as p, CborTag as q, type DupMapKeyMode as r, type CborByteString as s, type CborTextString as t };
327
+ export { type CborContext as C, DEFAULT_OPTIONS as D, type MapKeyOrder as M, type ParseOptions as P, type Result as R, type SourceMapEntry as S, type TaggedValue as T, type ParseResult as a, type ParseResultWithMap as b, type ParseError as c, type ParserLimits as d, type CborValue as e, type CborMap as f, type PlutusData as g, type PlutusConstr as h, type PlutusMap as i, type PlutusList as j, type PlutusInt as k, type PlutusBytes as l, DEFAULT_LIMITS as m, CborMajorType as n, CborAdditionalInfo as o, CborSimpleValue as p, CborTag as q, type DupMapKeyMode as r, type CborByteString as s, type CborTextString as t };
@@ -30,6 +30,19 @@ interface ParserLimits {
30
30
  * Following RFC 8949 Section 5.6 guidance
31
31
  */
32
32
  type DupMapKeyMode = 'allow' | 'warn' | 'reject';
33
+ /**
34
+ * Map key ordering for canonical/deterministic encoding and validation.
35
+ *
36
+ * - 'length-first': RFC 7049 Section 3.9 "Old Canonical CBOR" — shorter encoded
37
+ * keys sort first, ties broken bytewise. This is what Cardano CIP-21 mandates
38
+ * for transaction serialization, and is the default in this library.
39
+ * - 'bytewise': RFC 8949 Section 4.2.1 "Core Deterministic Encoding" — pure
40
+ * bytewise lexicographic order of the encoded keys (the modern generic default).
41
+ *
42
+ * @see https://cips.cardano.org/cip/CIP-21
43
+ * @see https://www.rfc-editor.org/rfc/rfc8949.html#section-4.2.1
44
+ */
45
+ type MapKeyOrder = 'length-first' | 'bytewise';
33
46
  /**
34
47
  * Parser options for controlling behavior
35
48
  */
@@ -55,6 +68,19 @@ interface ParseOptions {
55
68
  validateTagSemantics?: boolean;
56
69
  /** Validate Plutus constructor semantics (Tags 102, 121-127, 1280-1400) */
57
70
  validatePlutusSemantics?: boolean;
71
+ /**
72
+ * Map key ordering enforced when validateCanonical is set.
73
+ * Defaults to 'length-first' (Cardano CIP-21 / RFC 7049 Section 3.9).
74
+ * Use 'bytewise' for RFC 8949 Section 4.2.1 core deterministic order.
75
+ */
76
+ mapKeyOrder?: MapKeyOrder;
77
+ /**
78
+ * Reject trailing bytes after the top-level data item (well-formedness).
79
+ * Defaults to true for backward compatibility (decode returns bytesRead so
80
+ * callers can detect leftover data); automatically false-tightened, i.e. set
81
+ * to reject, in strict mode. Set explicitly to override.
82
+ */
83
+ allowTrailingData?: boolean;
58
84
  /** Resource limits */
59
85
  limits?: ParserLimits;
60
86
  }
@@ -298,4 +324,4 @@ type Result<T, E> = {
298
324
  error: E;
299
325
  };
300
326
 
301
- export { type CborContext as C, DEFAULT_OPTIONS as D, type ParseOptions as P, type Result as R, type SourceMapEntry as S, type TaggedValue as T, type ParseResult as a, type ParseResultWithMap as b, type ParseError as c, type ParserLimits as d, type CborValue as e, type CborMap as f, type PlutusData as g, type PlutusConstr as h, type PlutusMap as i, type PlutusList as j, type PlutusInt as k, type PlutusBytes as l, DEFAULT_LIMITS as m, CborMajorType as n, CborAdditionalInfo as o, CborSimpleValue as p, CborTag as q, type DupMapKeyMode as r, type CborByteString as s, type CborTextString as t };
327
+ export { type CborContext as C, DEFAULT_OPTIONS as D, type MapKeyOrder as M, type ParseOptions as P, type Result as R, type SourceMapEntry as S, type TaggedValue as T, type ParseResult as a, type ParseResultWithMap as b, type ParseError as c, type ParserLimits as d, type CborValue as e, type CborMap as f, type PlutusData as g, type PlutusConstr as h, type PlutusMap as i, type PlutusList as j, type PlutusInt as k, type PlutusBytes as l, DEFAULT_LIMITS as m, CborMajorType as n, CborAdditionalInfo as o, CborSimpleValue as p, CborTag as q, type DupMapKeyMode as r, type CborByteString as s, type CborTextString as t };
@@ -1,4 +1,4 @@
1
- import { h as PlutusConstr, s as CborByteString, t as CborTextString } from './types-DvNlfbKB.js';
1
+ import { h as PlutusConstr, M as MapKeyOrder, s as CborByteString, t as CborTextString } from './types-eG2qalpr.js';
2
2
 
3
3
  /**
4
4
  * CBOR Encoder Type Definitions
@@ -15,6 +15,12 @@ interface EncodeOptions {
15
15
  allowIndefinite?: boolean;
16
16
  /** Reject duplicate map keys */
17
17
  rejectDuplicateKeys?: boolean;
18
+ /**
19
+ * Map key ordering used in canonical mode.
20
+ * Defaults to 'length-first' (Cardano CIP-21 / RFC 7049 §3.9).
21
+ * Use 'bytewise' for RFC 8949 §4.2.1 core deterministic order.
22
+ */
23
+ mapKeyOrder?: MapKeyOrder;
18
24
  /** Maximum nesting depth */
19
25
  maxDepth?: number;
20
26
  /** Maximum output size in bytes */
@@ -56,8 +62,6 @@ interface TaggedValue {
56
62
  interface EncodeContext {
57
63
  /** Current nesting depth */
58
64
  depth: number;
59
- /** Bytes written so far */
60
- bytesWritten: number;
61
65
  /** Encoder options */
62
66
  options: Required<EncodeOptions>;
63
67
  }
@@ -1,4 +1,4 @@
1
- import { h as PlutusConstr, s as CborByteString, t as CborTextString } from './types-DvNlfbKB.cjs';
1
+ import { h as PlutusConstr, M as MapKeyOrder, s as CborByteString, t as CborTextString } from './types-eG2qalpr.cjs';
2
2
 
3
3
  /**
4
4
  * CBOR Encoder Type Definitions
@@ -15,6 +15,12 @@ interface EncodeOptions {
15
15
  allowIndefinite?: boolean;
16
16
  /** Reject duplicate map keys */
17
17
  rejectDuplicateKeys?: boolean;
18
+ /**
19
+ * Map key ordering used in canonical mode.
20
+ * Defaults to 'length-first' (Cardano CIP-21 / RFC 7049 §3.9).
21
+ * Use 'bytewise' for RFC 8949 §4.2.1 core deterministic order.
22
+ */
23
+ mapKeyOrder?: MapKeyOrder;
18
24
  /** Maximum nesting depth */
19
25
  maxDepth?: number;
20
26
  /** Maximum output size in bytes */
@@ -56,8 +62,6 @@ interface TaggedValue {
56
62
  interface EncodeContext {
57
63
  /** Current nesting depth */
58
64
  depth: number;
59
- /** Bytes written so far */
60
- bytesWritten: number;
61
65
  /** Encoder options */
62
66
  options: Required<EncodeOptions>;
63
67
  }
@@ -1,4 +1,4 @@
1
- import { P as ParseOptions, a as ParseResult, b as ParseResultWithMap, e as CborValue, S as SourceMapEntry } from './types-DvNlfbKB.js';
1
+ import { P as ParseOptions, a as ParseResult, b as ParseResultWithMap, e as CborValue, S as SourceMapEntry, h as PlutusConstr } from './types-eG2qalpr.cjs';
2
2
 
3
3
  /**
4
4
  * CBOR Main Parser Composable
@@ -19,9 +19,9 @@ import { P as ParseOptions, a as ParseResult, b as ParseResultWithMap, e as Cbor
19
19
  * ```
20
20
  */
21
21
  declare function useCborParser(): {
22
- parse: (hexString: string, options?: ParseOptions) => ParseResult;
23
- parseWithSourceMap: (hexString: string, options?: ParseOptions) => ParseResultWithMap;
24
- parseSequence: (hexString: string, options?: ParseOptions) => CborValue[];
22
+ parse: (input: string | Uint8Array, options?: ParseOptions) => ParseResult;
23
+ parseWithSourceMap: (input: string | Uint8Array, options?: ParseOptions) => ParseResultWithMap;
24
+ parseSequence: (input: string | Uint8Array, options?: ParseOptions) => CborValue[];
25
25
  parseSequenceWithSourceMap: (hexString: string, options?: ParseOptions) => {
26
26
  values: CborValue[];
27
27
  sourceMaps: SourceMapEntry[][];
@@ -47,6 +47,7 @@ declare function useCborParser(): {
47
47
  */
48
48
  declare function useCborInteger(): {
49
49
  parseInteger: (hexString: string, options?: ParseOptions) => ParseResult;
50
+ parseIntegerFromBuffer: (buffer: Uint8Array, offset: number, options?: ParseOptions) => ParseResult;
50
51
  };
51
52
 
52
53
  /**
@@ -115,6 +116,7 @@ declare function useCborFloat(): {
115
116
  parse: (hexString: string, options?: ParseOptions) => ParseResult;
116
117
  parseFloat: (hexString: string, options?: ParseOptions) => ParseResult;
117
118
  parseSimple: (hexString: string, _options?: ParseOptions) => ParseResult;
119
+ parseFromBuffer: (buffer: Uint8Array, offset: number, options?: ParseOptions) => ParseResult;
118
120
  };
119
121
 
120
122
  /**
@@ -137,6 +139,9 @@ declare function useCborFloat(): {
137
139
  declare function useCborTag(): {
138
140
  parseTag: (hexString: string, options?: ParseOptions) => ParseResult;
139
141
  parse: (hexString: string, options?: ParseOptions) => ParseResult;
142
+ parseTagFromBuffer: (buffer: Uint8Array, offset: number, options?: ParseOptions, tagDepth?: number) => ParseResult;
143
+ validateTagSemantics: (tagNumber: number, value: CborValue, options?: ParseOptions) => void;
144
+ decodePlutusConstructor: (tagNumber: number, value: CborValue) => PlutusConstr | null;
140
145
  };
141
146
 
142
147
  export { useCborInteger as a, useCborString as b, useCborCollection as c, useCborFloat as d, useCborTag as e, useCborParser as u };
@@ -1,4 +1,4 @@
1
- import { P as ParseOptions, a as ParseResult, b as ParseResultWithMap, e as CborValue, S as SourceMapEntry } from './types-DvNlfbKB.cjs';
1
+ import { P as ParseOptions, a as ParseResult, b as ParseResultWithMap, e as CborValue, S as SourceMapEntry, h as PlutusConstr } from './types-eG2qalpr.js';
2
2
 
3
3
  /**
4
4
  * CBOR Main Parser Composable
@@ -19,9 +19,9 @@ import { P as ParseOptions, a as ParseResult, b as ParseResultWithMap, e as Cbor
19
19
  * ```
20
20
  */
21
21
  declare function useCborParser(): {
22
- parse: (hexString: string, options?: ParseOptions) => ParseResult;
23
- parseWithSourceMap: (hexString: string, options?: ParseOptions) => ParseResultWithMap;
24
- parseSequence: (hexString: string, options?: ParseOptions) => CborValue[];
22
+ parse: (input: string | Uint8Array, options?: ParseOptions) => ParseResult;
23
+ parseWithSourceMap: (input: string | Uint8Array, options?: ParseOptions) => ParseResultWithMap;
24
+ parseSequence: (input: string | Uint8Array, options?: ParseOptions) => CborValue[];
25
25
  parseSequenceWithSourceMap: (hexString: string, options?: ParseOptions) => {
26
26
  values: CborValue[];
27
27
  sourceMaps: SourceMapEntry[][];
@@ -47,6 +47,7 @@ declare function useCborParser(): {
47
47
  */
48
48
  declare function useCborInteger(): {
49
49
  parseInteger: (hexString: string, options?: ParseOptions) => ParseResult;
50
+ parseIntegerFromBuffer: (buffer: Uint8Array, offset: number, options?: ParseOptions) => ParseResult;
50
51
  };
51
52
 
52
53
  /**
@@ -115,6 +116,7 @@ declare function useCborFloat(): {
115
116
  parse: (hexString: string, options?: ParseOptions) => ParseResult;
116
117
  parseFloat: (hexString: string, options?: ParseOptions) => ParseResult;
117
118
  parseSimple: (hexString: string, _options?: ParseOptions) => ParseResult;
119
+ parseFromBuffer: (buffer: Uint8Array, offset: number, options?: ParseOptions) => ParseResult;
118
120
  };
119
121
 
120
122
  /**
@@ -137,6 +139,9 @@ declare function useCborFloat(): {
137
139
  declare function useCborTag(): {
138
140
  parseTag: (hexString: string, options?: ParseOptions) => ParseResult;
139
141
  parse: (hexString: string, options?: ParseOptions) => ParseResult;
142
+ parseTagFromBuffer: (buffer: Uint8Array, offset: number, options?: ParseOptions, tagDepth?: number) => ParseResult;
143
+ validateTagSemantics: (tagNumber: number, value: CborValue, options?: ParseOptions) => void;
144
+ decodePlutusConstructor: (tagNumber: number, value: CborValue) => PlutusConstr | null;
140
145
  };
141
146
 
142
147
  export { useCborInteger as a, useCborString as b, useCborCollection as c, useCborFloat as d, useCborTag as e, useCborParser as u };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marcuspuchalla/nachos",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "NACHOS - Not Another CBOR Handling Object System. RFC 8949 CBOR encoder/decoder with full source map support for interactive debugging. Zero dependencies, TypeScript, works in Node.js and browsers.",
5
5
  "keywords": [
6
6
  "nachos",
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Audit Remediation Regression Tests
3
+ *
4
+ * Locks in the fixes from the June 2026 RFC 8949 conformance/security audit.
5
+ * Each test maps to a finding ID (H1, H2, M1-M5, L1, L3, L5).
6
+ */
7
+
8
+ import { describe, it, expect } from 'vitest'
9
+ import { decode, decodeWithSourceMap, encode, encodeToHex, toDiagnostic } from '../index'
10
+
11
+ describe('Audit fixes', () => {
12
+ describe('H1 — source-map parse path enforces tag-depth limit', () => {
13
+ const deepTag = 'c2'.repeat(60000) + '00'
14
+
15
+ it('decode() rejects deeply nested tags cleanly', () => {
16
+ expect(() => decode(deepTag, { limits: { maxTagDepth: 100 } }))
17
+ .toThrow(/Tag nesting depth/)
18
+ })
19
+
20
+ it('decodeWithSourceMap() rejects deeply nested tags cleanly (no stack overflow)', () => {
21
+ let err: Error | null = null
22
+ try { decodeWithSourceMap(deepTag, { limits: { maxTagDepth: 100 } }) }
23
+ catch (e) { err = e as Error }
24
+ expect(err).not.toBeNull()
25
+ expect(err!.constructor.name).toBe('Error') // NOT RangeError
26
+ expect(err!.message).toMatch(/Tag nesting depth/)
27
+ })
28
+
29
+ it('still decodes legitimately nested tags', () => {
30
+ const r = decode('c1c0' + '60', { limits: { maxTagDepth: 100 } }) // tag1(tag0(""))
31
+ expect((r.value as any).tag).toBe(1)
32
+ })
33
+ })
34
+
35
+ describe('H2 — map key ordering (CIP-21 length-first default, bytewise option)', () => {
36
+ // key "aa" -> 62 61 61 (len 3); key 1000000 -> 1a 00 0f 42 40 (len 5)
37
+ const m = new Map<any, any>([['aa', 1], [1000000, 2]])
38
+
39
+ it('canonical default is length-first (Cardano CIP-21)', () => {
40
+ expect(encodeToHex(m, { canonical: true })).toBe('a2' + '626161' + '01' + '1a000f4240' + '02')
41
+ })
42
+
43
+ it('mapKeyOrder:bytewise yields RFC 8949 §4.2.1 order', () => {
44
+ expect(encodeToHex(m, { canonical: true, mapKeyOrder: 'bytewise' }))
45
+ .toBe('a2' + '1a000f4240' + '02' + '626161' + '01')
46
+ })
47
+
48
+ const lenFirst = 'a2' + '626161' + '01' + '1a000f4240' + '02'
49
+ const byteWise = 'a2' + '1a000f4240' + '02' + '626161' + '01'
50
+
51
+ it('decoder (default) accepts length-first order', () => {
52
+ expect(() => decode(lenFirst, { validateCanonical: true })).not.toThrow()
53
+ })
54
+ it('decoder (bytewise) accepts bytewise order', () => {
55
+ expect(() => decode(byteWise, { validateCanonical: true, mapKeyOrder: 'bytewise' })).not.toThrow()
56
+ })
57
+ it('decoder (bytewise) rejects length-first order', () => {
58
+ expect(() => decode(lenFirst, { validateCanonical: true, mapKeyOrder: 'bytewise' }))
59
+ .toThrow(/not in canonical order/)
60
+ })
61
+
62
+ it('encoder output round-trips through its own canonical decoder (both orders)', () => {
63
+ const lf = encodeToHex(m, { canonical: true })
64
+ const bw = encodeToHex(m, { canonical: true, mapKeyOrder: 'bytewise' })
65
+ expect(() => decode(lf, { validateCanonical: true })).not.toThrow()
66
+ expect(() => decode(bw, { validateCanonical: true, mapKeyOrder: 'bytewise' })).not.toThrow()
67
+ })
68
+ })
69
+
70
+ describe('M1 — trailing data well-formedness', () => {
71
+ it('default lenient: reports bytesRead and ignores trailing', () => {
72
+ expect(decode('000102').bytesRead).toBe(1)
73
+ })
74
+ it('allowTrailingData:false rejects trailing bytes', () => {
75
+ expect(() => decode('000102', { allowTrailingData: false })).toThrow(/Trailing data/)
76
+ })
77
+ it('strict mode rejects trailing bytes', () => {
78
+ expect(() => decode('000102', { strict: true })).toThrow(/Trailing data/)
79
+ })
80
+ it('strict mode accepts an exact single item', () => {
81
+ expect(decode('00', { strict: true }).value).toBe(0)
82
+ })
83
+ })
84
+
85
+ describe('M2 — encoder maxDepth enforced across tag boundary', () => {
86
+ it('rejects deeply nested tagged values', () => {
87
+ let v: any = 0
88
+ for (let i = 0; i < 300; i++) v = { tag: 6, value: v }
89
+ expect(() => encode(v)).toThrow(/Maximum nesting depth/)
90
+ })
91
+ it('allows shallow nested tagged values', () => {
92
+ let v: any = 0
93
+ for (let i = 0; i < 10; i++) v = { tag: 6, value: v }
94
+ expect(() => encode(v)).not.toThrow()
95
+ })
96
+ })
97
+
98
+ describe('M4 — canonical mode enforces shortest-form tag numbers', () => {
99
+ it('rejects non-shortest tag number (d80100)', () => {
100
+ expect(() => decode('d80100', { validateCanonical: true }))
101
+ .toThrow(/Non-canonical/)
102
+ })
103
+ it('accepts shortest tag number (c100)', () => {
104
+ expect(() => decode('c100', { validateCanonical: true })).not.toThrow()
105
+ })
106
+ })
107
+
108
+ describe('M5 — float16 subnormals encode to shortest form and stay self-canonical', () => {
109
+ it('encode(2^-24) uses float16, not float32', () => {
110
+ expect(encodeToHex(5.960464477539063e-8)).toMatch(/^f9/)
111
+ })
112
+ it('encoder float output passes its own canonical validation', () => {
113
+ for (const v of [1.5, 65504, 5.960464477539063e-8, 0.1]) {
114
+ const h = encodeToHex(v)
115
+ expect(() => decode(h, { validateCanonical: true }), `value ${v} -> ${h}`).not.toThrow()
116
+ }
117
+ })
118
+ })
119
+
120
+ describe('L3 — readUint refuses values above MAX_SAFE_INTEGER (via decode)', () => {
121
+ it('8-byte integer above 2^53 decodes as BigInt without loss', () => {
122
+ // 0x1b + 0102030405060708 -> tag-free uint, larger than MAX_SAFE_INTEGER
123
+ const r = decode('1b0102030405060708')
124
+ expect(typeof r.value).toBe('bigint')
125
+ expect(r.value).toBe(0x0102030405060708n)
126
+ })
127
+ })
128
+
129
+ describe('L5 — diagnostic notation', () => {
130
+ it('renders indefinite-length arrays', () => {
131
+ expect(toDiagnostic(decode('9f0102ff').value)).toBe('[_ 1, 2]')
132
+ })
133
+ it('renders unassigned simple values', () => {
134
+ expect(toDiagnostic(decode('f820').value)).toBe('simple(32)')
135
+ })
136
+ it('renders indefinite-length text strings', () => {
137
+ const v = decode('7f626162ff').value // (_ "ab") from chunks "ab"
138
+ expect(toDiagnostic(v)).toContain('_')
139
+ })
140
+ })
141
+ })
@@ -123,6 +123,14 @@ describe('Public API - Functional Encode', () => {
123
123
  expect(result.hex).toBe('010203')
124
124
  expect(result.bytes).toEqual(new Uint8Array([0x01, 0x02, 0x03]))
125
125
  })
126
+
127
+ it('encode() should preserve -0', () => {
128
+ const result = encode(-0)
129
+ expect(result.hex).toBe('f98000')
130
+
131
+ const decoded = decode(result.hex)
132
+ expect(Object.is(decoded.value, -0)).toBe(true)
133
+ })
126
134
  })
127
135
 
128
136
  describe('Public API - Diagnostic Notation', () => {
@@ -277,6 +285,151 @@ describe('Public API - Utilities', () => {
277
285
  })
278
286
  })
279
287
 
288
+ describe('Public API - Uint8Array Decode', () => {
289
+ it('decode() should accept Uint8Array input for integers', () => {
290
+ const result = decode(new Uint8Array([0x18, 0x64]))
291
+ expect(result.value).toBe(100)
292
+ expect(result.bytesRead).toBe(2)
293
+ })
294
+
295
+ it('decode() should accept Uint8Array for small integers', () => {
296
+ const result = decode(new Uint8Array([0x01]))
297
+ expect(result.value).toBe(1)
298
+ expect(result.bytesRead).toBe(1)
299
+ })
300
+
301
+ it('decode(Uint8Array) should produce identical results to decode(hexString)', () => {
302
+ // Integer 100
303
+ const hexResult = decode('1864')
304
+ const bytesResult = decode(new Uint8Array([0x18, 0x64]))
305
+ expect(bytesResult.value).toEqual(hexResult.value)
306
+ expect(bytesResult.bytesRead).toEqual(hexResult.bytesRead)
307
+
308
+ // String "IETF"
309
+ const hexStr = decode('6449455446')
310
+ const bytesStr = decode(new Uint8Array([0x64, 0x49, 0x45, 0x54, 0x46]))
311
+ expect(bytesStr.value).toEqual(hexStr.value)
312
+ expect(bytesStr.bytesRead).toEqual(hexStr.bytesRead)
313
+
314
+ // Array [1, 2, 3]
315
+ const hexArr = decode('83010203')
316
+ const bytesArr = decode(new Uint8Array([0x83, 0x01, 0x02, 0x03]))
317
+ expect(bytesArr.value).toEqual(hexArr.value)
318
+ expect(bytesArr.bytesRead).toEqual(hexArr.bytesRead)
319
+ })
320
+
321
+ it('decode(Uint8Array) should enforce maxInputSize limit', () => {
322
+ const bytes = new Uint8Array([0x01])
323
+ // Should succeed with a large enough limit
324
+ expect(() => decode(bytes, { limits: { maxInputSize: 100 } })).not.toThrow()
325
+
326
+ // Should throw when bytes exceed the limit
327
+ const largeBytes = new Uint8Array(200)
328
+ largeBytes[0] = 0x01 // valid CBOR integer 1
329
+ expect(() => decode(largeBytes, { limits: { maxInputSize: 10 } })).toThrow(/exceeds limit/)
330
+ })
331
+
332
+ it('decode(Uint8Array) should handle tagged values', () => {
333
+ // d87980 = tag 121, empty array
334
+ const result = decode(new Uint8Array([0xd8, 0x79, 0x80]))
335
+ expect(result.value).toMatchObject({ tag: 121, value: [] })
336
+ })
337
+
338
+ it('decode(Uint8Array) should handle maps', () => {
339
+ // a16161 01 = {"a": 1}
340
+ const result = decode(new Uint8Array([0xa1, 0x61, 0x61, 0x01]))
341
+ expect(result.bytesRead).toBe(4)
342
+ // Map with string key "a" -> 1
343
+ expect(result.value).toBeInstanceOf(Map)
344
+ expect((result.value as Map<string, number>).get('a')).toBe(1)
345
+ })
346
+
347
+ it('decode(Uint8Array) should handle boolean and null', () => {
348
+ expect(decode(new Uint8Array([0xf5])).value).toBe(true)
349
+ expect(decode(new Uint8Array([0xf4])).value).toBe(false)
350
+ expect(decode(new Uint8Array([0xf6])).value).toBe(null)
351
+ })
352
+
353
+ it('decode(Uint8Array) should reject empty Uint8Array', () => {
354
+ expect(() => decode(new Uint8Array([]))).toThrow()
355
+ })
356
+
357
+ it('decode(Uint8Array) should accept options', () => {
358
+ const result = decode(new Uint8Array([0x18, 0x64]), { strict: true })
359
+ expect(result.value).toBe(100)
360
+ })
361
+ })
362
+
363
+ describe('Public API - Uint8Array DecodeWithSourceMap', () => {
364
+ it('decodeWithSourceMap() should accept Uint8Array input', () => {
365
+ // d87980 = tag 121, empty array
366
+ const result = decodeWithSourceMap(new Uint8Array([0xd8, 0x79, 0x80]))
367
+ expect(result.value).toMatchObject({ tag: 121, value: [] })
368
+ expect(result.sourceMap).toBeDefined()
369
+ expect(Array.isArray(result.sourceMap)).toBe(true)
370
+ expect(result.sourceMap.length).toBeGreaterThan(0)
371
+ })
372
+
373
+ it('decodeWithSourceMap(Uint8Array) should match hex string results', () => {
374
+ const hexResult = decodeWithSourceMap('d87980')
375
+ const bytesResult = decodeWithSourceMap(new Uint8Array([0xd8, 0x79, 0x80]))
376
+ expect(bytesResult.value).toEqual(hexResult.value)
377
+ expect(bytesResult.sourceMap.length).toBe(hexResult.sourceMap.length)
378
+ })
379
+ })
380
+
381
+ describe('Public API - Uint8Array CborDecoder Class', () => {
382
+ it('CborDecoder.decode() should accept Uint8Array', () => {
383
+ const decoder = new CborDecoder()
384
+ const result = decoder.decode(new Uint8Array([0x18, 0x64]))
385
+ expect(result.value).toBe(100)
386
+ expect(result.bytesRead).toBe(2)
387
+ })
388
+
389
+ it('CborDecoder.decodeWithSourceMap() should accept Uint8Array', () => {
390
+ const decoder = new CborDecoder()
391
+ const result = decoder.decodeWithSourceMap(new Uint8Array([0xd8, 0x79, 0x80]))
392
+ expect(result.value).toMatchObject({ tag: 121, value: [] })
393
+ expect(result.sourceMap).toBeDefined()
394
+ })
395
+
396
+ it('CborDecoder with options should work with Uint8Array', () => {
397
+ const decoder = new CborDecoder({ strict: true })
398
+ const result = decoder.decode(new Uint8Array([0x18, 0x64]))
399
+ expect(result.value).toBe(100)
400
+ })
401
+ })
402
+
403
+ describe('Public API - Uint8Array useCborParser', () => {
404
+ it('parse() should accept Uint8Array', () => {
405
+ const { parse } = useCborParser()
406
+ const result = parse(new Uint8Array([0x18, 0x64]))
407
+ expect(result.value).toBe(100)
408
+ expect(result.bytesRead).toBe(2)
409
+ })
410
+
411
+ it('parseWithSourceMap() should accept Uint8Array', () => {
412
+ const { parseWithSourceMap } = useCborParser()
413
+ const result = parseWithSourceMap(new Uint8Array([0xd8, 0x79, 0x80]))
414
+ expect(result.value).toMatchObject({ tag: 121, value: [] })
415
+ expect(result.sourceMap).toBeDefined()
416
+ })
417
+
418
+ it('parseSequence() should accept Uint8Array', () => {
419
+ const { parseSequence } = useCborParser()
420
+ // Three separate integers: 1, 2, 3
421
+ const result = parseSequence(new Uint8Array([0x01, 0x02, 0x03]))
422
+ expect(result).toEqual([1, 2, 3])
423
+ })
424
+
425
+ it('parseSequence(Uint8Array) should match hex string results', () => {
426
+ const { parseSequence } = useCborParser()
427
+ const hexResult = parseSequence('010203')
428
+ const bytesResult = parseSequence(new Uint8Array([0x01, 0x02, 0x03]))
429
+ expect(bytesResult).toEqual(hexResult)
430
+ })
431
+ })
432
+
280
433
  describe('Public API - Constants and Enums', () => {
281
434
  it('DEFAULT_OPTIONS should be exported', () => {
282
435
  expect(DEFAULT_OPTIONS).toBeDefined()
@@ -155,13 +155,12 @@ describe('Round-trip: Floats', () => {
155
155
  expect(roundTrip(0.0)).toBe(0)
156
156
  })
157
157
 
158
- it('should encode -0.0 as integer 0 (encoder treats -0 as integer)', () => {
159
- // -0.0 passes Number.isInteger() and Number.isSafeInteger(), so the
160
- // encoder uses integer encoding (producing 0x00), which decodes as 0.
161
- // This is expected CBOR behavior: -0 only preserves via float encoding.
158
+ it('should encode -0.0 as float16 preserving sign', () => {
159
+ // -0.0 is detected by the encoder and encoded as float16 (f98000),
160
+ // which correctly preserves the negative zero sign bit.
162
161
  const encoded = encode(-0.0)
163
- expect(encoded.hex).toBe('00')
164
- expect(roundTrip(-0.0)).toBe(0)
162
+ expect(encoded.hex).toBe('f98000')
163
+ expect(Object.is(roundTrip(-0.0), -0)).toBe(true)
165
164
  })
166
165
 
167
166
  it('should round-trip 1.5 (exact in float16)', () => {