This analysis examines the bitaps implementation of Shamir Secret Sharing found in both their JavaScript (jsbtc) and Python (pybtc) libraries. The implementation contains several critical vulnerabilities that deviate from cryptographically secure Shamir Secret Sharing practices.
Location: Python implementation /tmp/shamir_py.py lines 117-125
Issue: The polynomial coefficients are NOT required to be unique. The code only ensures uniqueness in the JavaScript implementation.
// Lines 128-137 in shamir_js.js
for (let i = 0; i < threshold - 1; i++) {
do {
if (ePointer >= e.length) {
ePointer = 0;
e = S.generateEntropy({hex:false});
}
w = e[ePointer++];
} while (q.includes(w)); // <-- ENSURES UNIQUENESS
q.push(w);
}# Lines 117-125 in shamir_py.py
for i in range(threshold - 1):
if e_i < len(e):
a = e[e_i]
e_i += 1
else:
e = generate_entropy(hex=False)
a = e[0]
e_i = 1
q.append(a) # <-- NO UNIQUENESS CHECK!Impact: If polynomial coefficients repeat, the effective polynomial degree is reduced. This means fewer shares may be required to recover the secret than intended.
Example Attack: If a 3-of-5 scheme has duplicate coefficients, it might effectively become a 2-of-5 scheme, breaking the threshold security.
Location: Python implementation lines 105-110
while len(shares) != total:
q = random.SystemRandom().randint(1, index_max)
if q in shares:
continue
shares_indexes.append(q)
shares[q] = b""Issue: Uses Python's random.SystemRandom() which is OS-dependent. While this uses /dev/urandom on Unix systems (which is acceptable), the implementation differs from the JavaScript version which uses a custom entropy generator.
// Lines 102-118
do {
if (ePointer >= e.length) {
e = S.generateEntropy({hex:false});
ePointer = 0;
}
index = e[ePointer] & index_mask;
if ((shares[index] === undefined)&&(index !== 0)) {
i++;
shares[index] = BF([]);
sharesIndexes.push(index)
}
ePointer++;
} while (i !== total);Concern: The JavaScript version uses masking which creates a bias toward lower indices when index_mask is not 255. For example, with 3-bit indices (mask=7), some indices may be sampled more frequently than others from the entropy pool.
Location: Both implementations exclude index 0
JavaScript: Line 111 if ((shares[index] === undefined)&&(index !== 0))
Python: Line 106 q = random.SystemRandom().randint(1, index_max)
Why This Matters:
- Index 0 is explicitly excluded from share generation
- When evaluating polynomial at x=0 during interpolation, this returns the secret directly
- This is correct behavior, but worth noting as implementation detail
Verification:
S.__shamirFn = (x, q) => {
let r = 0;
for (let a of q) r = S.__GF256_add(r, S.__GF256_mul(a, S.__GF256_pow(x, q.indexOf(a))));
return r;
};When x=0: GF256_pow(0, i) returns 0 for all i>0, so only q[0] (the secret) is returned.
Critical Finding: The JavaScript implementation uses a MODIFIED Lagrange interpolation formula.
# Lines 79-81
a = _gf256_sub(x, points[m][0]) # numerator: (x - x_m)
b = _gf256_sub(points[j][0], points[m][0]) # denominator: (x_j - x_m)
c = _gf256_div(a, b)// Lines 74-78 (with commented out standard version)
// let a = S.__GF256_sub(x, points[m][0]); // STANDARD (commented out)
let a = points[m][0]; // MODIFIED
// let b = S.__GF256_sub(points[j][0], points[m][0]); // STANDARD (commented out)
let b = S.__GF256_add(points[j][0], points[m][0]); // MODIFIED (XOR same as sub, but using add)
let c = S.__GF256_div(a, b);What Changed:
- Numerator:
(x - x_m)→x_m(assumes x=0) - Denominator:
(x_j - x_m)→(x_j XOR x_m)(equivalent in GF256 since add=sub=XOR)
Why This Works: The JavaScript version is hardcoded for x=0 evaluation only, which is acceptable since we always want to recover the secret at f(0). However, this means:
- The Python version is more flexible (can interpolate at any x)
- The JavaScript version is optimized but less general
- Both should produce identical results when recovering secrets
Potential Issue: This hardcoded optimization could mask implementation bugs if anyone tries to evaluate at x≠0.
Observation: Neither implementation includes:
- Share integrity checks
- Checksums to detect corruption
- Authentication to detect tampering
- Share metadata (threshold value, share index validation)
Impact:
- Corrupted shares will produce garbage output without error
- No way to detect if shares have been tampered with
- No protection against share substitution attacks
- Threshold must be tracked separately by the user
S.generateEntropy = (A = {}) => {
ARGS(A, {strength: 256, hex: true, sec256k1Order:true});
let b = S.Buffer.alloc(32);
let attempt = 0, p, f;
do {
f = true;
if (attempt++ > 100) throw new Error('Generate randomness failed');
S.getRandomValues(b);
if (A.sec256k1Order) {
p = new BN(b);
if ((p.gte(S.ECDSA_SEC256K1_ORDER))) continue;
}
try { S.randomnessTest(b); } catch (e) { f = false; }
}
while (!f);
b = b.slice(0, A.strength / 8);
return A.hex ? b.hex() : b;
};Concerns:
- Uses NIST SP 800-22 randomness tests (Monobit, Runs, Longest Run)
- May reject valid entropy if it fails statistical tests
- Could theoretically introduce bias by rejecting certain entropy patterns
- Maximum 100 attempts before failure
Analysis: While statistical tests seem prudent, they can introduce subtle biases. True random data can occasionally fail these tests. The rejection sampling could theoretically be exploited if an attacker can influence the RNG state.
from pybtc.functions.entropy import generate_entropyIssue: Cannot analyze the Python entropy generation without seeing the imported module. This is a black box.
Scenario: Target Python implementation where coefficients can repeat.
Method:
- Generate many share sets using Python implementation
- Analyze shares to detect patterns indicating reduced polynomial degree
- Attempt recovery with fewer shares than threshold
Probability: Depends on entropy and secret size. For a 3-of-5 scheme with 256 possible coefficient values:
- Probability of at least one duplicate in 2 coefficients: ~1/256 per byte
- For 32-byte secret: Expected ~12.5% of secrets have at least one byte with duplicate coefficients
Scenario: If JavaScript implementation generates biased indices, statistical analysis of many share sets could reveal patterns.
Method:
- Collect large number of share sets
- Analyze index distribution
- If bias exists, prioritize attacking shares with predictable indices
Likelihood: Low - the entropy generator uses strong randomness, but the masking operation could introduce measurable bias.
Scenario: JavaScript implementation has a retry loop that could have timing variations.
Method:
- Measure time to generate shares
- Longer generation times indicate more retries (duplicate coefficients)
- Target slower-generated shares as they may have patterns
Likelihood: Low - would require local execution access.
Scenario: Modify shares and see if recovery fails gracefully or leaks information.
Method:
- Corrupt one or more shares
- Attempt recovery with corrupted + valid shares
- Analyze error messages and partial recovery attempts
Impact: Could potentially leak information about valid shares through error analysis.
Scenario: The JavaScript entropy generator rejects "non-random" looking data.
Method:
- If attacker can influence RNG state (e.g., through VM manipulation)
- Force generation of entropy that passes tests but has reduced entropy
- Reduces search space for polynomial coefficients
Likelihood: Very low - requires deep system compromise.
- Random polynomial of degree (threshold - 1)
- Coefficients are unique and uniformly random
- Secret is the constant term (coefficient a₀)
- Shares are evaluations at random non-zero points
- Often includes metadata and checksums
- ✓ Correct polynomial construction in JavaScript
- ✗ Python allows duplicate coefficients (CRITICAL VULNERABILITY)
- ✓ Secret is constant term
- ✓ Correct GF(256) arithmetic
- ✗ No metadata or integrity checks
- ~ JavaScript uses hardcoded x=0 interpolation (optimization, not vulnerability)
- ~ Index generation bias potential in JavaScript
Share Structure: shares[index] = bytes([polynomial_evaluation])
Each share reveals:
- Share Index: The x-coordinate (1-255)
- Share Data: The y-coordinates (evaluations)
- Share Length: Equal to secret length
What Information is Hidden?
- Threshold: Not embedded in shares
- Total Shares: Not embedded in shares
- Secret Length: Revealed by share length
- Polynomial Coefficients: Cannot be recovered without threshold shares
- Length Leakage: Share length = secret length (reveals secret size)
- No Randomization: Each byte processed independently with same indices
- Pattern Analysis: If secret has repeating bytes, shares will have correlated patterns
- Statistical Analysis: Shares from same polynomial set share statistical properties
# Theoretical attack on Python implementation
from collections import Counter
def detect_weak_shares(shares, threshold):
"""
Attempt to detect if polynomial has duplicate coefficients
by analyzing share patterns
"""
# Collect all share values for each byte position
for byte_pos in range(len(next(iter(shares.values())))):
values = [shares[idx][byte_pos] for idx in shares]
# Try all combinations of (threshold-1) shares
# If we can recover secret with fewer shares than threshold,
# it indicates duplicate coefficients
for combo_size in range(2, threshold):
# Try combinations...
pass// Measure index distribution bias
const index_counts = {};
for (let i = 0; i < 10000; i++) {
const shares = split_secret(3, 5, Buffer.from("test"), 3); // 3-bit indices
for (let idx in shares) {
index_counts[idx] = (index_counts[idx] || 0) + 1;
}
}
// Analyze distribution - should be uniform- Avoid Python Implementation: The duplicate coefficient vulnerability is critical
- Use JavaScript Implementation: More secure, but still has limitations
- Add External Integrity Checks: Implement your own checksums
- Store Threshold Separately: No way to recover this from shares
- Test Share Recovery: Always verify shares can recover secret before distribution
- Consider Alternatives: Look at SLIP39 or other standardized implementations
- Fix Python Implementation: Add uniqueness check for coefficients
- Add Checksums: Include integrity verification in share format
- Embed Metadata: Include threshold and version information
- Standardize Implementations: Make Python and JavaScript identical
- Add Share Validation: Verify shares during recovery
- Audit Entropy Generation: Review statistical testing approach
- Document Deviations: Clearly document differences from standard SSS
The bitaps Shamir Secret Sharing implementation has one critical vulnerability in the Python version (duplicate coefficients) and several medium-severity concerns around entropy generation, lack of integrity checks, and implementation inconsistencies.
The JavaScript implementation is relatively secure for its core functionality but lacks important features like checksums and metadata. The Python implementation should not be used in production until the duplicate coefficient issue is fixed.
For critical applications, consider using battle-tested implementations like SLIP39 or libgfshare that include proper integrity checks and standardized formats.
-
JavaScript:
/tmp/shamir_js.js(169 lines)- Source:
https://github.com/bitaps-com/jsbtc/blob/master/src/functions/shamir_secret_sharing.js
- Source:
-
Python:
/tmp/shamir_py.py(146 lines)- Source:
https://github.com/bitaps-com/pybtc/blob/master/pybtc/functions/shamir.py
- Source:
-
BIP39 Mnemonic:
/tmp/bip39_mnemonic.js- Source:
https://github.com/bitaps-com/jsbtc/blob/master/src/functions/bip39_mnemonic.js
- Source:
Analysis Date: 2025-11-03 Analyzed By: Claude (Sonnet 4.5)