@btc-vision/bitcoin 6.5.6 → 7.0.0-alpha.1
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/HOW_TO_WRITE_GOOD_CODE.md +2436 -0
- package/benchmark/psbt-2000-inputs.bench.ts +178 -0
- package/benchmark/signing.bench.ts +147 -0
- package/browser/address.d.ts +57 -10
- 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 +12477 -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 +58 -11
- package/build/address.d.ts.map +1 -0
- package/build/address.js +82 -25
- 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 +202 -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 +35 -9
- package/build/payments/bip341.d.ts.map +1 -0
- package/build/payments/bip341.js +34 -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 -150
- 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 +428 -103
- 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 +465 -143
- 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 +117 -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 +406 -413
- 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 +36 -12
- 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 +101 -37
- 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 +169 -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 +68 -9
- package/scripts/bundle-ecc.ts +111 -0
- package/src/address.ts +91 -45
- package/src/bech32utils.ts +3 -3
- package/src/bip66.ts +34 -24
- package/src/block.ts +205 -86
- package/src/branded.ts +18 -0
- package/src/crypto.ts +64 -26
- package/src/ecc/context.ts +280 -0
- package/src/ecc/index.ts +14 -0
- package/src/ecc/types.ts +147 -0
- package/src/ecpair.d.ts +99 -0
- package/src/errors.ts +163 -0
- package/src/index.ts +112 -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 +32 -33
- package/src/payments/embed.ts +244 -41
- package/src/payments/index.ts +12 -10
- package/src/payments/p2ms.ts +497 -118
- package/src/payments/p2op.ts +432 -134
- package/src/payments/p2pk.ts +370 -72
- package/src/payments/p2pkh.ts +524 -130
- package/src/payments/p2sh.ts +572 -169
- package/src/payments/p2tr.ts +686 -194
- package/src/payments/p2wpkh.ts +482 -105
- package/src/payments/p2wsh.ts +524 -162
- package/src/payments/types.ts +80 -66
- package/src/psbt/bip371.ts +72 -51
- package/src/psbt/psbtutils.ts +39 -40
- package/src/psbt/types.ts +324 -0
- package/src/psbt/utils.ts +188 -0
- package/src/psbt/validation.ts +185 -0
- package/src/psbt.ts +608 -827
- package/src/pubkey.ts +22 -23
- package/src/push_data.ts +18 -16
- package/src/script.ts +81 -66
- package/src/script_number.ts +6 -6
- package/src/script_signature.ts +33 -36
- package/src/transaction.ts +462 -239
- package/src/types.ts +229 -100
- package/src/workers/WorkerSigningPool.node.ts +887 -0
- package/src/workers/WorkerSigningPool.ts +666 -0
- package/src/workers/ecc-bundle.ts +26 -0
- package/src/workers/index.ts +165 -0
- package/src/workers/psbt-parallel.ts +327 -0
- package/src/workers/signing-worker.ts +353 -0
- package/src/workers/types.ts +417 -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/typedoc.json +29 -0
- package/vite.config.browser.ts +3 -42
- package/vitest.config.integration.ts +2 -0
- package/browser/bufferutils.d.ts +0 -34
- package/browser/chunks/crypto-BhCpKpek.js +0 -2033
- package/browser/chunks/payments-B1wlSccx.js +0 -1089
- package/browser/chunks/psbt-BCNk7JUx.js +0 -4055
- 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,887 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node.js-specific worker signing pool implementation.
|
|
3
|
+
*
|
|
4
|
+
* Uses worker_threads module for true parallel execution.
|
|
5
|
+
* Private keys are isolated per-worker and zeroed immediately after signing.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { isMainThread, Worker } from 'worker_threads';
|
|
11
|
+
import { cpus } from 'os';
|
|
12
|
+
import type {
|
|
13
|
+
BatchSigningMessage,
|
|
14
|
+
BatchSigningResultMessage,
|
|
15
|
+
BatchSigningTask,
|
|
16
|
+
ParallelSignerKeyPair,
|
|
17
|
+
ParallelSigningResult,
|
|
18
|
+
PooledWorker,
|
|
19
|
+
SigningResultMessage,
|
|
20
|
+
SigningTask,
|
|
21
|
+
WorkerPoolConfig,
|
|
22
|
+
WorkerResponse,
|
|
23
|
+
} from './types.js';
|
|
24
|
+
import { isBatchResult, isWorkerReady, WorkerState } from './types.js';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* ECC library types for Node.js worker.
|
|
28
|
+
*/
|
|
29
|
+
export const NodeEccLibrary = {
|
|
30
|
+
/** Pure JS @noble/secp256k1 (default, ~12KB) */
|
|
31
|
+
Noble: 'noble',
|
|
32
|
+
/** WASM-based tiny-secp256k1 (faster, ~1.2MB) */
|
|
33
|
+
TinySecp256k1: 'tiny-secp256k1',
|
|
34
|
+
} as const;
|
|
35
|
+
|
|
36
|
+
export type NodeEccLibrary = (typeof NodeEccLibrary)[keyof typeof NodeEccLibrary];
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Extended configuration for Node.js worker pool.
|
|
40
|
+
*/
|
|
41
|
+
export interface NodeWorkerPoolConfig extends WorkerPoolConfig {
|
|
42
|
+
/**
|
|
43
|
+
* ECC library type for signing.
|
|
44
|
+
* - NodeEccLibrary.Noble: Pure JS @noble/secp256k1 (default, ~12KB)
|
|
45
|
+
* - NodeEccLibrary.TinySecp256k1: WASM-based tiny-secp256k1 (faster)
|
|
46
|
+
*
|
|
47
|
+
* Default: NodeEccLibrary.Noble
|
|
48
|
+
*/
|
|
49
|
+
readonly eccLibrary?: NodeEccLibrary;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Default configuration values for Node.js.
|
|
54
|
+
*/
|
|
55
|
+
const DEFAULT_CONFIG: Required<NodeWorkerPoolConfig> = {
|
|
56
|
+
workerCount: cpus().length,
|
|
57
|
+
taskTimeoutMs: 30000,
|
|
58
|
+
maxKeyHoldTimeMs: 5000,
|
|
59
|
+
verifySignatures: true,
|
|
60
|
+
preserveWorkers: false,
|
|
61
|
+
eccLibrary: NodeEccLibrary.TinySecp256k1,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Pending batch awaiting completion.
|
|
66
|
+
*/
|
|
67
|
+
interface PendingBatch {
|
|
68
|
+
readonly batchId: string;
|
|
69
|
+
readonly resolve: (result: BatchSigningResultMessage) => void;
|
|
70
|
+
readonly reject: (error: Error) => void;
|
|
71
|
+
readonly timeoutId: ReturnType<typeof setTimeout>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Node.js-specific pooled worker.
|
|
76
|
+
*/
|
|
77
|
+
interface NodePooledWorker extends Omit<PooledWorker, 'worker'> {
|
|
78
|
+
readonly worker: Worker;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Worker-based parallel signing pool for Node.js.
|
|
83
|
+
*
|
|
84
|
+
* Uses worker_threads for true parallel execution.
|
|
85
|
+
* Provides secure key handling with immediate zeroing after use.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```typescript
|
|
89
|
+
* import { NodeWorkerSigningPool } from '@btc-vision/bitcoin/workers';
|
|
90
|
+
*
|
|
91
|
+
* // Initialize pool at app startup
|
|
92
|
+
* const pool = NodeWorkerSigningPool.getInstance({ workerCount: 4 });
|
|
93
|
+
* pool.preserveWorkers();
|
|
94
|
+
*
|
|
95
|
+
* // Sign batch
|
|
96
|
+
* const result = await pool.signBatch(tasks, keyPair);
|
|
97
|
+
*
|
|
98
|
+
* // Cleanup at app shutdown
|
|
99
|
+
* await pool.shutdown();
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
export class NodeWorkerSigningPool {
|
|
103
|
+
/**
|
|
104
|
+
* Singleton instance.
|
|
105
|
+
*/
|
|
106
|
+
static #instance: NodeWorkerSigningPool | null = null;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Pool configuration.
|
|
110
|
+
*/
|
|
111
|
+
readonly #config: Required<NodeWorkerPoolConfig>;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Worker pool.
|
|
115
|
+
*/
|
|
116
|
+
readonly #workers: NodePooledWorker[] = [];
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Pending batches awaiting completion.
|
|
120
|
+
*/
|
|
121
|
+
readonly #pendingBatches: Map<string, PendingBatch> = new Map();
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Worker script as data URL.
|
|
125
|
+
*/
|
|
126
|
+
#workerScript: string | null = null;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Whether workers are preserved between batches.
|
|
130
|
+
*/
|
|
131
|
+
#preserveWorkers: boolean = false;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Next worker ID counter.
|
|
135
|
+
*/
|
|
136
|
+
#nextWorkerId: number = 0;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Next task ID counter.
|
|
140
|
+
*/
|
|
141
|
+
#nextTaskId: number = 0;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Whether the pool is initialized.
|
|
145
|
+
*/
|
|
146
|
+
#initialized: boolean = false;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Whether the pool is shutting down.
|
|
150
|
+
*/
|
|
151
|
+
#shuttingDown: boolean = false;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Creates a new NodeWorkerSigningPool.
|
|
155
|
+
*
|
|
156
|
+
* @param config - Pool configuration
|
|
157
|
+
*/
|
|
158
|
+
private constructor(config: NodeWorkerPoolConfig = {}) {
|
|
159
|
+
if (!isMainThread) {
|
|
160
|
+
throw new Error('NodeWorkerSigningPool can only be created in the main thread');
|
|
161
|
+
}
|
|
162
|
+
this.#config = { ...DEFAULT_CONFIG, ...config };
|
|
163
|
+
this.#preserveWorkers = this.#config.preserveWorkers;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Number of workers in the pool.
|
|
168
|
+
*/
|
|
169
|
+
public get workerCount(): number {
|
|
170
|
+
return this.#workers.length;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Number of idle workers available.
|
|
175
|
+
*/
|
|
176
|
+
public get idleWorkerCount(): number {
|
|
177
|
+
return this.#workers.filter((w) => w.state === WorkerState.Idle).length;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Number of busy workers.
|
|
182
|
+
*/
|
|
183
|
+
public get busyWorkerCount(): number {
|
|
184
|
+
return this.#workers.filter((w) => w.state === WorkerState.Busy).length;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Whether workers are being preserved between batches.
|
|
189
|
+
*/
|
|
190
|
+
public get isPreservingWorkers(): boolean {
|
|
191
|
+
return this.#preserveWorkers;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Gets the singleton pool instance.
|
|
196
|
+
*
|
|
197
|
+
* @param config - Optional configuration (only used on first call)
|
|
198
|
+
* @returns The singleton pool instance
|
|
199
|
+
*/
|
|
200
|
+
public static getInstance(config?: NodeWorkerPoolConfig): NodeWorkerSigningPool {
|
|
201
|
+
if (!NodeWorkerSigningPool.#instance) {
|
|
202
|
+
NodeWorkerSigningPool.#instance = new NodeWorkerSigningPool(config);
|
|
203
|
+
}
|
|
204
|
+
return NodeWorkerSigningPool.#instance;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Resets the singleton instance (for testing).
|
|
209
|
+
*/
|
|
210
|
+
public static resetInstance(): void {
|
|
211
|
+
if (NodeWorkerSigningPool.#instance) {
|
|
212
|
+
NodeWorkerSigningPool.#instance.shutdown().catch(() => {});
|
|
213
|
+
NodeWorkerSigningPool.#instance = null;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Enables worker preservation between signing batches.
|
|
219
|
+
*/
|
|
220
|
+
public preserveWorkers(): void {
|
|
221
|
+
this.#preserveWorkers = true;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Disables worker preservation.
|
|
226
|
+
*/
|
|
227
|
+
public releaseWorkers(): void {
|
|
228
|
+
this.#preserveWorkers = false;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Initializes the worker pool.
|
|
233
|
+
*
|
|
234
|
+
* @returns Promise that resolves when all workers are ready
|
|
235
|
+
*/
|
|
236
|
+
public async initialize(): Promise<void> {
|
|
237
|
+
if (this.#initialized) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (this.#shuttingDown) {
|
|
242
|
+
throw new Error('Cannot initialize pool while shutting down');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Create inline worker script
|
|
246
|
+
this.#workerScript = this.#createWorkerScript();
|
|
247
|
+
|
|
248
|
+
// Create workers
|
|
249
|
+
const workerPromises: Promise<void>[] = [];
|
|
250
|
+
for (let i = 0; i < this.#config.workerCount; i++) {
|
|
251
|
+
workerPromises.push(this.#createWorker());
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
await Promise.all(workerPromises);
|
|
255
|
+
this.#initialized = true;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Signs a batch of tasks in parallel.
|
|
260
|
+
*
|
|
261
|
+
* Tasks are distributed across workers and processed in batches for efficiency.
|
|
262
|
+
*
|
|
263
|
+
* @param tasks - Signing tasks
|
|
264
|
+
* @param keyPair - Key pair with getPrivateKey() method
|
|
265
|
+
* @returns Promise resolving to signing results
|
|
266
|
+
*/
|
|
267
|
+
public async signBatch(
|
|
268
|
+
tasks: readonly SigningTask[],
|
|
269
|
+
keyPair: ParallelSignerKeyPair,
|
|
270
|
+
): Promise<ParallelSigningResult> {
|
|
271
|
+
const startTime = performance.now();
|
|
272
|
+
|
|
273
|
+
// Initialize if needed
|
|
274
|
+
if (!this.#initialized) {
|
|
275
|
+
await this.initialize();
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (tasks.length === 0) {
|
|
279
|
+
return {
|
|
280
|
+
success: true,
|
|
281
|
+
signatures: new Map(),
|
|
282
|
+
errors: new Map(),
|
|
283
|
+
durationMs: performance.now() - startTime,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Distribute tasks across workers
|
|
288
|
+
const workerCount = Math.min(this.#workers.length, tasks.length);
|
|
289
|
+
const taskBatches: SigningTask[][] = Array.from({ length: workerCount }, () => []);
|
|
290
|
+
|
|
291
|
+
for (let i = 0; i < tasks.length; i++) {
|
|
292
|
+
taskBatches[i % workerCount]!.push(tasks[i]!);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Get private key once
|
|
296
|
+
const privateKey = keyPair.getPrivateKey();
|
|
297
|
+
|
|
298
|
+
try {
|
|
299
|
+
// Send batches to workers in parallel
|
|
300
|
+
const batchResults = await Promise.allSettled(
|
|
301
|
+
taskBatches.map((batch, index) =>
|
|
302
|
+
this.#signBatchOnWorker(batch, privateKey, keyPair.publicKey, index),
|
|
303
|
+
),
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
// Collect all results
|
|
307
|
+
const signatures = new Map<number, SigningResultMessage>();
|
|
308
|
+
const errors = new Map<number, string>();
|
|
309
|
+
|
|
310
|
+
for (const result of batchResults) {
|
|
311
|
+
if (result.status === 'fulfilled') {
|
|
312
|
+
const batchResult = result.value;
|
|
313
|
+
|
|
314
|
+
// Add successful signatures
|
|
315
|
+
for (const sig of batchResult.results) {
|
|
316
|
+
signatures.set(sig.inputIndex, {
|
|
317
|
+
type: 'result',
|
|
318
|
+
taskId: sig.taskId,
|
|
319
|
+
signature: sig.signature,
|
|
320
|
+
inputIndex: sig.inputIndex,
|
|
321
|
+
publicKey: sig.publicKey,
|
|
322
|
+
signatureType: sig.signatureType,
|
|
323
|
+
leafHash: sig.leafHash,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Add errors
|
|
328
|
+
for (const err of batchResult.errors) {
|
|
329
|
+
errors.set(err.inputIndex, err.error);
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
// Entire batch failed
|
|
333
|
+
const reason = result.reason as { message?: string } | undefined;
|
|
334
|
+
const errorMsg = reason?.message ?? 'Batch signing failed';
|
|
335
|
+
console.error('Batch signing failed:', errorMsg);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Cleanup workers if not preserving
|
|
340
|
+
if (!this.#preserveWorkers) {
|
|
341
|
+
await this.#terminateIdleWorkers();
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
success: errors.size === 0,
|
|
346
|
+
signatures,
|
|
347
|
+
errors,
|
|
348
|
+
durationMs: performance.now() - startTime,
|
|
349
|
+
};
|
|
350
|
+
} finally {
|
|
351
|
+
// SECURITY: Zero the key in main thread
|
|
352
|
+
privateKey.fill(0);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Shuts down the pool and terminates all workers.
|
|
358
|
+
*
|
|
359
|
+
* @returns Promise that resolves when all workers are terminated
|
|
360
|
+
*/
|
|
361
|
+
public async shutdown(): Promise<void> {
|
|
362
|
+
if (this.#shuttingDown) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
this.#shuttingDown = true;
|
|
367
|
+
|
|
368
|
+
// Terminate all workers
|
|
369
|
+
const terminatePromises = this.#workers.map((worker) => this.#terminateWorker(worker));
|
|
370
|
+
|
|
371
|
+
await Promise.all(terminatePromises);
|
|
372
|
+
|
|
373
|
+
// Clear state
|
|
374
|
+
this.#workers.length = 0;
|
|
375
|
+
this.#pendingBatches.clear();
|
|
376
|
+
this.#workerScript = null;
|
|
377
|
+
this.#initialized = false;
|
|
378
|
+
this.#shuttingDown = false;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Creates the inline worker script for Node.js worker_threads.
|
|
383
|
+
* Supports both @noble/secp256k1 (pure JS) and tiny-secp256k1 (WASM).
|
|
384
|
+
*/
|
|
385
|
+
#createWorkerScript(): string {
|
|
386
|
+
// Node.js worker_threads can directly require/import modules
|
|
387
|
+
const workerCode = `
|
|
388
|
+
const { parentPort } = require('worker_threads');
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Zero out a Uint8Array to clear sensitive data.
|
|
392
|
+
*/
|
|
393
|
+
function secureZero(arr) {
|
|
394
|
+
if (arr && arr.fill) {
|
|
395
|
+
arr.fill(0);
|
|
396
|
+
// Double-write to prevent optimization
|
|
397
|
+
for (let i = 0; i < arr.length; i++) {
|
|
398
|
+
arr[i] = 0;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* ECC library reference.
|
|
405
|
+
*/
|
|
406
|
+
let eccLib = null;
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Initialize ECC library based on type.
|
|
410
|
+
*/
|
|
411
|
+
async function initEcc(eccType) {
|
|
412
|
+
if (eccType === '${NodeEccLibrary.TinySecp256k1}') {
|
|
413
|
+
// Load tiny-secp256k1 (WASM-based, faster for batch operations)
|
|
414
|
+
const tinysecp = await import('tiny-secp256k1');
|
|
415
|
+
eccLib = {
|
|
416
|
+
sign: (hash, privateKey) => {
|
|
417
|
+
return tinysecp.sign(hash, privateKey);
|
|
418
|
+
},
|
|
419
|
+
signSchnorr: (hash, privateKey) => {
|
|
420
|
+
return tinysecp.signSchnorr(hash, privateKey);
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
} else {
|
|
424
|
+
// Default to @noble/secp256k1 (pure JS, no WASM)
|
|
425
|
+
const noble = await import('@noble/secp256k1');
|
|
426
|
+
eccLib = {
|
|
427
|
+
sign: (hash, privateKey) => {
|
|
428
|
+
// noble returns Signature object, we need raw bytes
|
|
429
|
+
const sig = noble.sign(hash, privateKey, { lowS: true });
|
|
430
|
+
return sig.toCompactRawBytes();
|
|
431
|
+
},
|
|
432
|
+
signSchnorr: (hash, privateKey) => {
|
|
433
|
+
return noble.schnorr.sign(hash, privateKey);
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
parentPort.on('message', async (msg) => {
|
|
440
|
+
switch (msg.type) {
|
|
441
|
+
case 'init':
|
|
442
|
+
try {
|
|
443
|
+
await initEcc(msg.eccType || 'tiny-secp256k1');
|
|
444
|
+
parentPort.postMessage({ type: 'ready' });
|
|
445
|
+
} catch (error) {
|
|
446
|
+
parentPort.postMessage({
|
|
447
|
+
type: 'error',
|
|
448
|
+
taskId: 'init',
|
|
449
|
+
error: 'Failed to load ECC library: ' + (error.message || error),
|
|
450
|
+
inputIndex: -1
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
break;
|
|
454
|
+
|
|
455
|
+
case 'sign':
|
|
456
|
+
handleSign(msg);
|
|
457
|
+
break;
|
|
458
|
+
|
|
459
|
+
case 'signBatch':
|
|
460
|
+
handleSignBatch(msg);
|
|
461
|
+
break;
|
|
462
|
+
|
|
463
|
+
case 'shutdown':
|
|
464
|
+
parentPort.postMessage({ type: 'shutdown-ack' });
|
|
465
|
+
process.exit(0);
|
|
466
|
+
break;
|
|
467
|
+
|
|
468
|
+
default:
|
|
469
|
+
parentPort.postMessage({
|
|
470
|
+
type: 'error',
|
|
471
|
+
taskId: msg.taskId || 'unknown',
|
|
472
|
+
error: 'Unknown message type: ' + msg.type,
|
|
473
|
+
inputIndex: msg.inputIndex || -1
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
function handleSign(msg) {
|
|
479
|
+
const {
|
|
480
|
+
taskId,
|
|
481
|
+
hash,
|
|
482
|
+
privateKey,
|
|
483
|
+
publicKey,
|
|
484
|
+
signatureType,
|
|
485
|
+
lowR,
|
|
486
|
+
inputIndex,
|
|
487
|
+
sighashType,
|
|
488
|
+
leafHash
|
|
489
|
+
} = msg;
|
|
490
|
+
|
|
491
|
+
// Validate inputs
|
|
492
|
+
if (!hash || hash.length !== 32) {
|
|
493
|
+
secureZero(privateKey);
|
|
494
|
+
parentPort.postMessage({
|
|
495
|
+
type: 'error',
|
|
496
|
+
taskId: taskId,
|
|
497
|
+
error: 'Invalid hash: must be 32 bytes',
|
|
498
|
+
inputIndex: inputIndex
|
|
499
|
+
});
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (!privateKey || privateKey.length !== 32) {
|
|
504
|
+
secureZero(privateKey);
|
|
505
|
+
parentPort.postMessage({
|
|
506
|
+
type: 'error',
|
|
507
|
+
taskId: taskId,
|
|
508
|
+
error: 'Invalid private key: must be 32 bytes',
|
|
509
|
+
inputIndex: inputIndex
|
|
510
|
+
});
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (!eccLib) {
|
|
515
|
+
secureZero(privateKey);
|
|
516
|
+
parentPort.postMessage({
|
|
517
|
+
type: 'error',
|
|
518
|
+
taskId: taskId,
|
|
519
|
+
error: 'ECC library not initialized. Call init first.',
|
|
520
|
+
inputIndex: inputIndex
|
|
521
|
+
});
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
let signature;
|
|
526
|
+
|
|
527
|
+
try {
|
|
528
|
+
if (signatureType === 1) {
|
|
529
|
+
// Schnorr signature (BIP340)
|
|
530
|
+
signature = eccLib.signSchnorr(hash, privateKey);
|
|
531
|
+
} else {
|
|
532
|
+
// ECDSA signature
|
|
533
|
+
signature = eccLib.sign(hash, privateKey, { lowR: lowR || false });
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
if (!signature) {
|
|
537
|
+
throw new Error('Signing returned null or undefined');
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
} catch (error) {
|
|
541
|
+
secureZero(privateKey);
|
|
542
|
+
parentPort.postMessage({
|
|
543
|
+
type: 'error',
|
|
544
|
+
taskId: taskId,
|
|
545
|
+
error: error.message || 'Signing failed',
|
|
546
|
+
inputIndex: inputIndex
|
|
547
|
+
});
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// CRITICAL: Zero the private key immediately
|
|
552
|
+
secureZero(privateKey);
|
|
553
|
+
|
|
554
|
+
const result = {
|
|
555
|
+
type: 'result',
|
|
556
|
+
taskId: taskId,
|
|
557
|
+
signature: signature,
|
|
558
|
+
inputIndex: inputIndex,
|
|
559
|
+
publicKey: publicKey,
|
|
560
|
+
signatureType: signatureType
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
if (leafHash) {
|
|
564
|
+
result.leafHash = leafHash;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
parentPort.postMessage(result);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function handleSignBatch(msg) {
|
|
571
|
+
const { batchId, tasks, privateKey } = msg;
|
|
572
|
+
const results = [];
|
|
573
|
+
const errors = [];
|
|
574
|
+
|
|
575
|
+
// Validate private key once
|
|
576
|
+
if (!privateKey || privateKey.length !== 32) {
|
|
577
|
+
secureZero(privateKey);
|
|
578
|
+
parentPort.postMessage({
|
|
579
|
+
type: 'batchResult',
|
|
580
|
+
batchId: batchId,
|
|
581
|
+
results: [],
|
|
582
|
+
errors: [{ inputIndex: -1, error: 'Invalid private key: must be 32 bytes' }]
|
|
583
|
+
});
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if (!eccLib) {
|
|
588
|
+
secureZero(privateKey);
|
|
589
|
+
parentPort.postMessage({
|
|
590
|
+
type: 'batchResult',
|
|
591
|
+
batchId: batchId,
|
|
592
|
+
results: [],
|
|
593
|
+
errors: [{ inputIndex: -1, error: 'ECC library not initialized. Call init first.' }]
|
|
594
|
+
});
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Process all tasks
|
|
599
|
+
for (const task of tasks) {
|
|
600
|
+
const { taskId, hash, publicKey, signatureType, lowR, inputIndex, sighashType, leafHash } = task;
|
|
601
|
+
|
|
602
|
+
// Validate hash
|
|
603
|
+
if (!hash || hash.length !== 32) {
|
|
604
|
+
errors.push({ taskId, inputIndex, error: 'Invalid hash: must be 32 bytes' });
|
|
605
|
+
continue;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
try {
|
|
609
|
+
let signature;
|
|
610
|
+
if (signatureType === 1) {
|
|
611
|
+
// Schnorr signature (BIP340)
|
|
612
|
+
signature = eccLib.signSchnorr(hash, privateKey);
|
|
613
|
+
} else {
|
|
614
|
+
// ECDSA signature
|
|
615
|
+
signature = eccLib.sign(hash, privateKey, { lowR: lowR || false });
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
if (!signature) {
|
|
619
|
+
throw new Error('Signing returned null or undefined');
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
const result = {
|
|
623
|
+
taskId: taskId,
|
|
624
|
+
signature: signature,
|
|
625
|
+
inputIndex: inputIndex,
|
|
626
|
+
publicKey: publicKey,
|
|
627
|
+
signatureType: signatureType
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
if (leafHash) {
|
|
631
|
+
result.leafHash = leafHash;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
results.push(result);
|
|
635
|
+
} catch (error) {
|
|
636
|
+
errors.push({ taskId, inputIndex, error: error.message || 'Signing failed' });
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// CRITICAL: Zero the private key after processing all tasks
|
|
641
|
+
secureZero(privateKey);
|
|
642
|
+
|
|
643
|
+
// Send batch result back
|
|
644
|
+
parentPort.postMessage({
|
|
645
|
+
type: 'batchResult',
|
|
646
|
+
batchId: batchId,
|
|
647
|
+
results: results,
|
|
648
|
+
errors: errors
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
`;
|
|
652
|
+
return workerCode;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Creates a new worker and adds it to the pool.
|
|
657
|
+
*/
|
|
658
|
+
async #createWorker(): Promise<void> {
|
|
659
|
+
if (!this.#workerScript) {
|
|
660
|
+
throw new Error('Worker script not created');
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
const workerId = this.#nextWorkerId++;
|
|
664
|
+
|
|
665
|
+
// Create worker with eval code
|
|
666
|
+
const worker = new Worker(this.#workerScript, {
|
|
667
|
+
eval: true,
|
|
668
|
+
name: `signing-worker-${workerId}`,
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
const pooledWorker: NodePooledWorker = {
|
|
672
|
+
id: workerId,
|
|
673
|
+
state: WorkerState.Initializing,
|
|
674
|
+
worker,
|
|
675
|
+
currentTaskId: null,
|
|
676
|
+
taskStartTime: null,
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
this.#workers.push(pooledWorker);
|
|
680
|
+
|
|
681
|
+
// Wait for worker to be ready
|
|
682
|
+
await new Promise<void>((resolve, reject) => {
|
|
683
|
+
const timeout = setTimeout(() => {
|
|
684
|
+
reject(new Error(`Worker ${workerId} initialization timeout`));
|
|
685
|
+
}, 10000);
|
|
686
|
+
|
|
687
|
+
const messageHandler = (data: WorkerResponse): void => {
|
|
688
|
+
if (isWorkerReady(data)) {
|
|
689
|
+
clearTimeout(timeout);
|
|
690
|
+
worker.off('message', messageHandler);
|
|
691
|
+
pooledWorker.state = WorkerState.Idle;
|
|
692
|
+
resolve();
|
|
693
|
+
}
|
|
694
|
+
};
|
|
695
|
+
|
|
696
|
+
worker.on('message', messageHandler);
|
|
697
|
+
worker.on('error', (error: Error) => {
|
|
698
|
+
clearTimeout(timeout);
|
|
699
|
+
reject(new Error(`Worker ${workerId} error: ${error.message}`));
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
// Send init message with ECC library type
|
|
703
|
+
worker.postMessage({
|
|
704
|
+
type: 'init',
|
|
705
|
+
eccType: this.#config.eccLibrary,
|
|
706
|
+
});
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
// Set up message handler for signing results
|
|
710
|
+
worker.on('message', (data: WorkerResponse) => {
|
|
711
|
+
this.#handleWorkerMessage(pooledWorker, data);
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Signs a batch of tasks on a specific worker.
|
|
717
|
+
*/
|
|
718
|
+
async #signBatchOnWorker(
|
|
719
|
+
tasks: readonly SigningTask[],
|
|
720
|
+
privateKey: Uint8Array,
|
|
721
|
+
publicKey: Uint8Array,
|
|
722
|
+
workerIndex: number,
|
|
723
|
+
): Promise<BatchSigningResultMessage> {
|
|
724
|
+
if (tasks.length === 0) {
|
|
725
|
+
return { type: 'batchResult', batchId: '', results: [], errors: [] };
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// Get worker at index (or wait for idle)
|
|
729
|
+
const worker = this.#workers[workerIndex] ?? (await this.#getIdleWorker());
|
|
730
|
+
|
|
731
|
+
// Generate unique batch ID
|
|
732
|
+
const batchId = `batch-${this.#nextTaskId++}`;
|
|
733
|
+
|
|
734
|
+
return new Promise<BatchSigningResultMessage>((resolve, reject) => {
|
|
735
|
+
// Set up timeout
|
|
736
|
+
const timeoutId = setTimeout(() => {
|
|
737
|
+
this.#pendingBatches.delete(batchId);
|
|
738
|
+
worker.state = WorkerState.Idle;
|
|
739
|
+
worker.currentTaskId = null;
|
|
740
|
+
worker.taskStartTime = null;
|
|
741
|
+
|
|
742
|
+
// SECURITY: Terminate worker that exceeded key hold time
|
|
743
|
+
this.#terminateWorker(worker).catch(() => {});
|
|
744
|
+
this.#createWorker().catch(() => {});
|
|
745
|
+
|
|
746
|
+
reject(new Error(`Batch signing timeout for ${tasks.length} tasks`));
|
|
747
|
+
}, this.#config.maxKeyHoldTimeMs);
|
|
748
|
+
|
|
749
|
+
// Store pending batch
|
|
750
|
+
const pendingBatch: PendingBatch = {
|
|
751
|
+
batchId,
|
|
752
|
+
resolve,
|
|
753
|
+
reject,
|
|
754
|
+
timeoutId,
|
|
755
|
+
};
|
|
756
|
+
this.#pendingBatches.set(batchId, pendingBatch);
|
|
757
|
+
|
|
758
|
+
// Mark worker as busy
|
|
759
|
+
worker.state = WorkerState.Busy;
|
|
760
|
+
worker.currentTaskId = batchId;
|
|
761
|
+
worker.taskStartTime = Date.now();
|
|
762
|
+
|
|
763
|
+
// Convert tasks to batch format
|
|
764
|
+
const batchTasks: BatchSigningTask[] = tasks.map((task) => ({
|
|
765
|
+
taskId: task.taskId,
|
|
766
|
+
hash: task.hash,
|
|
767
|
+
publicKey,
|
|
768
|
+
signatureType: task.signatureType,
|
|
769
|
+
lowR: task.lowR,
|
|
770
|
+
inputIndex: task.inputIndex,
|
|
771
|
+
sighashType: task.sighashType,
|
|
772
|
+
leafHash: task.leafHash,
|
|
773
|
+
}));
|
|
774
|
+
|
|
775
|
+
// Create batch message
|
|
776
|
+
const message: BatchSigningMessage = {
|
|
777
|
+
type: 'signBatch',
|
|
778
|
+
batchId,
|
|
779
|
+
tasks: batchTasks,
|
|
780
|
+
privateKey,
|
|
781
|
+
};
|
|
782
|
+
|
|
783
|
+
// Send to worker
|
|
784
|
+
worker.worker.postMessage(message);
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* Gets an idle worker, creating one if necessary.
|
|
790
|
+
*/
|
|
791
|
+
async #getIdleWorker(): Promise<NodePooledWorker> {
|
|
792
|
+
let worker = this.#workers.find((w) => w.state === WorkerState.Idle);
|
|
793
|
+
|
|
794
|
+
if (worker) {
|
|
795
|
+
return worker;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
if (this.#workers.length < this.#config.workerCount) {
|
|
799
|
+
await this.#createWorker();
|
|
800
|
+
worker = this.#workers.find((w) => w.state === WorkerState.Idle);
|
|
801
|
+
if (worker) {
|
|
802
|
+
return worker;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
return new Promise<NodePooledWorker>((resolve) => {
|
|
807
|
+
const checkInterval = setInterval(() => {
|
|
808
|
+
const idleWorker = this.#workers.find((w) => w.state === WorkerState.Idle);
|
|
809
|
+
if (idleWorker) {
|
|
810
|
+
clearInterval(checkInterval);
|
|
811
|
+
resolve(idleWorker);
|
|
812
|
+
}
|
|
813
|
+
}, 10);
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
/**
|
|
818
|
+
* Handles a message from a worker.
|
|
819
|
+
*/
|
|
820
|
+
#handleWorkerMessage(worker: NodePooledWorker, response: WorkerResponse): void {
|
|
821
|
+
if (isBatchResult(response)) {
|
|
822
|
+
const pending = this.#pendingBatches.get(response.batchId);
|
|
823
|
+
if (pending) {
|
|
824
|
+
clearTimeout(pending.timeoutId);
|
|
825
|
+
this.#pendingBatches.delete(response.batchId);
|
|
826
|
+
worker.state = WorkerState.Idle;
|
|
827
|
+
worker.currentTaskId = null;
|
|
828
|
+
worker.taskStartTime = null;
|
|
829
|
+
pending.resolve(response);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
// Ignore ready and shutdown-ack messages here (handled elsewhere)
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
/**
|
|
836
|
+
* Terminates a worker.
|
|
837
|
+
*/
|
|
838
|
+
async #terminateWorker(worker: NodePooledWorker): Promise<void> {
|
|
839
|
+
if (worker.state === WorkerState.Terminated) {
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
worker.state = WorkerState.ShuttingDown;
|
|
844
|
+
worker.worker.postMessage({ type: 'shutdown' });
|
|
845
|
+
|
|
846
|
+
await new Promise<void>((resolve) => {
|
|
847
|
+
const timeout = setTimeout(async () => {
|
|
848
|
+
await worker.worker.terminate();
|
|
849
|
+
worker.state = WorkerState.Terminated;
|
|
850
|
+
resolve();
|
|
851
|
+
}, 1000);
|
|
852
|
+
|
|
853
|
+
const handler = (data: WorkerResponse): void => {
|
|
854
|
+
if (data.type === 'shutdown-ack') {
|
|
855
|
+
clearTimeout(timeout);
|
|
856
|
+
worker.worker.off('message', handler);
|
|
857
|
+
void worker.worker.terminate().then(() => {
|
|
858
|
+
worker.state = WorkerState.Terminated;
|
|
859
|
+
resolve();
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
};
|
|
863
|
+
|
|
864
|
+
worker.worker.on('message', handler);
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
const index = this.#workers.indexOf(worker);
|
|
868
|
+
if (index >= 0) {
|
|
869
|
+
this.#workers.splice(index, 1);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
/**
|
|
874
|
+
* Terminates all idle workers.
|
|
875
|
+
*/
|
|
876
|
+
async #terminateIdleWorkers(): Promise<void> {
|
|
877
|
+
const idleWorkers = this.#workers.filter((w) => w.state === WorkerState.Idle);
|
|
878
|
+
await Promise.all(idleWorkers.map((w) => this.#terminateWorker(w)));
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
/**
|
|
883
|
+
* Convenience function to get the singleton pool instance.
|
|
884
|
+
*/
|
|
885
|
+
export function getNodeSigningPool(config?: NodeWorkerPoolConfig): NodeWorkerSigningPool {
|
|
886
|
+
return NodeWorkerSigningPool.getInstance(config);
|
|
887
|
+
}
|