Security Disclosure: Silent Recovery of Wrong Secret With Fewer Than Threshold Shares
Affected projects: bitaps-com/jsbtc, bitaps-com/pybtc
Affected files:
src/functions/shamir_secret_sharing.js — S.__restore_secret
pybtc/functions/shamir.py — restore_secret
Date: 2026-08-20
Bounty Category: 0.1 BTC — "Any bug in the implementation of the presented secret sharing scheme that can lead to loss of access and the inability to recover the original mnemonic phrase."
Summary
The restore_secret / __restore_secret function performs Lagrange interpolation over whatever shares are supplied and returns the result unconditionally. If the caller supplies fewer than threshold shares, the function silently returns a wrong secret with no error and no way to detect it.
For a wallet-recovery use case, this is a serious reliability and security defect — a user may believe they have recovered their seed phrase when they have not, and proceed to import an incorrect mnemonic into a wallet.
Root Cause
The reconstruction function does not verify:
- That at least
threshold shares are provided
- That the supplied shares are consistent with each other
- That the reconstructed secret matches any integrity check
In shamir_secret_sharing.js:
S.__restore_secret = (shares) => {
// ... only validates index range and buffer lengths ...
for (let i = 0; i < shareLength; i++) {
let points = [];
for (let z in shares) {
z = parseInt(z);
points.push([z, shares[z][i]]);
}
secret = BC([secret, BF([S.__shamirInterpolation(points)])]);
}
return secret; // Returns unconditionally — no threshold check!
};
With t = 3 (degree-2 polynomial) and only 2 shares supplied, Lagrange interpolation fits a degree-1 polynomial (a straight line) instead of the correct degree-2 polynomial. The result is a plausible-looking but completely incorrect 16-byte "secret."
Proof of Concept
Using the official Bitaps challenge shares published at https://bitaps.com/mnemonic/challenge:
Share 1 (x=3): session cigar grape merry useful churn fatal thought very any arm unaware
Share 2 (x=15): clock fresh security field caution effort gorilla speed plastic common tomato echo
When only these 2 shares are entered into the Bitaps mnemonic tool at https://bitaps.com/mnemonic and "Restore" is clicked, the tool silently produces an output mnemonic:
Incorrect "recovered" output: right budget hire coyote frog rebel race slush treat scissors case man
This output appears to be a valid share (valid BIP39 format, valid index x=6 encoded in checksum bits). A user could easily mistake this for a genuine third share, or combine all three (2 real + 1 fake) and get yet another wrong mnemonic:
Incorrect "recovered" mnemonic from 2 real + 1 fake: safe sponsor devote regular excuse shell grass ginger vivid series panda choice
This derives to address bc1qj99r00urws783y7cz53c552fzw8yfacnxmy3ca, not the challenge target bc1qyjwa0tf0en4x09magpuwmt2smpsrlaxwn85lh6.
No error is raised at any point. The user has no way to distinguish this wrong result from a correct recovery — except by noticing the funds aren't there.
Impact Scenario
User splits their 12-word seed with t = 3, n = 5
User loses one share and attempts recovery with only 2
restore_secret returns a plausible-looking 12-word mnemonic without error
User imports the wrong mnemonic into a wallet
Result: User believes they've recovered their wallet. They haven't. Funds remain locked behind the correct seed. Worse, the user may send new funds to addresses derived from the wrong mnemonic, causing irrecoverable loss.
This matches the bounty's stated category exactly: "Any bug in the implementation of the presented secret sharing scheme that can lead to loss of access and the inability to recover the original mnemonic phrase."
Recommended Fix
Add threshold verification and/or integrity checking:
Option A — Require threshold parameter (simplest):
python
Run
def restore_secret(shares, threshold):
if len(shares) < threshold:
raise ValueError(f"Need at least {threshold} shares, got {len(shares)}")
# ... rest of function ...
Option B — Embed a digest of the secret (strongest, similar to SLIP-39):
python
Run
def split_secret(threshold, total, secret):
digest = hashlib.sha256(secret).digest()[:4]
extended = secret + digest
shares = _split_secret_raw(threshold, total, extended)
return shares
def restore_secret(shares, threshold):
if len(shares) < threshold:
raise ValueError(f"Need at least {threshold} shares")
extended = _restore_secret_raw(shares)
secret, digest = extended[:-4], extended[-4:]
if hashlib.sha256(secret).digest()[:4] != digest:
raise ValueError("Share set inconsistent — wrong shares or insufficient threshold")
return secret
Reward Address
Please send the 0.1 BTC bug bounty to:
bc1q4szfp7e44rvvedyvxvypuwhtrch68zg0gcsf9j
Additional Notes
This issue is related to but distinct from Issue #23 and Issue #63. Issue #63 Finding 2 correctly identifies the absence of share integrity verification, but this report provides:
A concrete, reproducible PoC using the live challenge data
A clear demonstration of the user-facing impact
Specific fix recommendations
The vulnerability has been independently verified through both the live web tool at bitaps.com/mnemonic and direct analysis of the source code.
plaintext
Security Disclosure: Silent Recovery of Wrong Secret With Fewer Than Threshold Shares
Affected projects:
bitaps-com/jsbtc,bitaps-com/pybtcAffected files:
src/functions/shamir_secret_sharing.js—S.__restore_secretpybtc/functions/shamir.py—restore_secretDate: 2026-08-20
Bounty Category: 0.1 BTC — "Any bug in the implementation of the presented secret sharing scheme that can lead to loss of access and the inability to recover the original mnemonic phrase."
Summary
The
restore_secret/__restore_secretfunction performs Lagrange interpolation over whatever shares are supplied and returns the result unconditionally. If the caller supplies fewer thanthresholdshares, the function silently returns a wrong secret with no error and no way to detect it.For a wallet-recovery use case, this is a serious reliability and security defect — a user may believe they have recovered their seed phrase when they have not, and proceed to import an incorrect mnemonic into a wallet.
Root Cause
The reconstruction function does not verify:
thresholdshares are providedIn
shamir_secret_sharing.js: