@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,963 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi, type Mock } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
SignatureType,
|
|
4
|
+
WorkerState,
|
|
5
|
+
type BatchSigningTaskResult,
|
|
6
|
+
type SigningTask,
|
|
7
|
+
type ParallelSignerKeyPair,
|
|
8
|
+
type WorkerResponse,
|
|
9
|
+
type SigningResultMessage,
|
|
10
|
+
type WorkerPoolConfig,
|
|
11
|
+
} from '../src/workers/types.js';
|
|
12
|
+
|
|
13
|
+
// Mock Worker class for browser pool testing
|
|
14
|
+
class MockWorker {
|
|
15
|
+
public onmessage: ((event: { data: WorkerResponse }) => void) | null = null;
|
|
16
|
+
public onerror: ((error: Error) => void) | null = null;
|
|
17
|
+
private messageHandlers: Array<(event: { data: WorkerResponse }) => void> = [];
|
|
18
|
+
private errorHandlers: Array<(error: Error) => void> = [];
|
|
19
|
+
private terminated = false;
|
|
20
|
+
|
|
21
|
+
constructor(
|
|
22
|
+
public readonly url: string,
|
|
23
|
+
public readonly options?: { name?: string },
|
|
24
|
+
) {
|
|
25
|
+
// Simulate async ready after construction
|
|
26
|
+
setTimeout(() => {
|
|
27
|
+
if (!this.terminated) {
|
|
28
|
+
this.simulateMessage({ type: 'ready' });
|
|
29
|
+
}
|
|
30
|
+
}, 5);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
postMessage(data: unknown): void {
|
|
34
|
+
if (this.terminated) return;
|
|
35
|
+
|
|
36
|
+
const msg = data as { type: string; taskId?: string; privateKey?: Uint8Array };
|
|
37
|
+
|
|
38
|
+
if (msg.type === 'init') {
|
|
39
|
+
// Already sent ready in constructor
|
|
40
|
+
} else if (msg.type === 'signBatch') {
|
|
41
|
+
// Simulate batch signing - zero the key and return results
|
|
42
|
+
setTimeout(() => {
|
|
43
|
+
if (this.terminated) return;
|
|
44
|
+
|
|
45
|
+
const batchMsg = data as {
|
|
46
|
+
batchId: string;
|
|
47
|
+
tasks: Array<{
|
|
48
|
+
taskId: string;
|
|
49
|
+
inputIndex: number;
|
|
50
|
+
publicKey: Uint8Array;
|
|
51
|
+
signatureType: SignatureType;
|
|
52
|
+
leafHash?: Uint8Array;
|
|
53
|
+
}>;
|
|
54
|
+
privateKey?: Uint8Array;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Zero the private key (simulating what worker does)
|
|
58
|
+
if (batchMsg.privateKey) {
|
|
59
|
+
batchMsg.privateKey.fill(0);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Generate results for all tasks
|
|
63
|
+
const results = batchMsg.tasks.map((task): BatchSigningTaskResult => ({
|
|
64
|
+
taskId: task.taskId,
|
|
65
|
+
signature: new Uint8Array(64).fill(0xab),
|
|
66
|
+
inputIndex: task.inputIndex,
|
|
67
|
+
publicKey: task.publicKey,
|
|
68
|
+
signatureType: task.signatureType,
|
|
69
|
+
...(task.leafHash ? { leafHash: task.leafHash } : {}),
|
|
70
|
+
}));
|
|
71
|
+
|
|
72
|
+
this.simulateMessage({
|
|
73
|
+
type: 'batchResult',
|
|
74
|
+
batchId: batchMsg.batchId,
|
|
75
|
+
results,
|
|
76
|
+
errors: [],
|
|
77
|
+
});
|
|
78
|
+
}, 10);
|
|
79
|
+
} else if (msg.type === 'shutdown') {
|
|
80
|
+
setTimeout(() => {
|
|
81
|
+
this.simulateMessage({ type: 'shutdown-ack' });
|
|
82
|
+
}, 5);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
addEventListener(event: string, handler: (event: { data: WorkerResponse }) => void): void {
|
|
87
|
+
if (event === 'message') {
|
|
88
|
+
this.messageHandlers.push(handler);
|
|
89
|
+
} else if (event === 'error') {
|
|
90
|
+
this.errorHandlers.push(handler as unknown as (error: Error) => void);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
removeEventListener(event: string, handler: (event: { data: WorkerResponse }) => void): void {
|
|
95
|
+
if (event === 'message') {
|
|
96
|
+
const idx = this.messageHandlers.indexOf(handler);
|
|
97
|
+
if (idx >= 0) this.messageHandlers.splice(idx, 1);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
terminate(): void {
|
|
102
|
+
this.terminated = true;
|
|
103
|
+
this.messageHandlers = [];
|
|
104
|
+
this.errorHandlers = [];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private simulateMessage(data: WorkerResponse): void {
|
|
108
|
+
const event = { data };
|
|
109
|
+
if (this.onmessage) {
|
|
110
|
+
this.onmessage(event);
|
|
111
|
+
}
|
|
112
|
+
for (const handler of this.messageHandlers) {
|
|
113
|
+
handler(event);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
simulateError(error: Error): void {
|
|
118
|
+
if (this.onerror) {
|
|
119
|
+
this.onerror(error);
|
|
120
|
+
}
|
|
121
|
+
for (const handler of this.errorHandlers) {
|
|
122
|
+
handler(error);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Mock Worker that fails signing
|
|
128
|
+
class MockFailingWorker extends MockWorker {
|
|
129
|
+
postMessage(data: unknown): void {
|
|
130
|
+
if ((data as { type: string }).type === 'signBatch') {
|
|
131
|
+
setTimeout(() => {
|
|
132
|
+
const batchMsg = data as {
|
|
133
|
+
batchId: string;
|
|
134
|
+
tasks: Array<{
|
|
135
|
+
taskId: string;
|
|
136
|
+
inputIndex: number;
|
|
137
|
+
}>;
|
|
138
|
+
privateKey?: Uint8Array;
|
|
139
|
+
};
|
|
140
|
+
// Zero key even on failure
|
|
141
|
+
if (batchMsg.privateKey) {
|
|
142
|
+
batchMsg.privateKey.fill(0);
|
|
143
|
+
}
|
|
144
|
+
// Return errors for all tasks
|
|
145
|
+
const errors = batchMsg.tasks.map((task) => ({
|
|
146
|
+
taskId: task.taskId,
|
|
147
|
+
inputIndex: task.inputIndex,
|
|
148
|
+
error: 'Mock signing failure',
|
|
149
|
+
}));
|
|
150
|
+
this['simulateMessage']({
|
|
151
|
+
type: 'batchResult',
|
|
152
|
+
batchId: batchMsg.batchId,
|
|
153
|
+
results: [],
|
|
154
|
+
errors,
|
|
155
|
+
});
|
|
156
|
+
}, 10);
|
|
157
|
+
} else {
|
|
158
|
+
super.postMessage(data);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Mock Worker that times out (never responds)
|
|
164
|
+
class MockTimeoutWorker extends MockWorker {
|
|
165
|
+
postMessage(data: unknown): void {
|
|
166
|
+
if ((data as { type: string }).type === 'signBatch') {
|
|
167
|
+
// Never respond - simulates timeout
|
|
168
|
+
// Still zero the key for security
|
|
169
|
+
const batchMsg = data as { privateKey?: Uint8Array };
|
|
170
|
+
if (batchMsg.privateKey) {
|
|
171
|
+
batchMsg.privateKey.fill(0);
|
|
172
|
+
}
|
|
173
|
+
} else {
|
|
174
|
+
super.postMessage(data);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Mock URL for blob creation
|
|
180
|
+
const mockBlobUrls: string[] = [];
|
|
181
|
+
let blobUrlCounter = 0;
|
|
182
|
+
|
|
183
|
+
// Setup global mocks
|
|
184
|
+
beforeEach(() => {
|
|
185
|
+
// Mock URL.createObjectURL and URL.revokeObjectURL
|
|
186
|
+
if (typeof globalThis.URL === 'undefined') {
|
|
187
|
+
(globalThis as any).URL = class {
|
|
188
|
+
constructor(public href: string) {}
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
(globalThis.URL as any).createObjectURL = vi.fn((blob: Blob) => {
|
|
193
|
+
const url = `blob:mock-${blobUrlCounter++}`;
|
|
194
|
+
mockBlobUrls.push(url);
|
|
195
|
+
return url;
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
(globalThis.URL as any).revokeObjectURL = vi.fn((url: string) => {
|
|
199
|
+
const idx = mockBlobUrls.indexOf(url);
|
|
200
|
+
if (idx >= 0) mockBlobUrls.splice(idx, 1);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Mock Blob
|
|
204
|
+
if (typeof globalThis.Blob === 'undefined') {
|
|
205
|
+
(globalThis as any).Blob = class {
|
|
206
|
+
constructor(
|
|
207
|
+
public parts: string[],
|
|
208
|
+
public options?: { type: string },
|
|
209
|
+
) {}
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Mock Worker
|
|
214
|
+
(globalThis as any).Worker = MockWorker;
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
afterEach(() => {
|
|
218
|
+
vi.restoreAllMocks();
|
|
219
|
+
mockBlobUrls.length = 0;
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe('WorkerSigningPool', () => {
|
|
223
|
+
// Import dynamically after mocks are set up
|
|
224
|
+
let WorkerSigningPool: typeof import('../src/workers/WorkerSigningPool.js').WorkerSigningPool;
|
|
225
|
+
let getSigningPool: typeof import('../src/workers/WorkerSigningPool.js').getSigningPool;
|
|
226
|
+
|
|
227
|
+
beforeEach(async () => {
|
|
228
|
+
// Reset module cache to pick up mocks
|
|
229
|
+
vi.resetModules();
|
|
230
|
+
const module = await import('../src/workers/WorkerSigningPool.js');
|
|
231
|
+
WorkerSigningPool = module.WorkerSigningPool;
|
|
232
|
+
getSigningPool = module.getSigningPool;
|
|
233
|
+
|
|
234
|
+
// Reset singleton
|
|
235
|
+
WorkerSigningPool.resetInstance();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
afterEach(async () => {
|
|
239
|
+
// Ensure pool is shut down
|
|
240
|
+
try {
|
|
241
|
+
const pool = WorkerSigningPool.getInstance();
|
|
242
|
+
await pool.shutdown();
|
|
243
|
+
} catch {
|
|
244
|
+
// Ignore
|
|
245
|
+
}
|
|
246
|
+
WorkerSigningPool.resetInstance();
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
describe('Singleton Pattern', () => {
|
|
250
|
+
it('should return same instance on multiple calls', () => {
|
|
251
|
+
const pool1 = WorkerSigningPool.getInstance();
|
|
252
|
+
const pool2 = WorkerSigningPool.getInstance();
|
|
253
|
+
expect(pool1).toBe(pool2);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('should return same instance via getSigningPool helper', () => {
|
|
257
|
+
const pool1 = getSigningPool();
|
|
258
|
+
const pool2 = WorkerSigningPool.getInstance();
|
|
259
|
+
expect(pool1).toBe(pool2);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('should create new instance after reset', () => {
|
|
263
|
+
const pool1 = WorkerSigningPool.getInstance();
|
|
264
|
+
WorkerSigningPool.resetInstance();
|
|
265
|
+
const pool2 = WorkerSigningPool.getInstance();
|
|
266
|
+
expect(pool1).not.toBe(pool2);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('should accept config on first call', () => {
|
|
270
|
+
const pool = WorkerSigningPool.getInstance({ workerCount: 8 });
|
|
271
|
+
expect(pool).toBeDefined();
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
describe('Initialization', () => {
|
|
276
|
+
it('should initialize without error', async () => {
|
|
277
|
+
const pool = WorkerSigningPool.getInstance({ workerCount: 2 });
|
|
278
|
+
await expect(pool.initialize()).resolves.toBeUndefined();
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('should be idempotent', async () => {
|
|
282
|
+
const pool = WorkerSigningPool.getInstance({ workerCount: 2 });
|
|
283
|
+
await pool.initialize();
|
|
284
|
+
await pool.initialize(); // Second call should be no-op
|
|
285
|
+
expect(pool.workerCount).toBe(2);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it('should create specified number of workers', async () => {
|
|
289
|
+
const pool = WorkerSigningPool.getInstance({ workerCount: 4 });
|
|
290
|
+
await pool.initialize();
|
|
291
|
+
expect(pool.workerCount).toBe(4);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('should have all workers idle after init', async () => {
|
|
295
|
+
const pool = WorkerSigningPool.getInstance({ workerCount: 3 });
|
|
296
|
+
await pool.initialize();
|
|
297
|
+
expect(pool.idleWorkerCount).toBe(3);
|
|
298
|
+
expect(pool.busyWorkerCount).toBe(0);
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
describe('Worker Preservation', () => {
|
|
303
|
+
it('should not preserve workers by default', () => {
|
|
304
|
+
const pool = WorkerSigningPool.getInstance();
|
|
305
|
+
expect(pool.isPreservingWorkers).toBe(false);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('should preserve workers when enabled', () => {
|
|
309
|
+
const pool = WorkerSigningPool.getInstance();
|
|
310
|
+
pool.preserveWorkers();
|
|
311
|
+
expect(pool.isPreservingWorkers).toBe(true);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('should release workers when disabled', () => {
|
|
315
|
+
const pool = WorkerSigningPool.getInstance();
|
|
316
|
+
pool.preserveWorkers();
|
|
317
|
+
expect(pool.isPreservingWorkers).toBe(true);
|
|
318
|
+
pool.releaseWorkers();
|
|
319
|
+
expect(pool.isPreservingWorkers).toBe(false);
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
describe('Signing Batches', () => {
|
|
324
|
+
it('should sign empty batch successfully', async () => {
|
|
325
|
+
const pool = WorkerSigningPool.getInstance({ workerCount: 2 });
|
|
326
|
+
pool.preserveWorkers();
|
|
327
|
+
|
|
328
|
+
const keyPair: ParallelSignerKeyPair = {
|
|
329
|
+
publicKey: new Uint8Array(33).fill(0x02),
|
|
330
|
+
getPrivateKey: () => new Uint8Array(32).fill(0x42),
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
const result = await pool.signBatch([], keyPair);
|
|
334
|
+
|
|
335
|
+
expect(result.success).toBe(true);
|
|
336
|
+
expect(result.signatures.size).toBe(0);
|
|
337
|
+
expect(result.errors.size).toBe(0);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it('should sign single task', async () => {
|
|
341
|
+
const pool = WorkerSigningPool.getInstance({ workerCount: 2 });
|
|
342
|
+
pool.preserveWorkers();
|
|
343
|
+
|
|
344
|
+
const keyPair: ParallelSignerKeyPair = {
|
|
345
|
+
publicKey: new Uint8Array(33).fill(0x02),
|
|
346
|
+
getPrivateKey: () => new Uint8Array(32).fill(0x42),
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
const tasks: SigningTask[] = [
|
|
350
|
+
{
|
|
351
|
+
taskId: 'task-0',
|
|
352
|
+
inputIndex: 0,
|
|
353
|
+
hash: new Uint8Array(32).fill(0x11),
|
|
354
|
+
signatureType: SignatureType.ECDSA,
|
|
355
|
+
sighashType: 0x01,
|
|
356
|
+
},
|
|
357
|
+
];
|
|
358
|
+
|
|
359
|
+
const result = await pool.signBatch(tasks, keyPair);
|
|
360
|
+
|
|
361
|
+
expect(result.success).toBe(true);
|
|
362
|
+
expect(result.signatures.size).toBe(1);
|
|
363
|
+
expect(result.signatures.has(0)).toBe(true);
|
|
364
|
+
expect(result.errors.size).toBe(0);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it('should sign multiple tasks in parallel', async () => {
|
|
368
|
+
const pool = WorkerSigningPool.getInstance({ workerCount: 4 });
|
|
369
|
+
pool.preserveWorkers();
|
|
370
|
+
|
|
371
|
+
const keyPair: ParallelSignerKeyPair = {
|
|
372
|
+
publicKey: new Uint8Array(33).fill(0x02),
|
|
373
|
+
getPrivateKey: () => new Uint8Array(32).fill(0x42),
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
const tasks: SigningTask[] = [];
|
|
377
|
+
for (let i = 0; i < 10; i++) {
|
|
378
|
+
tasks.push({
|
|
379
|
+
taskId: `task-${i}`,
|
|
380
|
+
inputIndex: i,
|
|
381
|
+
hash: new Uint8Array(32).fill(i),
|
|
382
|
+
signatureType: SignatureType.ECDSA,
|
|
383
|
+
sighashType: 0x01,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const result = await pool.signBatch(tasks, keyPair);
|
|
388
|
+
|
|
389
|
+
expect(result.success).toBe(true);
|
|
390
|
+
expect(result.signatures.size).toBe(10);
|
|
391
|
+
expect(result.errors.size).toBe(0);
|
|
392
|
+
|
|
393
|
+
// Verify all signatures present
|
|
394
|
+
for (let i = 0; i < 10; i++) {
|
|
395
|
+
expect(result.signatures.has(i)).toBe(true);
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it('should handle Schnorr signatures', async () => {
|
|
400
|
+
const pool = WorkerSigningPool.getInstance({ workerCount: 2 });
|
|
401
|
+
pool.preserveWorkers();
|
|
402
|
+
|
|
403
|
+
const keyPair: ParallelSignerKeyPair = {
|
|
404
|
+
publicKey: new Uint8Array(32).fill(0x02),
|
|
405
|
+
getPrivateKey: () => new Uint8Array(32).fill(0x42),
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
const tasks: SigningTask[] = [
|
|
409
|
+
{
|
|
410
|
+
taskId: 'schnorr-0',
|
|
411
|
+
inputIndex: 0,
|
|
412
|
+
hash: new Uint8Array(32),
|
|
413
|
+
signatureType: SignatureType.Schnorr,
|
|
414
|
+
sighashType: 0x00,
|
|
415
|
+
},
|
|
416
|
+
];
|
|
417
|
+
|
|
418
|
+
const result = await pool.signBatch(tasks, keyPair);
|
|
419
|
+
|
|
420
|
+
expect(result.success).toBe(true);
|
|
421
|
+
expect(result.signatures.get(0)?.signatureType).toBe(SignatureType.Schnorr);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it('should handle mixed ECDSA and Schnorr', async () => {
|
|
425
|
+
const pool = WorkerSigningPool.getInstance({ workerCount: 2 });
|
|
426
|
+
pool.preserveWorkers();
|
|
427
|
+
|
|
428
|
+
const keyPair: ParallelSignerKeyPair = {
|
|
429
|
+
publicKey: new Uint8Array(33).fill(0x02),
|
|
430
|
+
getPrivateKey: () => new Uint8Array(32).fill(0x42),
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
const tasks: SigningTask[] = [
|
|
434
|
+
{
|
|
435
|
+
taskId: 'ecdsa-0',
|
|
436
|
+
inputIndex: 0,
|
|
437
|
+
hash: new Uint8Array(32),
|
|
438
|
+
signatureType: SignatureType.ECDSA,
|
|
439
|
+
sighashType: 0x01,
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
taskId: 'schnorr-1',
|
|
443
|
+
inputIndex: 1,
|
|
444
|
+
hash: new Uint8Array(32),
|
|
445
|
+
signatureType: SignatureType.Schnorr,
|
|
446
|
+
sighashType: 0x00,
|
|
447
|
+
},
|
|
448
|
+
];
|
|
449
|
+
|
|
450
|
+
const result = await pool.signBatch(tasks, keyPair);
|
|
451
|
+
|
|
452
|
+
expect(result.success).toBe(true);
|
|
453
|
+
expect(result.signatures.get(0)?.signatureType).toBe(SignatureType.ECDSA);
|
|
454
|
+
expect(result.signatures.get(1)?.signatureType).toBe(SignatureType.Schnorr);
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
it('should include leafHash for Taproot script-path', async () => {
|
|
458
|
+
const pool = WorkerSigningPool.getInstance({ workerCount: 2 });
|
|
459
|
+
pool.preserveWorkers();
|
|
460
|
+
|
|
461
|
+
const leafHash = new Uint8Array(32).fill(0xcd);
|
|
462
|
+
const keyPair: ParallelSignerKeyPair = {
|
|
463
|
+
publicKey: new Uint8Array(32).fill(0x02),
|
|
464
|
+
getPrivateKey: () => new Uint8Array(32).fill(0x42),
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
const tasks: SigningTask[] = [
|
|
468
|
+
{
|
|
469
|
+
taskId: 'script-path-0',
|
|
470
|
+
inputIndex: 0,
|
|
471
|
+
hash: new Uint8Array(32),
|
|
472
|
+
signatureType: SignatureType.Schnorr,
|
|
473
|
+
sighashType: 0x00,
|
|
474
|
+
leafHash,
|
|
475
|
+
},
|
|
476
|
+
];
|
|
477
|
+
|
|
478
|
+
const result = await pool.signBatch(tasks, keyPair);
|
|
479
|
+
|
|
480
|
+
expect(result.success).toBe(true);
|
|
481
|
+
expect(result.signatures.get(0)?.leafHash).toBeDefined();
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
it('should report duration', async () => {
|
|
485
|
+
const pool = WorkerSigningPool.getInstance({ workerCount: 2 });
|
|
486
|
+
pool.preserveWorkers();
|
|
487
|
+
|
|
488
|
+
const keyPair: ParallelSignerKeyPair = {
|
|
489
|
+
publicKey: new Uint8Array(33).fill(0x02),
|
|
490
|
+
getPrivateKey: () => new Uint8Array(32).fill(0x42),
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
const tasks: SigningTask[] = [
|
|
494
|
+
{
|
|
495
|
+
taskId: 'task-0',
|
|
496
|
+
inputIndex: 0,
|
|
497
|
+
hash: new Uint8Array(32),
|
|
498
|
+
signatureType: SignatureType.ECDSA,
|
|
499
|
+
sighashType: 0x01,
|
|
500
|
+
},
|
|
501
|
+
];
|
|
502
|
+
|
|
503
|
+
const result = await pool.signBatch(tasks, keyPair);
|
|
504
|
+
|
|
505
|
+
expect(result.durationMs).toBeGreaterThanOrEqual(0);
|
|
506
|
+
});
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
describe('Key Security', () => {
|
|
510
|
+
it('should zero private key after signing', async () => {
|
|
511
|
+
const pool = WorkerSigningPool.getInstance({ workerCount: 1 });
|
|
512
|
+
pool.preserveWorkers();
|
|
513
|
+
|
|
514
|
+
let capturedKey: Uint8Array | null = null;
|
|
515
|
+
const keyPair: ParallelSignerKeyPair = {
|
|
516
|
+
publicKey: new Uint8Array(33).fill(0x02),
|
|
517
|
+
getPrivateKey: () => {
|
|
518
|
+
capturedKey = new Uint8Array(32).fill(0x42);
|
|
519
|
+
return capturedKey;
|
|
520
|
+
},
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
const tasks: SigningTask[] = [
|
|
524
|
+
{
|
|
525
|
+
taskId: 'task-0',
|
|
526
|
+
inputIndex: 0,
|
|
527
|
+
hash: new Uint8Array(32),
|
|
528
|
+
signatureType: SignatureType.ECDSA,
|
|
529
|
+
sighashType: 0x01,
|
|
530
|
+
},
|
|
531
|
+
];
|
|
532
|
+
|
|
533
|
+
await pool.signBatch(tasks, keyPair);
|
|
534
|
+
|
|
535
|
+
// Key should be zeroed (both in worker and main thread)
|
|
536
|
+
expect(capturedKey).not.toBeNull();
|
|
537
|
+
expect(capturedKey!.every((b) => b === 0)).toBe(true);
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
it('should call getPrivateKey once per batch', async () => {
|
|
541
|
+
const pool = WorkerSigningPool.getInstance({ workerCount: 2 });
|
|
542
|
+
pool.preserveWorkers();
|
|
543
|
+
|
|
544
|
+
let callCount = 0;
|
|
545
|
+
const keyPair: ParallelSignerKeyPair = {
|
|
546
|
+
publicKey: new Uint8Array(33).fill(0x02),
|
|
547
|
+
getPrivateKey: () => {
|
|
548
|
+
callCount++;
|
|
549
|
+
return new Uint8Array(32).fill(0x42);
|
|
550
|
+
},
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
const tasks: SigningTask[] = [
|
|
554
|
+
{
|
|
555
|
+
taskId: 'task-0',
|
|
556
|
+
inputIndex: 0,
|
|
557
|
+
hash: new Uint8Array(32),
|
|
558
|
+
signatureType: SignatureType.ECDSA,
|
|
559
|
+
sighashType: 0x01,
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
taskId: 'task-1',
|
|
563
|
+
inputIndex: 1,
|
|
564
|
+
hash: new Uint8Array(32),
|
|
565
|
+
signatureType: SignatureType.ECDSA,
|
|
566
|
+
sighashType: 0x01,
|
|
567
|
+
},
|
|
568
|
+
];
|
|
569
|
+
|
|
570
|
+
await pool.signBatch(tasks, keyPair);
|
|
571
|
+
|
|
572
|
+
// Batch signing obtains the key once and distributes to workers
|
|
573
|
+
expect(callCount).toBe(1);
|
|
574
|
+
});
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
describe('Shutdown', () => {
|
|
578
|
+
it('should shutdown gracefully', async () => {
|
|
579
|
+
const pool = WorkerSigningPool.getInstance({ workerCount: 2 });
|
|
580
|
+
await pool.initialize();
|
|
581
|
+
expect(pool.workerCount).toBe(2);
|
|
582
|
+
|
|
583
|
+
await pool.shutdown();
|
|
584
|
+
expect(pool.workerCount).toBe(0);
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
it('should be idempotent', async () => {
|
|
588
|
+
const pool = WorkerSigningPool.getInstance({ workerCount: 2 });
|
|
589
|
+
await pool.initialize();
|
|
590
|
+
|
|
591
|
+
await pool.shutdown();
|
|
592
|
+
await pool.shutdown(); // Second call should be no-op
|
|
593
|
+
|
|
594
|
+
expect(pool.workerCount).toBe(0);
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
it('should allow reinitialization after shutdown', async () => {
|
|
598
|
+
WorkerSigningPool.resetInstance();
|
|
599
|
+
const pool = WorkerSigningPool.getInstance({ workerCount: 2 });
|
|
600
|
+
await pool.initialize();
|
|
601
|
+
await pool.shutdown();
|
|
602
|
+
|
|
603
|
+
WorkerSigningPool.resetInstance();
|
|
604
|
+
const pool2 = WorkerSigningPool.getInstance({ workerCount: 3 });
|
|
605
|
+
await pool2.initialize();
|
|
606
|
+
expect(pool2.workerCount).toBe(3);
|
|
607
|
+
});
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
describe('Worker Pool Without Preservation', () => {
|
|
611
|
+
it('should terminate workers after batch when not preserving', async () => {
|
|
612
|
+
const pool = WorkerSigningPool.getInstance({ workerCount: 2 });
|
|
613
|
+
// Don't call preserveWorkers()
|
|
614
|
+
|
|
615
|
+
const keyPair: ParallelSignerKeyPair = {
|
|
616
|
+
publicKey: new Uint8Array(33).fill(0x02),
|
|
617
|
+
getPrivateKey: () => new Uint8Array(32).fill(0x42),
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
const tasks: SigningTask[] = [
|
|
621
|
+
{
|
|
622
|
+
taskId: 'task-0',
|
|
623
|
+
inputIndex: 0,
|
|
624
|
+
hash: new Uint8Array(32),
|
|
625
|
+
signatureType: SignatureType.ECDSA,
|
|
626
|
+
sighashType: 0x01,
|
|
627
|
+
},
|
|
628
|
+
];
|
|
629
|
+
|
|
630
|
+
const result = await pool.signBatch(tasks, keyPair);
|
|
631
|
+
expect(result.success).toBe(true);
|
|
632
|
+
|
|
633
|
+
// Workers should be terminated after batch
|
|
634
|
+
// (In real implementation, idle workers are terminated)
|
|
635
|
+
});
|
|
636
|
+
});
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
describe('WorkerSigningPool Error Handling', () => {
|
|
640
|
+
let WorkerSigningPool: typeof import('../src/workers/WorkerSigningPool.js').WorkerSigningPool;
|
|
641
|
+
|
|
642
|
+
beforeEach(async () => {
|
|
643
|
+
vi.resetModules();
|
|
644
|
+
|
|
645
|
+
// Use failing worker
|
|
646
|
+
(globalThis as any).Worker = MockFailingWorker;
|
|
647
|
+
|
|
648
|
+
const module = await import('../src/workers/WorkerSigningPool.js');
|
|
649
|
+
WorkerSigningPool = module.WorkerSigningPool;
|
|
650
|
+
WorkerSigningPool.resetInstance();
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
afterEach(async () => {
|
|
654
|
+
try {
|
|
655
|
+
await WorkerSigningPool.getInstance().shutdown();
|
|
656
|
+
} catch {
|
|
657
|
+
// Ignore
|
|
658
|
+
}
|
|
659
|
+
WorkerSigningPool.resetInstance();
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
it('should handle signing failures', async () => {
|
|
663
|
+
const pool = WorkerSigningPool.getInstance({ workerCount: 1 });
|
|
664
|
+
pool.preserveWorkers();
|
|
665
|
+
|
|
666
|
+
const keyPair: ParallelSignerKeyPair = {
|
|
667
|
+
publicKey: new Uint8Array(33).fill(0x02),
|
|
668
|
+
getPrivateKey: () => new Uint8Array(32).fill(0x42),
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
const tasks: SigningTask[] = [
|
|
672
|
+
{
|
|
673
|
+
taskId: 'task-0',
|
|
674
|
+
inputIndex: 0,
|
|
675
|
+
hash: new Uint8Array(32),
|
|
676
|
+
signatureType: SignatureType.ECDSA,
|
|
677
|
+
sighashType: 0x01,
|
|
678
|
+
},
|
|
679
|
+
];
|
|
680
|
+
|
|
681
|
+
const result = await pool.signBatch(tasks, keyPair);
|
|
682
|
+
|
|
683
|
+
expect(result.success).toBe(false);
|
|
684
|
+
expect(result.errors.size).toBe(1);
|
|
685
|
+
expect(result.errors.get(0)).toBe('Mock signing failure');
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
it('should handle partial failures in batch', async () => {
|
|
689
|
+
// Reset to use regular worker for first call
|
|
690
|
+
vi.resetModules();
|
|
691
|
+
|
|
692
|
+
class MixedWorker extends MockWorker {
|
|
693
|
+
postMessage(data: unknown): void {
|
|
694
|
+
if ((data as { type: string }).type === 'signBatch') {
|
|
695
|
+
setTimeout(() => {
|
|
696
|
+
const batchMsg = data as {
|
|
697
|
+
batchId: string;
|
|
698
|
+
tasks: Array<{
|
|
699
|
+
taskId: string;
|
|
700
|
+
inputIndex: number;
|
|
701
|
+
publicKey: Uint8Array;
|
|
702
|
+
signatureType: SignatureType;
|
|
703
|
+
leafHash?: Uint8Array;
|
|
704
|
+
}>;
|
|
705
|
+
privateKey?: Uint8Array;
|
|
706
|
+
};
|
|
707
|
+
if (batchMsg.privateKey) batchMsg.privateKey.fill(0);
|
|
708
|
+
|
|
709
|
+
// Fail every other task
|
|
710
|
+
const results: BatchSigningTaskResult[] = [];
|
|
711
|
+
const errors: Array<{
|
|
712
|
+
taskId: string;
|
|
713
|
+
inputIndex: number;
|
|
714
|
+
error: string;
|
|
715
|
+
}> = [];
|
|
716
|
+
|
|
717
|
+
batchMsg.tasks.forEach((task, idx) => {
|
|
718
|
+
if (idx % 2 === 0) {
|
|
719
|
+
results.push({
|
|
720
|
+
taskId: task.taskId,
|
|
721
|
+
signature: new Uint8Array(64).fill(0xab),
|
|
722
|
+
inputIndex: task.inputIndex,
|
|
723
|
+
publicKey: task.publicKey,
|
|
724
|
+
signatureType: task.signatureType as SignatureType,
|
|
725
|
+
...(task.leafHash ? { leafHash: task.leafHash } : {}),
|
|
726
|
+
});
|
|
727
|
+
} else {
|
|
728
|
+
errors.push({
|
|
729
|
+
taskId: task.taskId,
|
|
730
|
+
inputIndex: task.inputIndex,
|
|
731
|
+
error: 'Simulated failure',
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
this['simulateMessage']({
|
|
737
|
+
type: 'batchResult',
|
|
738
|
+
batchId: batchMsg.batchId,
|
|
739
|
+
results,
|
|
740
|
+
errors,
|
|
741
|
+
});
|
|
742
|
+
}, 10);
|
|
743
|
+
} else {
|
|
744
|
+
super.postMessage(data);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
(globalThis as any).Worker = MixedWorker;
|
|
750
|
+
|
|
751
|
+
const module = await import('../src/workers/WorkerSigningPool.js');
|
|
752
|
+
const pool = module.WorkerSigningPool.getInstance({ workerCount: 2 });
|
|
753
|
+
pool.preserveWorkers();
|
|
754
|
+
|
|
755
|
+
const keyPair: ParallelSignerKeyPair = {
|
|
756
|
+
publicKey: new Uint8Array(33).fill(0x02),
|
|
757
|
+
getPrivateKey: () => new Uint8Array(32).fill(0x42),
|
|
758
|
+
};
|
|
759
|
+
|
|
760
|
+
const tasks: SigningTask[] = [
|
|
761
|
+
{
|
|
762
|
+
taskId: 'task-0',
|
|
763
|
+
inputIndex: 0,
|
|
764
|
+
hash: new Uint8Array(32),
|
|
765
|
+
signatureType: SignatureType.ECDSA,
|
|
766
|
+
sighashType: 0x01,
|
|
767
|
+
},
|
|
768
|
+
{
|
|
769
|
+
taskId: 'task-1',
|
|
770
|
+
inputIndex: 1,
|
|
771
|
+
hash: new Uint8Array(32),
|
|
772
|
+
signatureType: SignatureType.ECDSA,
|
|
773
|
+
sighashType: 0x01,
|
|
774
|
+
},
|
|
775
|
+
{
|
|
776
|
+
taskId: 'task-2',
|
|
777
|
+
inputIndex: 2,
|
|
778
|
+
hash: new Uint8Array(32),
|
|
779
|
+
signatureType: SignatureType.ECDSA,
|
|
780
|
+
sighashType: 0x01,
|
|
781
|
+
},
|
|
782
|
+
{
|
|
783
|
+
taskId: 'task-3',
|
|
784
|
+
inputIndex: 3,
|
|
785
|
+
hash: new Uint8Array(32),
|
|
786
|
+
signatureType: SignatureType.ECDSA,
|
|
787
|
+
sighashType: 0x01,
|
|
788
|
+
},
|
|
789
|
+
];
|
|
790
|
+
|
|
791
|
+
const result = await pool.signBatch(tasks, keyPair);
|
|
792
|
+
|
|
793
|
+
expect(result.success).toBe(false);
|
|
794
|
+
expect(result.signatures.size).toBe(2); // Half succeeded
|
|
795
|
+
expect(result.errors.size).toBe(2); // Half failed
|
|
796
|
+
|
|
797
|
+
await pool.shutdown();
|
|
798
|
+
module.WorkerSigningPool.resetInstance();
|
|
799
|
+
});
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
describe('WorkerSigningPool Timeout Handling', () => {
|
|
803
|
+
let WorkerSigningPool: typeof import('../src/workers/WorkerSigningPool.js').WorkerSigningPool;
|
|
804
|
+
|
|
805
|
+
beforeEach(async () => {
|
|
806
|
+
vi.resetModules();
|
|
807
|
+
|
|
808
|
+
// Use timeout worker
|
|
809
|
+
(globalThis as any).Worker = MockTimeoutWorker;
|
|
810
|
+
|
|
811
|
+
const module = await import('../src/workers/WorkerSigningPool.js');
|
|
812
|
+
WorkerSigningPool = module.WorkerSigningPool;
|
|
813
|
+
WorkerSigningPool.resetInstance();
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
afterEach(async () => {
|
|
817
|
+
try {
|
|
818
|
+
await WorkerSigningPool.getInstance().shutdown();
|
|
819
|
+
} catch {
|
|
820
|
+
// Ignore
|
|
821
|
+
}
|
|
822
|
+
WorkerSigningPool.resetInstance();
|
|
823
|
+
});
|
|
824
|
+
|
|
825
|
+
it('should timeout long-running tasks', async () => {
|
|
826
|
+
const pool = WorkerSigningPool.getInstance({
|
|
827
|
+
workerCount: 1,
|
|
828
|
+
maxKeyHoldTimeMs: 50, // Very short timeout for testing
|
|
829
|
+
});
|
|
830
|
+
pool.preserveWorkers();
|
|
831
|
+
|
|
832
|
+
const keyPair: ParallelSignerKeyPair = {
|
|
833
|
+
publicKey: new Uint8Array(33).fill(0x02),
|
|
834
|
+
getPrivateKey: () => new Uint8Array(32).fill(0x42),
|
|
835
|
+
};
|
|
836
|
+
|
|
837
|
+
const tasks: SigningTask[] = [
|
|
838
|
+
{
|
|
839
|
+
taskId: 'timeout-task',
|
|
840
|
+
inputIndex: 0,
|
|
841
|
+
hash: new Uint8Array(32),
|
|
842
|
+
signatureType: SignatureType.ECDSA,
|
|
843
|
+
sighashType: 0x01,
|
|
844
|
+
},
|
|
845
|
+
];
|
|
846
|
+
|
|
847
|
+
const result = await pool.signBatch(tasks, keyPair);
|
|
848
|
+
|
|
849
|
+
expect(result.success).toBe(false);
|
|
850
|
+
expect(result.errors.size).toBe(1);
|
|
851
|
+
expect(result.errors.get(0)).toContain('timeout');
|
|
852
|
+
}, 10000);
|
|
853
|
+
});
|
|
854
|
+
|
|
855
|
+
describe('Concurrent Batch Operations', () => {
|
|
856
|
+
let WorkerSigningPool: typeof import('../src/workers/WorkerSigningPool.js').WorkerSigningPool;
|
|
857
|
+
|
|
858
|
+
beforeEach(async () => {
|
|
859
|
+
vi.resetModules();
|
|
860
|
+
(globalThis as any).Worker = MockWorker;
|
|
861
|
+
|
|
862
|
+
const module = await import('../src/workers/WorkerSigningPool.js');
|
|
863
|
+
WorkerSigningPool = module.WorkerSigningPool;
|
|
864
|
+
WorkerSigningPool.resetInstance();
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
afterEach(async () => {
|
|
868
|
+
try {
|
|
869
|
+
await WorkerSigningPool.getInstance().shutdown();
|
|
870
|
+
} catch {
|
|
871
|
+
// Ignore
|
|
872
|
+
}
|
|
873
|
+
WorkerSigningPool.resetInstance();
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
it('should handle concurrent signBatch calls', async () => {
|
|
877
|
+
const pool = WorkerSigningPool.getInstance({ workerCount: 4 });
|
|
878
|
+
pool.preserveWorkers();
|
|
879
|
+
|
|
880
|
+
const keyPair: ParallelSignerKeyPair = {
|
|
881
|
+
publicKey: new Uint8Array(33).fill(0x02),
|
|
882
|
+
getPrivateKey: () => new Uint8Array(32).fill(0x42),
|
|
883
|
+
};
|
|
884
|
+
|
|
885
|
+
const createTasks = (prefix: string, count: number): SigningTask[] => {
|
|
886
|
+
const tasks: SigningTask[] = [];
|
|
887
|
+
for (let i = 0; i < count; i++) {
|
|
888
|
+
tasks.push({
|
|
889
|
+
taskId: `${prefix}-${i}`,
|
|
890
|
+
inputIndex: i,
|
|
891
|
+
hash: new Uint8Array(32).fill(i),
|
|
892
|
+
signatureType: SignatureType.ECDSA,
|
|
893
|
+
sighashType: 0x01,
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
return tasks;
|
|
897
|
+
};
|
|
898
|
+
|
|
899
|
+
// Start multiple batches concurrently
|
|
900
|
+
const [result1, result2, result3] = await Promise.all([
|
|
901
|
+
pool.signBatch(createTasks('batch1', 5), keyPair),
|
|
902
|
+
pool.signBatch(createTasks('batch2', 5), keyPair),
|
|
903
|
+
pool.signBatch(createTasks('batch3', 5), keyPair),
|
|
904
|
+
]);
|
|
905
|
+
|
|
906
|
+
expect(result1.success).toBe(true);
|
|
907
|
+
expect(result2.success).toBe(true);
|
|
908
|
+
expect(result3.success).toBe(true);
|
|
909
|
+
|
|
910
|
+
expect(result1.signatures.size).toBe(5);
|
|
911
|
+
expect(result2.signatures.size).toBe(5);
|
|
912
|
+
expect(result3.signatures.size).toBe(5);
|
|
913
|
+
});
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
describe('Large Batch Performance', () => {
|
|
917
|
+
let WorkerSigningPool: typeof import('../src/workers/WorkerSigningPool.js').WorkerSigningPool;
|
|
918
|
+
|
|
919
|
+
beforeEach(async () => {
|
|
920
|
+
vi.resetModules();
|
|
921
|
+
(globalThis as any).Worker = MockWorker;
|
|
922
|
+
|
|
923
|
+
const module = await import('../src/workers/WorkerSigningPool.js');
|
|
924
|
+
WorkerSigningPool = module.WorkerSigningPool;
|
|
925
|
+
WorkerSigningPool.resetInstance();
|
|
926
|
+
});
|
|
927
|
+
|
|
928
|
+
afterEach(async () => {
|
|
929
|
+
try {
|
|
930
|
+
await WorkerSigningPool.getInstance().shutdown();
|
|
931
|
+
} catch {
|
|
932
|
+
// Ignore
|
|
933
|
+
}
|
|
934
|
+
WorkerSigningPool.resetInstance();
|
|
935
|
+
});
|
|
936
|
+
|
|
937
|
+
it('should handle 100 inputs efficiently', async () => {
|
|
938
|
+
const pool = WorkerSigningPool.getInstance({ workerCount: 8 });
|
|
939
|
+
pool.preserveWorkers();
|
|
940
|
+
|
|
941
|
+
const keyPair: ParallelSignerKeyPair = {
|
|
942
|
+
publicKey: new Uint8Array(33).fill(0x02),
|
|
943
|
+
getPrivateKey: () => new Uint8Array(32).fill(0x42),
|
|
944
|
+
};
|
|
945
|
+
|
|
946
|
+
const tasks: SigningTask[] = [];
|
|
947
|
+
for (let i = 0; i < 100; i++) {
|
|
948
|
+
tasks.push({
|
|
949
|
+
taskId: `task-${i}`,
|
|
950
|
+
inputIndex: i,
|
|
951
|
+
hash: new Uint8Array(32).fill(i % 256),
|
|
952
|
+
signatureType: i % 2 === 0 ? SignatureType.ECDSA : SignatureType.Schnorr,
|
|
953
|
+
sighashType: i % 2 === 0 ? 0x01 : 0x00,
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
const result = await pool.signBatch(tasks, keyPair);
|
|
958
|
+
|
|
959
|
+
expect(result.success).toBe(true);
|
|
960
|
+
expect(result.signatures.size).toBe(100);
|
|
961
|
+
expect(result.errors.size).toBe(0);
|
|
962
|
+
}, 30000);
|
|
963
|
+
});
|