wire-mesh-core 1.41.0 → 1.43.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.
@@ -0,0 +1,149 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/domain/erasure-coding.ts
3
+ /**
4
+ * K-of-N erasure coding over GF(2^8) for wire-mesh#36's mailbox-opacity case (the shard half of #34's encrypt-then-shard design): a systematic Reed-Solomon construction where the first `dataShards` shards ARE the padded input split into equal runs, and the remaining shards are parity rows of a Vandermonde-style matrix. Any `dataShards` of `totalShards` reconstruct the input -- the property a single mailbox holder must NOT have (it holds one shard and never the epoch key either).
5
+ *
6
+ * Deliberately hand-written rather than a dependency, the same choice the ECIES construction made: the encode format must be byte-identical across this and the Rust implementation, so both pin the SAME vectors against the documented construction rather than trusting two third-party ports to agree. The math is linear algebra over GF(2^8) with the standard 0x11d polynomial (AES's field), not cryptography.
7
+ *
8
+ * The construction, pinned: pad the input with zero bytes to dataShards * ceil(len / dataShards); any shard index below dataShards is that shard's own equal run; parity shard j (0-based within the parity block) has, at each byte position, the GF dot product of the data runs at that position with the coefficient row (alpha^j, alpha^(j+1), ..., alpha^(j+dataShards-1)) where alpha = 0x02 (the field's generator). Decoding: build the dataShards x dataShards submatrix of the full encode matrix selected by the surviving shards' row indices, invert it over the field, and apply it to the surviving shards' bytes. The encode matrix's first dataShards rows are the identity (systematic), so the all-data-shards case needs no inversion at all.
9
+ */
10
+ /** The AES field's irreducible polynomial x^8+x^4+x^3+x+1, the conventional GF(2^8) choice. */
11
+ const GF_POLY = 285;
12
+ /** The bit that appears when a GF(2^8) value overflows a byte and needs the polynomial reduction. */
13
+ const GF_OVERFLOW_BIT = 256;
14
+ /** GF(2^8) caps total shards at 255 (a 256th row would repeat, breaking invertibility). */
15
+ const GF_MAX_SHARDS = 255;
16
+ const gfExp = Array.from({ length: 510 }, () => 0);
17
+ const gfLog = Array.from({ length: 256 }, () => 0);
18
+ {
19
+ let x = 1;
20
+ for (let power = 0; power < GF_MAX_SHARDS; power += 1) {
21
+ gfExp[power] = x;
22
+ gfLog[x] = power;
23
+ x <<= 1;
24
+ if (x & GF_OVERFLOW_BIT) x ^= GF_POLY;
25
+ }
26
+ for (let power = GF_MAX_SHARDS; power < 510; power += 1) gfExp[power] = gfExp[power - GF_MAX_SHARDS] ?? 0;
27
+ }
28
+ function gfMul(a, b) {
29
+ if (a === 0 || b === 0) return 0;
30
+ return gfExp[(gfLog[a] ?? 0) + (gfLog[b] ?? 0)] ?? 0;
31
+ }
32
+ function gfInv(a) {
33
+ return gfExp[GF_MAX_SHARDS - (gfLog[a] ?? 0)] ?? 1;
34
+ }
35
+ function validateConfig(config) {
36
+ const { dataShards, totalShards } = config;
37
+ if (!Number.isInteger(dataShards) || dataShards < 1) throw new Error(`dataShards must be a positive integer, got ${String(dataShards)}`);
38
+ if (!Number.isInteger(totalShards) || totalShards < dataShards) throw new Error(`totalShards must be an integer at least dataShards (${String(dataShards)}), got ${String(totalShards)}`);
39
+ if (totalShards > GF_MAX_SHARDS) throw new Error(`the GF(2^8) field supports at most ${String(GF_MAX_SHARDS)} shards, got ${String(totalShards)}`);
40
+ }
41
+ /** Row i of the encode matrix: identity rows for the data shards, alpha-power rows for parity. */
42
+ /**
43
+ * Row i of the encode matrix: identity rows for the data shards, Cauchy rows for parity. Cauchy, not Vandermonde, because only Cauchy guarantees EVERY square submatrix is invertible -- identity-plus-Vandermonde has singular K-subsets (found the hard way: parity-based reconstruction produced wrong bytes on some subsets). With parity row j carrying c_i = 1/(x_j XOR y_i) where x_j = row and y_i = column (all row/column values distinct across the whole matrix, no parity row index equals any data column index, so no denominator is zero), any K surviving rows reduce -- expanding along the identity rows -- to a square Cauchy submatrix, always invertible.
44
+ */
45
+ function encodeMatrixRow(row, dataShards) {
46
+ const coefficients = Array.from({ length: dataShards }, () => 0);
47
+ if (row < dataShards) {
48
+ coefficients[row] = 1;
49
+ return coefficients;
50
+ }
51
+ for (let column = 0; column < dataShards; column += 1) coefficients[column] = gfInv(row ^ column);
52
+ return coefficients;
53
+ }
54
+ /** Inverts a square matrix over GF(2^8) by Gauss-Jordan with an identity augmentation. */
55
+ function invertMatrix(matrix) {
56
+ const size = matrix.length;
57
+ const work = matrix.map((row, rowIndex) => {
58
+ const augmented = [...row, ...Array.from({ length: size }, () => 0)];
59
+ augmented[size + rowIndex] = 1;
60
+ return augmented;
61
+ });
62
+ for (let column = 0; column < size; column += 1) {
63
+ let pivotRow = -1;
64
+ for (let candidate = column; candidate < size; candidate += 1) if ((work[candidate]?.[column] ?? 0) !== 0) {
65
+ pivotRow = candidate;
66
+ break;
67
+ }
68
+ if (pivotRow === -1) throw new Error("matrix is singular over GF(2^8)");
69
+ if (pivotRow !== column) {
70
+ const swap = work[column] ?? [];
71
+ work[column] = work[pivotRow] ?? [];
72
+ work[pivotRow] = swap;
73
+ }
74
+ const pivotRowValues = work[column];
75
+ if (pivotRowValues === void 0) throw new Error("internal: pivot row vanished");
76
+ const pivotInverse = gfInv(pivotRowValues[column] ?? 1);
77
+ for (let c = 0; c < 2 * size; c += 1) pivotRowValues[c] = gfMul(pivotRowValues[c] ?? 0, pivotInverse);
78
+ for (let other = 0; other < size; other += 1) {
79
+ if (other === column) continue;
80
+ const factor = work[other]?.[column] ?? 0;
81
+ if (factor === 0) continue;
82
+ const target = work[other];
83
+ const source = work[column];
84
+ if (target === void 0 || source === void 0) throw new Error("internal: row vanished mid-inversion");
85
+ for (let c = 0; c < 2 * size; c += 1) target[c] = (target[c] ?? 0) ^ gfMul(factor, source[c] ?? 0);
86
+ }
87
+ }
88
+ return work.map((row) => row.slice(size));
89
+ }
90
+ async function encodeShards(data, config) {
91
+ validateConfig(config);
92
+ const { dataShards, totalShards } = config;
93
+ const shardLength = Math.ceil(data.length / dataShards);
94
+ const shards = [];
95
+ for (let i = 0; i < totalShards; i += 1) shards.push(new Uint8Array(new ArrayBuffer(shardLength)));
96
+ for (let i = 0; i < dataShards; i += 1) shards[i]?.set(data.subarray(i * shardLength, (i + 1) * shardLength));
97
+ for (let parityIndex = dataShards; parityIndex < totalShards; parityIndex += 1) {
98
+ const coefficients = encodeMatrixRow(parityIndex, dataShards);
99
+ for (let position = 0; position < shardLength; position += 1) {
100
+ let accumulated = 0;
101
+ for (let column = 0; column < dataShards; column += 1) accumulated ^= gfMul(coefficients[column] ?? 0, shards[column]?.[position] ?? 0);
102
+ const parityTarget = shards[parityIndex];
103
+ if (parityTarget !== void 0) parityTarget[position] = accumulated;
104
+ }
105
+ }
106
+ return Promise.resolve(shards);
107
+ }
108
+ async function decodeShards(presented, config, originalLength) {
109
+ validateConfig(config);
110
+ const { dataShards, totalShards } = config;
111
+ for (const entry of presented) if (entry.index < 0 || entry.index >= totalShards) throw new Error(`shard index ${String(entry.index)} is outside 0..${String(totalShards - 1)}`);
112
+ if (presented.length < dataShards) throw new Error(`need at least ${String(dataShards)} shards to reconstruct, got ${String(presented.length)}`);
113
+ const chosen = presented.slice(0, dataShards);
114
+ const firstChosen = chosen[0];
115
+ if (firstChosen === void 0) throw new Error("internal: chosen shard set is empty");
116
+ const shardLength = firstChosen.data.length;
117
+ const recovered = [];
118
+ if (chosen.every((entry) => entry.index < dataShards)) for (let dataIndex = 0; dataIndex < dataShards; dataIndex += 1) {
119
+ const entry = chosen.find((candidate) => candidate.index === dataIndex);
120
+ if (entry === void 0) throw new Error("internal: data shard missing after the all-present check");
121
+ recovered.push(entry.data);
122
+ }
123
+ else {
124
+ const inverse = invertMatrix(chosen.map((entry) => encodeMatrixRow(entry.index, dataShards)));
125
+ for (let dataIndex = 0; dataIndex < dataShards; dataIndex += 1) {
126
+ const row = new Uint8Array(shardLength);
127
+ for (let chosenIndex = 0; chosenIndex < dataShards; chosenIndex += 1) {
128
+ const coefficient = inverse[dataIndex]?.[chosenIndex] ?? 0;
129
+ if (coefficient === 0) continue;
130
+ for (let position = 0; position < shardLength; position += 1) row[position] = (row[position] ?? 0) ^ gfMul(coefficient, chosen[chosenIndex]?.data[position] ?? 0);
131
+ }
132
+ recovered.push(row);
133
+ }
134
+ }
135
+ const totalLength = dataShards * shardLength;
136
+ if (originalLength > totalLength) throw new Error(`originalLength ${String(originalLength)} exceeds the shards' capacity ${String(totalLength)} -- wrong manifest for these shards`);
137
+ const stitched = new Uint8Array(new ArrayBuffer(originalLength));
138
+ for (let position = 0; position < originalLength; position += 1) {
139
+ const shardIndex = Math.floor(position / shardLength);
140
+ const within = position % shardLength;
141
+ const byte = recovered[shardIndex]?.[within];
142
+ if (byte === void 0) throw new Error("shard position out of range while stitching");
143
+ stitched[position] = byte;
144
+ }
145
+ return Promise.resolve(stitched);
146
+ }
147
+ //#endregion
148
+ exports.decodeShards = decodeShards;
149
+ exports.encodeShards = encodeShards;
@@ -0,0 +1,27 @@
1
+ //#region src/domain/erasure-coding.d.ts
2
+ /**
3
+ * K-of-N erasure coding over GF(2^8) for wire-mesh#36's mailbox-opacity case (the shard half of #34's encrypt-then-shard design): a systematic Reed-Solomon construction where the first `dataShards` shards ARE the padded input split into equal runs, and the remaining shards are parity rows of a Vandermonde-style matrix. Any `dataShards` of `totalShards` reconstruct the input -- the property a single mailbox holder must NOT have (it holds one shard and never the epoch key either).
4
+ *
5
+ * Deliberately hand-written rather than a dependency, the same choice the ECIES construction made: the encode format must be byte-identical across this and the Rust implementation, so both pin the SAME vectors against the documented construction rather than trusting two third-party ports to agree. The math is linear algebra over GF(2^8) with the standard 0x11d polynomial (AES's field), not cryptography.
6
+ *
7
+ * The construction, pinned: pad the input with zero bytes to dataShards * ceil(len / dataShards); any shard index below dataShards is that shard's own equal run; parity shard j (0-based within the parity block) has, at each byte position, the GF dot product of the data runs at that position with the coefficient row (alpha^j, alpha^(j+1), ..., alpha^(j+dataShards-1)) where alpha = 0x02 (the field's generator). Decoding: build the dataShards x dataShards submatrix of the full encode matrix selected by the surviving shards' row indices, invert it over the field, and apply it to the surviving shards' bytes. The encode matrix's first dataShards rows are the identity (systematic), so the all-data-shards case needs no inversion at all.
8
+ */
9
+ export interface ShardConfig {
10
+ dataShards: number;
11
+ totalShards: number;
12
+ }
13
+ export declare function encodeShards(data: Uint8Array, config: Readonly<ShardConfig>): Promise<Uint8Array<ArrayBuffer>[]>;
14
+ /**
15
+ * Reconstructs the original bytes from any dataShards of the totalShards
16
+ * produced by encodeShards. originalLength is the manifest's own fact, not
17
+ * derivable from the shards (zero-padding is indistinguishable from a
18
+ * legitimate trailing zero byte), so the caller passes it -- #34's design
19
+ * puts the length in the shard manifest alongside the device/transfer-id list.
20
+ */
21
+ /** One surviving shard as presented for reconstruction: its ORIGINAL position among the encode output (the parity equation's row index), never its position in whichever array the caller collected survivors into -- confusing the two silently reconstructs garbage, which is exactly why the index is explicit rather than implied by array order. */
22
+ export interface PresentedShard {
23
+ index: number;
24
+ data: Uint8Array;
25
+ }
26
+ export declare function decodeShards(presented: readonly PresentedShard[], config: Readonly<ShardConfig>, originalLength: number): Promise<Uint8Array<ArrayBuffer>>;
27
+ //#endregion
@@ -0,0 +1,27 @@
1
+ //#region src/domain/erasure-coding.d.ts
2
+ /**
3
+ * K-of-N erasure coding over GF(2^8) for wire-mesh#36's mailbox-opacity case (the shard half of #34's encrypt-then-shard design): a systematic Reed-Solomon construction where the first `dataShards` shards ARE the padded input split into equal runs, and the remaining shards are parity rows of a Vandermonde-style matrix. Any `dataShards` of `totalShards` reconstruct the input -- the property a single mailbox holder must NOT have (it holds one shard and never the epoch key either).
4
+ *
5
+ * Deliberately hand-written rather than a dependency, the same choice the ECIES construction made: the encode format must be byte-identical across this and the Rust implementation, so both pin the SAME vectors against the documented construction rather than trusting two third-party ports to agree. The math is linear algebra over GF(2^8) with the standard 0x11d polynomial (AES's field), not cryptography.
6
+ *
7
+ * The construction, pinned: pad the input with zero bytes to dataShards * ceil(len / dataShards); any shard index below dataShards is that shard's own equal run; parity shard j (0-based within the parity block) has, at each byte position, the GF dot product of the data runs at that position with the coefficient row (alpha^j, alpha^(j+1), ..., alpha^(j+dataShards-1)) where alpha = 0x02 (the field's generator). Decoding: build the dataShards x dataShards submatrix of the full encode matrix selected by the surviving shards' row indices, invert it over the field, and apply it to the surviving shards' bytes. The encode matrix's first dataShards rows are the identity (systematic), so the all-data-shards case needs no inversion at all.
8
+ */
9
+ export interface ShardConfig {
10
+ dataShards: number;
11
+ totalShards: number;
12
+ }
13
+ export declare function encodeShards(data: Uint8Array, config: Readonly<ShardConfig>): Promise<Uint8Array<ArrayBuffer>[]>;
14
+ /**
15
+ * Reconstructs the original bytes from any dataShards of the totalShards
16
+ * produced by encodeShards. originalLength is the manifest's own fact, not
17
+ * derivable from the shards (zero-padding is indistinguishable from a
18
+ * legitimate trailing zero byte), so the caller passes it -- #34's design
19
+ * puts the length in the shard manifest alongside the device/transfer-id list.
20
+ */
21
+ /** One surviving shard as presented for reconstruction: its ORIGINAL position among the encode output (the parity equation's row index), never its position in whichever array the caller collected survivors into -- confusing the two silently reconstructs garbage, which is exactly why the index is explicit rather than implied by array order. */
22
+ export interface PresentedShard {
23
+ index: number;
24
+ data: Uint8Array;
25
+ }
26
+ export declare function decodeShards(presented: readonly PresentedShard[], config: Readonly<ShardConfig>, originalLength: number): Promise<Uint8Array<ArrayBuffer>>;
27
+ //#endregion
@@ -0,0 +1,147 @@
1
+ //#region src/domain/erasure-coding.ts
2
+ /**
3
+ * K-of-N erasure coding over GF(2^8) for wire-mesh#36's mailbox-opacity case (the shard half of #34's encrypt-then-shard design): a systematic Reed-Solomon construction where the first `dataShards` shards ARE the padded input split into equal runs, and the remaining shards are parity rows of a Vandermonde-style matrix. Any `dataShards` of `totalShards` reconstruct the input -- the property a single mailbox holder must NOT have (it holds one shard and never the epoch key either).
4
+ *
5
+ * Deliberately hand-written rather than a dependency, the same choice the ECIES construction made: the encode format must be byte-identical across this and the Rust implementation, so both pin the SAME vectors against the documented construction rather than trusting two third-party ports to agree. The math is linear algebra over GF(2^8) with the standard 0x11d polynomial (AES's field), not cryptography.
6
+ *
7
+ * The construction, pinned: pad the input with zero bytes to dataShards * ceil(len / dataShards); any shard index below dataShards is that shard's own equal run; parity shard j (0-based within the parity block) has, at each byte position, the GF dot product of the data runs at that position with the coefficient row (alpha^j, alpha^(j+1), ..., alpha^(j+dataShards-1)) where alpha = 0x02 (the field's generator). Decoding: build the dataShards x dataShards submatrix of the full encode matrix selected by the surviving shards' row indices, invert it over the field, and apply it to the surviving shards' bytes. The encode matrix's first dataShards rows are the identity (systematic), so the all-data-shards case needs no inversion at all.
8
+ */
9
+ /** The AES field's irreducible polynomial x^8+x^4+x^3+x+1, the conventional GF(2^8) choice. */
10
+ const GF_POLY = 285;
11
+ /** The bit that appears when a GF(2^8) value overflows a byte and needs the polynomial reduction. */
12
+ const GF_OVERFLOW_BIT = 256;
13
+ /** GF(2^8) caps total shards at 255 (a 256th row would repeat, breaking invertibility). */
14
+ const GF_MAX_SHARDS = 255;
15
+ const gfExp = Array.from({ length: 510 }, () => 0);
16
+ const gfLog = Array.from({ length: 256 }, () => 0);
17
+ {
18
+ let x = 1;
19
+ for (let power = 0; power < GF_MAX_SHARDS; power += 1) {
20
+ gfExp[power] = x;
21
+ gfLog[x] = power;
22
+ x <<= 1;
23
+ if (x & GF_OVERFLOW_BIT) x ^= GF_POLY;
24
+ }
25
+ for (let power = GF_MAX_SHARDS; power < 510; power += 1) gfExp[power] = gfExp[power - GF_MAX_SHARDS] ?? 0;
26
+ }
27
+ function gfMul(a, b) {
28
+ if (a === 0 || b === 0) return 0;
29
+ return gfExp[(gfLog[a] ?? 0) + (gfLog[b] ?? 0)] ?? 0;
30
+ }
31
+ function gfInv(a) {
32
+ return gfExp[GF_MAX_SHARDS - (gfLog[a] ?? 0)] ?? 1;
33
+ }
34
+ function validateConfig(config) {
35
+ const { dataShards, totalShards } = config;
36
+ if (!Number.isInteger(dataShards) || dataShards < 1) throw new Error(`dataShards must be a positive integer, got ${String(dataShards)}`);
37
+ if (!Number.isInteger(totalShards) || totalShards < dataShards) throw new Error(`totalShards must be an integer at least dataShards (${String(dataShards)}), got ${String(totalShards)}`);
38
+ if (totalShards > GF_MAX_SHARDS) throw new Error(`the GF(2^8) field supports at most ${String(GF_MAX_SHARDS)} shards, got ${String(totalShards)}`);
39
+ }
40
+ /** Row i of the encode matrix: identity rows for the data shards, alpha-power rows for parity. */
41
+ /**
42
+ * Row i of the encode matrix: identity rows for the data shards, Cauchy rows for parity. Cauchy, not Vandermonde, because only Cauchy guarantees EVERY square submatrix is invertible -- identity-plus-Vandermonde has singular K-subsets (found the hard way: parity-based reconstruction produced wrong bytes on some subsets). With parity row j carrying c_i = 1/(x_j XOR y_i) where x_j = row and y_i = column (all row/column values distinct across the whole matrix, no parity row index equals any data column index, so no denominator is zero), any K surviving rows reduce -- expanding along the identity rows -- to a square Cauchy submatrix, always invertible.
43
+ */
44
+ function encodeMatrixRow(row, dataShards) {
45
+ const coefficients = Array.from({ length: dataShards }, () => 0);
46
+ if (row < dataShards) {
47
+ coefficients[row] = 1;
48
+ return coefficients;
49
+ }
50
+ for (let column = 0; column < dataShards; column += 1) coefficients[column] = gfInv(row ^ column);
51
+ return coefficients;
52
+ }
53
+ /** Inverts a square matrix over GF(2^8) by Gauss-Jordan with an identity augmentation. */
54
+ function invertMatrix(matrix) {
55
+ const size = matrix.length;
56
+ const work = matrix.map((row, rowIndex) => {
57
+ const augmented = [...row, ...Array.from({ length: size }, () => 0)];
58
+ augmented[size + rowIndex] = 1;
59
+ return augmented;
60
+ });
61
+ for (let column = 0; column < size; column += 1) {
62
+ let pivotRow = -1;
63
+ for (let candidate = column; candidate < size; candidate += 1) if ((work[candidate]?.[column] ?? 0) !== 0) {
64
+ pivotRow = candidate;
65
+ break;
66
+ }
67
+ if (pivotRow === -1) throw new Error("matrix is singular over GF(2^8)");
68
+ if (pivotRow !== column) {
69
+ const swap = work[column] ?? [];
70
+ work[column] = work[pivotRow] ?? [];
71
+ work[pivotRow] = swap;
72
+ }
73
+ const pivotRowValues = work[column];
74
+ if (pivotRowValues === void 0) throw new Error("internal: pivot row vanished");
75
+ const pivotInverse = gfInv(pivotRowValues[column] ?? 1);
76
+ for (let c = 0; c < 2 * size; c += 1) pivotRowValues[c] = gfMul(pivotRowValues[c] ?? 0, pivotInverse);
77
+ for (let other = 0; other < size; other += 1) {
78
+ if (other === column) continue;
79
+ const factor = work[other]?.[column] ?? 0;
80
+ if (factor === 0) continue;
81
+ const target = work[other];
82
+ const source = work[column];
83
+ if (target === void 0 || source === void 0) throw new Error("internal: row vanished mid-inversion");
84
+ for (let c = 0; c < 2 * size; c += 1) target[c] = (target[c] ?? 0) ^ gfMul(factor, source[c] ?? 0);
85
+ }
86
+ }
87
+ return work.map((row) => row.slice(size));
88
+ }
89
+ async function encodeShards(data, config) {
90
+ validateConfig(config);
91
+ const { dataShards, totalShards } = config;
92
+ const shardLength = Math.ceil(data.length / dataShards);
93
+ const shards = [];
94
+ for (let i = 0; i < totalShards; i += 1) shards.push(new Uint8Array(new ArrayBuffer(shardLength)));
95
+ for (let i = 0; i < dataShards; i += 1) shards[i]?.set(data.subarray(i * shardLength, (i + 1) * shardLength));
96
+ for (let parityIndex = dataShards; parityIndex < totalShards; parityIndex += 1) {
97
+ const coefficients = encodeMatrixRow(parityIndex, dataShards);
98
+ for (let position = 0; position < shardLength; position += 1) {
99
+ let accumulated = 0;
100
+ for (let column = 0; column < dataShards; column += 1) accumulated ^= gfMul(coefficients[column] ?? 0, shards[column]?.[position] ?? 0);
101
+ const parityTarget = shards[parityIndex];
102
+ if (parityTarget !== void 0) parityTarget[position] = accumulated;
103
+ }
104
+ }
105
+ return Promise.resolve(shards);
106
+ }
107
+ async function decodeShards(presented, config, originalLength) {
108
+ validateConfig(config);
109
+ const { dataShards, totalShards } = config;
110
+ for (const entry of presented) if (entry.index < 0 || entry.index >= totalShards) throw new Error(`shard index ${String(entry.index)} is outside 0..${String(totalShards - 1)}`);
111
+ if (presented.length < dataShards) throw new Error(`need at least ${String(dataShards)} shards to reconstruct, got ${String(presented.length)}`);
112
+ const chosen = presented.slice(0, dataShards);
113
+ const firstChosen = chosen[0];
114
+ if (firstChosen === void 0) throw new Error("internal: chosen shard set is empty");
115
+ const shardLength = firstChosen.data.length;
116
+ const recovered = [];
117
+ if (chosen.every((entry) => entry.index < dataShards)) for (let dataIndex = 0; dataIndex < dataShards; dataIndex += 1) {
118
+ const entry = chosen.find((candidate) => candidate.index === dataIndex);
119
+ if (entry === void 0) throw new Error("internal: data shard missing after the all-present check");
120
+ recovered.push(entry.data);
121
+ }
122
+ else {
123
+ const inverse = invertMatrix(chosen.map((entry) => encodeMatrixRow(entry.index, dataShards)));
124
+ for (let dataIndex = 0; dataIndex < dataShards; dataIndex += 1) {
125
+ const row = new Uint8Array(shardLength);
126
+ for (let chosenIndex = 0; chosenIndex < dataShards; chosenIndex += 1) {
127
+ const coefficient = inverse[dataIndex]?.[chosenIndex] ?? 0;
128
+ if (coefficient === 0) continue;
129
+ for (let position = 0; position < shardLength; position += 1) row[position] = (row[position] ?? 0) ^ gfMul(coefficient, chosen[chosenIndex]?.data[position] ?? 0);
130
+ }
131
+ recovered.push(row);
132
+ }
133
+ }
134
+ const totalLength = dataShards * shardLength;
135
+ if (originalLength > totalLength) throw new Error(`originalLength ${String(originalLength)} exceeds the shards' capacity ${String(totalLength)} -- wrong manifest for these shards`);
136
+ const stitched = new Uint8Array(new ArrayBuffer(originalLength));
137
+ for (let position = 0; position < originalLength; position += 1) {
138
+ const shardIndex = Math.floor(position / shardLength);
139
+ const within = position % shardLength;
140
+ const byte = recovered[shardIndex]?.[within];
141
+ if (byte === void 0) throw new Error("shard position out of range while stitching");
142
+ stitched[position] = byte;
143
+ }
144
+ return Promise.resolve(stitched);
145
+ }
146
+ //#endregion
147
+ export { decodeShards, encodeShards };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wire-mesh-core",
3
- "version": "1.41.0",
3
+ "version": "1.43.0",
4
4
  "type": "module",
5
5
  "packageManager": "pnpm@12.4.1+sha512.2e81e399d73fe8390dab25e06aa788ab7a5908248d2f5a370f82b481147a6a7a367bf8048f9a6fdb6460f21a66f0542dedb8b94ca2c8723596741920b1656d4c",
6
6
  "repository": {
@@ -110,6 +110,10 @@
110
110
  "import": "./dist/domain/device-id.mjs",
111
111
  "require": "./dist/domain/device-id.cjs"
112
112
  },
113
+ "./domain/erasure-coding": {
114
+ "import": "./dist/domain/erasure-coding.mjs",
115
+ "require": "./dist/domain/erasure-coding.cjs"
116
+ },
113
117
  "./domain/grant-candidates": {
114
118
  "import": "./dist/domain/grant-candidates.mjs",
115
119
  "require": "./dist/domain/grant-candidates.cjs"