-
Notifications
You must be signed in to change notification settings - Fork 32
Description
- Incorrect Power Indexing in __shamirFn
Problem:
for (let a of q) r = S.__GF256_add(r, S.__GF256_mul(a, S.__GF256_pow(x, q.indexOf(a))));
q.indexOf(a) is not safe in case of duplicate values — it returns the first index.
Polynomial evaluation should use actual index position, not indexOf.
✅ Fix:
for (let i = 0; i < q.length; i++) {
r = S.__GF256_add(r, S.__GF256_mul(q[i], S.__GF256_pow(x, i)));
}
- Broken Lagrange Interpolation Denominator
Problem in __shamirInterpolation:
let a = points[m][0];
let b = S.__GF256_add(points[j][0], points[m][0]); // <-- incorrect
let c = S.__GF256_div(a, b);
This deviates from the standard Lagrange interpolation, where:
L_j(x) = \prod_{m \ne j} \frac{x - x_m}{x_j - x_m}
✅ Fix:
let numerator = S.__GF256_sub(0, points[m][0]); // x = 0 for secret recovery
let denominator = S.__GF256_sub(points[j][0], points[m][0]);
let fraction = S.__GF256_div(numerator, denominator);
p_j_x = S.__GF256_mul(p_j_x, fraction);
- Random Coefficients May Include Duplicates
Problem:
do {
w = e[ePointer++];
} while (q.includes(w));
You're rejecting duplicate coefficients unnecessarily. Shamir's scheme allows duplicate y-values, and it's unlikely to matter for securely chosen randomness.
This can also reduce entropy or cause rejection loops.
✅ Optional Fix:
Remove this rejection unless you have a specific reason to avoid duplicates in coefficients.
- Ambiguous Bit Mask in Share Indexing
Problem:
let index_mask = 2 ** indexBits - 1;
You're using indexBits=8 by default, which is fine, but if someone uses fewer bits, the randomness space shrinks.
Might be okay depending on use-case, but it's worth noting for security guarantees.
✅ Summary Fixes (Required)
Fix Description
✅ __shamirFn Use loop index, not indexOf()
✅ __shamirInterpolation Use sub, not add, for Lagrange denominator
Input:
threshold: minimum number of shares needed to reconstruct.
total: total number of shares to generate.
secret: the secret split byte-by-byte.
indexBits: how many bits to use for the x-coordinate (defaults to 8).
It generates total number of shares with unique non-zero x-coordinates.
For each byte of the secret:
Constructs a random polynomial of degree threshold - 1, with secret byte as the constant term.
Evaluates this polynomial at all chosen x-values to produce shares.
- Invalid index selection due to indexBits logic
let index_mask = 2**indexBits - 1;
if (total > index_mask) throw new Error("index bits is to low");
Should be:
if (total > index_mask + 1) throw new Error("index bits too low");
Reason: index_mask is the maximum value, and you skip index 0, so the number of usable indexes is only index_mask.
Fix suggestion:
if (total > index_mask) throw new Error("index bits too low (max usable: " + index_mask + ")");
- index !== 0 check: Why is index 0 skipped?
if ((shares[index] === undefined)&&(index !== 0)) {
Skipping index === 0 may break polynomial interpolation if 0 is a valid input (which it usually is). In classic Shamir's Secret Sharing, x = 0 is often used as the secret.
If you're avoiding x=0 deliberately (e.g., to avoid exposing the secret directly), document this explicitly.
However, this introduces bias and reduces the available index pool.
✅ Skipping x=0 is valid if and only if your Shamir reconstruction algorithm accounts for that and expects only x > 0.
- q.includes(w) check reduces entropy
do {
...
} while (q.includes(w));
This ensures coefficients are unique, but in standard SSS, polynomial coefficients do not need to be unique.
You're reducing entropy unnecessarily by rejecting duplicates.
Fix suggestion: Remove the q.includes(w) check for more entropy and better randomness.
- Ambiguous / unclear functions:
You use:
BF([])
BC([...])
S.__shamirFn(i, q)
These are likely:
BF = byte field / finite field byte conversion
BC = byte concatenation?
S.__shamirFn(x, coeffs) = evaluate polynomial
If these are not secure finite field arithmetic functions over GF(256), then your SSS implementation may break or become insecure.
Ensure: All arithmetic is done in the correct finite field (GF(256)), not modulo 256 unless using proper irreducible polynomial.
- Re-using entropy multiple times
You're generating entropy once and consuming it gradually:
e = S.generateEntropy({hex:false});
...
if (ePointer >= e.length) {
e = S.generateEntropy({hex:false});
ePointer = 0;
}
This is fine if S.generateEntropy gives truly random, cryptographically secure bytes. Just be aware of overusing small entropy pools.
✅ Use a cryptographically secure RNG and re-seed adequately.
- Returned structure is unclear
You return:
return shares;
But shares is an object where each key is the x-coordinate, and value is... a buffer?
This makes sense, but you may want to return it as:
return sharesIndexes.map(i => ({ x: i, y: shares[i] }));
For better serialization and reconstruction clarity.
🧠 Suggested Improvements
Add type validation for inputs.
Consider not skipping index 0 (or explicitly document why you are).
Don't enforce uniqueness of polynomial coefficients.
Clarify or rename BF and BC functions.
Return shares as { x, y } tuples, not an object map.
Add test vectors (e.g., compare with known SSS implementations).
✅ Example of improvement
if (threshold > 255 || total > 255)
throw new Error("threshold and total must be <= 255");
if (threshold > total)
throw new Error("threshold cannot exceed total");
const index_mask = 2 ** indexBits - 1;
if (total > index_mask)
throw new Error("indexBits too low to generate unique indices");
const shares = {};
const shareIndices = [];
let entropy = S.generateEntropy({ hex: false });
let ePointer = 0;
while (shareIndices.length < total) {
if (ePointer >= entropy.length) {
entropy = S.generateEntropy({ hex: false });
ePointer = 0;
}
const index = entropy[ePointer++] & index_mask;
if (index !== 0 && !(index in shares)) {
shares[index] = BF([]);
shareIndices.push(index);
}
}
entropy = S.generateEntropy({ hex: false });
ePointer = 0;
for (let b = 0; b < secret.length; b++) {
const coeffs = [secret[b]];
for (let j = 1; j < threshold; j++) {
if (ePointer >= entropy.length) {
entropy = S.generateEntropy({ hex: false });
ePointer = 0;
}
coeffs.push(entropy[ePointer++]); // allow duplicates
}
for (const i of shareIndices) {
const y = S.__shamirFn(i, coeffs);
shares[i] = BC([shares[i], BF([y])]);
}
}
return shareIndices.map(i => ({ x: i, y: shares[i] }));
Btc address is bc1qtvxzxt2pq20xd27pyarjhwv2ve8pl93e06qjy4