@bcts/uniform-resources 1.0.0-alpha.12 → 1.0.0-alpha.14
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/dist/index.cjs +191 -213
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -249
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +13 -249
- package/dist/index.d.mts.map +1 -1
- package/dist/index.iife.js +192 -215
- package/dist/index.iife.js.map +1 -1
- package/dist/index.mjs +192 -200
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -5
- package/src/error.ts +11 -0
- package/src/fountain.ts +15 -35
- package/src/index.ts +5 -24
- package/src/multipart-decoder.ts +30 -36
- package/src/multipart-encoder.ts +6 -46
- package/src/utils.ts +7 -10
- package/src/xoshiro.ts +170 -76
package/dist/index.cjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
let _bcts_crypto = require("@bcts/crypto");
|
|
1
2
|
|
|
2
3
|
//#region ../dcbor/dist/index.mjs
|
|
3
4
|
var __create = Object.create;
|
|
@@ -4898,7 +4899,13 @@ var TagsStore = class {
|
|
|
4898
4899
|
/**
|
|
4899
4900
|
* Insert a tag into the registry.
|
|
4900
4901
|
*
|
|
4901
|
-
*
|
|
4902
|
+
* Matches Rust's TagsStore::insert() behavior:
|
|
4903
|
+
* - Throws if the tag name is undefined or empty
|
|
4904
|
+
* - Throws if a tag with the same value exists with a different name
|
|
4905
|
+
* - Allows re-registering the same tag value with the same name
|
|
4906
|
+
*
|
|
4907
|
+
* @param tag - The tag to register (must have a non-empty name)
|
|
4908
|
+
* @throws Error if tag has no name, empty name, or conflicts with existing registration
|
|
4902
4909
|
*
|
|
4903
4910
|
* @example
|
|
4904
4911
|
* ```typescript
|
|
@@ -4907,9 +4914,13 @@ var TagsStore = class {
|
|
|
4907
4914
|
* ```
|
|
4908
4915
|
*/
|
|
4909
4916
|
insert(tag) {
|
|
4917
|
+
const name = tag.name;
|
|
4918
|
+
if (name === void 0 || name === "") throw new Error(`Tag ${tag.value} must have a non-empty name`);
|
|
4910
4919
|
const key = this.#valueKey(tag.value);
|
|
4920
|
+
const existing = this.#tagsByValue.get(key);
|
|
4921
|
+
if (existing?.name !== void 0 && existing.name !== name) throw new Error(`Attempt to register tag: ${tag.value} '${existing.name}' with different name: '${name}'`);
|
|
4911
4922
|
this.#tagsByValue.set(key, tag);
|
|
4912
|
-
|
|
4923
|
+
this.#tagsByName.set(name, tag);
|
|
4913
4924
|
}
|
|
4914
4925
|
/**
|
|
4915
4926
|
* Insert multiple tags into the registry.
|
|
@@ -4947,21 +4958,6 @@ var TagsStore = class {
|
|
|
4947
4958
|
const key = this.#valueKey(tagValue$1);
|
|
4948
4959
|
this.#summarizers.set(key, summarizer);
|
|
4949
4960
|
}
|
|
4950
|
-
/**
|
|
4951
|
-
* Remove a tag from the registry.
|
|
4952
|
-
*
|
|
4953
|
-
* @param tagValue - The numeric tag value to remove
|
|
4954
|
-
* @returns true if a tag was removed, false otherwise
|
|
4955
|
-
*/
|
|
4956
|
-
remove(tagValue$1) {
|
|
4957
|
-
const key = this.#valueKey(tagValue$1);
|
|
4958
|
-
const tag = this.#tagsByValue.get(key);
|
|
4959
|
-
if (tag === void 0) return false;
|
|
4960
|
-
this.#tagsByValue.delete(key);
|
|
4961
|
-
if (tag.name !== void 0) this.#tagsByName.delete(tag.name);
|
|
4962
|
-
this.#summarizers.delete(key);
|
|
4963
|
-
return true;
|
|
4964
|
-
}
|
|
4965
4961
|
assignedNameForTag(tag) {
|
|
4966
4962
|
const key = this.#valueKey(tag.value);
|
|
4967
4963
|
return this.#tagsByValue.get(key)?.name;
|
|
@@ -4985,30 +4981,6 @@ var TagsStore = class {
|
|
|
4985
4981
|
return this.#summarizers.get(key);
|
|
4986
4982
|
}
|
|
4987
4983
|
/**
|
|
4988
|
-
* Get all registered tags.
|
|
4989
|
-
*
|
|
4990
|
-
* @returns Array of all registered tags
|
|
4991
|
-
*/
|
|
4992
|
-
getAllTags() {
|
|
4993
|
-
return Array.from(this.#tagsByValue.values());
|
|
4994
|
-
}
|
|
4995
|
-
/**
|
|
4996
|
-
* Clear all registered tags and summarizers.
|
|
4997
|
-
*/
|
|
4998
|
-
clear() {
|
|
4999
|
-
this.#tagsByValue.clear();
|
|
5000
|
-
this.#tagsByName.clear();
|
|
5001
|
-
this.#summarizers.clear();
|
|
5002
|
-
}
|
|
5003
|
-
/**
|
|
5004
|
-
* Get the number of registered tags.
|
|
5005
|
-
*
|
|
5006
|
-
* @returns Number of tags in the registry
|
|
5007
|
-
*/
|
|
5008
|
-
get size() {
|
|
5009
|
-
return this.#tagsByValue.size;
|
|
5010
|
-
}
|
|
5011
|
-
/**
|
|
5012
4984
|
* Create a string key for a numeric tag value.
|
|
5013
4985
|
* Handles both number and bigint types.
|
|
5014
4986
|
*
|
|
@@ -5371,11 +5343,17 @@ function formatMap(map, opts) {
|
|
|
5371
5343
|
}
|
|
5372
5344
|
/**
|
|
5373
5345
|
* Format tagged value.
|
|
5346
|
+
*
|
|
5347
|
+
* Matches Rust's diag_item() for Tagged case.
|
|
5374
5348
|
*/
|
|
5375
5349
|
function formatTagged(tag, content, opts) {
|
|
5376
5350
|
if (opts.summarize === true) {
|
|
5377
5351
|
const summarizer = resolveTagsStore(opts.tags)?.summarizer(tag);
|
|
5378
|
-
if (summarizer !== void 0)
|
|
5352
|
+
if (summarizer !== void 0) {
|
|
5353
|
+
const result$1 = summarizer(content, opts.flat ?? false);
|
|
5354
|
+
if (result$1.ok) return result$1.value;
|
|
5355
|
+
else return `<error: ${result$1.error.type === "Custom" ? result$1.error.message : result$1.error.type === "WrongTag" ? `expected CBOR tag ${result$1.error.expected.value}, but got ${result$1.error.actual.value}` : result$1.error.type}>`;
|
|
5356
|
+
}
|
|
5379
5357
|
}
|
|
5380
5358
|
let comment;
|
|
5381
5359
|
if (opts.annotate === true) {
|
|
@@ -6006,21 +5984,20 @@ let EdgeType = /* @__PURE__ */ function(EdgeType$1) {
|
|
|
6006
5984
|
* @param cbor - The CBOR value to traverse
|
|
6007
5985
|
* @param initialState - Initial state value
|
|
6008
5986
|
* @param visitor - Function to call for each element
|
|
6009
|
-
* @returns Final state after traversal
|
|
6010
5987
|
*
|
|
6011
5988
|
* @example
|
|
6012
5989
|
* ```typescript
|
|
6013
|
-
* // Count all text strings in a structure
|
|
6014
|
-
*
|
|
5990
|
+
* // Count all text strings in a structure using RefCell-like pattern
|
|
5991
|
+
* const count = { value: 0 };
|
|
6015
5992
|
*
|
|
6016
5993
|
* const structure = cbor({ name: 'Alice', tags: ['urgent', 'draft'] });
|
|
6017
|
-
*
|
|
5994
|
+
* walk(structure, null, (element, level, edge, state) => {
|
|
6018
5995
|
* if (element.type === 'single' && element.cbor.type === MajorType.Text) {
|
|
6019
|
-
*
|
|
5996
|
+
* count.value++;
|
|
6020
5997
|
* }
|
|
6021
5998
|
* return [state, false];
|
|
6022
5999
|
* });
|
|
6023
|
-
* console.log(
|
|
6000
|
+
* console.log(count.value); // 3 (name, urgent, draft)
|
|
6024
6001
|
* ```
|
|
6025
6002
|
*
|
|
6026
6003
|
* @example
|
|
@@ -6029,7 +6006,7 @@ let EdgeType = /* @__PURE__ */ function(EdgeType$1) {
|
|
|
6029
6006
|
* const structure = cbor([1, 2, 3, 'found', 5, 6]);
|
|
6030
6007
|
* let found = false;
|
|
6031
6008
|
*
|
|
6032
|
-
* walk(structure, null, (element, level, edge) => {
|
|
6009
|
+
* walk(structure, null, (element, level, edge, state) => {
|
|
6033
6010
|
* if (element.type === 'single' &&
|
|
6034
6011
|
* element.cbor.type === MajorType.Text &&
|
|
6035
6012
|
* element.cbor.value === 'found') {
|
|
@@ -6041,7 +6018,7 @@ let EdgeType = /* @__PURE__ */ function(EdgeType$1) {
|
|
|
6041
6018
|
* ```
|
|
6042
6019
|
*/
|
|
6043
6020
|
const walk = (cbor$1, initialState, visitor) => {
|
|
6044
|
-
|
|
6021
|
+
walkInternal(cbor$1, 0, { type: EdgeType.None }, initialState, visitor);
|
|
6045
6022
|
};
|
|
6046
6023
|
/**
|
|
6047
6024
|
* Internal recursive walk implementation.
|
|
@@ -6525,7 +6502,7 @@ const attachMethods = (obj) => {
|
|
|
6525
6502
|
return this.value;
|
|
6526
6503
|
},
|
|
6527
6504
|
walk(initialState, visitor) {
|
|
6528
|
-
|
|
6505
|
+
walk(this, initialState, visitor);
|
|
6529
6506
|
},
|
|
6530
6507
|
validateTag(expectedTags) {
|
|
6531
6508
|
if (this.type !== MajorType.Tagged) throw new CborError({ type: "WrongType" });
|
|
@@ -6661,6 +6638,16 @@ var CBORError = class extends URError {
|
|
|
6661
6638
|
}
|
|
6662
6639
|
};
|
|
6663
6640
|
/**
|
|
6641
|
+
* Error type for UR decoder errors.
|
|
6642
|
+
* Matches Rust's Error::UR(String) variant.
|
|
6643
|
+
*/
|
|
6644
|
+
var URDecodeError = class extends URError {
|
|
6645
|
+
constructor(message) {
|
|
6646
|
+
super(`UR decoder error (${message})`);
|
|
6647
|
+
this.name = "URDecodeError";
|
|
6648
|
+
}
|
|
6649
|
+
};
|
|
6650
|
+
/**
|
|
6664
6651
|
* Helper function to check if a result is an error.
|
|
6665
6652
|
*/
|
|
6666
6653
|
function isError(result) {
|
|
@@ -6689,13 +6676,6 @@ function isValidURType(urType) {
|
|
|
6689
6676
|
return Array.from(urType).every((char) => isURTypeChar(char));
|
|
6690
6677
|
}
|
|
6691
6678
|
/**
|
|
6692
|
-
* Validates and returns a UR type, or throws an error if invalid.
|
|
6693
|
-
*/
|
|
6694
|
-
function validateURType(urType) {
|
|
6695
|
-
if (!isValidURType(urType)) throw new InvalidTypeError();
|
|
6696
|
-
return urType;
|
|
6697
|
-
}
|
|
6698
|
-
/**
|
|
6699
6679
|
* Bytewords for encoding/decoding bytes as words.
|
|
6700
6680
|
* See: https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-004-bytewords.md
|
|
6701
6681
|
*/
|
|
@@ -7266,7 +7246,7 @@ function encodeBytemojisIdentifier(data) {
|
|
|
7266
7246
|
let BytewordsStyle = /* @__PURE__ */ function(BytewordsStyle$1) {
|
|
7267
7247
|
/** Full 4-letter words separated by spaces */
|
|
7268
7248
|
BytewordsStyle$1["Standard"] = "standard";
|
|
7269
|
-
/** Full 4-letter words
|
|
7249
|
+
/** Full 4-letter words separated by hyphens (URI-safe) */
|
|
7270
7250
|
BytewordsStyle$1["Uri"] = "uri";
|
|
7271
7251
|
/** First and last character only (minimal) - used by UR encoding */
|
|
7272
7252
|
BytewordsStyle$1["Minimal"] = "minimal";
|
|
@@ -7342,7 +7322,7 @@ function encodeBytewords(data, style = BytewordsStyle.Minimal) {
|
|
|
7342
7322
|
}
|
|
7343
7323
|
switch (style) {
|
|
7344
7324
|
case BytewordsStyle.Standard: return words.join(" ");
|
|
7345
|
-
case BytewordsStyle.Uri:
|
|
7325
|
+
case BytewordsStyle.Uri: return words.join("-");
|
|
7346
7326
|
case BytewordsStyle.Minimal: return words.join("");
|
|
7347
7327
|
}
|
|
7348
7328
|
}
|
|
@@ -7362,14 +7342,11 @@ function decodeBytewords(encoded, style = BytewordsStyle.Minimal) {
|
|
|
7362
7342
|
});
|
|
7363
7343
|
break;
|
|
7364
7344
|
case BytewordsStyle.Uri:
|
|
7365
|
-
|
|
7366
|
-
bytes = [];
|
|
7367
|
-
for (let i = 0; i < lowercased.length; i += 4) {
|
|
7368
|
-
const word = lowercased.slice(i, i + 4);
|
|
7345
|
+
bytes = lowercased.split("-").map((word) => {
|
|
7369
7346
|
const index = BYTEWORDS_MAP.get(word);
|
|
7370
7347
|
if (index === void 0) throw new Error(`Invalid byteword: ${word}`);
|
|
7371
|
-
|
|
7372
|
-
}
|
|
7348
|
+
return index;
|
|
7349
|
+
});
|
|
7373
7350
|
break;
|
|
7374
7351
|
case BytewordsStyle.Minimal:
|
|
7375
7352
|
if (lowercased.length % 2 !== 0) throw new Error("Invalid minimal bytewords length");
|
|
@@ -7674,6 +7651,7 @@ function isURCodable(obj) {
|
|
|
7674
7651
|
* for deterministic fragment selection in fountain codes.
|
|
7675
7652
|
*
|
|
7676
7653
|
* Reference: https://prng.di.unimi.it/
|
|
7654
|
+
* BC-UR Reference: https://github.com/nicklockwood/fountain-codes
|
|
7677
7655
|
*/
|
|
7678
7656
|
const MAX_UINT64 = BigInt("0xffffffffffffffff");
|
|
7679
7657
|
/**
|
|
@@ -7693,21 +7671,28 @@ function rotl(x, k) {
|
|
|
7693
7671
|
var Xoshiro256 = class Xoshiro256 {
|
|
7694
7672
|
s;
|
|
7695
7673
|
/**
|
|
7696
|
-
* Creates a new Xoshiro256** instance from a seed.
|
|
7674
|
+
* Creates a new Xoshiro256** instance from a 32-byte seed.
|
|
7697
7675
|
*
|
|
7698
|
-
* The seed
|
|
7699
|
-
*
|
|
7676
|
+
* The seed must be exactly 32 bytes (256 bits). The bytes are interpreted
|
|
7677
|
+
* using the BC-UR reference algorithm: each 8-byte chunk is read as
|
|
7678
|
+
* big-endian then stored as little-endian for the state.
|
|
7700
7679
|
*
|
|
7701
|
-
* @param seed - The seed bytes (
|
|
7680
|
+
* @param seed - The seed bytes (must be exactly 32 bytes)
|
|
7702
7681
|
*/
|
|
7703
7682
|
constructor(seed) {
|
|
7704
|
-
|
|
7705
|
-
|
|
7706
|
-
|
|
7707
|
-
|
|
7708
|
-
|
|
7709
|
-
|
|
7683
|
+
if (seed.length !== 32) throw new Error(`Seed must be 32 bytes, got ${seed.length}`);
|
|
7684
|
+
const s = [
|
|
7685
|
+
0n,
|
|
7686
|
+
0n,
|
|
7687
|
+
0n,
|
|
7688
|
+
0n
|
|
7710
7689
|
];
|
|
7690
|
+
for (let i = 0; i < 4; i++) {
|
|
7691
|
+
let v = 0n;
|
|
7692
|
+
for (let n = 0; n < 8; n++) v = v << 8n | BigInt(seed[8 * i + n] ?? 0);
|
|
7693
|
+
s[i] = v;
|
|
7694
|
+
}
|
|
7695
|
+
this.s = s;
|
|
7711
7696
|
}
|
|
7712
7697
|
/**
|
|
7713
7698
|
* Creates a Xoshiro256** instance from raw state values.
|
|
@@ -7724,33 +7709,6 @@ var Xoshiro256 = class Xoshiro256 {
|
|
|
7724
7709
|
return instance;
|
|
7725
7710
|
}
|
|
7726
7711
|
/**
|
|
7727
|
-
* Simple hash function for seeding.
|
|
7728
|
-
* This is a basic implementation - in production use SHA-256.
|
|
7729
|
-
*/
|
|
7730
|
-
hashSeed(seed) {
|
|
7731
|
-
const result = new Uint8Array(32);
|
|
7732
|
-
if (seed.length === 0) return result;
|
|
7733
|
-
for (let i = 0; i < 32; i++) {
|
|
7734
|
-
let hash = 0;
|
|
7735
|
-
for (const byte of seed) hash = hash * 31 + byte + i >>> 0;
|
|
7736
|
-
hash ^= hash >>> 16;
|
|
7737
|
-
hash = hash * 2246822507 >>> 0;
|
|
7738
|
-
hash ^= hash >>> 13;
|
|
7739
|
-
hash = hash * 3266489909 >>> 0;
|
|
7740
|
-
hash ^= hash >>> 16;
|
|
7741
|
-
result[i] = hash & 255;
|
|
7742
|
-
}
|
|
7743
|
-
return result;
|
|
7744
|
-
}
|
|
7745
|
-
/**
|
|
7746
|
-
* Converts 8 bytes to a 64-bit BigInt (little-endian).
|
|
7747
|
-
*/
|
|
7748
|
-
bytesToBigInt(bytes) {
|
|
7749
|
-
let result = 0n;
|
|
7750
|
-
for (let i = 7; i >= 0; i--) result = result << 8n | BigInt(bytes[i] ?? 0);
|
|
7751
|
-
return result;
|
|
7752
|
-
}
|
|
7753
|
-
/**
|
|
7754
7712
|
* Generates the next 64-bit random value.
|
|
7755
7713
|
*/
|
|
7756
7714
|
next() {
|
|
@@ -7766,17 +7724,19 @@ var Xoshiro256 = class Xoshiro256 {
|
|
|
7766
7724
|
}
|
|
7767
7725
|
/**
|
|
7768
7726
|
* Generates a random double in [0, 1).
|
|
7727
|
+
* Matches BC-UR reference: self.next() as f64 / (u64::MAX as f64 + 1.0)
|
|
7769
7728
|
*/
|
|
7770
7729
|
nextDouble() {
|
|
7771
7730
|
const value = this.next();
|
|
7772
|
-
return Number(value
|
|
7731
|
+
return Number(value) / 0x10000000000000000;
|
|
7773
7732
|
}
|
|
7774
7733
|
/**
|
|
7775
|
-
* Generates a random integer in [low, high).
|
|
7734
|
+
* Generates a random integer in [low, high] (inclusive).
|
|
7735
|
+
* Matches BC-UR reference: (self.next_double() * ((high - low + 1) as f64)) as u64 + low
|
|
7776
7736
|
*/
|
|
7777
7737
|
nextInt(low, high) {
|
|
7778
|
-
const range$1 = high - low;
|
|
7779
|
-
return
|
|
7738
|
+
const range$1 = high - low + 1;
|
|
7739
|
+
return Math.floor(this.nextDouble() * range$1) + low;
|
|
7780
7740
|
}
|
|
7781
7741
|
/**
|
|
7782
7742
|
* Generates a random byte [0, 255].
|
|
@@ -7792,24 +7752,102 @@ var Xoshiro256 = class Xoshiro256 {
|
|
|
7792
7752
|
for (let i = 0; i < count; i++) result[i] = this.nextByte();
|
|
7793
7753
|
return result;
|
|
7794
7754
|
}
|
|
7755
|
+
/**
|
|
7756
|
+
* Shuffles items by repeatedly picking random indices.
|
|
7757
|
+
* Matches BC-UR reference implementation.
|
|
7758
|
+
*/
|
|
7759
|
+
shuffled(items) {
|
|
7760
|
+
const source = [...items];
|
|
7761
|
+
const shuffled = [];
|
|
7762
|
+
while (source.length > 0) {
|
|
7763
|
+
const index = this.nextInt(0, source.length - 1);
|
|
7764
|
+
const item = source.splice(index, 1)[0];
|
|
7765
|
+
if (item !== void 0) shuffled.push(item);
|
|
7766
|
+
}
|
|
7767
|
+
return shuffled;
|
|
7768
|
+
}
|
|
7769
|
+
/**
|
|
7770
|
+
* Chooses the degree (number of fragments to mix) using a weighted sampler.
|
|
7771
|
+
* Uses the robust soliton distribution with weights [1/1, 1/2, 1/3, ..., 1/n].
|
|
7772
|
+
* Matches BC-UR reference implementation.
|
|
7773
|
+
*/
|
|
7774
|
+
chooseDegree(seqLen) {
|
|
7775
|
+
const weights = [];
|
|
7776
|
+
for (let i = 1; i <= seqLen; i++) weights.push(1 / i);
|
|
7777
|
+
return new WeightedSampler(weights).next(this) + 1;
|
|
7778
|
+
}
|
|
7779
|
+
};
|
|
7780
|
+
/**
|
|
7781
|
+
* Weighted sampler using Vose's alias method.
|
|
7782
|
+
* Allows O(1) sampling from a discrete probability distribution.
|
|
7783
|
+
*/
|
|
7784
|
+
var WeightedSampler = class {
|
|
7785
|
+
aliases;
|
|
7786
|
+
probs;
|
|
7787
|
+
constructor(weights) {
|
|
7788
|
+
const n = weights.length;
|
|
7789
|
+
if (n === 0) throw new Error("Weights array cannot be empty");
|
|
7790
|
+
const sum = weights.reduce((a, b) => a + b, 0);
|
|
7791
|
+
if (sum <= 0) throw new Error("Weights must sum to a positive value");
|
|
7792
|
+
const normalized = weights.map((w) => w * n / sum);
|
|
7793
|
+
this.aliases = Array.from({ length: n }).fill(0);
|
|
7794
|
+
this.probs = Array.from({ length: n }).fill(0);
|
|
7795
|
+
const small = [];
|
|
7796
|
+
const large = [];
|
|
7797
|
+
for (let i = n - 1; i >= 0; i--) if (normalized[i] < 1) small.push(i);
|
|
7798
|
+
else large.push(i);
|
|
7799
|
+
while (small.length > 0 && large.length > 0) {
|
|
7800
|
+
const a = small.pop();
|
|
7801
|
+
const g = large.pop();
|
|
7802
|
+
if (a === void 0 || g === void 0) break;
|
|
7803
|
+
this.probs[a] = normalized[a] ?? 0;
|
|
7804
|
+
this.aliases[a] = g;
|
|
7805
|
+
normalized[g] = (normalized[g] ?? 0) + (normalized[a] ?? 0) - 1;
|
|
7806
|
+
if (normalized[g] !== void 0 && normalized[g] < 1) small.push(g);
|
|
7807
|
+
else large.push(g);
|
|
7808
|
+
}
|
|
7809
|
+
while (large.length > 0) {
|
|
7810
|
+
const g = large.pop();
|
|
7811
|
+
if (g === void 0) break;
|
|
7812
|
+
this.probs[g] = 1;
|
|
7813
|
+
}
|
|
7814
|
+
while (small.length > 0) {
|
|
7815
|
+
const a = small.pop();
|
|
7816
|
+
if (a === void 0) break;
|
|
7817
|
+
this.probs[a] = 1;
|
|
7818
|
+
}
|
|
7819
|
+
}
|
|
7820
|
+
/**
|
|
7821
|
+
* Sample from the distribution.
|
|
7822
|
+
*/
|
|
7823
|
+
next(rng) {
|
|
7824
|
+
const r1 = rng.nextDouble();
|
|
7825
|
+
const r2 = rng.nextDouble();
|
|
7826
|
+
const n = this.probs.length;
|
|
7827
|
+
const i = Math.floor(n * r1);
|
|
7828
|
+
if (r2 < this.probs[i]) return i;
|
|
7829
|
+
else return this.aliases[i];
|
|
7830
|
+
}
|
|
7795
7831
|
};
|
|
7796
7832
|
/**
|
|
7797
|
-
* Creates a
|
|
7833
|
+
* Creates a Xoshiro256 PRNG instance from message checksum and sequence number.
|
|
7834
|
+
*
|
|
7835
|
+
* This creates an 8-byte seed by concatenating seqNum and checksum (both in
|
|
7836
|
+
* big-endian), then hashes it with SHA-256 to get the 32-byte seed for Xoshiro.
|
|
7798
7837
|
*
|
|
7799
|
-
* This
|
|
7800
|
-
* for a given message and part number.
|
|
7838
|
+
* This matches the BC-UR reference implementation.
|
|
7801
7839
|
*/
|
|
7802
7840
|
function createSeed(checksum, seqNum) {
|
|
7803
|
-
const
|
|
7804
|
-
|
|
7805
|
-
|
|
7806
|
-
|
|
7807
|
-
|
|
7808
|
-
|
|
7809
|
-
|
|
7810
|
-
|
|
7811
|
-
|
|
7812
|
-
return
|
|
7841
|
+
const seed8 = new Uint8Array(8);
|
|
7842
|
+
seed8[0] = seqNum >>> 24 & 255;
|
|
7843
|
+
seed8[1] = seqNum >>> 16 & 255;
|
|
7844
|
+
seed8[2] = seqNum >>> 8 & 255;
|
|
7845
|
+
seed8[3] = seqNum & 255;
|
|
7846
|
+
seed8[4] = checksum >>> 24 & 255;
|
|
7847
|
+
seed8[5] = checksum >>> 16 & 255;
|
|
7848
|
+
seed8[6] = checksum >>> 8 & 255;
|
|
7849
|
+
seed8[7] = checksum & 255;
|
|
7850
|
+
return (0, _bcts_crypto.sha256)(seed8);
|
|
7813
7851
|
}
|
|
7814
7852
|
|
|
7815
7853
|
//#endregion
|
|
@@ -7856,6 +7894,11 @@ function xorBytes(a, b) {
|
|
|
7856
7894
|
* This uses a seeded Xoshiro256** PRNG to deterministically select fragments,
|
|
7857
7895
|
* ensuring encoder and decoder agree without explicit coordination.
|
|
7858
7896
|
*
|
|
7897
|
+
* The algorithm matches the BC-UR reference implementation:
|
|
7898
|
+
* 1. For pure parts (seqNum <= seqLen), return single fragment index
|
|
7899
|
+
* 2. For mixed parts, use weighted sampling to choose degree
|
|
7900
|
+
* 3. Shuffle all indices and take the first 'degree' indices
|
|
7901
|
+
*
|
|
7859
7902
|
* @param seqNum - The sequence number (1-based)
|
|
7860
7903
|
* @param seqLen - Total number of pure fragments
|
|
7861
7904
|
* @param checksum - CRC32 checksum of the message
|
|
@@ -7864,26 +7907,10 @@ function xorBytes(a, b) {
|
|
|
7864
7907
|
function chooseFragments(seqNum, seqLen, checksum) {
|
|
7865
7908
|
if (seqNum <= seqLen) return [seqNum - 1];
|
|
7866
7909
|
const rng = new Xoshiro256(createSeed(checksum, seqNum));
|
|
7867
|
-
const degree = chooseDegree(
|
|
7868
|
-
const
|
|
7869
|
-
|
|
7870
|
-
|
|
7871
|
-
indices.add(index);
|
|
7872
|
-
}
|
|
7873
|
-
return Array.from(indices).sort((a, b) => a - b);
|
|
7874
|
-
}
|
|
7875
|
-
/**
|
|
7876
|
-
* Chooses the degree (number of fragments to mix) using a simplified
|
|
7877
|
-
* robust soliton distribution.
|
|
7878
|
-
*
|
|
7879
|
-
* This ensures good coverage of fragments for efficient decoding.
|
|
7880
|
-
*/
|
|
7881
|
-
function chooseDegree(rng, seqLen) {
|
|
7882
|
-
const r = rng.nextDouble();
|
|
7883
|
-
if (r < .5) return 1;
|
|
7884
|
-
else if (r < .75) return 2;
|
|
7885
|
-
else if (r < .9) return Math.min(3, seqLen);
|
|
7886
|
-
else return Math.min(rng.nextInt(4, seqLen + 1), seqLen);
|
|
7910
|
+
const degree = rng.chooseDegree(seqLen);
|
|
7911
|
+
const allIndices = [];
|
|
7912
|
+
for (let i = 0; i < seqLen; i++) allIndices.push(i);
|
|
7913
|
+
return rng.shuffled(allIndices).slice(0, degree);
|
|
7887
7914
|
}
|
|
7888
7915
|
/**
|
|
7889
7916
|
* Mixes the selected fragments using XOR.
|
|
@@ -8123,14 +8150,6 @@ var MultipartEncoder = class {
|
|
|
8123
8150
|
this._fountainEncoder = new FountainEncoder(ur.cbor().toData(), maxFragmentLen);
|
|
8124
8151
|
}
|
|
8125
8152
|
/**
|
|
8126
|
-
* Returns whether the message fits in a single part.
|
|
8127
|
-
*
|
|
8128
|
-
* For single-part messages, consider using UR.string() directly.
|
|
8129
|
-
*/
|
|
8130
|
-
isSinglePart() {
|
|
8131
|
-
return this._fountainEncoder.isSinglePart();
|
|
8132
|
-
}
|
|
8133
|
-
/**
|
|
8134
8153
|
* Gets the next part of the encoding.
|
|
8135
8154
|
*
|
|
8136
8155
|
* Parts 1 through seqLen are "pure" fragments containing one piece each.
|
|
@@ -8158,20 +8177,17 @@ var MultipartEncoder = class {
|
|
|
8158
8177
|
return `ur:${this._ur.urTypeStr()}/${part.seqNum}-${part.seqLen}/${encoded}`;
|
|
8159
8178
|
}
|
|
8160
8179
|
/**
|
|
8161
|
-
* Encodes part metadata and data
|
|
8180
|
+
* Encodes part metadata and data as CBOR for bytewords encoding.
|
|
8181
|
+
* Format: CBOR array [seqNum, seqLen, messageLen, checksum, data]
|
|
8162
8182
|
*/
|
|
8163
8183
|
_encodePartData(part) {
|
|
8164
|
-
|
|
8165
|
-
|
|
8166
|
-
|
|
8167
|
-
|
|
8168
|
-
|
|
8169
|
-
|
|
8170
|
-
|
|
8171
|
-
result[6] = part.checksum >>> 8 & 255;
|
|
8172
|
-
result[7] = part.checksum & 255;
|
|
8173
|
-
result.set(part.data, 8);
|
|
8174
|
-
return result;
|
|
8184
|
+
return cbor([
|
|
8185
|
+
part.seqNum,
|
|
8186
|
+
part.seqLen,
|
|
8187
|
+
part.messageLen,
|
|
8188
|
+
part.checksum,
|
|
8189
|
+
part.data
|
|
8190
|
+
]).toData();
|
|
8175
8191
|
}
|
|
8176
8192
|
/**
|
|
8177
8193
|
* Gets the current part index.
|
|
@@ -8188,22 +8204,6 @@ var MultipartEncoder = class {
|
|
|
8188
8204
|
partsCount() {
|
|
8189
8205
|
return this._fountainEncoder.seqLen;
|
|
8190
8206
|
}
|
|
8191
|
-
/**
|
|
8192
|
-
* Checks if all pure parts have been emitted.
|
|
8193
|
-
*
|
|
8194
|
-
* Even after this returns true, you can continue calling nextPart()
|
|
8195
|
-
* to generate additional rateless parts for redundancy.
|
|
8196
|
-
*/
|
|
8197
|
-
isComplete() {
|
|
8198
|
-
return this._fountainEncoder.isComplete();
|
|
8199
|
-
}
|
|
8200
|
-
/**
|
|
8201
|
-
* Resets the encoder to start from the beginning.
|
|
8202
|
-
*/
|
|
8203
|
-
reset() {
|
|
8204
|
-
this._currentIndex = 0;
|
|
8205
|
-
this._fountainEncoder.reset();
|
|
8206
|
-
}
|
|
8207
8207
|
};
|
|
8208
8208
|
|
|
8209
8209
|
//#endregion
|
|
@@ -8285,16 +8285,23 @@ var MultipartDecoder = class {
|
|
|
8285
8285
|
}
|
|
8286
8286
|
/**
|
|
8287
8287
|
* Decodes a multipart UR's fountain part data.
|
|
8288
|
+
*
|
|
8289
|
+
* The multipart body is a CBOR array: [seqNum, seqLen, messageLen, checksum, data]
|
|
8288
8290
|
*/
|
|
8289
8291
|
_decodeFountainPart(partInfo) {
|
|
8290
|
-
const
|
|
8291
|
-
if (
|
|
8292
|
-
const
|
|
8293
|
-
|
|
8294
|
-
const
|
|
8292
|
+
const decoded = decodeCbor(decodeBytewords(partInfo.encodedData, BytewordsStyle.Minimal));
|
|
8293
|
+
if (decoded.type !== MajorType.Array) throw new URError("Invalid multipart data: expected CBOR array");
|
|
8294
|
+
const items = decoded.value;
|
|
8295
|
+
if (items.length !== 5) throw new URError(`Invalid multipart data: expected 5 elements, got ${items.length}`);
|
|
8296
|
+
const seqNum = Number(items[0].value);
|
|
8297
|
+
const seqLen = Number(items[1].value);
|
|
8298
|
+
const messageLen = Number(items[2].value);
|
|
8299
|
+
const checksum = Number(items[3].value);
|
|
8300
|
+
const data = items[4].value;
|
|
8301
|
+
if (seqNum !== partInfo.seqNum || seqLen !== partInfo.seqLen) throw new URError(`Multipart metadata mismatch: URL says ${partInfo.seqNum}-${partInfo.seqLen}, CBOR says ${seqNum}-${seqLen}`);
|
|
8295
8302
|
return {
|
|
8296
|
-
seqNum
|
|
8297
|
-
seqLen
|
|
8303
|
+
seqNum,
|
|
8304
|
+
seqLen,
|
|
8298
8305
|
messageLen,
|
|
8299
8306
|
checksum,
|
|
8300
8307
|
data
|
|
@@ -8314,48 +8321,25 @@ var MultipartDecoder = class {
|
|
|
8314
8321
|
message() {
|
|
8315
8322
|
return this._decodedMessage;
|
|
8316
8323
|
}
|
|
8317
|
-
/**
|
|
8318
|
-
* Returns the decoding progress as a fraction (0 to 1).
|
|
8319
|
-
*/
|
|
8320
|
-
progress() {
|
|
8321
|
-
if (this._decodedMessage !== null) return 1;
|
|
8322
|
-
if (this._fountainDecoder === null) return 0;
|
|
8323
|
-
return this._fountainDecoder.progress();
|
|
8324
|
-
}
|
|
8325
|
-
/**
|
|
8326
|
-
* Resets the decoder to receive a new message.
|
|
8327
|
-
*/
|
|
8328
|
-
reset() {
|
|
8329
|
-
this._urType = null;
|
|
8330
|
-
this._fountainDecoder = null;
|
|
8331
|
-
this._decodedMessage = null;
|
|
8332
|
-
}
|
|
8333
8324
|
};
|
|
8334
8325
|
|
|
8335
8326
|
//#endregion
|
|
8336
8327
|
exports.BYTEMOJIS = BYTEMOJIS;
|
|
8337
8328
|
exports.BYTEWORDS = BYTEWORDS;
|
|
8338
|
-
exports.BYTEWORDS_MAP = BYTEWORDS_MAP;
|
|
8339
8329
|
exports.BytewordsError = BytewordsError;
|
|
8340
8330
|
exports.BytewordsStyle = BytewordsStyle;
|
|
8341
8331
|
exports.CBORError = CBORError;
|
|
8342
|
-
exports.FountainDecoder = FountainDecoder;
|
|
8343
|
-
exports.FountainEncoder = FountainEncoder;
|
|
8344
8332
|
exports.InvalidSchemeError = InvalidSchemeError;
|
|
8345
8333
|
exports.InvalidTypeError = InvalidTypeError;
|
|
8346
|
-
exports.MINIMAL_BYTEWORDS_MAP = MINIMAL_BYTEWORDS_MAP;
|
|
8347
8334
|
exports.MultipartDecoder = MultipartDecoder;
|
|
8348
8335
|
exports.MultipartEncoder = MultipartEncoder;
|
|
8349
8336
|
exports.NotSinglePartError = NotSinglePartError;
|
|
8350
8337
|
exports.TypeUnspecifiedError = TypeUnspecifiedError;
|
|
8351
8338
|
exports.UR = UR;
|
|
8339
|
+
exports.URDecodeError = URDecodeError;
|
|
8352
8340
|
exports.URError = URError;
|
|
8353
8341
|
exports.URType = URType;
|
|
8354
8342
|
exports.UnexpectedTypeError = UnexpectedTypeError;
|
|
8355
|
-
exports.Xoshiro256 = Xoshiro256;
|
|
8356
|
-
exports.chooseFragments = chooseFragments;
|
|
8357
|
-
exports.crc32 = crc32;
|
|
8358
|
-
exports.createSeed = createSeed;
|
|
8359
8343
|
exports.decodeBytewords = decodeBytewords;
|
|
8360
8344
|
exports.encodeBytemojisIdentifier = encodeBytemojisIdentifier;
|
|
8361
8345
|
exports.encodeBytewords = encodeBytewords;
|
|
@@ -8364,10 +8348,4 @@ exports.isError = isError;
|
|
|
8364
8348
|
exports.isURCodable = isURCodable;
|
|
8365
8349
|
exports.isURDecodable = isURDecodable;
|
|
8366
8350
|
exports.isUREncodable = isUREncodable;
|
|
8367
|
-
exports.isURTypeChar = isURTypeChar;
|
|
8368
|
-
exports.isValidURType = isValidURType;
|
|
8369
|
-
exports.mixFragments = mixFragments;
|
|
8370
|
-
exports.splitMessage = splitMessage;
|
|
8371
|
-
exports.validateURType = validateURType;
|
|
8372
|
-
exports.xorBytes = xorBytes;
|
|
8373
8351
|
//# sourceMappingURL=index.cjs.map
|