@btc-vision/bitcoin 6.5.5 → 7.0.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AUDIT/README.md +9 -0
- package/HOW_TO_WRITE_GOOD_CODE.md +2436 -0
- package/SECURITY.md +27 -0
- package/benchmark/psbt-2000-inputs.bench.ts +178 -0
- package/benchmark/signing.bench.ts +147 -0
- package/browser/address.d.ts +56 -9
- package/browser/address.d.ts.map +1 -0
- package/browser/bech32utils.d.ts +9 -1
- package/browser/bech32utils.d.ts.map +1 -0
- package/browser/bip66.d.ts +11 -6
- package/browser/bip66.d.ts.map +1 -0
- package/browser/block.d.ts +117 -11
- package/browser/block.d.ts.map +1 -0
- package/browser/branded.d.ts +20 -0
- package/browser/branded.d.ts.map +1 -0
- package/browser/crypto/crypto.d.ts +1 -0
- package/browser/crypto/crypto.d.ts.map +1 -0
- package/browser/crypto.d.ts +46 -7
- package/browser/crypto.d.ts.map +1 -0
- package/browser/ecc/context.d.ts +129 -0
- package/browser/ecc/context.d.ts.map +1 -0
- package/browser/ecc/index.d.ts +11 -0
- package/browser/ecc/index.d.ts.map +1 -0
- package/browser/ecc/types.d.ts +128 -0
- package/browser/ecc/types.d.ts.map +1 -0
- package/browser/ecpair.d.ts +99 -0
- package/browser/errors.d.ts +124 -0
- package/browser/errors.d.ts.map +1 -0
- package/browser/index.d.ts +32 -5
- package/browser/index.d.ts.map +1 -0
- package/browser/index.js +12482 -101
- package/browser/io/BinaryReader.d.ts +276 -0
- package/browser/io/BinaryReader.d.ts.map +1 -0
- package/browser/io/BinaryWriter.d.ts +391 -0
- package/browser/io/BinaryWriter.d.ts.map +1 -0
- package/browser/io/MemoryPool.d.ts +220 -0
- package/browser/io/MemoryPool.d.ts.map +1 -0
- package/browser/io/base64.d.ts +13 -0
- package/browser/io/base64.d.ts.map +1 -0
- package/browser/io/hex.d.ts +67 -0
- package/browser/io/hex.d.ts.map +1 -0
- package/browser/io/index.d.ts +17 -0
- package/browser/io/index.d.ts.map +1 -0
- package/browser/io/utils.d.ts +199 -0
- package/browser/io/utils.d.ts.map +1 -0
- package/browser/merkle.d.ts +10 -1
- package/browser/merkle.d.ts.map +1 -0
- package/browser/networks.d.ts +70 -9
- package/browser/networks.d.ts.map +1 -0
- package/browser/opcodes.d.ts +1 -0
- package/browser/opcodes.d.ts.map +1 -0
- package/browser/payments/bip341.d.ts +35 -9
- package/browser/payments/bip341.d.ts.map +1 -0
- package/browser/payments/embed.d.ts +112 -1
- package/browser/payments/embed.d.ts.map +1 -0
- package/browser/payments/index.d.ts +17 -10
- package/browser/payments/index.d.ts.map +1 -0
- package/browser/payments/p2ms.d.ts +150 -0
- package/browser/payments/p2ms.d.ts.map +1 -0
- package/browser/payments/p2op.d.ts +150 -24
- package/browser/payments/p2op.d.ts.map +1 -0
- package/browser/payments/p2pk.d.ts +154 -1
- package/browser/payments/p2pk.d.ts.map +1 -0
- package/browser/payments/p2pkh.d.ts +176 -1
- package/browser/payments/p2pkh.d.ts.map +1 -0
- package/browser/payments/p2sh.d.ts +150 -1
- package/browser/payments/p2sh.d.ts.map +1 -0
- package/browser/payments/p2tr.d.ts +185 -1
- package/browser/payments/p2tr.d.ts.map +1 -0
- package/browser/payments/p2wpkh.d.ts +161 -1
- package/browser/payments/p2wpkh.d.ts.map +1 -0
- package/browser/payments/p2wsh.d.ts +146 -1
- package/browser/payments/p2wsh.d.ts.map +1 -0
- package/browser/payments/types.d.ts +94 -64
- package/browser/payments/types.d.ts.map +1 -0
- package/browser/psbt/bip371.d.ts +34 -8
- package/browser/psbt/bip371.d.ts.map +1 -0
- package/browser/psbt/psbtutils.d.ts +56 -16
- package/browser/psbt/psbtutils.d.ts.map +1 -0
- package/browser/psbt/types.d.ts +245 -0
- package/browser/psbt/types.d.ts.map +1 -0
- package/browser/psbt/utils.d.ts +64 -0
- package/browser/psbt/utils.d.ts.map +1 -0
- package/browser/psbt/validation.d.ts +84 -0
- package/browser/psbt/validation.d.ts.map +1 -0
- package/browser/psbt.d.ts +82 -118
- package/browser/psbt.d.ts.map +1 -0
- package/browser/pubkey.d.ts +27 -6
- package/browser/pubkey.d.ts.map +1 -0
- package/browser/push_data.d.ts +24 -2
- package/browser/push_data.d.ts.map +1 -0
- package/browser/script.d.ts +33 -8
- package/browser/script.d.ts.map +1 -0
- package/browser/script_number.d.ts +17 -0
- package/browser/script_number.d.ts.map +1 -0
- package/browser/script_signature.d.ts +23 -5
- package/browser/script_signature.d.ts.map +1 -0
- package/browser/transaction.d.ts +160 -18
- package/browser/transaction.d.ts.map +1 -0
- package/browser/types.d.ts +36 -38
- package/browser/types.d.ts.map +1 -0
- package/browser/workers/WorkerSigningPool.d.ts +143 -0
- package/browser/workers/WorkerSigningPool.d.ts.map +1 -0
- package/browser/workers/WorkerSigningPool.node.d.ts +116 -0
- package/browser/workers/WorkerSigningPool.node.d.ts.map +1 -0
- package/browser/workers/ecc-bundle.d.ts +25 -0
- package/browser/workers/ecc-bundle.d.ts.map +1 -0
- package/browser/workers/index.d.ts +91 -0
- package/browser/workers/index.d.ts.map +1 -0
- package/browser/workers/psbt-parallel.d.ts +88 -0
- package/browser/workers/psbt-parallel.d.ts.map +1 -0
- package/browser/workers/signing-worker.d.ts +37 -0
- package/browser/workers/signing-worker.d.ts.map +1 -0
- package/browser/workers/types.d.ts +365 -0
- package/browser/workers/types.d.ts.map +1 -0
- package/build/address.d.ts +57 -10
- package/build/address.d.ts.map +1 -0
- package/build/address.js +80 -24
- package/build/address.js.map +1 -0
- package/build/bech32utils.d.ts +9 -1
- package/build/bech32utils.d.ts.map +1 -0
- package/build/bech32utils.js +10 -2
- package/build/bech32utils.js.map +1 -0
- package/build/bip66.d.ts +11 -6
- package/build/bip66.d.ts.map +1 -0
- package/build/bip66.js +32 -3
- package/build/bip66.js.map +1 -0
- package/build/block.d.ts +117 -11
- package/build/block.d.ts.map +1 -0
- package/build/block.js +204 -72
- package/build/block.js.map +1 -0
- package/build/branded.d.ts +20 -0
- package/build/branded.d.ts.map +1 -0
- package/build/branded.js +7 -0
- package/build/branded.js.map +1 -0
- package/build/crypto/crypto.d.ts +1 -0
- package/build/crypto/crypto.d.ts.map +1 -0
- package/build/crypto/crypto.js +1 -0
- package/build/crypto/crypto.js.map +1 -0
- package/build/crypto.d.ts +46 -7
- package/build/crypto.d.ts.map +1 -0
- package/build/crypto.js +65 -20
- package/build/crypto.js.map +1 -0
- package/build/ecc/context.d.ts +135 -0
- package/build/ecc/context.d.ts.map +1 -0
- package/build/ecc/context.js +232 -0
- package/build/ecc/context.js.map +1 -0
- package/build/ecc/index.d.ts +11 -0
- package/build/ecc/index.d.ts.map +1 -0
- package/build/ecc/index.js +11 -0
- package/build/ecc/index.js.map +1 -0
- package/build/ecc/types.d.ts +134 -0
- package/build/ecc/types.d.ts.map +1 -0
- package/build/ecc/types.js +8 -0
- package/build/ecc/types.js.map +1 -0
- package/build/errors.d.ts +124 -0
- package/build/errors.d.ts.map +1 -0
- package/build/errors.js +155 -0
- package/build/errors.js.map +1 -0
- package/build/index.d.ts +32 -5
- package/build/index.d.ts.map +1 -0
- package/build/index.js +26 -3
- package/build/index.js.map +1 -0
- package/build/io/BinaryReader.d.ts +276 -0
- package/build/io/BinaryReader.d.ts.map +1 -0
- package/build/io/BinaryReader.js +425 -0
- package/build/io/BinaryReader.js.map +1 -0
- package/build/io/BinaryWriter.d.ts +391 -0
- package/build/io/BinaryWriter.d.ts.map +1 -0
- package/build/io/BinaryWriter.js +611 -0
- package/build/io/BinaryWriter.js.map +1 -0
- package/build/io/MemoryPool.d.ts +220 -0
- package/build/io/MemoryPool.d.ts.map +1 -0
- package/build/io/MemoryPool.js +309 -0
- package/build/io/MemoryPool.js.map +1 -0
- package/build/io/base64.d.ts +13 -0
- package/build/io/base64.d.ts.map +1 -0
- package/build/io/base64.js +20 -0
- package/build/io/base64.js.map +1 -0
- package/build/io/hex.d.ts +67 -0
- package/build/io/hex.d.ts.map +1 -0
- package/build/io/hex.js +138 -0
- package/build/io/hex.js.map +1 -0
- package/build/io/index.d.ts +17 -0
- package/build/io/index.d.ts.map +1 -0
- package/build/io/index.js +23 -0
- package/build/io/index.js.map +1 -0
- package/build/io/utils.d.ts +199 -0
- package/build/io/utils.d.ts.map +1 -0
- package/build/io/utils.js +271 -0
- package/build/io/utils.js.map +1 -0
- package/build/merkle.d.ts +10 -1
- package/build/merkle.d.ts.map +1 -0
- package/build/merkle.js +12 -1
- package/build/merkle.js.map +1 -0
- package/build/networks.d.ts +70 -9
- package/build/networks.d.ts.map +1 -0
- package/build/networks.js +90 -4
- package/build/networks.js.map +1 -0
- package/build/opcodes.d.ts +1 -0
- package/build/opcodes.d.ts.map +1 -0
- package/build/opcodes.js +1 -0
- package/build/opcodes.js.map +1 -0
- package/build/payments/bip341.d.ts +36 -9
- package/build/payments/bip341.d.ts.map +1 -0
- package/build/payments/bip341.js +35 -15
- package/build/payments/bip341.js.map +1 -0
- package/build/payments/embed.d.ts +120 -1
- package/build/payments/embed.d.ts.map +1 -0
- package/build/payments/embed.js +215 -34
- package/build/payments/embed.js.map +1 -0
- package/build/payments/index.d.ts +17 -10
- package/build/payments/index.d.ts.map +1 -0
- package/build/payments/index.js +20 -10
- package/build/payments/index.js.map +1 -0
- package/build/payments/p2ms.d.ts +159 -1
- package/build/payments/p2ms.d.ts.map +1 -0
- package/build/payments/p2ms.js +427 -108
- package/build/payments/p2ms.js.map +1 -0
- package/build/payments/p2op.d.ts +158 -24
- package/build/payments/p2op.d.ts.map +1 -0
- package/build/payments/p2op.js +379 -93
- package/build/payments/p2op.js.map +1 -0
- package/build/payments/p2pk.d.ts +162 -1
- package/build/payments/p2pk.d.ts.map +1 -0
- package/build/payments/p2pk.js +327 -58
- package/build/payments/p2pk.js.map +1 -0
- package/build/payments/p2pkh.d.ts +185 -1
- package/build/payments/p2pkh.d.ts.map +1 -0
- package/build/payments/p2pkh.js +467 -114
- package/build/payments/p2pkh.js.map +1 -0
- package/build/payments/p2sh.d.ts +159 -1
- package/build/payments/p2sh.d.ts.map +1 -0
- package/build/payments/p2sh.js +500 -152
- package/build/payments/p2sh.js.map +1 -0
- package/build/payments/p2tr.d.ts +193 -1
- package/build/payments/p2tr.d.ts.map +1 -0
- package/build/payments/p2tr.js +592 -174
- package/build/payments/p2tr.js.map +1 -0
- package/build/payments/p2wpkh.d.ts +170 -1
- package/build/payments/p2wpkh.d.ts.map +1 -0
- package/build/payments/p2wpkh.js +429 -104
- package/build/payments/p2wpkh.js.map +1 -0
- package/build/payments/p2wsh.d.ts +155 -1
- package/build/payments/p2wsh.d.ts.map +1 -0
- package/build/payments/p2wsh.js +466 -144
- package/build/payments/p2wsh.js.map +1 -0
- package/build/payments/types.d.ts +98 -64
- package/build/payments/types.d.ts.map +1 -0
- package/build/payments/types.js +17 -13
- package/build/payments/types.js.map +1 -0
- package/build/psbt/bip371.d.ts +35 -9
- package/build/psbt/bip371.d.ts.map +1 -0
- package/build/psbt/bip371.js +113 -28
- package/build/psbt/bip371.js.map +1 -0
- package/build/psbt/psbtutils.d.ts +56 -16
- package/build/psbt/psbtutils.d.ts.map +1 -0
- package/build/psbt/psbtutils.js +71 -16
- package/build/psbt/psbtutils.js.map +1 -0
- package/build/psbt/types.d.ts +249 -0
- package/build/psbt/types.d.ts.map +1 -0
- package/build/psbt/types.js +6 -0
- package/build/psbt/types.js.map +1 -0
- package/build/psbt/utils.d.ts +68 -0
- package/build/psbt/utils.d.ts.map +1 -0
- package/build/psbt/utils.js +171 -0
- package/build/psbt/utils.js.map +1 -0
- package/build/psbt/validation.d.ts +88 -0
- package/build/psbt/validation.d.ts.map +1 -0
- package/build/psbt/validation.js +149 -0
- package/build/psbt/validation.js.map +1 -0
- package/build/psbt.d.ts +84 -120
- package/build/psbt.d.ts.map +1 -0
- package/build/psbt.js +411 -412
- package/build/psbt.js.map +1 -0
- package/build/pubkey.d.ts +27 -6
- package/build/pubkey.d.ts.map +1 -0
- package/build/pubkey.js +37 -13
- package/build/pubkey.js.map +1 -0
- package/build/push_data.d.ts +24 -2
- package/build/push_data.d.ts.map +1 -0
- package/build/push_data.js +44 -12
- package/build/push_data.js.map +1 -0
- package/build/script.d.ts +33 -8
- package/build/script.d.ts.map +1 -0
- package/build/script.js +100 -36
- package/build/script.js.map +1 -0
- package/build/script_number.d.ts +17 -0
- package/build/script_number.d.ts.map +1 -0
- package/build/script_number.js +19 -0
- package/build/script_number.js.map +1 -0
- package/build/script_signature.d.ts +23 -5
- package/build/script_signature.d.ts.map +1 -0
- package/build/script_signature.js +48 -15
- package/build/script_signature.js.map +1 -0
- package/build/transaction.d.ts +160 -18
- package/build/transaction.d.ts.map +1 -0
- package/build/transaction.js +443 -176
- package/build/transaction.js.map +1 -0
- package/build/tsconfig.build.tsbuildinfo +1 -0
- package/build/types.d.ts +36 -38
- package/build/types.d.ts.map +1 -0
- package/build/types.js +175 -57
- package/build/types.js.map +1 -0
- package/build/workers/WorkerSigningPool.d.ts +174 -0
- package/build/workers/WorkerSigningPool.d.ts.map +1 -0
- package/build/workers/WorkerSigningPool.js +553 -0
- package/build/workers/WorkerSigningPool.js.map +1 -0
- package/build/workers/WorkerSigningPool.node.d.ts +124 -0
- package/build/workers/WorkerSigningPool.node.d.ts.map +1 -0
- package/build/workers/WorkerSigningPool.node.js +753 -0
- package/build/workers/WorkerSigningPool.node.js.map +1 -0
- package/build/workers/ecc-bundle.d.ts +25 -0
- package/build/workers/ecc-bundle.d.ts.map +1 -0
- package/build/workers/ecc-bundle.js +25 -0
- package/build/workers/ecc-bundle.js.map +1 -0
- package/build/workers/index.d.ts +91 -0
- package/build/workers/index.d.ts.map +1 -0
- package/build/workers/index.js +114 -0
- package/build/workers/index.js.map +1 -0
- package/build/workers/psbt-parallel.d.ts +117 -0
- package/build/workers/psbt-parallel.d.ts.map +1 -0
- package/build/workers/psbt-parallel.js +233 -0
- package/build/workers/psbt-parallel.js.map +1 -0
- package/build/workers/signing-worker.d.ts +37 -0
- package/build/workers/signing-worker.d.ts.map +1 -0
- package/build/workers/signing-worker.js +350 -0
- package/build/workers/signing-worker.js.map +1 -0
- package/build/workers/types.d.ts +365 -0
- package/build/workers/types.d.ts.map +1 -0
- package/build/workers/types.js +60 -0
- package/build/workers/types.js.map +1 -0
- package/package.json +83 -25
- package/scripts/bundle-ecc.ts +111 -0
- package/src/address.ts +81 -44
- package/src/bech32utils.ts +3 -3
- package/src/bip66.ts +34 -24
- package/src/block.ts +196 -84
- package/src/branded.ts +18 -0
- package/src/crypto.ts +64 -26
- package/src/ecc/context.ts +277 -0
- package/src/ecc/index.ts +14 -0
- package/src/ecc/types.ts +154 -0
- package/src/ecpair.d.ts +99 -0
- package/src/errors.ts +163 -0
- package/src/index.ts +113 -9
- package/src/io/BinaryReader.ts +461 -0
- package/src/io/BinaryWriter.ts +696 -0
- package/src/io/MemoryPool.ts +343 -0
- package/src/io/base64.ts +20 -0
- package/src/io/hex.ts +155 -0
- package/src/io/index.ts +41 -0
- package/src/io/utils.ts +283 -0
- package/src/merkle.ts +14 -9
- package/src/networks.ts +9 -9
- package/src/payments/bip341.ts +34 -33
- package/src/payments/embed.ts +244 -41
- package/src/payments/index.ts +12 -10
- package/src/payments/p2ms.ts +490 -118
- package/src/payments/p2op.ts +431 -133
- package/src/payments/p2pk.ts +370 -72
- package/src/payments/p2pkh.ts +524 -130
- package/src/payments/p2sh.ts +572 -172
- package/src/payments/p2tr.ts +686 -194
- package/src/payments/p2wpkh.ts +484 -107
- package/src/payments/p2wsh.ts +526 -164
- package/src/payments/types.ts +80 -66
- package/src/psbt/bip371.ts +68 -51
- package/src/psbt/psbtutils.ts +39 -40
- package/src/psbt/types.ts +331 -0
- package/src/psbt/utils.ts +188 -0
- package/src/psbt/validation.ts +192 -0
- package/src/psbt.ts +566 -809
- package/src/pubkey.ts +24 -25
- package/src/push_data.ts +18 -16
- package/src/script.ts +82 -64
- package/src/script_number.ts +6 -6
- package/src/script_signature.ts +33 -36
- package/src/transaction.ts +458 -238
- package/src/types.ts +231 -100
- package/src/workers/WorkerSigningPool.node.ts +887 -0
- package/src/workers/WorkerSigningPool.ts +670 -0
- package/src/workers/ecc-bundle.ts +26 -0
- package/src/workers/index.ts +165 -0
- package/src/workers/psbt-parallel.ts +332 -0
- package/src/workers/signing-worker.ts +353 -0
- package/src/workers/types.ts +413 -0
- package/test/address.spec.ts +9 -6
- package/test/bitcoin.core.spec.ts +16 -17
- package/test/block.spec.ts +8 -7
- package/test/bufferutils.spec.ts +228 -214
- package/test/crypto.spec.ts +19 -11
- package/test/fixtures/p2pk.json +0 -8
- package/test/fixtures/p2pkh.json +1 -1
- package/test/fixtures/p2sh.json +1 -1
- package/test/fixtures/script.json +1 -1
- package/test/fixtures/transaction.json +2 -2
- package/test/integration/_regtest.ts +25 -0
- package/test/integration/addresses.spec.ts +4 -3
- package/test/integration/bip32.spec.ts +2 -1
- package/test/integration/blocks.spec.ts +1 -1
- package/test/integration/cltv.spec.ts +18 -16
- package/test/integration/csv.spec.ts +37 -64
- package/test/integration/payments.spec.ts +5 -3
- package/test/integration/taproot.spec.ts +76 -83
- package/test/integration/transactions.spec.ts +38 -35
- package/test/payments.spec.ts +35 -13
- package/test/payments.utils.ts +17 -16
- package/test/psbt.spec.ts +111 -100
- package/test/script.spec.ts +11 -10
- package/test/script_signature.spec.ts +9 -11
- package/test/taproot-cache.spec.ts +694 -0
- package/test/transaction.spec.ts +32 -40
- package/test/types.spec.ts +74 -29
- package/test/workers-pool.spec.ts +963 -0
- package/test/workers-signing.spec.ts +635 -0
- package/test/workers.spec.ts +1390 -0
- package/tsconfig.base.json +34 -18
- package/tsconfig.browser.json +15 -0
- package/tsconfig.build.json +5 -0
- package/tsconfig.json +5 -14
- package/vite.config.browser.ts +3 -42
- package/vitest.config.integration.ts +11 -0
- package/browser/bufferutils.d.ts +0 -34
- package/browser/chunks/crypto-BhCpKpek.js +0 -2033
- package/browser/chunks/payments-yjA0Evsv.js +0 -1089
- package/browser/chunks/psbt-URK2hBFc.js +0 -4039
- package/browser/chunks/script-DyPItFEl.js +0 -318
- package/browser/chunks/transaction-C_UbhMGn.js +0 -432
- package/browser/chunks/utils-DNZi-T5W.js +0 -761
- package/browser/ecc_lib.d.ts +0 -3
- package/browser/hooks/AdvancedSignatureManager.d.ts +0 -16
- package/browser/hooks/HookedSigner.d.ts +0 -4
- package/browser/hooks/SignatureManager.d.ts +0 -13
- package/browser/payments/lazy.d.ts +0 -2
- package/browser/typeforce.d.ts +0 -38
- package/build/bufferutils.d.ts +0 -34
- package/build/bufferutils.js +0 -141
- package/build/ecc_lib.d.ts +0 -3
- package/build/ecc_lib.js +0 -61
- package/build/hooks/AdvancedSignatureManager.d.ts +0 -16
- package/build/hooks/AdvancedSignatureManager.js +0 -52
- package/build/hooks/HookedSigner.d.ts +0 -4
- package/build/hooks/HookedSigner.js +0 -64
- package/build/hooks/SignatureManager.d.ts +0 -13
- package/build/hooks/SignatureManager.js +0 -45
- package/build/payments/lazy.d.ts +0 -2
- package/build/payments/lazy.js +0 -28
- package/build/tsconfig.tsbuildinfo +0 -1
- package/src/bufferutils.ts +0 -188
- package/src/ecc_lib.ts +0 -94
- package/src/hooks/AdvancedSignatureManager.ts +0 -104
- package/src/hooks/HookedSigner.ts +0 -108
- package/src/hooks/SignatureManager.ts +0 -84
- package/src/payments/lazy.ts +0 -28
- package/src/typeforce.d.ts +0 -38
- package/tsconfig.webpack.json +0 -18
|
@@ -0,0 +1,670 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker-based parallel signing pool.
|
|
3
|
+
*
|
|
4
|
+
* Provides secure parallel signature computation using worker threads.
|
|
5
|
+
* Private keys are isolated per-worker and zeroed immediately after signing.
|
|
6
|
+
*
|
|
7
|
+
* SECURITY ARCHITECTURE:
|
|
8
|
+
* - Keys are NEVER shared via SharedArrayBuffer
|
|
9
|
+
* - Each key is cloned to ONE worker via postMessage
|
|
10
|
+
* - Keys are zeroed in worker immediately after signing
|
|
11
|
+
* - Workers can be preserved for performance or terminated for security
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { WorkerSigningPool } from '@btc-vision/bitcoin';
|
|
16
|
+
*
|
|
17
|
+
* // Get singleton pool instance
|
|
18
|
+
* const pool = WorkerSigningPool.getInstance();
|
|
19
|
+
*
|
|
20
|
+
* // Preserve workers for multiple signing operations (faster)
|
|
21
|
+
* pool.preserveWorkers();
|
|
22
|
+
*
|
|
23
|
+
* // Sign multiple inputs in parallel
|
|
24
|
+
* const result = await pool.signBatch(tasks, keyPair);
|
|
25
|
+
*
|
|
26
|
+
* // Shutdown when done (optional - cleans up workers)
|
|
27
|
+
* await pool.shutdown();
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @packageDocumentation
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import type {
|
|
34
|
+
WorkerPoolConfig,
|
|
35
|
+
SigningTask,
|
|
36
|
+
ParallelSignerKeyPair,
|
|
37
|
+
ParallelSigningResult,
|
|
38
|
+
SigningResultMessage,
|
|
39
|
+
WorkerResponse,
|
|
40
|
+
BatchSigningMessage,
|
|
41
|
+
BatchSigningTask,
|
|
42
|
+
BatchSigningResultMessage,
|
|
43
|
+
PooledWorker,
|
|
44
|
+
} from './types.js';
|
|
45
|
+
import {
|
|
46
|
+
WorkerState,
|
|
47
|
+
isBatchResult,
|
|
48
|
+
isWorkerReady,
|
|
49
|
+
} from './types.js';
|
|
50
|
+
import { createWorkerBlobUrl, revokeWorkerBlobUrl } from './signing-worker.js';
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Default configuration values.
|
|
54
|
+
*/
|
|
55
|
+
const DEFAULT_CONFIG: Required<WorkerPoolConfig> = {
|
|
56
|
+
workerCount: typeof navigator !== 'undefined' ? navigator.hardwareConcurrency || 4 : 4,
|
|
57
|
+
taskTimeoutMs: 30000,
|
|
58
|
+
maxKeyHoldTimeMs: 5000,
|
|
59
|
+
verifySignatures: true,
|
|
60
|
+
preserveWorkers: false,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Pending batch awaiting completion.
|
|
65
|
+
*/
|
|
66
|
+
interface PendingBatch {
|
|
67
|
+
readonly batchId: string;
|
|
68
|
+
readonly resolve: (result: BatchSigningResultMessage) => void;
|
|
69
|
+
readonly reject: (error: Error) => void;
|
|
70
|
+
readonly timeoutId: ReturnType<typeof setTimeout>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Worker-based parallel signing pool.
|
|
75
|
+
*
|
|
76
|
+
* Manages a pool of worker threads for parallel signature computation.
|
|
77
|
+
* Provides secure key handling with immediate zeroing after use.
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```typescript
|
|
81
|
+
* // Initialize pool at app startup
|
|
82
|
+
* const pool = WorkerSigningPool.getInstance({ workerCount: 4 });
|
|
83
|
+
* pool.preserveWorkers();
|
|
84
|
+
*
|
|
85
|
+
* // Use for PSBT signing
|
|
86
|
+
* const tasks = prepareSigningTasks(psbt, keyPair);
|
|
87
|
+
* const result = await pool.signBatch(tasks, keyPair);
|
|
88
|
+
*
|
|
89
|
+
* // Apply signatures to PSBT
|
|
90
|
+
* applySignatures(psbt, result.signatures);
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
export class WorkerSigningPool {
|
|
94
|
+
/**
|
|
95
|
+
* Singleton instance.
|
|
96
|
+
*/
|
|
97
|
+
static #instance: WorkerSigningPool | null = null;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Pool configuration.
|
|
101
|
+
*/
|
|
102
|
+
readonly #config: Required<WorkerPoolConfig>;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Worker pool.
|
|
106
|
+
*/
|
|
107
|
+
readonly #workers: PooledWorker[] = [];
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Pending batches awaiting completion.
|
|
111
|
+
*/
|
|
112
|
+
readonly #pendingBatches: Map<string, PendingBatch> = new Map();
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Worker blob URL (shared across all workers).
|
|
116
|
+
*/
|
|
117
|
+
#workerBlobUrl: string | null = null;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Whether workers are preserved between batches.
|
|
121
|
+
*/
|
|
122
|
+
#preserveWorkers: boolean = false;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Next worker ID counter.
|
|
126
|
+
*/
|
|
127
|
+
#nextWorkerId: number = 0;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Next task ID counter.
|
|
131
|
+
*/
|
|
132
|
+
#nextTaskId: number = 0;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Whether the pool is initialized.
|
|
136
|
+
*/
|
|
137
|
+
#initialized: boolean = false;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Whether the pool is shutting down.
|
|
141
|
+
*/
|
|
142
|
+
#shuttingDown: boolean = false;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Creates a new WorkerSigningPool.
|
|
146
|
+
*
|
|
147
|
+
* @param config - Pool configuration
|
|
148
|
+
*/
|
|
149
|
+
private constructor(config: WorkerPoolConfig = {}) {
|
|
150
|
+
this.#config = { ...DEFAULT_CONFIG, ...config };
|
|
151
|
+
this.#preserveWorkers = this.#config.preserveWorkers;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Gets the singleton pool instance.
|
|
156
|
+
*
|
|
157
|
+
* @param config - Optional configuration (only used on first call)
|
|
158
|
+
* @returns The singleton pool instance
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* ```typescript
|
|
162
|
+
* const pool = WorkerSigningPool.getInstance({ workerCount: 8 });
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
165
|
+
public static getInstance(config?: WorkerPoolConfig): WorkerSigningPool {
|
|
166
|
+
if (!WorkerSigningPool.#instance) {
|
|
167
|
+
WorkerSigningPool.#instance = new WorkerSigningPool(config);
|
|
168
|
+
}
|
|
169
|
+
return WorkerSigningPool.#instance;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Resets the singleton instance (for testing).
|
|
174
|
+
*/
|
|
175
|
+
public static resetInstance(): void {
|
|
176
|
+
if (WorkerSigningPool.#instance) {
|
|
177
|
+
WorkerSigningPool.#instance.shutdown().catch(() => {});
|
|
178
|
+
WorkerSigningPool.#instance = null;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Number of workers in the pool.
|
|
184
|
+
*/
|
|
185
|
+
public get workerCount(): number {
|
|
186
|
+
return this.#workers.length;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Number of idle workers available.
|
|
191
|
+
*/
|
|
192
|
+
public get idleWorkerCount(): number {
|
|
193
|
+
return this.#workers.filter((w) => w.state === WorkerState.Idle).length;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Number of busy workers.
|
|
198
|
+
*/
|
|
199
|
+
public get busyWorkerCount(): number {
|
|
200
|
+
return this.#workers.filter((w) => w.state === WorkerState.Busy).length;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Whether workers are being preserved between batches.
|
|
205
|
+
*/
|
|
206
|
+
public get isPreservingWorkers(): boolean {
|
|
207
|
+
return this.#preserveWorkers;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Enables worker preservation between signing batches.
|
|
212
|
+
*
|
|
213
|
+
* When enabled, workers remain alive after completing a batch,
|
|
214
|
+
* ready for the next signing operation. This is faster but
|
|
215
|
+
* keeps workers in memory.
|
|
216
|
+
*
|
|
217
|
+
* Call shutdown() when done to terminate all workers.
|
|
218
|
+
*
|
|
219
|
+
* @example
|
|
220
|
+
* ```typescript
|
|
221
|
+
* const pool = WorkerSigningPool.getInstance();
|
|
222
|
+
* pool.preserveWorkers(); // Enable at app startup
|
|
223
|
+
*
|
|
224
|
+
* // ... do many signing operations ...
|
|
225
|
+
*
|
|
226
|
+
* await pool.shutdown(); // Cleanup at app shutdown
|
|
227
|
+
* ```
|
|
228
|
+
*/
|
|
229
|
+
public preserveWorkers(): void {
|
|
230
|
+
this.#preserveWorkers = true;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Disables worker preservation.
|
|
235
|
+
*
|
|
236
|
+
* Workers will be terminated after each signing batch.
|
|
237
|
+
* More secure (no persistent workers) but slower for multiple batches.
|
|
238
|
+
*/
|
|
239
|
+
public releaseWorkers(): void {
|
|
240
|
+
this.#preserveWorkers = false;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Initializes the worker pool.
|
|
245
|
+
*
|
|
246
|
+
* Creates workers and waits for them to be ready.
|
|
247
|
+
* Called automatically on first signBatch() if not called manually.
|
|
248
|
+
*
|
|
249
|
+
* @returns Promise that resolves when all workers are ready
|
|
250
|
+
*/
|
|
251
|
+
public async initialize(): Promise<void> {
|
|
252
|
+
if (this.#initialized) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (this.#shuttingDown) {
|
|
257
|
+
throw new Error('Cannot initialize pool while shutting down');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Create worker blob URL (ECC library is bundled at compile time)
|
|
261
|
+
this.#workerBlobUrl = createWorkerBlobUrl();
|
|
262
|
+
|
|
263
|
+
// Create workers
|
|
264
|
+
const workerPromises: Promise<void>[] = [];
|
|
265
|
+
for (let i = 0; i < this.#config.workerCount; i++) {
|
|
266
|
+
workerPromises.push(this.#createWorker());
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
await Promise.all(workerPromises);
|
|
270
|
+
this.#initialized = true;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Signs a batch of tasks in parallel.
|
|
275
|
+
*
|
|
276
|
+
* SECURITY: Private keys are obtained via keyPair.getPrivateKey() and
|
|
277
|
+
* cloned to workers. Keys are zeroed in workers immediately after signing.
|
|
278
|
+
*
|
|
279
|
+
* Tasks are distributed across workers and processed in batches for efficiency.
|
|
280
|
+
*
|
|
281
|
+
* @param tasks - Signing tasks (hashes, input indices, etc.)
|
|
282
|
+
* @param keyPair - Key pair with getPrivateKey() method
|
|
283
|
+
* @returns Promise resolving to signing results
|
|
284
|
+
*
|
|
285
|
+
* @example
|
|
286
|
+
* ```typescript
|
|
287
|
+
* const tasks: SigningTask[] = [
|
|
288
|
+
* { taskId: '1', inputIndex: 0, hash: hash0, signatureType: SignatureType.ECDSA, sighashType: 0x01 },
|
|
289
|
+
* { taskId: '2', inputIndex: 1, hash: hash1, signatureType: SignatureType.Schnorr, sighashType: 0x00 },
|
|
290
|
+
* ];
|
|
291
|
+
*
|
|
292
|
+
* const result = await pool.signBatch(tasks, keyPair);
|
|
293
|
+
*
|
|
294
|
+
* if (result.success) {
|
|
295
|
+
* for (const [inputIndex, sig] of result.signatures) {
|
|
296
|
+
* console.log(`Input ${inputIndex}: ${sig.signature}`);
|
|
297
|
+
* }
|
|
298
|
+
* }
|
|
299
|
+
* ```
|
|
300
|
+
*/
|
|
301
|
+
public async signBatch(
|
|
302
|
+
tasks: readonly SigningTask[],
|
|
303
|
+
keyPair: ParallelSignerKeyPair,
|
|
304
|
+
): Promise<ParallelSigningResult> {
|
|
305
|
+
const startTime = performance.now();
|
|
306
|
+
|
|
307
|
+
// Initialize if needed
|
|
308
|
+
if (!this.#initialized) {
|
|
309
|
+
await this.initialize();
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (tasks.length === 0) {
|
|
313
|
+
return {
|
|
314
|
+
success: true,
|
|
315
|
+
signatures: new Map(),
|
|
316
|
+
errors: new Map(),
|
|
317
|
+
durationMs: performance.now() - startTime,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Distribute tasks across workers
|
|
322
|
+
const workerCount = Math.min(this.#workers.length, tasks.length);
|
|
323
|
+
const taskBatches: SigningTask[][] = Array.from({ length: workerCount }, () => []);
|
|
324
|
+
|
|
325
|
+
for (let i = 0; i < tasks.length; i++) {
|
|
326
|
+
taskBatches[i % workerCount]!.push(tasks[i]!);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Get private key once
|
|
330
|
+
const privateKey = keyPair.getPrivateKey();
|
|
331
|
+
|
|
332
|
+
try {
|
|
333
|
+
// Send batches to workers in parallel
|
|
334
|
+
const batchResults = await Promise.allSettled(
|
|
335
|
+
taskBatches.map((batch, index) =>
|
|
336
|
+
this.#signBatchOnWorker(batch, privateKey, keyPair.publicKey, index),
|
|
337
|
+
),
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
// Collect all results
|
|
341
|
+
const signatures = new Map<number, SigningResultMessage>();
|
|
342
|
+
const errors = new Map<number, string>();
|
|
343
|
+
|
|
344
|
+
for (let i = 0; i < batchResults.length; i++) {
|
|
345
|
+
const result = batchResults[i]!;
|
|
346
|
+
if (result.status === 'fulfilled') {
|
|
347
|
+
const batchResult = result.value;
|
|
348
|
+
|
|
349
|
+
// Add successful signatures
|
|
350
|
+
for (const sig of batchResult.results) {
|
|
351
|
+
signatures.set(sig.inputIndex, {
|
|
352
|
+
type: 'result',
|
|
353
|
+
taskId: sig.taskId,
|
|
354
|
+
signature: sig.signature,
|
|
355
|
+
inputIndex: sig.inputIndex,
|
|
356
|
+
publicKey: sig.publicKey,
|
|
357
|
+
signatureType: sig.signatureType,
|
|
358
|
+
leafHash: sig.leafHash,
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Add errors
|
|
363
|
+
for (const err of batchResult.errors) {
|
|
364
|
+
errors.set(err.inputIndex, err.error);
|
|
365
|
+
}
|
|
366
|
+
} else {
|
|
367
|
+
// Entire batch failed - mark all tasks in batch as failed
|
|
368
|
+
const reason = result.reason as { message?: string } | undefined;
|
|
369
|
+
const errorMsg = reason?.message ?? 'Batch signing failed';
|
|
370
|
+
|
|
371
|
+
// Add error for each task in the failed batch
|
|
372
|
+
const failedBatch = taskBatches[i]!;
|
|
373
|
+
for (const task of failedBatch) {
|
|
374
|
+
errors.set(task.inputIndex, errorMsg);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Cleanup workers if not preserving
|
|
380
|
+
if (!this.#preserveWorkers) {
|
|
381
|
+
await this.#terminateIdleWorkers();
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return {
|
|
385
|
+
success: errors.size === 0,
|
|
386
|
+
signatures,
|
|
387
|
+
errors,
|
|
388
|
+
durationMs: performance.now() - startTime,
|
|
389
|
+
};
|
|
390
|
+
} finally {
|
|
391
|
+
// SECURITY: Zero the key in main thread
|
|
392
|
+
privateKey.fill(0);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Shuts down the pool and terminates all workers.
|
|
398
|
+
*
|
|
399
|
+
* Call this when the application is done with signing operations.
|
|
400
|
+
*
|
|
401
|
+
* @returns Promise that resolves when all workers are terminated
|
|
402
|
+
*/
|
|
403
|
+
public async shutdown(): Promise<void> {
|
|
404
|
+
if (this.#shuttingDown) {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
this.#shuttingDown = true;
|
|
409
|
+
|
|
410
|
+
// Terminate all workers
|
|
411
|
+
const terminatePromises = this.#workers.map((worker) => this.#terminateWorker(worker));
|
|
412
|
+
|
|
413
|
+
await Promise.all(terminatePromises);
|
|
414
|
+
|
|
415
|
+
// Clear state
|
|
416
|
+
this.#workers.length = 0;
|
|
417
|
+
this.#pendingBatches.clear();
|
|
418
|
+
|
|
419
|
+
// Revoke blob URL
|
|
420
|
+
if (this.#workerBlobUrl) {
|
|
421
|
+
revokeWorkerBlobUrl(this.#workerBlobUrl);
|
|
422
|
+
this.#workerBlobUrl = null;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
this.#initialized = false;
|
|
426
|
+
this.#shuttingDown = false;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Creates a new worker and adds it to the pool.
|
|
431
|
+
*/
|
|
432
|
+
async #createWorker(): Promise<void> {
|
|
433
|
+
if (!this.#workerBlobUrl) {
|
|
434
|
+
throw new Error('Worker blob URL not created');
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const workerId = this.#nextWorkerId++;
|
|
438
|
+
|
|
439
|
+
// Create worker from blob URL
|
|
440
|
+
const worker = new Worker(this.#workerBlobUrl, {
|
|
441
|
+
name: `signing-worker-${workerId}`,
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
const pooledWorker: PooledWorker = {
|
|
445
|
+
id: workerId,
|
|
446
|
+
state: WorkerState.Initializing,
|
|
447
|
+
worker,
|
|
448
|
+
currentTaskId: null,
|
|
449
|
+
taskStartTime: null,
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
this.#workers.push(pooledWorker);
|
|
453
|
+
|
|
454
|
+
// Wait for worker to be ready
|
|
455
|
+
await new Promise<void>((resolve, reject) => {
|
|
456
|
+
const timeout = setTimeout(() => {
|
|
457
|
+
reject(new Error(`Worker ${workerId} initialization timeout`));
|
|
458
|
+
}, 10000);
|
|
459
|
+
|
|
460
|
+
const messageHandler = (event: MessageEvent<WorkerResponse>): void => {
|
|
461
|
+
if (isWorkerReady(event.data)) {
|
|
462
|
+
clearTimeout(timeout);
|
|
463
|
+
worker.removeEventListener('message', messageHandler);
|
|
464
|
+
pooledWorker.state = WorkerState.Idle;
|
|
465
|
+
resolve();
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
worker.addEventListener('message', messageHandler);
|
|
470
|
+
worker.addEventListener('error', (error) => {
|
|
471
|
+
clearTimeout(timeout);
|
|
472
|
+
reject(new Error(`Worker ${workerId} error: ${error.message}`));
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
// Send init message (ECC library is bundled in worker code)
|
|
476
|
+
worker.postMessage({
|
|
477
|
+
type: 'init',
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
// Set up message handler for signing results
|
|
482
|
+
worker.addEventListener('message', (event: MessageEvent<WorkerResponse>) => {
|
|
483
|
+
this.#handleWorkerMessage(pooledWorker, event.data);
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Signs a batch of tasks on a specific worker.
|
|
489
|
+
*/
|
|
490
|
+
async #signBatchOnWorker(
|
|
491
|
+
tasks: readonly SigningTask[],
|
|
492
|
+
privateKey: Uint8Array,
|
|
493
|
+
publicKey: Uint8Array,
|
|
494
|
+
workerIndex: number,
|
|
495
|
+
): Promise<BatchSigningResultMessage> {
|
|
496
|
+
if (tasks.length === 0) {
|
|
497
|
+
return { type: 'batchResult', batchId: '', results: [], errors: [] };
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Get worker at index (or wait for idle)
|
|
501
|
+
const worker = this.#workers[workerIndex] ?? (await this.#getIdleWorker());
|
|
502
|
+
|
|
503
|
+
// Generate unique batch ID
|
|
504
|
+
const batchId = `batch-${this.#nextTaskId++}`;
|
|
505
|
+
|
|
506
|
+
return new Promise<BatchSigningResultMessage>((resolve, reject) => {
|
|
507
|
+
// Set up timeout
|
|
508
|
+
const timeoutId = setTimeout(() => {
|
|
509
|
+
this.#pendingBatches.delete(batchId);
|
|
510
|
+
worker.state = WorkerState.Idle;
|
|
511
|
+
worker.currentTaskId = null;
|
|
512
|
+
worker.taskStartTime = null;
|
|
513
|
+
|
|
514
|
+
// SECURITY: Terminate worker that exceeded key hold time
|
|
515
|
+
this.#terminateWorker(worker).catch(() => {});
|
|
516
|
+
this.#createWorker().catch(() => {});
|
|
517
|
+
|
|
518
|
+
reject(new Error(`Batch signing timeout for ${tasks.length} tasks`));
|
|
519
|
+
}, this.#config.maxKeyHoldTimeMs);
|
|
520
|
+
|
|
521
|
+
// Store pending batch
|
|
522
|
+
const pendingBatch: PendingBatch = {
|
|
523
|
+
batchId,
|
|
524
|
+
resolve,
|
|
525
|
+
reject,
|
|
526
|
+
timeoutId,
|
|
527
|
+
};
|
|
528
|
+
this.#pendingBatches.set(batchId, pendingBatch);
|
|
529
|
+
|
|
530
|
+
// Mark worker as busy
|
|
531
|
+
worker.state = WorkerState.Busy;
|
|
532
|
+
worker.currentTaskId = batchId;
|
|
533
|
+
worker.taskStartTime = Date.now();
|
|
534
|
+
|
|
535
|
+
// Convert tasks to batch format
|
|
536
|
+
const batchTasks: BatchSigningTask[] = tasks.map((task) => ({
|
|
537
|
+
taskId: task.taskId,
|
|
538
|
+
hash: task.hash,
|
|
539
|
+
publicKey,
|
|
540
|
+
signatureType: task.signatureType,
|
|
541
|
+
lowR: task.lowR,
|
|
542
|
+
inputIndex: task.inputIndex,
|
|
543
|
+
sighashType: task.sighashType,
|
|
544
|
+
leafHash: task.leafHash,
|
|
545
|
+
}));
|
|
546
|
+
|
|
547
|
+
// Create batch message
|
|
548
|
+
// SECURITY: privateKey is cloned via postMessage, NOT shared
|
|
549
|
+
const message: BatchSigningMessage = {
|
|
550
|
+
type: 'signBatch',
|
|
551
|
+
batchId,
|
|
552
|
+
tasks: batchTasks,
|
|
553
|
+
privateKey, // Cloned to worker, zeroed there after all signatures
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
// Send to worker
|
|
557
|
+
worker.worker.postMessage(message);
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Gets an idle worker, creating one if necessary.
|
|
563
|
+
*/
|
|
564
|
+
async #getIdleWorker(): Promise<PooledWorker> {
|
|
565
|
+
// Find idle worker
|
|
566
|
+
let worker = this.#workers.find((w) => w.state === WorkerState.Idle);
|
|
567
|
+
|
|
568
|
+
if (worker) {
|
|
569
|
+
return worker;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// No idle workers - wait for one to become available
|
|
573
|
+
// or create a new one if under limit
|
|
574
|
+
if (this.#workers.length < this.#config.workerCount) {
|
|
575
|
+
await this.#createWorker();
|
|
576
|
+
worker = this.#workers.find((w) => w.state === WorkerState.Idle);
|
|
577
|
+
if (worker) {
|
|
578
|
+
return worker;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// Wait for any worker to become idle
|
|
583
|
+
return new Promise<PooledWorker>((resolve) => {
|
|
584
|
+
const checkInterval = setInterval(() => {
|
|
585
|
+
const idleWorker = this.#workers.find((w) => w.state === WorkerState.Idle);
|
|
586
|
+
if (idleWorker) {
|
|
587
|
+
clearInterval(checkInterval);
|
|
588
|
+
resolve(idleWorker);
|
|
589
|
+
}
|
|
590
|
+
}, 10);
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Handles a message from a worker.
|
|
596
|
+
*/
|
|
597
|
+
#handleWorkerMessage(worker: PooledWorker, response: WorkerResponse): void {
|
|
598
|
+
if (isBatchResult(response)) {
|
|
599
|
+
const pending = this.#pendingBatches.get(response.batchId);
|
|
600
|
+
if (pending) {
|
|
601
|
+
clearTimeout(pending.timeoutId);
|
|
602
|
+
this.#pendingBatches.delete(response.batchId);
|
|
603
|
+
worker.state = WorkerState.Idle;
|
|
604
|
+
worker.currentTaskId = null;
|
|
605
|
+
worker.taskStartTime = null;
|
|
606
|
+
pending.resolve(response);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
// Ignore ready and shutdown-ack messages here (handled elsewhere)
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* Terminates a worker.
|
|
614
|
+
*/
|
|
615
|
+
async #terminateWorker(worker: PooledWorker): Promise<void> {
|
|
616
|
+
if (worker.state === WorkerState.Terminated) {
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
worker.state = WorkerState.ShuttingDown;
|
|
621
|
+
|
|
622
|
+
// Send shutdown message
|
|
623
|
+
worker.worker.postMessage({ type: 'shutdown' });
|
|
624
|
+
|
|
625
|
+
// Wait briefly for acknowledgment, then terminate
|
|
626
|
+
await new Promise<void>((resolve) => {
|
|
627
|
+
const timeout = setTimeout(() => {
|
|
628
|
+
worker.worker.terminate();
|
|
629
|
+
worker.state = WorkerState.Terminated;
|
|
630
|
+
resolve();
|
|
631
|
+
}, 1000);
|
|
632
|
+
|
|
633
|
+
const handler = (event: MessageEvent<WorkerResponse>): void => {
|
|
634
|
+
if (event.data.type === 'shutdown-ack') {
|
|
635
|
+
clearTimeout(timeout);
|
|
636
|
+
worker.worker.removeEventListener('message', handler);
|
|
637
|
+
worker.worker.terminate();
|
|
638
|
+
worker.state = WorkerState.Terminated;
|
|
639
|
+
resolve();
|
|
640
|
+
}
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
worker.worker.addEventListener('message', handler);
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
// Remove from pool
|
|
647
|
+
const index = this.#workers.indexOf(worker);
|
|
648
|
+
if (index >= 0) {
|
|
649
|
+
this.#workers.splice(index, 1);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Terminates all idle workers.
|
|
655
|
+
*/
|
|
656
|
+
async #terminateIdleWorkers(): Promise<void> {
|
|
657
|
+
const idleWorkers = this.#workers.filter((w) => w.state === WorkerState.Idle);
|
|
658
|
+
await Promise.all(idleWorkers.map((w) => this.#terminateWorker(w)));
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* Convenience function to get the singleton pool instance.
|
|
664
|
+
*
|
|
665
|
+
* @param config - Optional configuration
|
|
666
|
+
* @returns The singleton pool instance
|
|
667
|
+
*/
|
|
668
|
+
export function getSigningPool(config?: WorkerPoolConfig): WorkerSigningPool {
|
|
669
|
+
return WorkerSigningPool.getInstance(config);
|
|
670
|
+
}
|