@bcts/dcbor 1.0.0-alpha.8 → 1.0.0-beta.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/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@bcts/dcbor",
3
- "version": "1.0.0-alpha.8",
3
+ "version": "1.0.0-beta.0",
4
4
  "type": "module",
5
5
  "description": "Blockchain Commons Deterministic CBOR (dCBOR) for TypeScript",
6
6
  "license": "BSD-2-Clause-Patent",
7
- "author": "Leonardo Custodio <leonardo@custodio.me>",
8
- "homepage": "https://github.com/leonardocustodio/bcts",
7
+ "author": "Parity Technologies <admin@parity.io> (https://parity.io)",
8
+ "homepage": "https://bcts.dev",
9
9
  "repository": {
10
10
  "type": "git",
11
- "url": "https://github.com/leonardocustodio/bcts",
11
+ "url": "https://github.com/paritytech/bcts",
12
12
  "directory": "packages/dcbor"
13
13
  },
14
14
  "bugs": {
15
- "url": "https://github.com/leonardocustodio/bcts/issues"
15
+ "url": "https://github.com/paritytech/bcts/issues"
16
16
  },
17
17
  "main": "dist/index.cjs",
18
18
  "module": "dist/index.mjs",
@@ -33,11 +33,8 @@
33
33
  ],
34
34
  "scripts": {
35
35
  "build": "tsdown",
36
- "dev": "tsdown --watch",
37
36
  "test": "vitest run",
38
37
  "test:watch": "vitest",
39
- "test:cli": "vitest run tests/cli.test.ts",
40
- "test:examples": "npm run build && node scripts/test-examples.js",
41
38
  "lint": "eslint 'src/**/*.ts' 'tests/**/*.ts'",
42
39
  "lint:fix": "eslint 'src/**/*.ts' 'tests/**/*.ts' --fix",
43
40
  "typecheck": "tsc --noEmit",
@@ -61,16 +58,17 @@
61
58
  "devDependencies": {
62
59
  "@bcts/eslint": "^0.1.0",
63
60
  "@bcts/tsconfig": "^0.1.0",
64
- "@eslint/js": "^9.39.1",
61
+ "@eslint/js": "^10.0.1",
65
62
  "@types/collections": "^5.1.5",
66
- "@typescript-eslint/eslint-plugin": "^8.49.0",
67
- "@typescript-eslint/parser": "^8.49.0",
68
- "eslint": "^9.39.1",
63
+ "@types/node": "^25.6.0",
64
+ "@typescript-eslint/eslint-plugin": "^8.59.0",
65
+ "@typescript-eslint/parser": "^8.59.0",
66
+ "eslint": "^10.2.1",
69
67
  "ts-node": "^10.9.2",
70
- "tsdown": "^0.17.2",
71
- "typedoc": "^0.28.15",
72
- "typescript": "^5.9.3",
73
- "vitest": "^3.2.4"
68
+ "tsdown": "^0.21.0",
69
+ "typedoc": "^0.28.19",
70
+ "typescript": "^6.0.3",
71
+ "vitest": "^4.1.5"
74
72
  },
75
73
  "dependencies": {
76
74
  "byte-data": "^19.0.1",
package/src/bignum.ts ADDED
@@ -0,0 +1,347 @@
1
+ /**
2
+ * Copyright © 2023-2026 Blockchain Commons, LLC
3
+ * Copyright © 2025-2026 Parity Technologies
4
+ *
5
+ *
6
+ * CBOR bignum (tags 2 and 3) support.
7
+ *
8
+ * This module provides conversion between CBOR and JavaScript BigInt types,
9
+ * implementing RFC 8949 §3.4.3 (Bignums) with dCBOR/CDE canonical encoding rules.
10
+ *
11
+ * Encoding:
12
+ * - `biguintToCbor` always encodes as tag 2 (positive bignum) with a byte
13
+ * string content.
14
+ * - `bigintToCbor` encodes as tag 2 for non-negative values or tag 3
15
+ * (negative bignum) for negative values.
16
+ * - No numeric reduction is performed: values are always encoded as bignums,
17
+ * even if they would fit in normal CBOR integers.
18
+ *
19
+ * Decoding:
20
+ * - Accepts CBOR integers (major types 0 and 1) and converts them to bigints.
21
+ * - Accepts tag 2 (positive bignum) and tag 3 (negative bignum) with byte
22
+ * string content.
23
+ * - Enforces shortest-form canonical representation for bignum magnitudes.
24
+ * - Rejects floating-point values.
25
+ *
26
+ * @module bignum
27
+ */
28
+
29
+ import { type Cbor, MajorType, toTaggedValue } from "./cbor";
30
+ import { CborError } from "./error";
31
+
32
+ // CBOR tag values (local constants matching tags.ts, avoiding circular import)
33
+ const TAG_2_POSITIVE_BIGNUM = 2;
34
+ const TAG_3_NEGATIVE_BIGNUM = 3;
35
+
36
+ /**
37
+ * Validates that a bignum magnitude byte string is in shortest canonical form.
38
+ *
39
+ * Matches Rust's `validate_bignum_magnitude()`.
40
+ *
41
+ * Rules:
42
+ * - For positive bignums (tag 2): empty byte string represents zero;
43
+ * non-empty must not have leading zero bytes.
44
+ * - For negative bignums (tag 3): byte string must not be empty
45
+ * (magnitude zero is encoded as `0x00`); must not have leading zero bytes
46
+ * except when the magnitude is zero (single `0x00`).
47
+ *
48
+ * @param bytes - The magnitude byte string to validate
49
+ * @param isNegative - Whether this is for a negative bignum (tag 3)
50
+ * @throws CborError with type NonCanonicalNumeric on validation failure
51
+ */
52
+ export function validateBignumMagnitude(bytes: Uint8Array, isNegative: boolean): void {
53
+ if (isNegative) {
54
+ // Tag 3: byte string must not be empty
55
+ if (bytes.length === 0) {
56
+ throw new CborError({ type: "NonCanonicalNumeric" });
57
+ }
58
+ // No leading zeros unless the entire magnitude is zero (single 0x00 byte)
59
+ if (bytes.length > 1 && bytes[0] === 0) {
60
+ throw new CborError({ type: "NonCanonicalNumeric" });
61
+ }
62
+ } else {
63
+ // Tag 2: empty byte string is valid (represents zero)
64
+ // Non-empty must not have leading zeros
65
+ if (bytes.length > 0 && bytes[0] === 0) {
66
+ throw new CborError({ type: "NonCanonicalNumeric" });
67
+ }
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Strips leading zero bytes from a byte array, returning the minimal
73
+ * representation.
74
+ *
75
+ * Matches Rust's `strip_leading_zeros()`.
76
+ *
77
+ * @param bytes - The byte array to strip
78
+ * @returns A subarray with leading zeros removed
79
+ */
80
+ export function stripLeadingZeros(bytes: Uint8Array): Uint8Array {
81
+ let start = 0;
82
+ while (start < bytes.length && bytes[start] === 0) {
83
+ start++;
84
+ }
85
+ return bytes.subarray(start);
86
+ }
87
+
88
+ /**
89
+ * Convert a non-negative bigint to a big-endian byte array.
90
+ *
91
+ * Zero returns an empty Uint8Array.
92
+ *
93
+ * @param value - A non-negative bigint value
94
+ * @returns Big-endian byte representation
95
+ */
96
+ export function bigintToBytes(value: bigint): Uint8Array {
97
+ if (value === 0n) return new Uint8Array(0);
98
+ const hex = value.toString(16);
99
+ const padded = hex.length % 2 !== 0 ? `0${hex}` : hex;
100
+ const bytes = new Uint8Array(padded.length / 2);
101
+ for (let i = 0; i < bytes.length; i++) {
102
+ bytes[i] = parseInt(padded.substring(i * 2, i * 2 + 2), 16);
103
+ }
104
+ return bytes;
105
+ }
106
+
107
+ /**
108
+ * Convert a big-endian byte array to a bigint.
109
+ *
110
+ * Empty array returns 0n.
111
+ *
112
+ * @param bytes - Big-endian byte representation
113
+ * @returns The bigint value
114
+ */
115
+ export function bytesToBigint(bytes: Uint8Array): bigint {
116
+ if (bytes.length === 0) return 0n;
117
+ let result = 0n;
118
+ for (const byte of bytes) {
119
+ result = (result << 8n) | BigInt(byte);
120
+ }
121
+ return result;
122
+ }
123
+
124
+ /**
125
+ * Encode a non-negative bigint as a CBOR tag 2 (positive bignum).
126
+ *
127
+ * Matches Rust's `From<BigUint> for CBOR`.
128
+ *
129
+ * The value is always encoded as a bignum regardless of size.
130
+ * Zero is encoded as tag 2 with an empty byte string.
131
+ *
132
+ * @param value - A non-negative bigint (must be >= 0n)
133
+ * @returns CBOR tagged value
134
+ * @throws CborError with type OutOfRange if value is negative
135
+ */
136
+ export function biguintToCbor(value: bigint): Cbor {
137
+ if (value < 0n) {
138
+ throw new CborError({ type: "OutOfRange" });
139
+ }
140
+ const bytes = bigintToBytes(value);
141
+ const stripped = stripLeadingZeros(bytes);
142
+ return toTaggedValue(TAG_2_POSITIVE_BIGNUM, stripped);
143
+ }
144
+
145
+ /**
146
+ * Encode a bigint as a CBOR tag 2 or tag 3 bignum.
147
+ *
148
+ * Matches Rust's `From<BigInt> for CBOR`.
149
+ *
150
+ * - Non-negative values use tag 2 (positive bignum).
151
+ * - Negative values use tag 3 (negative bignum), where the encoded
152
+ * magnitude is `|value| - 1` per RFC 8949.
153
+ *
154
+ * @param value - Any bigint value
155
+ * @returns CBOR tagged value
156
+ */
157
+ export function bigintToCbor(value: bigint): Cbor {
158
+ if (value >= 0n) {
159
+ return biguintToCbor(value);
160
+ }
161
+ // Negative: use tag 3 with magnitude = |value| - 1
162
+ // For value = -1, magnitude = 1, so n = 0 -> encode as 0x00
163
+ // For value = -2, magnitude = 2, so n = 1 -> encode as 0x01
164
+ const magnitude = -value;
165
+ const n = magnitude - 1n;
166
+ const bytes = bigintToBytes(n);
167
+ const stripped = stripLeadingZeros(bytes);
168
+ // For n = 0 (value = -1), bigintToBytes returns empty, but we need 0x00
169
+ const contentBytes = stripped.length === 0 ? new Uint8Array([0]) : stripped;
170
+ return toTaggedValue(TAG_3_NEGATIVE_BIGNUM, contentBytes);
171
+ }
172
+
173
+ /**
174
+ * Decode a BigUint from an untagged CBOR byte string.
175
+ *
176
+ * Matches Rust's `biguint_from_untagged_cbor()`.
177
+ *
178
+ * This function is intended for use in tag summarizers where the tag has
179
+ * already been stripped. It expects a CBOR byte string representing the
180
+ * big-endian magnitude of a positive bignum (tag 2 content).
181
+ *
182
+ * Enforces canonical encoding: no leading zero bytes (except empty for zero).
183
+ *
184
+ * @param cbor - A CBOR value that should be a byte string
185
+ * @returns Non-negative bigint
186
+ * @throws CborError with type WrongType if not a byte string
187
+ * @throws CborError with type NonCanonicalNumeric if encoding is non-canonical
188
+ */
189
+ export function biguintFromUntaggedCbor(cbor: Cbor): bigint {
190
+ if (cbor.type !== MajorType.ByteString) {
191
+ throw new CborError({ type: "WrongType" });
192
+ }
193
+ const bytes = cbor.value;
194
+ validateBignumMagnitude(bytes, false);
195
+ return bytesToBigint(bytes);
196
+ }
197
+
198
+ /**
199
+ * Decode a BigInt from an untagged CBOR byte string for a negative bignum.
200
+ *
201
+ * Matches Rust's `bigint_from_negative_untagged_cbor()`.
202
+ *
203
+ * This function is intended for use in tag summarizers where the tag has
204
+ * already been stripped. It expects a CBOR byte string representing `n` where
205
+ * the actual value is `-1 - n` (tag 3 content per RFC 8949).
206
+ *
207
+ * Enforces canonical encoding: no leading zero bytes (except single `0x00`
208
+ * for -1).
209
+ *
210
+ * @param cbor - A CBOR value that should be a byte string
211
+ * @returns Negative bigint
212
+ * @throws CborError with type WrongType if not a byte string
213
+ * @throws CborError with type NonCanonicalNumeric if encoding is non-canonical
214
+ */
215
+ export function bigintFromNegativeUntaggedCbor(cbor: Cbor): bigint {
216
+ if (cbor.type !== MajorType.ByteString) {
217
+ throw new CborError({ type: "WrongType" });
218
+ }
219
+ const bytes = cbor.value;
220
+ validateBignumMagnitude(bytes, true);
221
+ const n = bytesToBigint(bytes);
222
+ const magnitude = n + 1n;
223
+ return -magnitude;
224
+ }
225
+
226
+ /**
227
+ * Convert CBOR to a non-negative bigint.
228
+ *
229
+ * Matches Rust's `TryFrom<CBOR> for BigUint`.
230
+ *
231
+ * Accepts:
232
+ * - Major type 0 (unsigned integer)
233
+ * - Tag 2 (positive bignum) with canonical byte string
234
+ *
235
+ * Rejects:
236
+ * - Major type 1 (negative integer) -> OutOfRange
237
+ * - Tag 3 (negative bignum) -> OutOfRange
238
+ * - Floating-point values -> WrongType
239
+ * - Non-canonical bignum encodings -> NonCanonicalNumeric
240
+ *
241
+ * @param cbor - The CBOR value to convert
242
+ * @returns Non-negative bigint
243
+ * @throws CborError
244
+ */
245
+ export function cborToBiguint(cbor: Cbor): bigint {
246
+ switch (cbor.type) {
247
+ case MajorType.Unsigned:
248
+ return BigInt(cbor.value);
249
+ case MajorType.Negative:
250
+ throw new CborError({ type: "OutOfRange" });
251
+ case MajorType.Tagged: {
252
+ // bigint-aware comparison: avoids `Number(tag)` precision loss for
253
+ // tag values > MAX_SAFE_INTEGER.
254
+ if (tagEquals(cbor.tag, TAG_2_POSITIVE_BIGNUM)) {
255
+ const inner = cbor.value;
256
+ if (inner.type !== MajorType.ByteString) {
257
+ throw new CborError({ type: "WrongType" });
258
+ }
259
+ const bytes = inner.value;
260
+ validateBignumMagnitude(bytes, false);
261
+ return bytesToBigint(bytes);
262
+ } else if (tagEquals(cbor.tag, TAG_3_NEGATIVE_BIGNUM)) {
263
+ throw new CborError({ type: "OutOfRange" });
264
+ }
265
+ throw new CborError({ type: "WrongType" });
266
+ }
267
+ case MajorType.ByteString:
268
+ case MajorType.Text:
269
+ case MajorType.Array:
270
+ case MajorType.Map:
271
+ case MajorType.Simple:
272
+ throw new CborError({ type: "WrongType" });
273
+ }
274
+ }
275
+
276
+ /**
277
+ * Compare a CBOR tag value (which may be `number` or `bigint`) against a
278
+ * small numeric literal. Avoids the `Number(tag) === literal` precision
279
+ * loss for huge bigint tags.
280
+ */
281
+ function tagEquals(tag: number | bigint, literal: number): boolean {
282
+ return typeof tag === "bigint" ? tag === BigInt(literal) : tag === literal;
283
+ }
284
+
285
+ /**
286
+ * Convert CBOR to a bigint (any sign).
287
+ *
288
+ * Matches Rust's `TryFrom<CBOR> for BigInt`.
289
+ *
290
+ * Accepts:
291
+ * - Major type 0 (unsigned integer)
292
+ * - Major type 1 (negative integer)
293
+ * - Tag 2 (positive bignum) with canonical byte string
294
+ * - Tag 3 (negative bignum) with canonical byte string
295
+ *
296
+ * Rejects:
297
+ * - Floating-point values -> WrongType
298
+ * - Non-canonical bignum encodings -> NonCanonicalNumeric
299
+ *
300
+ * @param cbor - The CBOR value to convert
301
+ * @returns A bigint value
302
+ * @throws CborError
303
+ */
304
+ export function cborToBigint(cbor: Cbor): bigint {
305
+ switch (cbor.type) {
306
+ case MajorType.Unsigned:
307
+ return BigInt(cbor.value);
308
+ case MajorType.Negative: {
309
+ // CBOR negative: value stored is n where actual = -1 - n
310
+ const n = BigInt(cbor.value);
311
+ const magnitude = n + 1n;
312
+ return -magnitude;
313
+ }
314
+ case MajorType.Tagged: {
315
+ if (tagEquals(cbor.tag, TAG_2_POSITIVE_BIGNUM)) {
316
+ const inner = cbor.value;
317
+ if (inner.type !== MajorType.ByteString) {
318
+ throw new CborError({ type: "WrongType" });
319
+ }
320
+ const bytes = inner.value;
321
+ validateBignumMagnitude(bytes, false);
322
+ const mag = bytesToBigint(bytes);
323
+ if (mag === 0n) {
324
+ return 0n;
325
+ }
326
+ return mag;
327
+ } else if (tagEquals(cbor.tag, TAG_3_NEGATIVE_BIGNUM)) {
328
+ const inner = cbor.value;
329
+ if (inner.type !== MajorType.ByteString) {
330
+ throw new CborError({ type: "WrongType" });
331
+ }
332
+ const bytes = inner.value;
333
+ validateBignumMagnitude(bytes, true);
334
+ const n = bytesToBigint(bytes);
335
+ const magnitude = n + 1n;
336
+ return -magnitude;
337
+ }
338
+ throw new CborError({ type: "WrongType" });
339
+ }
340
+ case MajorType.ByteString:
341
+ case MajorType.Text:
342
+ case MajorType.Array:
343
+ case MajorType.Map:
344
+ case MajorType.Simple:
345
+ throw new CborError({ type: "WrongType" });
346
+ }
347
+ }
@@ -1,4 +1,8 @@
1
1
  /**
2
+ * Copyright © 2023-2026 Blockchain Commons, LLC
3
+ * Copyright © 2025-2026 Parity Technologies
4
+ *
5
+ *
2
6
  * Byte string utilities for dCBOR.
3
7
  *
4
8
  * Represents a CBOR byte string (major type 2).
@@ -44,7 +48,7 @@ import { CborError } from "./error";
44
48
  * ```
45
49
  */
46
50
  export class ByteString {
47
- #data: Uint8Array;
51
+ private _data: Uint8Array;
48
52
 
49
53
  /**
50
54
  * Creates a new `ByteString` from a Uint8Array or array of bytes.
@@ -62,9 +66,9 @@ export class ByteString {
62
66
  */
63
67
  constructor(data: Uint8Array | number[]) {
64
68
  if (Array.isArray(data)) {
65
- this.#data = new Uint8Array(data);
69
+ this._data = new Uint8Array(data);
66
70
  } else {
67
- this.#data = new Uint8Array(data);
71
+ this._data = new Uint8Array(data);
68
72
  }
69
73
  }
70
74
 
@@ -103,7 +107,7 @@ export class ByteString {
103
107
  * ```
104
108
  */
105
109
  data(): Uint8Array {
106
- return this.#data;
110
+ return this._data;
107
111
  }
108
112
 
109
113
  /**
@@ -121,7 +125,7 @@ export class ByteString {
121
125
  * ```
122
126
  */
123
127
  len(): number {
124
- return this.#data.length;
128
+ return this._data.length;
125
129
  }
126
130
 
127
131
  /**
@@ -139,7 +143,7 @@ export class ByteString {
139
143
  * ```
140
144
  */
141
145
  isEmpty(): boolean {
142
- return this.#data.length === 0;
146
+ return this._data.length === 0;
143
147
  }
144
148
 
145
149
  /**
@@ -160,10 +164,10 @@ export class ByteString {
160
164
  */
161
165
  extend(other: Uint8Array | number[]): void {
162
166
  const otherArray = Array.isArray(other) ? new Uint8Array(other) : other;
163
- const newData = new Uint8Array(this.#data.length + otherArray.length);
164
- newData.set(this.#data, 0);
165
- newData.set(otherArray, this.#data.length);
166
- this.#data = newData;
167
+ const newData = new Uint8Array(this._data.length + otherArray.length);
168
+ newData.set(this._data, 0);
169
+ newData.set(otherArray, this._data.length);
170
+ this._data = newData;
167
171
  }
168
172
 
169
173
  /**
@@ -184,7 +188,7 @@ export class ByteString {
184
188
  * ```
185
189
  */
186
190
  toUint8Array(): Uint8Array {
187
- return new Uint8Array(this.#data);
191
+ return new Uint8Array(this._data);
188
192
  }
189
193
 
190
194
  /**
@@ -211,7 +215,7 @@ export class ByteString {
211
215
  * ```
212
216
  */
213
217
  iter(): Iterator<number> {
214
- return this.#data.values();
218
+ return this._data.values();
215
219
  }
216
220
 
217
221
  /**
@@ -233,7 +237,7 @@ export class ByteString {
233
237
  * ```
234
238
  */
235
239
  toCbor(): Cbor {
236
- return toCbor(this.#data);
240
+ return toCbor(this._data);
237
241
  }
238
242
 
239
243
  /**
@@ -272,7 +276,7 @@ export class ByteString {
272
276
  * @returns Byte at index or undefined
273
277
  */
274
278
  at(index: number): number | undefined {
275
- return this.#data[index];
279
+ return this._data[index];
276
280
  }
277
281
 
278
282
  /**
@@ -282,9 +286,9 @@ export class ByteString {
282
286
  * @returns true if equal
283
287
  */
284
288
  equals(other: ByteString): boolean {
285
- if (this.#data.length !== other.#data.length) return false;
286
- for (let i = 0; i < this.#data.length; i++) {
287
- if (this.#data[i] !== other.#data[i]) return false;
289
+ if (this._data.length !== other._data.length) return false;
290
+ for (let i = 0; i < this._data.length; i++) {
291
+ if (this._data[i] !== other._data[i]) return false;
288
292
  }
289
293
  return true;
290
294
  }
@@ -1,4 +1,8 @@
1
1
  /**
2
+ * Copyright © 2023-2026 Blockchain Commons, LLC
3
+ * Copyright © 2025-2026 Parity Technologies
4
+ *
5
+ *
2
6
  * CBOR Encoding and Decoding Interfaces.
3
7
  *
4
8
  * These interfaces provide functionality for converting between TypeScript types and
@@ -1,4 +1,8 @@
1
1
  /**
2
+ * Copyright © 2023-2026 Blockchain Commons, LLC
3
+ * Copyright © 2025-2026 Parity Technologies
4
+ *
5
+ *
2
6
  * Tagged CBOR encoding and decoding support.
3
7
  *
4
8
  * This module provides the `CborTaggedCodable` interface, which serves as a
@@ -1,4 +1,8 @@
1
1
  /**
2
+ * Copyright © 2023-2026 Blockchain Commons, LLC
3
+ * Copyright © 2025-2026 Parity Technologies
4
+ *
5
+ *
2
6
  * Tagged CBOR decoding support.
3
7
  *
4
8
  * This module provides the `CborTaggedDecodable` interface, which enables types to
@@ -15,6 +19,7 @@ import { type Cbor, MajorType } from "./cbor";
15
19
  import type { CborTagged } from "./cbor-tagged";
16
20
  import type { Tag } from "./tag";
17
21
  import { CborError } from "./error";
22
+ import { decodeCbor } from "./decode";
18
23
 
19
24
  /**
20
25
  * Interface for types that can be decoded from CBOR with a specific tag.
@@ -154,15 +159,16 @@ export const validateTag = (cbor: Cbor, expectedTags: Tag[]): Tag => {
154
159
  throw new CborError({ type: "WrongType" });
155
160
  }
156
161
 
157
- const expectedValues = expectedTags.map((t) => t.value);
158
162
  const tagValue = cbor.tag;
159
-
160
163
  const matchingTag = expectedTags.find((t) => t.value === tagValue);
161
164
  if (matchingTag === undefined) {
162
- const expectedStr = expectedValues.join(" or ");
165
+ // Mirror Rust's `Error::WrongTag(expected, actual)` — produce the
166
+ // structured WrongTag variant rather than a stringly-typed Custom
167
+ // error so callers can branch on `error.type === "WrongTag"`.
163
168
  throw new CborError({
164
- type: "Custom",
165
- message: `Wrong tag: expected ${expectedStr}, got ${tagValue}`,
169
+ type: "WrongTag",
170
+ expected: expectedTags[0],
171
+ actual: { value: tagValue },
166
172
  });
167
173
  }
168
174
 
@@ -182,3 +188,25 @@ export const extractTaggedContent = (cbor: Cbor): Cbor => {
182
188
  }
183
189
  return cbor.value;
184
190
  };
191
+
192
+ /**
193
+ * Default `fromTaggedCborData()` implementation — `decodeCbor(data)` then
194
+ * delegate to the decodable's `fromTaggedCbor()`.
195
+ *
196
+ * Mirrors Rust's default `from_tagged_cbor_data` impl on
197
+ * `CBORTaggedDecodable`.
198
+ */
199
+ export function fromTaggedCborData<T>(decodable: CborTaggedDecodable<T>, data: Uint8Array): T {
200
+ return decodable.fromTaggedCbor(decodeCbor(data));
201
+ }
202
+
203
+ /**
204
+ * Default `fromUntaggedCborData()` implementation — `decodeCbor(data)`
205
+ * then delegate to the decodable's `fromUntaggedCbor()`.
206
+ *
207
+ * Mirrors Rust's default `from_untagged_cbor_data` impl on
208
+ * `CBORTaggedDecodable`.
209
+ */
210
+ export function fromUntaggedCborData<T>(decodable: CborTaggedDecodable<T>, data: Uint8Array): T {
211
+ return decodable.fromUntaggedCbor(decodeCbor(data));
212
+ }
@@ -1,4 +1,8 @@
1
1
  /**
2
+ * Copyright © 2023-2026 Blockchain Commons, LLC
3
+ * Copyright © 2025-2026 Parity Technologies
4
+ *
5
+ *
2
6
  * Tagged CBOR encoding support.
3
7
  *
4
8
  * This module provides the `CborTaggedEncodable` interface, which enables types to
@@ -136,3 +140,14 @@ export const createTaggedCbor = (encodable: CborTaggedEncodable): Cbor => {
136
140
  value: untagged,
137
141
  });
138
142
  };
143
+
144
+ /**
145
+ * Default `taggedCborData()` implementation — `taggedCbor().toData()`.
146
+ *
147
+ * Mirrors Rust's default `tagged_cbor_data()` impl on
148
+ * `CBORTaggedEncodable`. TypeScript interfaces don't carry method bodies,
149
+ * so this helper plays the role of the Rust trait default; concrete types
150
+ * call it from their own `taggedCborData()` if they don't need to override.
151
+ */
152
+ export const taggedCborData = (encodable: CborTaggedEncodable): Uint8Array =>
153
+ (encodable.taggedCbor() as { toData(): Uint8Array }).toData();
@@ -1,4 +1,8 @@
1
1
  /**
2
+ * Copyright © 2023-2026 Blockchain Commons, LLC
3
+ * Copyright © 2025-2026 Parity Technologies
4
+ *
5
+ *
2
6
  * Base trait for types that have associated CBOR tags.
3
7
  *
4
8
  * CBOR allows values to be "tagged" with semantic information using tag
@@ -95,10 +99,16 @@ export interface CborTagged {
95
99
  }
96
100
 
97
101
  // Re-export interfaces and functions from separate modules for convenience
98
- export { type CborTaggedEncodable, createTaggedCbor } from "./cbor-tagged-encodable";
102
+ export {
103
+ type CborTaggedEncodable,
104
+ createTaggedCbor,
105
+ taggedCborData,
106
+ } from "./cbor-tagged-encodable";
99
107
  export {
100
108
  type CborTaggedDecodable,
101
109
  validateTag,
102
110
  extractTaggedContent,
111
+ fromTaggedCborData,
112
+ fromUntaggedCborData,
103
113
  } from "./cbor-tagged-decodable";
104
114
  export { type CborTaggedCodable } from "./cbor-tagged-codable";