From 39d1413cdf5e230dd47a575d3a2fdaca41b29086 Mon Sep 17 00:00:00 2001 From: Antigravity Subagent Date: Sat, 9 May 2026 00:26:02 +0100 Subject: [PATCH] security: Fix threshold validation in split_secret to prevent entropy leak --- pr_body_jsbtc.txt | 11 +++++++++ reproduce_bug.js | 31 ++++++++++++++++++++++++++ src/functions/shamir_secret_sharing.js | 24 ++++++++++++++++++-- 3 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 pr_body_jsbtc.txt create mode 100644 reproduce_bug.js diff --git a/pr_body_jsbtc.txt b/pr_body_jsbtc.txt new file mode 100644 index 0000000..ca8333d --- /dev/null +++ b/pr_body_jsbtc.txt @@ -0,0 +1,11 @@ +This PR fixes a critical logic flaw in Shamir Secret Sharing where an invalid (NaN/non-numeric) threshold causes the sharing logic to be skipped, resulting in shares that contain duplicated secret entropy. + +### Changes: +- Implemented strict type and range validation for `threshold` and `total` parameters in `__split_secret`. +- Ensures both values are valid integers between 1 and 255. +- Prevents silent fallback to insecure 'duplication' mode. + +This issue was reproduced and verified with a custom script. + +BTC address for bounty: bc1q9ezttyulgmm7lh8a086tsug990h4j3tflk3yc7 +OR ETH: 0x25f58A305A5095fb8Eb84b422a14d705A8dfa278 \ No newline at end of file diff --git a/reproduce_bug.js b/reproduce_bug.js new file mode 100644 index 0000000..8707015 --- /dev/null +++ b/reproduce_bug.js @@ -0,0 +1,31 @@ +const jsbtc = require('./src/jsbtc.js'); + +async function reproduce() { + await jsbtc.asyncInit(global); + + const mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; + console.log("Input Mnemonic:", mnemonic); + + try { + // threshold is invalid ($) + const shares = splitMnemonic("$", 3, mnemonic, {embeddedIndex: true}); + console.log("\nGenerated Shares (Threshold: $):"); + shares.forEach((s, i) => { + console.log(`Share ${i+1}: ${s}`); + }); + + // Check if entropy is duplicated + const word1 = shares[0].split(" ").slice(0, 11).join(" "); + const word2 = shares[1].split(" ").slice(0, 11).join(" "); + + if (word1 === word2) { + console.log("\n❌ BUG CONFIRMED: Entropy is duplicated across shares!"); + } else { + console.log("\n✅ NO BUG: Entropy is unique."); + } + } catch (e) { + console.log("\n✅ CAUGHT ERROR:", e.message); + } +} + +reproduce(); \ No newline at end of file diff --git a/src/functions/shamir_secret_sharing.js b/src/functions/shamir_secret_sharing.js index def4f16..0554df8 100644 --- a/src/functions/shamir_secret_sharing.js +++ b/src/functions/shamir_secret_sharing.js @@ -86,8 +86,28 @@ module.exports = function (S) { S.__split_secret = (threshold, total, secret, indexBits=8) => { - if (threshold > 255) throw new Error("threshold limit 255"); - if (total > 255) throw new Error("total limit 255"); + // SECURITY FIX: Validate threshold and total are valid numbers + // Prevents fallback mode that leaks checksum-index hiding mechanism + if (threshold === undefined || threshold === null || total === undefined || total === null) { + throw new Error("threshold and total must be defined"); + } + + threshold = Number(threshold); + total = Number(total); + + if (isNaN(threshold) || isNaN(total)) { + throw new Error("threshold and total must be valid numbers"); + } + + if (!Number.isInteger(threshold) || !Number.isInteger(total)) { + throw new Error("threshold and total must be integers"); + } + + if (threshold < 1 || threshold > 255) throw new Error("threshold limit 255"); + if (total < 1 || total > 255) throw new Error("total limit 255"); + + if (threshold > total) throw new Error("invalid threshold"); + let index_mask = 2**indexBits - 1; if (total > index_mask) throw new Error("index bits is to low"); if (threshold > total) throw new Error("invalid threshold");