@bcts/uniform-resources 1.0.0-alpha.9 → 1.0.0-beta.1

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/uniform-resources",
3
- "version": "1.0.0-alpha.9",
3
+ "version": "1.0.0-beta.1",
4
4
  "type": "module",
5
5
  "description": "Blockchain Commons Uniform Resources (UR) 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/uniform-resources"
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,14 +33,14 @@
33
33
  ],
34
34
  "scripts": {
35
35
  "build": "tsdown",
36
- "dev": "tsdown --watch",
37
36
  "test": "vitest run",
38
37
  "test:watch": "vitest",
39
- "lint": "eslint 'src/**/*.ts'",
40
- "lint:fix": "eslint 'src/**/*.ts' --fix",
38
+ "lint": "eslint 'src/**/*.ts' 'tests/**/*.ts'",
39
+ "lint:fix": "eslint 'src/**/*.ts' 'tests/**/*.ts' --fix",
41
40
  "typecheck": "tsc --noEmit",
42
41
  "clean": "rm -rf dist",
43
- "docs": "typedoc"
42
+ "docs": "typedoc",
43
+ "prepublishOnly": "npm run clean && npm run build && npm test"
44
44
  },
45
45
  "keywords": [
46
46
  "uniform-resources",
@@ -55,20 +55,21 @@
55
55
  "node": ">=18.0.0"
56
56
  },
57
57
  "dependencies": {
58
- "@bcts/dcbor": "^1.0.0-alpha.9"
58
+ "@bcts/crypto": "^1.0.0-beta.1",
59
+ "@bcts/dcbor": "^1.0.0-beta.1"
59
60
  },
60
61
  "devDependencies": {
61
62
  "@bcts/eslint": "^0.1.0",
62
63
  "@bcts/tsconfig": "^0.1.0",
63
- "@eslint/js": "^9.39.1",
64
- "@types/node": "^24.10.2",
64
+ "@eslint/js": "^10.0.1",
65
+ "@types/node": "^25.9.1",
65
66
  "@types/pako": "^2.0.4",
66
- "eslint": "^9.39.1",
67
- "prettier": "^3.7.4",
67
+ "eslint": "^10.4.0",
68
+ "prettier": "^3.8.3",
68
69
  "ts-node": "^10.9.2",
69
- "tsdown": "^0.17.2",
70
- "typedoc": "^0.28.15",
71
- "typescript": "^5.9.3",
72
- "vitest": "^3.2.4"
70
+ "tsdown": "^0.22.0",
71
+ "typedoc": "^0.28.19",
72
+ "typescript": "^6.0.3",
73
+ "vitest": "^4.1.7"
73
74
  }
74
75
  }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Copyright © 2023-2026 Blockchain Commons, LLC
3
+ * Copyright © 2025-2026 Parity Technologies
4
+ *
5
+ *
6
+ * Namespace-style re-export of the bytewords helpers in `./utils`.
7
+ *
8
+ * Mirrors Rust's `bc_ur::bytewords` module (`bc-ur-rust/src/bytewords.rs`)
9
+ * so that callers can write
10
+ *
11
+ * ```ts
12
+ * import { bytewords } from "@bcts/uniform-resources";
13
+ * bytewords.encode(data, bytewords.Style.Minimal);
14
+ * bytewords.identifier(fourByteSlice);
15
+ * ```
16
+ *
17
+ * matching the ergonomics of
18
+ *
19
+ * ```rust
20
+ * use bc_ur::bytewords;
21
+ * bytewords::encode(data, bytewords::Style::Minimal);
22
+ * bytewords::identifier(four_byte_slice);
23
+ * ```
24
+ *
25
+ * This is purely an alias module — every symbol below is the same
26
+ * function/value already exported individually from the package root.
27
+ */
28
+
29
+ export {
30
+ BYTEWORDS,
31
+ BYTEMOJIS,
32
+ BytewordsStyle as Style,
33
+ encodeBytewords as encode,
34
+ decodeBytewords as decode,
35
+ encodeBytewordsIdentifier as identifier,
36
+ encodeBytemojisIdentifier as bytemojiIdentifier,
37
+ encodeToWords,
38
+ encodeToBytemojis,
39
+ encodeToMinimalBytewords,
40
+ isValidBytemoji,
41
+ canonicalizeByteword,
42
+ } from "./utils";
package/src/error.ts CHANGED
@@ -1,4 +1,8 @@
1
1
  /**
2
+ * Copyright © 2023-2026 Blockchain Commons, LLC
3
+ * Copyright © 2025-2026 Parity Technologies
4
+ *
5
+ *
2
6
  * Error type for UR encoding/decoding operations.
3
7
  */
4
8
  export class URError extends Error {
@@ -10,30 +14,36 @@ export class URError extends Error {
10
14
 
11
15
  /**
12
16
  * Error type for invalid UR schemes.
17
+ *
18
+ * Message matches Rust bc-ur-rust/src/error.rs: `invalid UR scheme`.
13
19
  */
14
20
  export class InvalidSchemeError extends URError {
15
21
  constructor() {
16
- super("Invalid UR scheme");
22
+ super("invalid UR scheme");
17
23
  this.name = "InvalidSchemeError";
18
24
  }
19
25
  }
20
26
 
21
27
  /**
22
28
  * Error type for unspecified UR types.
29
+ *
30
+ * Message matches Rust bc-ur-rust/src/error.rs: `no UR type specified`.
23
31
  */
24
32
  export class TypeUnspecifiedError extends URError {
25
33
  constructor() {
26
- super("No UR type specified");
34
+ super("no UR type specified");
27
35
  this.name = "TypeUnspecifiedError";
28
36
  }
29
37
  }
30
38
 
31
39
  /**
32
40
  * Error type for invalid UR types.
41
+ *
42
+ * Message matches Rust bc-ur-rust/src/error.rs: `invalid UR type`.
33
43
  */
34
44
  export class InvalidTypeError extends URError {
35
45
  constructor() {
36
- super("Invalid UR type");
46
+ super("invalid UR type");
37
47
  this.name = "InvalidTypeError";
38
48
  }
39
49
  }
@@ -50,34 +60,52 @@ export class NotSinglePartError extends URError {
50
60
 
51
61
  /**
52
62
  * Error type for unexpected UR types.
63
+ *
64
+ * Message matches Rust bc-ur-rust/src/error.rs:
65
+ * `expected UR type {expected}, but found {found}`.
53
66
  */
54
67
  export class UnexpectedTypeError extends URError {
55
68
  constructor(expected: string, found: string) {
56
- super(`Expected UR type ${expected}, but found ${found}`);
69
+ super(`expected UR type ${expected}, but found ${found}`);
57
70
  this.name = "UnexpectedTypeError";
58
71
  }
59
72
  }
60
73
 
61
74
  /**
62
75
  * Error type for Bytewords encoding/decoding errors.
76
+ *
77
+ * Message matches Rust bc-ur-rust/src/error.rs: `Bytewords error ({0})`.
63
78
  */
64
79
  export class BytewordsError extends URError {
65
80
  constructor(message: string) {
66
- super(`Bytewords error: ${message}`);
81
+ super(`Bytewords error (${message})`);
67
82
  this.name = "BytewordsError";
68
83
  }
69
84
  }
70
85
 
71
86
  /**
72
87
  * Error type for CBOR encoding/decoding errors.
88
+ *
89
+ * Message matches Rust bc-ur-rust/src/error.rs: `CBOR error ({0})`.
73
90
  */
74
91
  export class CBORError extends URError {
75
92
  constructor(message: string) {
76
- super(`CBOR error: ${message}`);
93
+ super(`CBOR error (${message})`);
77
94
  this.name = "CBORError";
78
95
  }
79
96
  }
80
97
 
98
+ /**
99
+ * Error type for UR decoder errors.
100
+ * Matches Rust's Error::UR(String) variant.
101
+ */
102
+ export class URDecodeError extends URError {
103
+ constructor(message: string) {
104
+ super(`UR decoder error (${message})`);
105
+ this.name = "URDecodeError";
106
+ }
107
+ }
108
+
81
109
  export type Result<T> = T | Error;
82
110
 
83
111
  /**
package/src/fountain.ts CHANGED
@@ -1,4 +1,8 @@
1
1
  /**
2
+ * Copyright © 2023-2026 Blockchain Commons, LLC
3
+ * Copyright © 2025-2026 Parity Technologies
4
+ *
5
+ *
2
6
  * Fountain code implementation for multipart URs.
3
7
  *
4
8
  * This implements a hybrid fixed-rate and rateless fountain code system
@@ -30,27 +34,86 @@ export interface FountainPart {
30
34
  }
31
35
 
32
36
  /**
33
- * Splits data into fragments of the specified size.
37
+ * Calculates the quotient of `a` and `b`, rounded toward positive infinity.
38
+ *
39
+ * Mirrors Rust `ur-0.4.1/src/fountain.rs::div_ceil`.
34
40
  */
35
- export function splitMessage(message: Uint8Array, fragmentLen: number): Uint8Array[] {
36
- const fragments: Uint8Array[] = [];
37
- const fragmentCount = Math.ceil(message.length / fragmentLen);
38
-
39
- for (let i = 0; i < fragmentCount; i++) {
40
- const start = i * fragmentLen;
41
- const end = Math.min(start + fragmentLen, message.length);
42
- const fragment = new Uint8Array(fragmentLen);
41
+ function divCeil(a: number, b: number): number {
42
+ const d = Math.floor(a / b);
43
+ const r = a % b;
44
+ return r > 0 ? d + 1 : d;
45
+ }
43
46
 
44
- // Copy data and pad with zeros if needed
45
- const sourceSlice = message.slice(start, end);
46
- fragment.set(sourceSlice);
47
+ /**
48
+ * Computes the optimal fragment length for a given message length and
49
+ * maximum fragment length.
50
+ *
51
+ * The algorithm:
52
+ * fragment_count = ceil(data_length / max_fragment_length)
53
+ * fragment_length = ceil(data_length / fragment_count)
54
+ *
55
+ * This produces fragments that are as balanced as possible while still
56
+ * respecting `maxFragmentLen` as an upper bound on each fragment. For
57
+ * example, a 10-byte message with `maxFragmentLen = 6` yields a fragment
58
+ * length of 5 (so two even 5-byte fragments) rather than 6 (one full
59
+ * fragment plus a 4-byte tail).
60
+ *
61
+ * Mirrors Rust `ur-0.4.1/src/fountain.rs::fragment_length` byte-for-byte.
62
+ */
63
+ export function fragmentLength(dataLength: number, maxFragmentLength: number): number {
64
+ const fragmentCount = divCeil(dataLength, maxFragmentLength);
65
+ return divCeil(dataLength, fragmentCount);
66
+ }
47
67
 
48
- fragments.push(fragment);
68
+ /**
69
+ * Splits `data` into a list of `fragmentLen`-sized chunks, zero-padding
70
+ * the last chunk if necessary so that every chunk is exactly
71
+ * `fragmentLen` bytes long.
72
+ *
73
+ * Note: `fragmentLen` is the **already-computed** fragment length (see
74
+ * {@link fragmentLength}), not the user-facing maximum fragment length.
75
+ *
76
+ * Mirrors Rust `ur-0.4.1/src/fountain.rs::partition` byte-for-byte.
77
+ */
78
+ export function partition(data: Uint8Array, fragmentLen: number): Uint8Array[] {
79
+ if (fragmentLen < 1) {
80
+ throw new Error("fragment length must be at least 1");
49
81
  }
82
+ const remainder = data.length % fragmentLen;
83
+ const padding = remainder === 0 ? 0 : fragmentLen - remainder;
84
+ const padded = new Uint8Array(data.length + padding);
85
+ padded.set(data);
86
+ // Trailing bytes are already zero by Uint8Array's default initialization.
50
87
 
88
+ const fragments: Uint8Array[] = [];
89
+ for (let start = 0; start < padded.length; start += fragmentLen) {
90
+ fragments.push(padded.slice(start, start + fragmentLen));
91
+ }
51
92
  return fragments;
52
93
  }
53
94
 
95
+ /**
96
+ * Convenience: compute the optimal fragment length for `message` given
97
+ * `maxFragmentLen` and partition the message into that many fragments.
98
+ *
99
+ * Equivalent to:
100
+ * ```ts
101
+ * partition(message, fragmentLength(message.length, maxFragmentLen))
102
+ * ```
103
+ *
104
+ * This is what {@link FountainEncoder} does internally when constructing
105
+ * its fragment table.
106
+ */
107
+ export function splitMessage(message: Uint8Array, maxFragmentLen: number): Uint8Array[] {
108
+ if (maxFragmentLen < 1) {
109
+ throw new Error("max fragment length must be at least 1");
110
+ }
111
+ if (message.length === 0) {
112
+ return [];
113
+ }
114
+ return partition(message, fragmentLength(message.length, maxFragmentLen));
115
+ }
116
+
54
117
  /**
55
118
  * XOR two Uint8Arrays together.
56
119
  */
@@ -71,6 +134,11 @@ export function xorBytes(a: Uint8Array, b: Uint8Array): Uint8Array {
71
134
  * This uses a seeded Xoshiro256** PRNG to deterministically select fragments,
72
135
  * ensuring encoder and decoder agree without explicit coordination.
73
136
  *
137
+ * The algorithm matches the BC-UR reference implementation:
138
+ * 1. For pure parts (seqNum <= seqLen), return single fragment index
139
+ * 2. For mixed parts, use weighted sampling to choose degree
140
+ * 3. Shuffle all indices and take the first 'degree' indices
141
+ *
74
142
  * @param seqNum - The sequence number (1-based)
75
143
  * @param seqLen - Total number of pure fragments
76
144
  * @param checksum - CRC32 checksum of the message
@@ -86,43 +154,18 @@ export function chooseFragments(seqNum: number, seqLen: number, checksum: number
86
154
  const seed = createSeed(checksum, seqNum);
87
155
  const rng = new Xoshiro256(seed);
88
156
 
89
- // Choose degree (number of fragments to mix)
90
- // Uses a simplified soliton distribution
91
- const degree = chooseDegree(rng, seqLen);
157
+ // Choose degree using weighted sampler (1/k distribution)
158
+ const degree = rng.chooseDegree(seqLen);
92
159
 
93
- // Choose which fragments to include
94
- const indices = new Set<number>();
95
- while (indices.size < degree) {
96
- const index = rng.nextInt(0, seqLen);
97
- indices.add(index);
160
+ // Create array of all indices [0, 1, 2, ..., seqLen-1]
161
+ const allIndices: number[] = [];
162
+ for (let i = 0; i < seqLen; i++) {
163
+ allIndices.push(i);
98
164
  }
99
165
 
100
- return Array.from(indices).sort((a, b) => a - b);
101
- }
102
-
103
- /**
104
- * Chooses the degree (number of fragments to mix) using a simplified
105
- * robust soliton distribution.
106
- *
107
- * This ensures good coverage of fragments for efficient decoding.
108
- */
109
- function chooseDegree(rng: Xoshiro256, seqLen: number): number {
110
- // Use a simplified distribution that tends toward lower degrees
111
- // but can occasionally include more fragments
112
- const r = rng.nextDouble();
113
-
114
- // Probability distribution favoring lower degrees
115
- // Based on robust soliton distribution
116
- if (r < 0.5) {
117
- return 1;
118
- } else if (r < 0.75) {
119
- return 2;
120
- } else if (r < 0.9) {
121
- return Math.min(3, seqLen);
122
- } else {
123
- // Higher degrees are less common but help with convergence
124
- return Math.min(rng.nextInt(4, seqLen + 1), seqLen);
125
- }
166
+ // Shuffle all indices and take the first 'degree' indices
167
+ const shuffled = rng.shuffled(allIndices);
168
+ return shuffled.slice(0, degree);
126
169
  }
127
170
 
128
171
  /**
@@ -160,15 +203,25 @@ export class FountainEncoder {
160
203
  *
161
204
  * @param message - The message to encode
162
205
  * @param maxFragmentLen - Maximum length of each fragment
206
+ *
207
+ * @throws if `message` is empty (mirrors Rust `Error::EmptyMessage`).
208
+ * @throws if `maxFragmentLen < 1` (mirrors Rust `Error::InvalidFragmentLen`).
163
209
  */
164
210
  constructor(message: Uint8Array, maxFragmentLen: number) {
211
+ if (message.length === 0) {
212
+ throw new Error("expected non-empty message");
213
+ }
165
214
  if (maxFragmentLen < 1) {
166
- throw new Error("Fragment length must be at least 1");
215
+ throw new Error("expected positive maximum fragment length");
167
216
  }
168
217
 
169
218
  this.messageLen = message.length;
170
219
  this.checksum = crc32(message);
171
- this.fragments = splitMessage(message, maxFragmentLen);
220
+ // Mirrors Rust `Encoder::new`:
221
+ // let fragment_length = fragment_length(message.len(), max_fragment_length);
222
+ // let fragments = partition(message.to_vec(), fragment_length);
223
+ const optimalLen = fragmentLength(message.length, maxFragmentLen);
224
+ this.fragments = partition(message, optimalLen);
172
225
  }
173
226
 
174
227
  /**
@@ -232,54 +285,86 @@ export class FountainDecoder {
232
285
  private seqLen: number | null = null;
233
286
  private messageLen: number | null = null;
234
287
  private checksum: number | null = null;
288
+ private fragmentLen: number | null = null;
235
289
 
236
290
  // Storage for received data
237
291
  private readonly pureFragments = new Map<number, Uint8Array>();
238
292
  private readonly mixedParts = new Map<number, { indices: number[]; data: Uint8Array }>();
293
+ // Set of already-received `indices` keys (joined by `,`) — Rust uses
294
+ // `BTreeSet<Vec<usize>>` so two parts producing the same index set are
295
+ // deduped even when they have different sequence numbers. Mirrors
296
+ // `ur-0.4.1/src/fountain.rs::Decoder.received`.
297
+ private readonly receivedIndexSets = new Set<string>();
239
298
 
240
299
  /**
241
300
  * Receives a fountain part and attempts to decode.
242
301
  *
243
302
  * @param part - The fountain part to receive
244
- * @returns true if the message is now complete
303
+ * @returns `true` if this part contributed new information,
304
+ * `false` if it was an exact duplicate of a part already seen
305
+ * (or if the decoder was already complete).
306
+ *
307
+ * @throws if the part is empty or inconsistent with previously received
308
+ * parts. Mirrors Rust `Error::EmptyPart` and `Error::InconsistentPart`.
245
309
  */
246
310
  receive(part: FountainPart): boolean {
311
+ // Mirrors Rust `Decoder::receive`:
312
+ // if self.complete() { return Ok(false); }
313
+ if (this.isComplete()) {
314
+ return false;
315
+ }
316
+
317
+ // Mirrors Rust's eager EmptyPart check.
318
+ if (part.seqLen === 0 || part.data.length === 0 || part.messageLen === 0) {
319
+ throw new Error("expected non-empty part");
320
+ }
321
+
247
322
  // Initialize on first part
248
323
  if (this.seqLen === null) {
249
324
  this.seqLen = part.seqLen;
250
325
  this.messageLen = part.messageLen;
251
326
  this.checksum = part.checksum;
252
- }
253
-
254
- // Validate consistency
255
- if (
327
+ this.fragmentLen = part.data.length;
328
+ } else if (
329
+ // Mirrors Rust `Decoder::validate` exactly: every metadata field
330
+ // (sequence_count, message_length, checksum, fragment_length) must
331
+ // match across all received parts.
256
332
  part.seqLen !== this.seqLen ||
257
333
  part.messageLen !== this.messageLen ||
258
- part.checksum !== this.checksum
334
+ part.checksum !== this.checksum ||
335
+ part.data.length !== this.fragmentLen
259
336
  ) {
260
- throw new Error("Inconsistent part metadata");
337
+ throw new Error("part is inconsistent with previous ones");
261
338
  }
262
339
 
263
- // Determine which fragments this part contains
264
- const indices = chooseFragments(part.seqNum, this.seqLen, this.checksum);
340
+ // Determine which fragments this part contains.
341
+ const indices = chooseFragments(part.seqNum, this.seqLen, this.checksum ?? 0);
342
+ // Rust sorts the indices implicitly via `BTreeSet` membership; we
343
+ // explicitly sort the key so that two parts whose `chooseFragments`
344
+ // output is the same multiset (regardless of order) collapse to the
345
+ // same dedup key. In practice `chooseFragments` already produces a
346
+ // deterministic shuffle, so this is just defensive.
347
+ const indexSetKey = [...indices].sort((a, b) => a - b).join(",");
348
+ if (this.receivedIndexSets.has(indexSetKey)) {
349
+ return false;
350
+ }
351
+ this.receivedIndexSets.add(indexSetKey);
265
352
 
266
353
  if (indices.length === 1) {
267
- // Pure fragment
354
+ // Pure fragment (or degree-1 mixed that acts like pure).
268
355
  const index = indices[0];
269
356
  if (!this.pureFragments.has(index)) {
270
357
  this.pureFragments.set(index, part.data);
271
358
  }
272
359
  } else {
273
- // Mixed fragment - store for later reduction
274
- if (!this.mixedParts.has(part.seqNum)) {
275
- this.mixedParts.set(part.seqNum, { indices, data: part.data });
276
- }
360
+ // Mixed fragment - store for later reduction.
361
+ this.mixedParts.set(part.seqNum, { indices, data: part.data });
277
362
  }
278
363
 
279
- // Try to reduce mixed parts
364
+ // Try to reduce mixed parts.
280
365
  this.reduceMixedParts();
281
366
 
282
- return this.isComplete();
367
+ return true;
283
368
  }
284
369
 
285
370
  /**
@@ -391,7 +476,9 @@ export class FountainDecoder {
391
476
  this.seqLen = null;
392
477
  this.messageLen = null;
393
478
  this.checksum = null;
479
+ this.fragmentLen = null;
394
480
  this.pureFragments.clear();
395
481
  this.mixedParts.clear();
482
+ this.receivedIndexSets.clear();
396
483
  }
397
484
  }
package/src/index.ts CHANGED
@@ -1,10 +1,17 @@
1
+ /**
2
+ * Copyright © 2023-2026 Blockchain Commons, LLC
3
+ * Copyright © 2025-2026 Parity Technologies
4
+ *
5
+ */
6
+
1
7
  // Core types
2
8
  export { UR } from "./ur";
3
9
  export { URType } from "./ur-type";
4
10
 
5
- // Error types
11
+ // Error types (matching Rust's Error enum variants)
6
12
  export {
7
13
  URError,
14
+ URDecodeError,
8
15
  InvalidSchemeError,
9
16
  TypeUnspecifiedError,
10
17
  InvalidTypeError,
@@ -18,9 +25,9 @@ export {
18
25
  export type { Result } from "./error";
19
26
 
20
27
  // Traits/Interfaces
21
- export { isUREncodable } from "./ur-encodable";
28
+ export { isUREncodable, urFromEncodable, urStringFromEncodable } from "./ur-encodable";
22
29
  export type { UREncodable } from "./ur-encodable";
23
- export { isURDecodable } from "./ur-decodable";
30
+ export { isURDecodable, decodableFromUR, decodableFromURString } from "./ur-decodable";
24
31
  export type { URDecodable } from "./ur-decodable";
25
32
  export { isURCodable } from "./ur-codable";
26
33
  export type { URCodable } from "./ur-codable";
@@ -29,33 +36,27 @@ export type { URCodable } from "./ur-codable";
29
36
  export { MultipartEncoder } from "./multipart-encoder";
30
37
  export { MultipartDecoder } from "./multipart-decoder";
31
38
 
32
- // Fountain codes (for advanced multipart handling)
33
- export {
34
- FountainEncoder,
35
- FountainDecoder,
36
- splitMessage,
37
- xorBytes,
38
- chooseFragments,
39
- mixFragments,
40
- } from "./fountain";
41
- export type { FountainPart } from "./fountain";
42
-
43
- // PRNG for deterministic fountain code mixing
44
- export { Xoshiro256, createSeed } from "./xoshiro";
45
-
46
- // Utilities
39
+ // URType validation helpers (mirroring Rust's `URTypeChar` / `URTypeString`
40
+ // trait sugar in `bc-ur-rust/src/utils.rs`).
41
+ export { isURTypeChar, isValidURType, validateURType } from "./utils";
42
+
43
+ // Bytewords module (matching Rust's pub mod bytewords)
47
44
  export {
48
- isURTypeChar,
49
- isValidURType,
50
- validateURType,
51
45
  BYTEWORDS,
52
- BYTEWORDS_MAP,
53
46
  BYTEMOJIS,
54
- encodeBytewordsIdentifier,
55
- encodeBytemojisIdentifier,
56
47
  BytewordsStyle,
57
48
  encodeBytewords,
58
49
  decodeBytewords,
59
- crc32,
60
- MINIMAL_BYTEWORDS_MAP,
50
+ encodeBytewordsIdentifier,
51
+ encodeBytemojisIdentifier,
52
+ encodeToWords,
53
+ encodeToBytemojis,
54
+ encodeToMinimalBytewords,
55
+ isValidBytemoji,
56
+ canonicalizeByteword,
61
57
  } from "./utils";
58
+
59
+ // Namespace-style re-export so callers can write `bytewords.encode(...)` /
60
+ // `bytewords.decode(...)` to mirror Rust's `bc_ur::bytewords::encode(...)` etc.
61
+ // Tracked in PARITY_AUDIT.md §3.1 / §4.5.
62
+ export * as bytewords from "./bytewords-namespace";