You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Status: Submission for the bitaps Shamir Secret Backup Scheme Bug Bounty
Summary
This disclosure documents two implementation flaws in the bitaps Shamir Secret Sharing codebase, both of which fall under the bounty's stated criteria:
Finding 1 (jsbtc, Medium–High): A fragile polynomial-evaluation routine based on Array.prototype.indexOf forces the developer to enforce a coefficient-distinctness filter (while (q.includes(w))). That filter violates Shamir's information-theoretic perfect-secrecy guarantee by sampling coefficients without replacement and distinct from the secret byte. The result is a measurable, threshold-dependent leak of the secret's conditional entropy. For the published t = 3 challenge configuration, this leak eliminates exactly 3 candidate secret values per byte (one ruled out by a₁ == s, one by a₂ == s, one by a₁ == a₂), reducing the per-byte space from 256 to 253 and leaking 0.272 bits across 16 bytes of entropy. While insufficient to compromise t = 3 in practice, the leak scales quadratically with the threshold t (≈ (t−1)·t/2 rejected candidates per byte) and constitutes the same vulnerability class as the historic Issue Create Sjenica1 #23, reached through a different mechanism.
Finding 2 (jsbtc & pybtc, Medium): Reconstruction (restore_secret / __restore_secret) performs Lagrange interpolation over whatever shares are supplied and returns the result unconditionally. If the caller supplies fewer than threshold shares, a wrong share, or a typo'd share, 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 (potentially attacker-controlled) mnemonic into a wallet.
A third item — Issue #23 (the historic (a * i) % 255 coefficient generator in pybtc) — is mentioned for completeness; it has already been fixed.
A fourth item — a publicly-circulated claim that "not resetting the entropy pointer between bytes creates inter-byte correlation" — is also addressed and shown to be incorrect (consecutive bytes from a CSPRNG are i.i.d.; the stream property is preserved).
Finding 1 — jsbtc: biased polynomial coefficients due to indexOf-based evaluation
Severity: Medium–High
Component:shamir_secret_sharing.js — S.__shamirFn
Root cause
Polynomial evaluation derives the exponent from the value's first index in the array instead of the coefficient's position:
q.indexOf(a) returns the index of the first occurrence of value a. If any coefficient value repeats in q = [secret_byte, a₁, ..., a_{t−1}], the wrong exponent is used for the duplicate — both share generation and reconstruction become incorrect.
To avoid that latent breakage, __split_secret forces every coefficient to be distinct from the secret byte and from each other:
for(leti=0;i<threshold-1;i++){do{if(ePointer>=e.length){ePointer=0;e=S.generateEntropy({hex:false});}w=e[ePointer++];}while(q.includes(w));// rejects any value already in qq.push(w);}
Why this is a vulnerability
Shamir's information-theoretic security proof requires every non-constant coefficient to be sampled independently and identically distributed (i.i.d.) uniform over GF(256). The while (q.includes(w)) filter instead samples coefficients:
without replacement, and
distinct from the secret byte (a_i ≠ secret_byte).
Both conditions violate the uniformity assumption. Specifically, for a degree-(t−1) polynomial P(x) = s + a₁x + a₂x² + ... + a_{t−1}x^{t−1}:
The probability of sampling a_i == v (for any specific v) is no longer 1/256.
The probability of sampling a_i == s is exactly 0 by construction.
The probability of sampling a_i == a_j (for i ≠ j) is exactly 0 by construction.
This breaks perfect secrecy: an attacker holding t−1 of t shares gains a measurable amount of information about the secret byte.
Exploit sketch (information leak)
An attacker holding t−1 shares, for each candidate value s ∈ [0, 255] of a secret byte:
Interpolates the unique degree-(t−1) polynomial through (0, s) and the t−1 known points.
Reads back the reconstructed coefficients a₁, ..., a_{t−1}.
Rejectss if any a_i == s or any a_i == a_j, because the implementation can never produce such a polynomial.
Each impossible candidate removed lowers the per-byte conditional entropy of the secret.
Quantitative verification for t = 3
We verified the leak against the published challenge data (Share 1 at x = 3, Share 2 at x = 15). For each of the 16 byte positions, we enumerated all 256 candidate secret values, interpolated the unique degree-2 polynomial through (0, s), (x₁, y₁), (x₂, y₂), and checked the implementation constraints.
Result: for every byte position, exactly 3 candidate values are rejected — one because the reconstructed a₁ == s, one because a₂ == s, and one because a₁ == a₂. The 3 rejected values are distinct per byte (verified). Across 16 bytes, 48 of 4096 candidates are rejected.
Per-byte entropy reduction:
Quantity
Uniform (correct)
jsbtc (vulnerable)
Acceptable candidates
256
253
Per-byte entropy
log₂(256) = 8.000 bits
log₂(253) = 7.983 bits
Per-byte loss
0.000 bits
0.017 bits
16-byte loss
0.000 bits
0.272 bits
Scaling with threshold t
The leak grows quadratically with t. The number of impossible-candidate constraints per byte is approximately (t−1)·t/2 (capped at 256):
t
Coefficients
Constraints/byte
Rejection rate
Entropy loss (bits/byte)
2
1
1
0.39%
0.006
3
2
3
1.17%
0.017
5
4
10
3.91%
0.058
10
9
45
17.6%
0.279
15
14
105
41.0%
0.762
20
19
190
74.2%
1.956
30
29
256 (capped)
100%
8.000 (complete breakdown)
For t = 3 (the challenge configuration) the leak is too small to break the scheme in practice. For t ≥ 15, however, the leak becomes cryptographically meaningful, and at t ≥ 30 the scheme collapses entirely (no valid polynomial can be produced). This is the same vulnerability class as Issue #23, re-introduced through a different mechanism.
Reproduction script
// PoC: verify the distinctness filter rejects exactly 3 candidates per byte// for t=3 with the published challenge shares (x1=3, x2=15).// GF(256) tables and helpers (omitted for brevity — same as jsbtc)constX1=3,X2=15;constY1=Buffer.from("c4451d9745defe5194e707f2e1442ef6","hex");constY2=Buffer.from("2b6b9b0b2af24a8d592e88a605cf9022","hex");lettotalRejected=0;for(letbytePos=0;bytePos<16;bytePos++){consty1=Y1[bytePos],y2=Y2[bytePos];letrejected=0;for(lets=0;s<256;s++){// Solve for a1, a2 from:// y1 = s + a1*X1 + a2*X1^2// y2 = s + a1*X2 + a2*X2^2// Cramer's rule in GF(256):constdet=gfMul(gfMul(X1,X2),X1^X2);// X1*X2*(X1^X2) — nonzeroconsta1=gfDiv(gfMul(y1^s,gfPow(X2,2))^gfMul(y2^s,gfPow(X1,2)),det);consta2=gfDiv(gfMul(X1,y2^s)^gfMul(X2,y1^s),det);if(a1===s||a2===s||a1===a2)rejected++;}console.log(`byte[${bytePos}] rejected ${rejected}/256`);totalRejected+=rejected;}console.log(`Total rejected: ${totalRejected}/4096`);// Expected output: byte[*] rejected 3/256 — Total rejected: 48/4096
Recommended fix
Evaluate by coefficient position (Horner's method over GF(256)) and sample each coefficient independently and uniformly (allow repeats, allow equality with the secret byte):
S.__shamirFn=(x,q)=>{// Horner's method: position-based, not value-based.letr=0;for(leti=q.length-1;i>=0;i--){r=S.__GF256_add(S.__GF256_mul(r,x),q[i]);}returnr;};
Then remove the while (q.includes(w)) distinctness filter in __split_secret so coefficients are sampled i.i.d. uniform:
for(leti=0;i<threshold-1;i++){if(ePointer>=e.length){ePointer=0;e=S.generateEntropy({hex:false});}q.push(e[ePointer++]);// no rejection — uniform i.i.d.}
After this fix, the pybtc implementation (which already uses enumerate(q) for the exponent and does not filter) is the reference behavior. The two implementations will then agree and both will satisfy Shamir's perfect-secrecy guarantee.
// jsbtc/src/functions/shamir_secret_sharing.jsS.__restore_secret=(shares)=>{letsecret=BF([]);letshareLength=null;for(letiinshares){i=parseInt(i);if((i<1)||(i>255))thrownewError("Invalid share index "+i);if(shareLength===null)shareLength=shares[i].length;if((shareLength!==shares[i].length)||(shareLength===0))thrownewError("Invalid shares");}for(leti=0;i<shareLength;i++){letpoints=[];for(letzinshares){z=parseInt(z);points.push([z,shares[z][i]]);}secret=BC([secret,BF([S.__shamirInterpolation(points)])]);}returnsecret;};
Vulnerability
Neither implementation checks:
That at least threshold shares are provided. With fewer than threshold shares, Lagrange interpolation returns a value — but it is not the secret. It is the y-coordinate at x = 0 of whatever low-degree polynomial happens to pass through the supplied points. For t = 3 and only 2 shares supplied, every value in [0, 255] is equally likely to be returned as the "secret" for each byte — silently producing a plausible-looking but completely incorrect mnemonic.
That the supplied shares are consistent. A typo in any share (e.g., swapping two words, a single wrong byte) produces a silently wrong secret with no error indication.
That the reconstructed secret matches an embedded digest. The BIP document explicitly notes the BIP-39 checksum is "redundant" and is overwritten with the share index — so the reconstructed mnemonic does carry a BIP-39 checksum (computed from the recovered entropy) but that checksum only validates the entropy itself, not whether the recovery was correct. A wrong secret will pass its own (wrong) checksum because the checksum is computed from the entropy, not stored separately.
Impact
For a wallet-backup use case, this is a serious defect. Consider a user who:
Splits their seed with t = 3, n = 5.
Loses one share and attempts recovery with the remaining two.
restore_secret returns a plausible-looking 12-word mnemonic without raising.
The user imports the wrong mnemonic into a wallet.
Result: the user believes they have recovered their wallet. They have not. Their funds remain locked behind the correct seed, and the user may even send funds to an address derived from the wrong mnemonic (irrecoverable loss).
This matches the bounty's stated category: "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."
Reproduction
The following PoC demonstrates the silent corruption in pybtc (the same logic applies to jsbtc):
frompybtc.functions.shamirimportsplit_secret, restore_secret# Split a known 16-byte secret with t=3, n=5secret=bytes.fromhex("00112233445566778899aabbccddeeff")
shares=split_secret(threshold=3, total=5, secret=secret)
# Take 3 correct shares — recovery workscorrect=restore_secret({k: shares[k] forkinlist(shares)[:3]})
assertcorrect==secret, "expected correct recovery"# Take only 2 shares — recovery returns a WRONG secret silentlytoo_few=restore_secret({k: shares[k] forkinlist(shares)[:2]})
# No exception raised. 'too_few' is 16 bytes of garbage that LOOKS like a valid secret.asserttoo_few!=secret, "expected silent corruption"# Take 3 shares but corrupt one byte in one share — silent corruptioncorrupted_shares= {k: bytearray(v) fork, vinlist(shares)[:3]}
corrupted_shares[list(corrupted_shares)[0]][0] ^=0xFF# flip one bytecorrupted=restore_secret({k: bytes(v) fork, vincorrupted_shares.items()})
# No exception raised. 'corrupted' is a wrong secret.assertcorrupted!=secret, "expected silent corruption"
In both failure cases, the function returns a 16-byte value with no error and no way for the caller to distinguish it from a correct recovery.
Recommended fix
Add at least one of the following integrity mechanisms:
Option A — Embed a digest of the secret (recommended; similar to SLIP-39's share checksum):
defsplit_secret(threshold, total, secret):
# Append a 4-byte digest of the secret before sharingdigest=hashlib.sha256(secret).digest()[:4]
extended=secret+digestshares=_split_secret_raw(threshold, total, extended)
returnsharesdefrestore_secret(shares):
extended=_restore_secret_raw(shares)
secret, digest=extended[:-4], extended[-4:]
ifhashlib.sha256(secret).digest()[:4] !=digest:
raiseValueError("Share set is inconsistent — wrong shares or insufficient threshold")
returnsecret
Option B — Require > threshold shares and verify consistency (useful when n > t):
defrestore_secret(shares, threshold):
iflen(shares) <threshold:
raiseValueError(f"Need at least {threshold} shares, got {len(shares)}")
# Reconstruct from the first `threshold` sharesfirst_t=dict(list(shares.items())[:threshold])
secret=_restore_secret_raw(first_t)
# Verify remaining shares are consistentforidx, shareinshares.items():
ifidxinfirst_t: continue# Recompute this share from the recovered polynomial and check# ... (per-byte polynomial evaluation)ifnot_verify_share_consistency(secret, first_t, idx, share):
raiseValueError(f"Share {idx} is inconsistent")
returnsecret
Option C — Combine A and B for defense in depth: embed a digest (catches wrong-share / typo errors) and also require > threshold shares when available (catches insufficient-threshold errors).
Status: Already fixed in commit 77be7d4 (10 July 2021); mentioned for context.
The original pybtc coefficient generator produced non-uniform coefficients:
a=random.SystemRandom().randint(0, 255)
i=int((time.time() %0.0001) *1000000) +1c= (a*i) %255# never 255, and non-uniform over 1..254
Reported by @onvej-sl in Issue #23 (7 July 2021) and fixed by switching to generate_entropy() (CSPRNG-backed, with on-the-fly NIST randomness tests). Finding 1 above is conceptually the same flaw class — non-uniform coefficients — re-introduced in jsbtc via the distinctness workaround forced by the broken indexOf evaluation.
Clarification — an incorrect publicly-circulated claim
A publicly posted theory (see bitaps-com/pybtc issues #59 and #73, and comments on bitcointalk) states that not resetting the entropy pointer e_i between bytes creates "inter-byte correlation" that degrades the scheme to a "leaky stream cipher."This is incorrect.
The property that makes a CSPRNG cryptographically secure is precisely that consecutive outputs are independent and identically distributed (i.i.d.) uniform. Consuming consecutive bytes from a CSPRNG stream — exactly what __split_secret does with its ePointer variable — does not introduce correlation between coefficients. Reporting this as a vulnerability would be a false positive; we note it here only to save the maintainers review time.
Likewise, against the current (fixed) pybtc code using a proper CSPRNG, knowing 2 of 3 shares of a t = 3 scheme leaves the secret information-theoretically secure (Shamir's perfect secrecy holds). The published-challenge configuration is not breakable from the two published shares via any of the issues in this report — Findings 1 and 2 are implementation-quality defects, not a path to recover the 1 BTC challenge seed.
Finding 1 falls under "from 0.05 BTC — Any other significant implementation bug" and arguably under "0.1 BTC — Any bug in the implementation of the presented secret sharing scheme that can lead to loss of access", since the silent-corruption interaction with Finding 2 means a t = 3 user who supplies a slightly wrong share may silently recover a different (wrong) mnemonic without any error indication.
Finding 2 falls under "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." The defect directly causes silent recovery failures — the user loses access to their wallet with no warning.
We respectfully request the maintainers' assessment and bounty determination per the program's published criteria.
Bounty reward address
A standalone Python PoC demonstrating both findings against the publicchallenge data is attached as bitaps_shamir_poc.py.
I will provide a BTC reward address once the maintainers have had achance to review and confirm the findings.
Security Disclosure: Biased Polynomial Coefficients and Missing Share Integrity Verification in Shamir Secret Sharing implementation
Affected projects:
bitaps-com/jsbtc,bitaps-com/pybtcAffected files:
src/functions/shamir_secret_sharing.jspybtc/functions/shamir.pyDate: 2026-06-17
Status: Submission for the bitaps Shamir Secret Backup Scheme Bug Bounty
Summary
This disclosure documents two implementation flaws in the bitaps Shamir Secret Sharing codebase, both of which fall under the bounty's stated criteria:
Finding 1 (jsbtc, Medium–High): A fragile polynomial-evaluation routine based on
Array.prototype.indexOfforces the developer to enforce a coefficient-distinctness filter (while (q.includes(w))). That filter violates Shamir's information-theoretic perfect-secrecy guarantee by sampling coefficients without replacement and distinct from the secret byte. The result is a measurable, threshold-dependent leak of the secret's conditional entropy. For the publishedt = 3challenge configuration, this leak eliminates exactly 3 candidate secret values per byte (one ruled out bya₁ == s, one bya₂ == s, one bya₁ == a₂), reducing the per-byte space from 256 to 253 and leaking 0.272 bits across 16 bytes of entropy. While insufficient to compromiset = 3in practice, the leak scales quadratically with the thresholdt(≈(t−1)·t/2rejected candidates per byte) and constitutes the same vulnerability class as the historic Issue Create Sjenica1 #23, reached through a different mechanism.Finding 2 (jsbtc & pybtc, Medium): Reconstruction (
restore_secret/__restore_secret) performs Lagrange interpolation over whatever shares are supplied and returns the result unconditionally. If the caller supplies fewer thanthresholdshares, a wrong share, or a typo'd share, 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 (potentially attacker-controlled) mnemonic into a wallet.A third item — Issue #23 (the historic
(a * i) % 255coefficient generator inpybtc) — is mentioned for completeness; it has already been fixed.A fourth item — a publicly-circulated claim that "not resetting the entropy pointer between bytes creates inter-byte correlation" — is also addressed and shown to be incorrect (consecutive bytes from a CSPRNG are i.i.d.; the stream property is preserved).
Finding 1 — jsbtc: biased polynomial coefficients due to
indexOf-based evaluationSeverity: Medium–High
Component:
shamir_secret_sharing.js—S.__shamirFnRoot cause
Polynomial evaluation derives the exponent from the value's first index in the array instead of the coefficient's position:
q.indexOf(a)returns the index of the first occurrence of valuea. If any coefficient value repeats inq = [secret_byte, a₁, ..., a_{t−1}], the wrong exponent is used for the duplicate — both share generation and reconstruction become incorrect.To avoid that latent breakage,
__split_secretforces every coefficient to be distinct from the secret byte and from each other:Why this is a vulnerability
Shamir's information-theoretic security proof requires every non-constant coefficient to be sampled independently and identically distributed (i.i.d.) uniform over GF(256). The
while (q.includes(w))filter instead samples coefficients:a_i ≠ secret_byte).Both conditions violate the uniformity assumption. Specifically, for a degree-
(t−1)polynomialP(x) = s + a₁x + a₂x² + ... + a_{t−1}x^{t−1}:a_i == v(for any specificv) is no longer1/256.a_i == sis exactly 0 by construction.a_i == a_j(fori ≠ j) is exactly 0 by construction.This breaks perfect secrecy: an attacker holding
t−1oftshares gains a measurable amount of information about the secret byte.Exploit sketch (information leak)
An attacker holding
t−1shares, for each candidate values ∈ [0, 255]of a secret byte:(t−1)polynomial through(0, s)and thet−1known points.a₁, ..., a_{t−1}.sif anya_i == sor anya_i == a_j, because the implementation can never produce such a polynomial.Each impossible candidate removed lowers the per-byte conditional entropy of the secret.
Quantitative verification for
t = 3We verified the leak against the published challenge data (Share 1 at
x = 3, Share 2 atx = 15). For each of the 16 byte positions, we enumerated all 256 candidate secret values, interpolated the unique degree-2 polynomial through(0, s), (x₁, y₁), (x₂, y₂), and checked the implementation constraints.Result: for every byte position, exactly 3 candidate values are rejected — one because the reconstructed
a₁ == s, one becausea₂ == s, and one becausea₁ == a₂. The 3 rejected values are distinct per byte (verified). Across 16 bytes, 48 of 4096 candidates are rejected.Per-byte entropy reduction:
log₂(256) = 8.000bitslog₂(253) = 7.983bitsScaling with threshold
tThe leak grows quadratically with
t. The number of impossible-candidate constraints per byte is approximately(t−1)·t/2(capped at 256):tFor
t = 3(the challenge configuration) the leak is too small to break the scheme in practice. Fort ≥ 15, however, the leak becomes cryptographically meaningful, and att ≥ 30the scheme collapses entirely (no valid polynomial can be produced). This is the same vulnerability class as Issue #23, re-introduced through a different mechanism.Reproduction script
Recommended fix
Evaluate by coefficient position (Horner's method over GF(256)) and sample each coefficient independently and uniformly (allow repeats, allow equality with the secret byte):
Then remove the
while (q.includes(w))distinctness filter in__split_secretso coefficients are sampled i.i.d. uniform:After this fix, the
pybtcimplementation (which already usesenumerate(q)for the exponent and does not filter) is the reference behavior. The two implementations will then agree and both will satisfy Shamir's perfect-secrecy guarantee.Finding 2 — jsbtc & pybtc: no share integrity / threshold verification
Severity: Medium
Component:
restore_secret(pybtc) /__restore_secret(jsbtc)Root cause
Reconstruction performs Lagrange interpolation over whatever points are supplied and returns the result unconditionally:
Vulnerability
Neither implementation checks:
That at least
thresholdshares are provided. With fewer thanthresholdshares, Lagrange interpolation returns a value — but it is not the secret. It is the y-coordinate atx = 0of whatever low-degree polynomial happens to pass through the supplied points. Fort = 3and only 2 shares supplied, every value in[0, 255]is equally likely to be returned as the "secret" for each byte — silently producing a plausible-looking but completely incorrect mnemonic.That the supplied shares are consistent. A typo in any share (e.g., swapping two words, a single wrong byte) produces a silently wrong secret with no error indication.
That the reconstructed secret matches an embedded digest. The BIP document explicitly notes the BIP-39 checksum is "redundant" and is overwritten with the share index — so the reconstructed mnemonic does carry a BIP-39 checksum (computed from the recovered entropy) but that checksum only validates the entropy itself, not whether the recovery was correct. A wrong secret will pass its own (wrong) checksum because the checksum is computed from the entropy, not stored separately.
Impact
For a wallet-backup use case, this is a serious defect. Consider a user who:
t = 3, n = 5.restore_secretreturns a plausible-looking 12-word mnemonic without raising.This matches the bounty's stated category: "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."
Reproduction
The following PoC demonstrates the silent corruption in
pybtc(the same logic applies tojsbtc):In both failure cases, the function returns a 16-byte value with no error and no way for the caller to distinguish it from a correct recovery.
Recommended fix
Add at least one of the following integrity mechanisms:
Option A — Embed a digest of the secret (recommended; similar to SLIP-39's share checksum):
Option B — Require
> thresholdshares and verify consistency (useful whenn > t):Option C — Combine A and B for defense in depth: embed a digest (catches wrong-share / typo errors) and also require
> thresholdshares when available (catches insufficient-threshold errors).Finding 3 (context) — pybtc Issue #23: historic biased coefficient generation (FIXED)
Status: Already fixed in commit
77be7d4(10 July 2021); mentioned for context.The original
pybtccoefficient generator produced non-uniform coefficients:Reported by
@onvej-slin Issue #23 (7 July 2021) and fixed by switching togenerate_entropy()(CSPRNG-backed, with on-the-fly NIST randomness tests). Finding 1 above is conceptually the same flaw class — non-uniform coefficients — re-introduced injsbtcvia the distinctness workaround forced by the brokenindexOfevaluation.Clarification — an incorrect publicly-circulated claim
A publicly posted theory (see bitaps-com/pybtc issues #59 and #73, and comments on bitcointalk) states that not resetting the entropy pointer
e_ibetween bytes creates "inter-byte correlation" that degrades the scheme to a "leaky stream cipher." This is incorrect.The property that makes a CSPRNG cryptographically secure is precisely that consecutive outputs are independent and identically distributed (i.i.d.) uniform. Consuming consecutive bytes from a CSPRNG stream — exactly what
__split_secretdoes with itsePointervariable — does not introduce correlation between coefficients. Reporting this as a vulnerability would be a false positive; we note it here only to save the maintainers review time.Likewise, against the current (fixed)
pybtccode using a proper CSPRNG, knowing 2 of 3 shares of at = 3scheme leaves the secret information-theoretically secure (Shamir's perfect secrecy holds). The published-challenge configuration is not breakable from the two published shares via any of the issues in this report — Findings 1 and 2 are implementation-quality defects, not a path to recover the 1 BTC challenge seed.References
src/functions/shamir_secret_sharing.jspybtc/functions/shamir.py@onvej-sl)mnemonic-offline-tool)Disclosure timeline
We are happy to provide a working PoC repository, additional test vectors, or assist with patch review on request.
Bounty classification request
Per the bitaps Bug Bounty program at https://bitaps.com/mnemonic/challenge:
Finding 1 falls under "from 0.05 BTC — Any other significant implementation bug" and arguably under "0.1 BTC — Any bug in the implementation of the presented secret sharing scheme that can lead to loss of access", since the silent-corruption interaction with Finding 2 means a
t = 3user who supplies a slightly wrong share may silently recover a different (wrong) mnemonic without any error indication.Finding 2 falls under "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." The defect directly causes silent recovery failures — the user loses access to their wallet with no warning.
We respectfully request the maintainers' assessment and bounty determination per the program's published criteria.
Bounty reward address
A standalone Python PoC demonstrating both findings against the publicchallenge data is attached as bitaps_shamir_poc.py.
I will provide a BTC reward address once the maintainers have had achance to review and confirm the findings.
bitaps_shamir_poc.py