@btc-vision/bitcoin 6.5.6 → 7.0.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +56 -9
- package/browser/address.d.ts.map +1 -0
- package/browser/bech32utils.d.ts +9 -1
- package/browser/bech32utils.d.ts.map +1 -0
- package/browser/bip66.d.ts +11 -6
- package/browser/bip66.d.ts.map +1 -0
- package/browser/block.d.ts +117 -11
- package/browser/block.d.ts.map +1 -0
- package/browser/branded.d.ts +20 -0
- package/browser/branded.d.ts.map +1 -0
- package/browser/crypto/crypto.d.ts +1 -0
- package/browser/crypto/crypto.d.ts.map +1 -0
- package/browser/crypto.d.ts +46 -7
- package/browser/crypto.d.ts.map +1 -0
- package/browser/ecc/context.d.ts +129 -0
- package/browser/ecc/context.d.ts.map +1 -0
- package/browser/ecc/index.d.ts +11 -0
- package/browser/ecc/index.d.ts.map +1 -0
- package/browser/ecc/types.d.ts +128 -0
- package/browser/ecc/types.d.ts.map +1 -0
- package/browser/ecpair.d.ts +99 -0
- package/browser/errors.d.ts +124 -0
- package/browser/errors.d.ts.map +1 -0
- package/browser/index.d.ts +32 -5
- package/browser/index.d.ts.map +1 -0
- package/browser/index.js +12482 -101
- package/browser/io/BinaryReader.d.ts +276 -0
- package/browser/io/BinaryReader.d.ts.map +1 -0
- package/browser/io/BinaryWriter.d.ts +391 -0
- package/browser/io/BinaryWriter.d.ts.map +1 -0
- package/browser/io/MemoryPool.d.ts +220 -0
- package/browser/io/MemoryPool.d.ts.map +1 -0
- package/browser/io/base64.d.ts +13 -0
- package/browser/io/base64.d.ts.map +1 -0
- package/browser/io/hex.d.ts +67 -0
- package/browser/io/hex.d.ts.map +1 -0
- package/browser/io/index.d.ts +17 -0
- package/browser/io/index.d.ts.map +1 -0
- package/browser/io/utils.d.ts +199 -0
- package/browser/io/utils.d.ts.map +1 -0
- package/browser/merkle.d.ts +10 -1
- package/browser/merkle.d.ts.map +1 -0
- package/browser/networks.d.ts +70 -9
- package/browser/networks.d.ts.map +1 -0
- package/browser/opcodes.d.ts +1 -0
- package/browser/opcodes.d.ts.map +1 -0
- package/browser/payments/bip341.d.ts +35 -9
- package/browser/payments/bip341.d.ts.map +1 -0
- package/browser/payments/embed.d.ts +112 -1
- package/browser/payments/embed.d.ts.map +1 -0
- package/browser/payments/index.d.ts +17 -10
- package/browser/payments/index.d.ts.map +1 -0
- package/browser/payments/p2ms.d.ts +150 -0
- package/browser/payments/p2ms.d.ts.map +1 -0
- package/browser/payments/p2op.d.ts +150 -24
- package/browser/payments/p2op.d.ts.map +1 -0
- package/browser/payments/p2pk.d.ts +154 -1
- package/browser/payments/p2pk.d.ts.map +1 -0
- package/browser/payments/p2pkh.d.ts +176 -1
- package/browser/payments/p2pkh.d.ts.map +1 -0
- package/browser/payments/p2sh.d.ts +150 -1
- package/browser/payments/p2sh.d.ts.map +1 -0
- package/browser/payments/p2tr.d.ts +185 -1
- package/browser/payments/p2tr.d.ts.map +1 -0
- package/browser/payments/p2wpkh.d.ts +161 -1
- package/browser/payments/p2wpkh.d.ts.map +1 -0
- package/browser/payments/p2wsh.d.ts +146 -1
- package/browser/payments/p2wsh.d.ts.map +1 -0
- package/browser/payments/types.d.ts +94 -64
- package/browser/payments/types.d.ts.map +1 -0
- package/browser/psbt/bip371.d.ts +34 -8
- package/browser/psbt/bip371.d.ts.map +1 -0
- package/browser/psbt/psbtutils.d.ts +56 -16
- package/browser/psbt/psbtutils.d.ts.map +1 -0
- package/browser/psbt/types.d.ts +245 -0
- package/browser/psbt/types.d.ts.map +1 -0
- package/browser/psbt/utils.d.ts +64 -0
- package/browser/psbt/utils.d.ts.map +1 -0
- package/browser/psbt/validation.d.ts +84 -0
- package/browser/psbt/validation.d.ts.map +1 -0
- package/browser/psbt.d.ts +82 -118
- package/browser/psbt.d.ts.map +1 -0
- package/browser/pubkey.d.ts +27 -6
- package/browser/pubkey.d.ts.map +1 -0
- package/browser/push_data.d.ts +24 -2
- package/browser/push_data.d.ts.map +1 -0
- package/browser/script.d.ts +33 -8
- package/browser/script.d.ts.map +1 -0
- package/browser/script_number.d.ts +17 -0
- package/browser/script_number.d.ts.map +1 -0
- package/browser/script_signature.d.ts +23 -5
- package/browser/script_signature.d.ts.map +1 -0
- package/browser/transaction.d.ts +160 -18
- package/browser/transaction.d.ts.map +1 -0
- package/browser/types.d.ts +36 -38
- package/browser/types.d.ts.map +1 -0
- package/browser/workers/WorkerSigningPool.d.ts +143 -0
- package/browser/workers/WorkerSigningPool.d.ts.map +1 -0
- package/browser/workers/WorkerSigningPool.node.d.ts +116 -0
- package/browser/workers/WorkerSigningPool.node.d.ts.map +1 -0
- package/browser/workers/ecc-bundle.d.ts +25 -0
- package/browser/workers/ecc-bundle.d.ts.map +1 -0
- package/browser/workers/index.d.ts +91 -0
- package/browser/workers/index.d.ts.map +1 -0
- package/browser/workers/psbt-parallel.d.ts +88 -0
- package/browser/workers/psbt-parallel.d.ts.map +1 -0
- package/browser/workers/signing-worker.d.ts +37 -0
- package/browser/workers/signing-worker.d.ts.map +1 -0
- package/browser/workers/types.d.ts +365 -0
- package/browser/workers/types.d.ts.map +1 -0
- package/build/address.d.ts +57 -10
- package/build/address.d.ts.map +1 -0
- package/build/address.js +80 -24
- package/build/address.js.map +1 -0
- package/build/bech32utils.d.ts +9 -1
- package/build/bech32utils.d.ts.map +1 -0
- package/build/bech32utils.js +10 -2
- package/build/bech32utils.js.map +1 -0
- package/build/bip66.d.ts +11 -6
- package/build/bip66.d.ts.map +1 -0
- package/build/bip66.js +32 -3
- package/build/bip66.js.map +1 -0
- package/build/block.d.ts +117 -11
- package/build/block.d.ts.map +1 -0
- package/build/block.js +204 -72
- package/build/block.js.map +1 -0
- package/build/branded.d.ts +20 -0
- package/build/branded.d.ts.map +1 -0
- package/build/branded.js +7 -0
- package/build/branded.js.map +1 -0
- package/build/crypto/crypto.d.ts +1 -0
- package/build/crypto/crypto.d.ts.map +1 -0
- package/build/crypto/crypto.js +1 -0
- package/build/crypto/crypto.js.map +1 -0
- package/build/crypto.d.ts +46 -7
- package/build/crypto.d.ts.map +1 -0
- package/build/crypto.js +65 -20
- package/build/crypto.js.map +1 -0
- package/build/ecc/context.d.ts +135 -0
- package/build/ecc/context.d.ts.map +1 -0
- package/build/ecc/context.js +232 -0
- package/build/ecc/context.js.map +1 -0
- package/build/ecc/index.d.ts +11 -0
- package/build/ecc/index.d.ts.map +1 -0
- package/build/ecc/index.js +11 -0
- package/build/ecc/index.js.map +1 -0
- package/build/ecc/types.d.ts +134 -0
- package/build/ecc/types.d.ts.map +1 -0
- package/build/ecc/types.js +8 -0
- package/build/ecc/types.js.map +1 -0
- package/build/errors.d.ts +124 -0
- package/build/errors.d.ts.map +1 -0
- package/build/errors.js +155 -0
- package/build/errors.js.map +1 -0
- package/build/index.d.ts +32 -5
- package/build/index.d.ts.map +1 -0
- package/build/index.js +26 -3
- package/build/index.js.map +1 -0
- package/build/io/BinaryReader.d.ts +276 -0
- package/build/io/BinaryReader.d.ts.map +1 -0
- package/build/io/BinaryReader.js +425 -0
- package/build/io/BinaryReader.js.map +1 -0
- package/build/io/BinaryWriter.d.ts +391 -0
- package/build/io/BinaryWriter.d.ts.map +1 -0
- package/build/io/BinaryWriter.js +611 -0
- package/build/io/BinaryWriter.js.map +1 -0
- package/build/io/MemoryPool.d.ts +220 -0
- package/build/io/MemoryPool.d.ts.map +1 -0
- package/build/io/MemoryPool.js +309 -0
- package/build/io/MemoryPool.js.map +1 -0
- package/build/io/base64.d.ts +13 -0
- package/build/io/base64.d.ts.map +1 -0
- package/build/io/base64.js +20 -0
- package/build/io/base64.js.map +1 -0
- package/build/io/hex.d.ts +67 -0
- package/build/io/hex.d.ts.map +1 -0
- package/build/io/hex.js +138 -0
- package/build/io/hex.js.map +1 -0
- package/build/io/index.d.ts +17 -0
- package/build/io/index.d.ts.map +1 -0
- package/build/io/index.js +23 -0
- package/build/io/index.js.map +1 -0
- package/build/io/utils.d.ts +199 -0
- package/build/io/utils.d.ts.map +1 -0
- package/build/io/utils.js +271 -0
- package/build/io/utils.js.map +1 -0
- package/build/merkle.d.ts +10 -1
- package/build/merkle.d.ts.map +1 -0
- package/build/merkle.js +12 -1
- package/build/merkle.js.map +1 -0
- package/build/networks.d.ts +70 -9
- package/build/networks.d.ts.map +1 -0
- package/build/networks.js +90 -4
- package/build/networks.js.map +1 -0
- package/build/opcodes.d.ts +1 -0
- package/build/opcodes.d.ts.map +1 -0
- package/build/opcodes.js +1 -0
- package/build/opcodes.js.map +1 -0
- package/build/payments/bip341.d.ts +36 -9
- package/build/payments/bip341.d.ts.map +1 -0
- package/build/payments/bip341.js +35 -15
- package/build/payments/bip341.js.map +1 -0
- package/build/payments/embed.d.ts +120 -1
- package/build/payments/embed.d.ts.map +1 -0
- package/build/payments/embed.js +215 -34
- package/build/payments/embed.js.map +1 -0
- package/build/payments/index.d.ts +17 -10
- package/build/payments/index.d.ts.map +1 -0
- package/build/payments/index.js +20 -10
- package/build/payments/index.js.map +1 -0
- package/build/payments/p2ms.d.ts +159 -1
- package/build/payments/p2ms.d.ts.map +1 -0
- package/build/payments/p2ms.js +427 -108
- package/build/payments/p2ms.js.map +1 -0
- package/build/payments/p2op.d.ts +158 -24
- package/build/payments/p2op.d.ts.map +1 -0
- package/build/payments/p2op.js +379 -93
- package/build/payments/p2op.js.map +1 -0
- package/build/payments/p2pk.d.ts +162 -1
- package/build/payments/p2pk.d.ts.map +1 -0
- package/build/payments/p2pk.js +327 -58
- package/build/payments/p2pk.js.map +1 -0
- package/build/payments/p2pkh.d.ts +185 -1
- package/build/payments/p2pkh.d.ts.map +1 -0
- package/build/payments/p2pkh.js +467 -114
- package/build/payments/p2pkh.js.map +1 -0
- package/build/payments/p2sh.d.ts +159 -1
- package/build/payments/p2sh.d.ts.map +1 -0
- package/build/payments/p2sh.js +500 -152
- package/build/payments/p2sh.js.map +1 -0
- package/build/payments/p2tr.d.ts +193 -1
- package/build/payments/p2tr.d.ts.map +1 -0
- package/build/payments/p2tr.js +592 -174
- package/build/payments/p2tr.js.map +1 -0
- package/build/payments/p2wpkh.d.ts +170 -1
- package/build/payments/p2wpkh.d.ts.map +1 -0
- package/build/payments/p2wpkh.js +429 -104
- package/build/payments/p2wpkh.js.map +1 -0
- package/build/payments/p2wsh.d.ts +155 -1
- package/build/payments/p2wsh.d.ts.map +1 -0
- package/build/payments/p2wsh.js +466 -144
- package/build/payments/p2wsh.js.map +1 -0
- package/build/payments/types.d.ts +98 -64
- package/build/payments/types.d.ts.map +1 -0
- package/build/payments/types.js +17 -13
- package/build/payments/types.js.map +1 -0
- package/build/psbt/bip371.d.ts +35 -9
- package/build/psbt/bip371.d.ts.map +1 -0
- package/build/psbt/bip371.js +113 -28
- package/build/psbt/bip371.js.map +1 -0
- package/build/psbt/psbtutils.d.ts +56 -16
- package/build/psbt/psbtutils.d.ts.map +1 -0
- package/build/psbt/psbtutils.js +71 -16
- package/build/psbt/psbtutils.js.map +1 -0
- package/build/psbt/types.d.ts +249 -0
- package/build/psbt/types.d.ts.map +1 -0
- package/build/psbt/types.js +6 -0
- package/build/psbt/types.js.map +1 -0
- package/build/psbt/utils.d.ts +68 -0
- package/build/psbt/utils.d.ts.map +1 -0
- package/build/psbt/utils.js +171 -0
- package/build/psbt/utils.js.map +1 -0
- package/build/psbt/validation.d.ts +88 -0
- package/build/psbt/validation.d.ts.map +1 -0
- package/build/psbt/validation.js +149 -0
- package/build/psbt/validation.js.map +1 -0
- package/build/psbt.d.ts +84 -120
- package/build/psbt.d.ts.map +1 -0
- package/build/psbt.js +411 -412
- package/build/psbt.js.map +1 -0
- package/build/pubkey.d.ts +27 -6
- package/build/pubkey.d.ts.map +1 -0
- package/build/pubkey.js +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 +100 -36
- package/build/script.js.map +1 -0
- package/build/script_number.d.ts +17 -0
- package/build/script_number.d.ts.map +1 -0
- package/build/script_number.js +19 -0
- package/build/script_number.js.map +1 -0
- package/build/script_signature.d.ts +23 -5
- package/build/script_signature.d.ts.map +1 -0
- package/build/script_signature.js +48 -15
- package/build/script_signature.js.map +1 -0
- package/build/transaction.d.ts +160 -18
- package/build/transaction.d.ts.map +1 -0
- package/build/transaction.js +443 -176
- package/build/transaction.js.map +1 -0
- package/build/tsconfig.build.tsbuildinfo +1 -0
- package/build/types.d.ts +36 -38
- package/build/types.d.ts.map +1 -0
- package/build/types.js +175 -57
- package/build/types.js.map +1 -0
- package/build/workers/WorkerSigningPool.d.ts +174 -0
- package/build/workers/WorkerSigningPool.d.ts.map +1 -0
- package/build/workers/WorkerSigningPool.js +553 -0
- package/build/workers/WorkerSigningPool.js.map +1 -0
- package/build/workers/WorkerSigningPool.node.d.ts +124 -0
- package/build/workers/WorkerSigningPool.node.d.ts.map +1 -0
- package/build/workers/WorkerSigningPool.node.js +753 -0
- package/build/workers/WorkerSigningPool.node.js.map +1 -0
- package/build/workers/ecc-bundle.d.ts +25 -0
- package/build/workers/ecc-bundle.d.ts.map +1 -0
- package/build/workers/ecc-bundle.js +25 -0
- package/build/workers/ecc-bundle.js.map +1 -0
- package/build/workers/index.d.ts +91 -0
- package/build/workers/index.d.ts.map +1 -0
- package/build/workers/index.js +114 -0
- package/build/workers/index.js.map +1 -0
- package/build/workers/psbt-parallel.d.ts +117 -0
- package/build/workers/psbt-parallel.d.ts.map +1 -0
- package/build/workers/psbt-parallel.js +233 -0
- package/build/workers/psbt-parallel.js.map +1 -0
- package/build/workers/signing-worker.d.ts +37 -0
- package/build/workers/signing-worker.d.ts.map +1 -0
- package/build/workers/signing-worker.js +350 -0
- package/build/workers/signing-worker.js.map +1 -0
- package/build/workers/types.d.ts +365 -0
- package/build/workers/types.d.ts.map +1 -0
- package/build/workers/types.js +60 -0
- package/build/workers/types.js.map +1 -0
- package/package.json +66 -8
- package/scripts/bundle-ecc.ts +111 -0
- package/src/address.ts +81 -44
- package/src/bech32utils.ts +3 -3
- package/src/bip66.ts +34 -24
- package/src/block.ts +196 -84
- package/src/branded.ts +18 -0
- package/src/crypto.ts +64 -26
- package/src/ecc/context.ts +277 -0
- package/src/ecc/index.ts +14 -0
- package/src/ecc/types.ts +154 -0
- package/src/ecpair.d.ts +99 -0
- package/src/errors.ts +163 -0
- package/src/index.ts +113 -9
- package/src/io/BinaryReader.ts +461 -0
- package/src/io/BinaryWriter.ts +696 -0
- package/src/io/MemoryPool.ts +343 -0
- package/src/io/base64.ts +20 -0
- package/src/io/hex.ts +155 -0
- package/src/io/index.ts +41 -0
- package/src/io/utils.ts +283 -0
- package/src/merkle.ts +14 -9
- package/src/networks.ts +9 -9
- package/src/payments/bip341.ts +34 -33
- package/src/payments/embed.ts +244 -41
- package/src/payments/index.ts +12 -10
- package/src/payments/p2ms.ts +490 -118
- package/src/payments/p2op.ts +431 -133
- package/src/payments/p2pk.ts +370 -72
- package/src/payments/p2pkh.ts +524 -130
- package/src/payments/p2sh.ts +572 -172
- package/src/payments/p2tr.ts +686 -194
- package/src/payments/p2wpkh.ts +484 -107
- package/src/payments/p2wsh.ts +526 -164
- package/src/payments/types.ts +80 -66
- package/src/psbt/bip371.ts +68 -51
- package/src/psbt/psbtutils.ts +39 -40
- package/src/psbt/types.ts +331 -0
- package/src/psbt/utils.ts +188 -0
- package/src/psbt/validation.ts +192 -0
- package/src/psbt.ts +566 -809
- package/src/pubkey.ts +22 -23
- package/src/push_data.ts +18 -16
- package/src/script.ts +82 -64
- package/src/script_number.ts +6 -6
- package/src/script_signature.ts +33 -36
- package/src/transaction.ts +458 -238
- package/src/types.ts +231 -100
- package/src/workers/WorkerSigningPool.node.ts +887 -0
- package/src/workers/WorkerSigningPool.ts +670 -0
- package/src/workers/ecc-bundle.ts +26 -0
- package/src/workers/index.ts +165 -0
- package/src/workers/psbt-parallel.ts +332 -0
- package/src/workers/signing-worker.ts +353 -0
- package/src/workers/types.ts +413 -0
- package/test/address.spec.ts +9 -6
- package/test/bitcoin.core.spec.ts +16 -17
- package/test/block.spec.ts +8 -7
- package/test/bufferutils.spec.ts +228 -214
- package/test/crypto.spec.ts +19 -11
- package/test/fixtures/p2pk.json +0 -8
- package/test/fixtures/p2pkh.json +1 -1
- package/test/fixtures/p2sh.json +1 -1
- package/test/fixtures/script.json +1 -1
- package/test/fixtures/transaction.json +2 -2
- package/test/integration/_regtest.ts +25 -0
- package/test/integration/addresses.spec.ts +4 -3
- package/test/integration/bip32.spec.ts +2 -1
- package/test/integration/blocks.spec.ts +1 -1
- package/test/integration/cltv.spec.ts +18 -16
- package/test/integration/csv.spec.ts +37 -64
- package/test/integration/payments.spec.ts +5 -3
- package/test/integration/taproot.spec.ts +76 -83
- package/test/integration/transactions.spec.ts +38 -35
- package/test/payments.spec.ts +35 -13
- package/test/payments.utils.ts +17 -16
- package/test/psbt.spec.ts +111 -100
- package/test/script.spec.ts +11 -10
- package/test/script_signature.spec.ts +9 -11
- package/test/taproot-cache.spec.ts +694 -0
- package/test/transaction.spec.ts +32 -40
- package/test/types.spec.ts +74 -29
- package/test/workers-pool.spec.ts +963 -0
- package/test/workers-signing.spec.ts +635 -0
- package/test/workers.spec.ts +1390 -0
- package/tsconfig.base.json +34 -18
- package/tsconfig.browser.json +15 -0
- package/tsconfig.build.json +5 -0
- package/tsconfig.json +5 -14
- package/vite.config.browser.ts +3 -42
- package/vitest.config.integration.ts +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,1390 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi, type Mock } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
SignatureType,
|
|
4
|
+
WorkerState,
|
|
5
|
+
isSigningError,
|
|
6
|
+
isSigningResult,
|
|
7
|
+
isWorkerReady,
|
|
8
|
+
type WorkerResponse,
|
|
9
|
+
type SigningResultMessage,
|
|
10
|
+
type SigningErrorMessage,
|
|
11
|
+
type WorkerReadyMessage,
|
|
12
|
+
type SigningTask,
|
|
13
|
+
type ParallelSignerKeyPair,
|
|
14
|
+
} from '../src/workers/types.js';
|
|
15
|
+
import { generateWorkerCode } from '../src/workers/signing-worker.js';
|
|
16
|
+
|
|
17
|
+
describe('Worker Types', () => {
|
|
18
|
+
describe('SignatureType', () => {
|
|
19
|
+
it('should have ECDSA = 0', () => {
|
|
20
|
+
expect(SignatureType.ECDSA).toBe(0);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should have Schnorr = 1', () => {
|
|
24
|
+
expect(SignatureType.Schnorr).toBe(1);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should be const (readonly)', () => {
|
|
28
|
+
// TypeScript ensures this at compile time, but verify runtime values exist
|
|
29
|
+
expect(Object.isFrozen(SignatureType)).toBe(false); // as const doesn't freeze
|
|
30
|
+
expect(SignatureType.ECDSA).toBeDefined();
|
|
31
|
+
expect(SignatureType.Schnorr).toBeDefined();
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('WorkerState', () => {
|
|
36
|
+
it('should have correct state values', () => {
|
|
37
|
+
expect(WorkerState.Initializing).toBe(0);
|
|
38
|
+
expect(WorkerState.Idle).toBe(1);
|
|
39
|
+
expect(WorkerState.Busy).toBe(2);
|
|
40
|
+
expect(WorkerState.ShuttingDown).toBe(3);
|
|
41
|
+
expect(WorkerState.Terminated).toBe(4);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should have unique values', () => {
|
|
45
|
+
const values = Object.values(WorkerState);
|
|
46
|
+
const uniqueValues = new Set(values);
|
|
47
|
+
expect(uniqueValues.size).toBe(values.length);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('Type Guards', () => {
|
|
52
|
+
describe('isSigningError', () => {
|
|
53
|
+
it('should return true for error responses', () => {
|
|
54
|
+
const errorResponse: SigningErrorMessage = {
|
|
55
|
+
type: 'error',
|
|
56
|
+
taskId: 'test-1',
|
|
57
|
+
error: 'Test error',
|
|
58
|
+
inputIndex: 0,
|
|
59
|
+
};
|
|
60
|
+
expect(isSigningError(errorResponse)).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should return false for result responses', () => {
|
|
64
|
+
const resultResponse: SigningResultMessage = {
|
|
65
|
+
type: 'result',
|
|
66
|
+
taskId: 'test-1',
|
|
67
|
+
signature: new Uint8Array(64),
|
|
68
|
+
inputIndex: 0,
|
|
69
|
+
publicKey: new Uint8Array(33),
|
|
70
|
+
signatureType: SignatureType.ECDSA,
|
|
71
|
+
};
|
|
72
|
+
expect(isSigningError(resultResponse)).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should return false for ready responses', () => {
|
|
76
|
+
const readyResponse: WorkerReadyMessage = {
|
|
77
|
+
type: 'ready',
|
|
78
|
+
};
|
|
79
|
+
expect(isSigningError(readyResponse)).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('isSigningResult', () => {
|
|
84
|
+
it('should return true for result responses', () => {
|
|
85
|
+
const resultResponse: SigningResultMessage = {
|
|
86
|
+
type: 'result',
|
|
87
|
+
taskId: 'test-1',
|
|
88
|
+
signature: new Uint8Array(64),
|
|
89
|
+
inputIndex: 0,
|
|
90
|
+
publicKey: new Uint8Array(33),
|
|
91
|
+
signatureType: SignatureType.Schnorr,
|
|
92
|
+
};
|
|
93
|
+
expect(isSigningResult(resultResponse)).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should return false for error responses', () => {
|
|
97
|
+
const errorResponse: SigningErrorMessage = {
|
|
98
|
+
type: 'error',
|
|
99
|
+
taskId: 'test-1',
|
|
100
|
+
error: 'Test error',
|
|
101
|
+
inputIndex: 0,
|
|
102
|
+
};
|
|
103
|
+
expect(isSigningResult(errorResponse)).toBe(false);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should return false for ready responses', () => {
|
|
107
|
+
const readyResponse: WorkerReadyMessage = {
|
|
108
|
+
type: 'ready',
|
|
109
|
+
};
|
|
110
|
+
expect(isSigningResult(readyResponse)).toBe(false);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('isWorkerReady', () => {
|
|
115
|
+
it('should return true for ready responses', () => {
|
|
116
|
+
const readyResponse: WorkerReadyMessage = {
|
|
117
|
+
type: 'ready',
|
|
118
|
+
};
|
|
119
|
+
expect(isWorkerReady(readyResponse)).toBe(true);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should return false for result responses', () => {
|
|
123
|
+
const resultResponse: SigningResultMessage = {
|
|
124
|
+
type: 'result',
|
|
125
|
+
taskId: 'test-1',
|
|
126
|
+
signature: new Uint8Array(64),
|
|
127
|
+
inputIndex: 0,
|
|
128
|
+
publicKey: new Uint8Array(33),
|
|
129
|
+
signatureType: SignatureType.ECDSA,
|
|
130
|
+
};
|
|
131
|
+
expect(isWorkerReady(resultResponse)).toBe(false);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should return false for error responses', () => {
|
|
135
|
+
const errorResponse: SigningErrorMessage = {
|
|
136
|
+
type: 'error',
|
|
137
|
+
taskId: 'test-1',
|
|
138
|
+
error: 'Test error',
|
|
139
|
+
inputIndex: 0,
|
|
140
|
+
};
|
|
141
|
+
expect(isWorkerReady(errorResponse)).toBe(false);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe('Worker Code Generation', () => {
|
|
148
|
+
describe('generateWorkerCode', () => {
|
|
149
|
+
let workerCode: string;
|
|
150
|
+
|
|
151
|
+
beforeEach(() => {
|
|
152
|
+
workerCode = generateWorkerCode();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should generate non-empty code', () => {
|
|
156
|
+
expect(workerCode).toBeTruthy();
|
|
157
|
+
expect(workerCode.length).toBeGreaterThan(100);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should contain secureZero function', () => {
|
|
161
|
+
expect(workerCode).toContain('secureZero');
|
|
162
|
+
expect(workerCode).toContain('arr.fill(0)');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should contain message handler', () => {
|
|
166
|
+
expect(workerCode).toContain('self.onmessage');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should handle init message type', () => {
|
|
170
|
+
expect(workerCode).toContain("case 'init':");
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should handle sign message type', () => {
|
|
174
|
+
expect(workerCode).toContain("case 'sign':");
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('should handle shutdown message type', () => {
|
|
178
|
+
expect(workerCode).toContain("case 'shutdown':");
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('should validate hash length (32 bytes)', () => {
|
|
182
|
+
expect(workerCode).toContain('hash.length !== 32');
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should validate private key length (32 bytes)', () => {
|
|
186
|
+
expect(workerCode).toContain('privateKey.length !== 32');
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('should zero private key after signing', () => {
|
|
190
|
+
// Check that secureZero is called on privateKey
|
|
191
|
+
expect(workerCode).toContain('secureZero(privateKey)');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should handle Schnorr signatures (signatureType === 1)', () => {
|
|
195
|
+
expect(workerCode).toContain('signatureType === 1');
|
|
196
|
+
expect(workerCode).toContain('signSchnorr');
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('should handle ECDSA signatures', () => {
|
|
200
|
+
expect(workerCode).toContain('eccLib.sign');
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('should post ready message after init', () => {
|
|
204
|
+
expect(workerCode).toContain("type: 'ready'");
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('should post shutdown-ack message on shutdown', () => {
|
|
208
|
+
expect(workerCode).toContain("type: 'shutdown-ack'");
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('should call self.close() on shutdown', () => {
|
|
212
|
+
expect(workerCode).toContain('self.close()');
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('should include error handling', () => {
|
|
216
|
+
expect(workerCode).toContain('catch');
|
|
217
|
+
expect(workerCode).toContain("type: 'error'");
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('should support lowR for ECDSA', () => {
|
|
221
|
+
expect(workerCode).toContain('lowR');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should support leafHash for Taproot', () => {
|
|
225
|
+
expect(workerCode).toContain('leafHash');
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
describe('SigningTask Interface', () => {
|
|
231
|
+
it('should accept valid ECDSA task', () => {
|
|
232
|
+
const task: SigningTask = {
|
|
233
|
+
taskId: 'ecdsa-task-1',
|
|
234
|
+
inputIndex: 0,
|
|
235
|
+
hash: new Uint8Array(32),
|
|
236
|
+
signatureType: SignatureType.ECDSA,
|
|
237
|
+
sighashType: 0x01, // SIGHASH_ALL
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
expect(task.taskId).toBe('ecdsa-task-1');
|
|
241
|
+
expect(task.inputIndex).toBe(0);
|
|
242
|
+
expect(task.hash.length).toBe(32);
|
|
243
|
+
expect(task.signatureType).toBe(SignatureType.ECDSA);
|
|
244
|
+
expect(task.sighashType).toBe(0x01);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('should accept valid Schnorr task', () => {
|
|
248
|
+
const task: SigningTask = {
|
|
249
|
+
taskId: 'schnorr-task-1',
|
|
250
|
+
inputIndex: 1,
|
|
251
|
+
hash: new Uint8Array(32).fill(0xab),
|
|
252
|
+
signatureType: SignatureType.Schnorr,
|
|
253
|
+
sighashType: 0x00, // SIGHASH_DEFAULT
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
expect(task.signatureType).toBe(SignatureType.Schnorr);
|
|
257
|
+
expect(task.sighashType).toBe(0x00);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('should accept task with lowR option', () => {
|
|
261
|
+
const task: SigningTask = {
|
|
262
|
+
taskId: 'ecdsa-lowr-1',
|
|
263
|
+
inputIndex: 0,
|
|
264
|
+
hash: new Uint8Array(32),
|
|
265
|
+
signatureType: SignatureType.ECDSA,
|
|
266
|
+
sighashType: 0x01,
|
|
267
|
+
lowR: true,
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
expect(task.lowR).toBe(true);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('should accept task with leafHash for Taproot script-path', () => {
|
|
274
|
+
const leafHash = new Uint8Array(32).fill(0xcd);
|
|
275
|
+
const task: SigningTask = {
|
|
276
|
+
taskId: 'taproot-script-1',
|
|
277
|
+
inputIndex: 2,
|
|
278
|
+
hash: new Uint8Array(32),
|
|
279
|
+
signatureType: SignatureType.Schnorr,
|
|
280
|
+
sighashType: 0x00,
|
|
281
|
+
leafHash,
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
expect(task.leafHash).toBeDefined();
|
|
285
|
+
expect(task.leafHash?.length).toBe(32);
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
describe('ParallelSignerKeyPair Interface', () => {
|
|
290
|
+
it('should accept valid key pair with getPrivateKey', () => {
|
|
291
|
+
const privateKey = new Uint8Array(32).fill(0x42);
|
|
292
|
+
const publicKey = new Uint8Array(33).fill(0x02);
|
|
293
|
+
|
|
294
|
+
const keyPair: ParallelSignerKeyPair = {
|
|
295
|
+
publicKey,
|
|
296
|
+
getPrivateKey: () => privateKey,
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
expect(keyPair.publicKey.length).toBe(33);
|
|
300
|
+
expect(keyPair.getPrivateKey().length).toBe(32);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('should accept key pair with optional sign method', () => {
|
|
304
|
+
const privateKey = new Uint8Array(32).fill(0x42);
|
|
305
|
+
const publicKey = new Uint8Array(33).fill(0x02);
|
|
306
|
+
|
|
307
|
+
const keyPair: ParallelSignerKeyPair = {
|
|
308
|
+
publicKey,
|
|
309
|
+
getPrivateKey: () => privateKey,
|
|
310
|
+
sign: (hash: Uint8Array, _lowR?: boolean) => new Uint8Array(64), // DER signature
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
expect(keyPair.sign).toBeDefined();
|
|
314
|
+
expect(keyPair.sign!(new Uint8Array(32)).length).toBe(64);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('should accept key pair with optional signSchnorr method', () => {
|
|
318
|
+
const privateKey = new Uint8Array(32).fill(0x42);
|
|
319
|
+
const publicKey = new Uint8Array(33).fill(0x02);
|
|
320
|
+
|
|
321
|
+
const keyPair: ParallelSignerKeyPair = {
|
|
322
|
+
publicKey,
|
|
323
|
+
getPrivateKey: () => privateKey,
|
|
324
|
+
signSchnorr: (hash: Uint8Array) => new Uint8Array(64), // Schnorr signature
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
expect(keyPair.signSchnorr).toBeDefined();
|
|
328
|
+
expect(keyPair.signSchnorr!(new Uint8Array(32)).length).toBe(64);
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
describe('SigningResultMessage', () => {
|
|
333
|
+
it('should represent ECDSA result correctly', () => {
|
|
334
|
+
const result: SigningResultMessage = {
|
|
335
|
+
type: 'result',
|
|
336
|
+
taskId: 'test-ecdsa',
|
|
337
|
+
signature: new Uint8Array(71), // DER-encoded ECDSA
|
|
338
|
+
inputIndex: 0,
|
|
339
|
+
publicKey: new Uint8Array(33),
|
|
340
|
+
signatureType: SignatureType.ECDSA,
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
expect(result.type).toBe('result');
|
|
344
|
+
expect(result.signatureType).toBe(SignatureType.ECDSA);
|
|
345
|
+
expect(result.leafHash).toBeUndefined();
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it('should represent Schnorr key-path result correctly', () => {
|
|
349
|
+
const result: SigningResultMessage = {
|
|
350
|
+
type: 'result',
|
|
351
|
+
taskId: 'test-schnorr-key',
|
|
352
|
+
signature: new Uint8Array(64), // Raw Schnorr
|
|
353
|
+
inputIndex: 1,
|
|
354
|
+
publicKey: new Uint8Array(32), // x-only pubkey for Taproot
|
|
355
|
+
signatureType: SignatureType.Schnorr,
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
expect(result.signatureType).toBe(SignatureType.Schnorr);
|
|
359
|
+
expect(result.signature.length).toBe(64);
|
|
360
|
+
expect(result.leafHash).toBeUndefined();
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it('should represent Schnorr script-path result correctly', () => {
|
|
364
|
+
const leafHash = new Uint8Array(32).fill(0xef);
|
|
365
|
+
const result: SigningResultMessage = {
|
|
366
|
+
type: 'result',
|
|
367
|
+
taskId: 'test-schnorr-script',
|
|
368
|
+
signature: new Uint8Array(64),
|
|
369
|
+
inputIndex: 2,
|
|
370
|
+
publicKey: new Uint8Array(32),
|
|
371
|
+
signatureType: SignatureType.Schnorr,
|
|
372
|
+
leafHash,
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
expect(result.leafHash).toBeDefined();
|
|
376
|
+
expect(result.leafHash?.length).toBe(32);
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
describe('SigningErrorMessage', () => {
|
|
381
|
+
it('should represent error correctly', () => {
|
|
382
|
+
const error: SigningErrorMessage = {
|
|
383
|
+
type: 'error',
|
|
384
|
+
taskId: 'failed-task',
|
|
385
|
+
error: 'Invalid private key',
|
|
386
|
+
inputIndex: 3,
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
expect(error.type).toBe('error');
|
|
390
|
+
expect(error.error).toBe('Invalid private key');
|
|
391
|
+
expect(error.inputIndex).toBe(3);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it('should handle various error messages', () => {
|
|
395
|
+
const errors = [
|
|
396
|
+
'Invalid hash: must be 32 bytes',
|
|
397
|
+
'Invalid private key: must be 32 bytes',
|
|
398
|
+
'ECC library not initialized',
|
|
399
|
+
'Signing failed',
|
|
400
|
+
'ECC library does not support Schnorr signatures',
|
|
401
|
+
];
|
|
402
|
+
|
|
403
|
+
for (const errorMsg of errors) {
|
|
404
|
+
const error: SigningErrorMessage = {
|
|
405
|
+
type: 'error',
|
|
406
|
+
taskId: `error-${errorMsg.slice(0, 10)}`,
|
|
407
|
+
error: errorMsg,
|
|
408
|
+
inputIndex: 0,
|
|
409
|
+
};
|
|
410
|
+
expect(error.error).toBe(errorMsg);
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
describe('Edge Cases', () => {
|
|
416
|
+
describe('Hash Validation', () => {
|
|
417
|
+
it('should reject hash shorter than 32 bytes', () => {
|
|
418
|
+
const task: SigningTask = {
|
|
419
|
+
taskId: 'short-hash',
|
|
420
|
+
inputIndex: 0,
|
|
421
|
+
hash: new Uint8Array(31), // Too short
|
|
422
|
+
signatureType: SignatureType.ECDSA,
|
|
423
|
+
sighashType: 0x01,
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
// The worker would reject this, but we can verify the task structure
|
|
427
|
+
expect(task.hash.length).toBe(31);
|
|
428
|
+
expect(task.hash.length).not.toBe(32);
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
it('should reject hash longer than 32 bytes', () => {
|
|
432
|
+
const task: SigningTask = {
|
|
433
|
+
taskId: 'long-hash',
|
|
434
|
+
inputIndex: 0,
|
|
435
|
+
hash: new Uint8Array(33), // Too long
|
|
436
|
+
signatureType: SignatureType.ECDSA,
|
|
437
|
+
sighashType: 0x01,
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
expect(task.hash.length).toBe(33);
|
|
441
|
+
expect(task.hash.length).not.toBe(32);
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
it('should accept exactly 32-byte hash', () => {
|
|
445
|
+
const task: SigningTask = {
|
|
446
|
+
taskId: 'valid-hash',
|
|
447
|
+
inputIndex: 0,
|
|
448
|
+
hash: new Uint8Array(32),
|
|
449
|
+
signatureType: SignatureType.ECDSA,
|
|
450
|
+
sighashType: 0x01,
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
expect(task.hash.length).toBe(32);
|
|
454
|
+
});
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
describe('Input Index Bounds', () => {
|
|
458
|
+
it('should handle input index 0', () => {
|
|
459
|
+
const task: SigningTask = {
|
|
460
|
+
taskId: 'index-0',
|
|
461
|
+
inputIndex: 0,
|
|
462
|
+
hash: new Uint8Array(32),
|
|
463
|
+
signatureType: SignatureType.ECDSA,
|
|
464
|
+
sighashType: 0x01,
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
expect(task.inputIndex).toBe(0);
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
it('should handle large input indices', () => {
|
|
471
|
+
const task: SigningTask = {
|
|
472
|
+
taskId: 'large-index',
|
|
473
|
+
inputIndex: 999,
|
|
474
|
+
hash: new Uint8Array(32),
|
|
475
|
+
signatureType: SignatureType.ECDSA,
|
|
476
|
+
sighashType: 0x01,
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
expect(task.inputIndex).toBe(999);
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
it('should handle negative input index (edge case)', () => {
|
|
483
|
+
// Note: In practice, this would be validated elsewhere
|
|
484
|
+
const task: SigningTask = {
|
|
485
|
+
taskId: 'negative-index',
|
|
486
|
+
inputIndex: -1,
|
|
487
|
+
hash: new Uint8Array(32),
|
|
488
|
+
signatureType: SignatureType.ECDSA,
|
|
489
|
+
sighashType: 0x01,
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
expect(task.inputIndex).toBe(-1);
|
|
493
|
+
});
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
describe('Sighash Types', () => {
|
|
497
|
+
it('should handle SIGHASH_ALL (0x01)', () => {
|
|
498
|
+
const task: SigningTask = {
|
|
499
|
+
taskId: 'sighash-all',
|
|
500
|
+
inputIndex: 0,
|
|
501
|
+
hash: new Uint8Array(32),
|
|
502
|
+
signatureType: SignatureType.ECDSA,
|
|
503
|
+
sighashType: 0x01,
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
expect(task.sighashType).toBe(0x01);
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
it('should handle SIGHASH_NONE (0x02)', () => {
|
|
510
|
+
const task: SigningTask = {
|
|
511
|
+
taskId: 'sighash-none',
|
|
512
|
+
inputIndex: 0,
|
|
513
|
+
hash: new Uint8Array(32),
|
|
514
|
+
signatureType: SignatureType.ECDSA,
|
|
515
|
+
sighashType: 0x02,
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
expect(task.sighashType).toBe(0x02);
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
it('should handle SIGHASH_SINGLE (0x03)', () => {
|
|
522
|
+
const task: SigningTask = {
|
|
523
|
+
taskId: 'sighash-single',
|
|
524
|
+
inputIndex: 0,
|
|
525
|
+
hash: new Uint8Array(32),
|
|
526
|
+
signatureType: SignatureType.ECDSA,
|
|
527
|
+
sighashType: 0x03,
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
expect(task.sighashType).toBe(0x03);
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
it('should handle SIGHASH_ANYONECANPAY (0x80)', () => {
|
|
534
|
+
const task: SigningTask = {
|
|
535
|
+
taskId: 'sighash-anyonecanpay',
|
|
536
|
+
inputIndex: 0,
|
|
537
|
+
hash: new Uint8Array(32),
|
|
538
|
+
signatureType: SignatureType.ECDSA,
|
|
539
|
+
sighashType: 0x81, // ALL | ANYONECANPAY
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
expect(task.sighashType).toBe(0x81);
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
it('should handle SIGHASH_DEFAULT (0x00) for Taproot', () => {
|
|
546
|
+
const task: SigningTask = {
|
|
547
|
+
taskId: 'sighash-default',
|
|
548
|
+
inputIndex: 0,
|
|
549
|
+
hash: new Uint8Array(32),
|
|
550
|
+
signatureType: SignatureType.Schnorr,
|
|
551
|
+
sighashType: 0x00,
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
expect(task.sighashType).toBe(0x00);
|
|
555
|
+
});
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
describe('Public Key Formats', () => {
|
|
559
|
+
it('should handle compressed public key (33 bytes)', () => {
|
|
560
|
+
const publicKey = new Uint8Array(33);
|
|
561
|
+
publicKey[0] = 0x02; // Even y-coordinate prefix
|
|
562
|
+
|
|
563
|
+
const keyPair: ParallelSignerKeyPair = {
|
|
564
|
+
publicKey,
|
|
565
|
+
getPrivateKey: () => new Uint8Array(32),
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
expect(keyPair.publicKey.length).toBe(33);
|
|
569
|
+
expect(keyPair.publicKey[0]).toBe(0x02);
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
it('should handle compressed public key (odd prefix)', () => {
|
|
573
|
+
const publicKey = new Uint8Array(33);
|
|
574
|
+
publicKey[0] = 0x03; // Odd y-coordinate prefix
|
|
575
|
+
|
|
576
|
+
const keyPair: ParallelSignerKeyPair = {
|
|
577
|
+
publicKey,
|
|
578
|
+
getPrivateKey: () => new Uint8Array(32),
|
|
579
|
+
};
|
|
580
|
+
|
|
581
|
+
expect(keyPair.publicKey[0]).toBe(0x03);
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
it('should handle uncompressed public key (65 bytes)', () => {
|
|
585
|
+
const publicKey = new Uint8Array(65);
|
|
586
|
+
publicKey[0] = 0x04; // Uncompressed prefix
|
|
587
|
+
|
|
588
|
+
const keyPair: ParallelSignerKeyPair = {
|
|
589
|
+
publicKey,
|
|
590
|
+
getPrivateKey: () => new Uint8Array(32),
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
expect(keyPair.publicKey.length).toBe(65);
|
|
594
|
+
expect(keyPair.publicKey[0]).toBe(0x04);
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
it('should handle x-only public key (32 bytes) for Taproot', () => {
|
|
598
|
+
const publicKey = new Uint8Array(32); // x-only, no prefix
|
|
599
|
+
|
|
600
|
+
const keyPair: ParallelSignerKeyPair = {
|
|
601
|
+
publicKey,
|
|
602
|
+
getPrivateKey: () => new Uint8Array(32),
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
expect(keyPair.publicKey.length).toBe(32);
|
|
606
|
+
});
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
describe('Secure Zero Behavior', () => {
|
|
610
|
+
it('should verify secureZero is in worker code', () => {
|
|
611
|
+
const code = generateWorkerCode();
|
|
612
|
+
|
|
613
|
+
// Verify secureZero function exists
|
|
614
|
+
expect(code).toContain('function secureZero(arr)');
|
|
615
|
+
|
|
616
|
+
// Verify it uses fill(0)
|
|
617
|
+
expect(code).toContain('arr.fill(0)');
|
|
618
|
+
|
|
619
|
+
// Verify it's called on privateKey
|
|
620
|
+
const privateKeyZeroCount = (code.match(/secureZero\(privateKey\)/g) || []).length;
|
|
621
|
+
expect(privateKeyZeroCount).toBeGreaterThanOrEqual(2); // At least on success and error paths
|
|
622
|
+
});
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
describe('Empty and Null Cases', () => {
|
|
626
|
+
it('should handle empty task ID', () => {
|
|
627
|
+
const task: SigningTask = {
|
|
628
|
+
taskId: '',
|
|
629
|
+
inputIndex: 0,
|
|
630
|
+
hash: new Uint8Array(32),
|
|
631
|
+
signatureType: SignatureType.ECDSA,
|
|
632
|
+
sighashType: 0x01,
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
expect(task.taskId).toBe('');
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
it('should handle zero-filled hash', () => {
|
|
639
|
+
const task: SigningTask = {
|
|
640
|
+
taskId: 'zero-hash',
|
|
641
|
+
inputIndex: 0,
|
|
642
|
+
hash: new Uint8Array(32).fill(0),
|
|
643
|
+
signatureType: SignatureType.ECDSA,
|
|
644
|
+
sighashType: 0x01,
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
expect(task.hash.every((b) => b === 0)).toBe(true);
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
it('should handle max value hash', () => {
|
|
651
|
+
const task: SigningTask = {
|
|
652
|
+
taskId: 'max-hash',
|
|
653
|
+
inputIndex: 0,
|
|
654
|
+
hash: new Uint8Array(32).fill(0xff),
|
|
655
|
+
signatureType: SignatureType.ECDSA,
|
|
656
|
+
sighashType: 0x01,
|
|
657
|
+
};
|
|
658
|
+
|
|
659
|
+
expect(task.hash.every((b) => b === 0xff)).toBe(true);
|
|
660
|
+
});
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
describe('Concurrent Task IDs', () => {
|
|
664
|
+
it('should allow unique task IDs', () => {
|
|
665
|
+
const tasks: SigningTask[] = [];
|
|
666
|
+
for (let i = 0; i < 100; i++) {
|
|
667
|
+
tasks.push({
|
|
668
|
+
taskId: `task-${i}`,
|
|
669
|
+
inputIndex: i,
|
|
670
|
+
hash: new Uint8Array(32),
|
|
671
|
+
signatureType: SignatureType.ECDSA,
|
|
672
|
+
sighashType: 0x01,
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const taskIds = tasks.map((t) => t.taskId);
|
|
677
|
+
const uniqueIds = new Set(taskIds);
|
|
678
|
+
expect(uniqueIds.size).toBe(100);
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
it('should allow duplicate task IDs (implementation handles this)', () => {
|
|
682
|
+
const task1: SigningTask = {
|
|
683
|
+
taskId: 'duplicate-id',
|
|
684
|
+
inputIndex: 0,
|
|
685
|
+
hash: new Uint8Array(32),
|
|
686
|
+
signatureType: SignatureType.ECDSA,
|
|
687
|
+
sighashType: 0x01,
|
|
688
|
+
};
|
|
689
|
+
|
|
690
|
+
const task2: SigningTask = {
|
|
691
|
+
taskId: 'duplicate-id', // Same ID
|
|
692
|
+
inputIndex: 1,
|
|
693
|
+
hash: new Uint8Array(32),
|
|
694
|
+
signatureType: SignatureType.ECDSA,
|
|
695
|
+
sighashType: 0x01,
|
|
696
|
+
};
|
|
697
|
+
|
|
698
|
+
// Pool implementation should handle this, but tasks can have same ID
|
|
699
|
+
expect(task1.taskId).toBe(task2.taskId);
|
|
700
|
+
expect(task1.inputIndex).not.toBe(task2.inputIndex);
|
|
701
|
+
});
|
|
702
|
+
});
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
describe('Memory Safety', () => {
|
|
706
|
+
it('should allow getPrivateKey to return fresh array each time', () => {
|
|
707
|
+
let callCount = 0;
|
|
708
|
+
const keyPair: ParallelSignerKeyPair = {
|
|
709
|
+
publicKey: new Uint8Array(33),
|
|
710
|
+
getPrivateKey: () => {
|
|
711
|
+
callCount++;
|
|
712
|
+
return new Uint8Array(32).fill(callCount);
|
|
713
|
+
},
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
const key1 = keyPair.getPrivateKey();
|
|
717
|
+
const key2 = keyPair.getPrivateKey();
|
|
718
|
+
|
|
719
|
+
expect(key1[0]).toBe(1);
|
|
720
|
+
expect(key2[0]).toBe(2);
|
|
721
|
+
expect(callCount).toBe(2);
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
it('should allow zeroing returned private key', () => {
|
|
725
|
+
const originalKey = new Uint8Array(32).fill(0x42);
|
|
726
|
+
const keyPair: ParallelSignerKeyPair = {
|
|
727
|
+
publicKey: new Uint8Array(33),
|
|
728
|
+
getPrivateKey: () => {
|
|
729
|
+
// Return a copy so original isn't affected
|
|
730
|
+
const copy = new Uint8Array(originalKey);
|
|
731
|
+
return copy;
|
|
732
|
+
},
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
const key = keyPair.getPrivateKey();
|
|
736
|
+
expect(key[0]).toBe(0x42);
|
|
737
|
+
|
|
738
|
+
// Zero the key (simulating what worker does)
|
|
739
|
+
key.fill(0);
|
|
740
|
+
expect(key[0]).toBe(0);
|
|
741
|
+
|
|
742
|
+
// Original should be unaffected
|
|
743
|
+
expect(originalKey[0]).toBe(0x42);
|
|
744
|
+
});
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
describe('Signature Verification Helpers', () => {
|
|
748
|
+
it('should identify ECDSA signatures by type', () => {
|
|
749
|
+
const result: SigningResultMessage = {
|
|
750
|
+
type: 'result',
|
|
751
|
+
taskId: 'test',
|
|
752
|
+
signature: new Uint8Array(71),
|
|
753
|
+
inputIndex: 0,
|
|
754
|
+
publicKey: new Uint8Array(33),
|
|
755
|
+
signatureType: SignatureType.ECDSA,
|
|
756
|
+
};
|
|
757
|
+
|
|
758
|
+
expect(result.signatureType === SignatureType.ECDSA).toBe(true);
|
|
759
|
+
expect(result.signatureType === SignatureType.Schnorr).toBe(false);
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
it('should identify Schnorr signatures by type', () => {
|
|
763
|
+
const result: SigningResultMessage = {
|
|
764
|
+
type: 'result',
|
|
765
|
+
taskId: 'test',
|
|
766
|
+
signature: new Uint8Array(64),
|
|
767
|
+
inputIndex: 0,
|
|
768
|
+
publicKey: new Uint8Array(32),
|
|
769
|
+
signatureType: SignatureType.Schnorr,
|
|
770
|
+
};
|
|
771
|
+
|
|
772
|
+
expect(result.signatureType === SignatureType.Schnorr).toBe(true);
|
|
773
|
+
expect(result.signatureType === SignatureType.ECDSA).toBe(false);
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
it('should distinguish key-path from script-path Taproot', () => {
|
|
777
|
+
const keyPathResult: SigningResultMessage = {
|
|
778
|
+
type: 'result',
|
|
779
|
+
taskId: 'keypath',
|
|
780
|
+
signature: new Uint8Array(64),
|
|
781
|
+
inputIndex: 0,
|
|
782
|
+
publicKey: new Uint8Array(32),
|
|
783
|
+
signatureType: SignatureType.Schnorr,
|
|
784
|
+
// No leafHash = key-path
|
|
785
|
+
};
|
|
786
|
+
|
|
787
|
+
const scriptPathResult: SigningResultMessage = {
|
|
788
|
+
type: 'result',
|
|
789
|
+
taskId: 'scriptpath',
|
|
790
|
+
signature: new Uint8Array(64),
|
|
791
|
+
inputIndex: 0,
|
|
792
|
+
publicKey: new Uint8Array(32),
|
|
793
|
+
signatureType: SignatureType.Schnorr,
|
|
794
|
+
leafHash: new Uint8Array(32), // Has leafHash = script-path
|
|
795
|
+
};
|
|
796
|
+
|
|
797
|
+
expect(keyPathResult.leafHash).toBeUndefined();
|
|
798
|
+
expect(scriptPathResult.leafHash).toBeDefined();
|
|
799
|
+
});
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
describe('Worker Code Security', () => {
|
|
803
|
+
describe('Secure Zero Implementation', () => {
|
|
804
|
+
it('should zero Uint8Array correctly', () => {
|
|
805
|
+
// Simulate the secureZero function from worker code
|
|
806
|
+
const secureZero = (arr: Uint8Array | null | undefined): void => {
|
|
807
|
+
if (arr && arr.fill) {
|
|
808
|
+
arr.fill(0);
|
|
809
|
+
}
|
|
810
|
+
};
|
|
811
|
+
|
|
812
|
+
const data = new Uint8Array([1, 2, 3, 4, 5]);
|
|
813
|
+
expect(data[0]).toBe(1);
|
|
814
|
+
|
|
815
|
+
secureZero(data);
|
|
816
|
+
|
|
817
|
+
expect(data[0]).toBe(0);
|
|
818
|
+
expect(data.every((b) => b === 0)).toBe(true);
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
it('should handle null safely', () => {
|
|
822
|
+
const secureZero = (arr: Uint8Array | null | undefined): void => {
|
|
823
|
+
if (arr && arr.fill) {
|
|
824
|
+
arr.fill(0);
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
|
|
828
|
+
// Should not throw
|
|
829
|
+
expect(() => secureZero(null)).not.toThrow();
|
|
830
|
+
expect(() => secureZero(undefined)).not.toThrow();
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
it('should handle empty array', () => {
|
|
834
|
+
const secureZero = (arr: Uint8Array | null | undefined): void => {
|
|
835
|
+
if (arr && arr.fill) {
|
|
836
|
+
arr.fill(0);
|
|
837
|
+
}
|
|
838
|
+
};
|
|
839
|
+
|
|
840
|
+
const empty = new Uint8Array(0);
|
|
841
|
+
expect(() => secureZero(empty)).not.toThrow();
|
|
842
|
+
expect(empty.length).toBe(0);
|
|
843
|
+
});
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
describe('Message Type Handling', () => {
|
|
847
|
+
it('should recognize all valid message types', () => {
|
|
848
|
+
const validTypes = ['init', 'sign', 'shutdown'];
|
|
849
|
+
const code = generateWorkerCode();
|
|
850
|
+
|
|
851
|
+
for (const type of validTypes) {
|
|
852
|
+
expect(code).toContain(`case '${type}':`);
|
|
853
|
+
}
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
it('should handle unknown message types', () => {
|
|
857
|
+
const code = generateWorkerCode();
|
|
858
|
+
expect(code).toContain('default:');
|
|
859
|
+
expect(code).toContain('Unknown message type');
|
|
860
|
+
});
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
describe('ECC Library Validation', () => {
|
|
864
|
+
it('should have ECC library bundled at compile time', () => {
|
|
865
|
+
const code = generateWorkerCode();
|
|
866
|
+
// ECC library is bundled directly, no runtime check needed
|
|
867
|
+
expect(code).toContain('eccBundle');
|
|
868
|
+
expect(code).toContain('nobleBundle');
|
|
869
|
+
expect(code).toContain('eccLib');
|
|
870
|
+
});
|
|
871
|
+
|
|
872
|
+
it('should check for Schnorr support', () => {
|
|
873
|
+
const code = generateWorkerCode();
|
|
874
|
+
expect(code).toContain('signSchnorr');
|
|
875
|
+
expect(code).toContain('ECC library does not support Schnorr');
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
it('should check for ECDSA support', () => {
|
|
879
|
+
const code = generateWorkerCode();
|
|
880
|
+
expect(code).toContain('eccLib.sign');
|
|
881
|
+
expect(code).toContain('ECC library does not support ECDSA');
|
|
882
|
+
});
|
|
883
|
+
});
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
describe('Blob URL Creation', () => {
|
|
887
|
+
// Skip these tests if URL.createObjectURL is not available (Node.js)
|
|
888
|
+
const hasURLSupport =
|
|
889
|
+
typeof URL !== 'undefined' &&
|
|
890
|
+
typeof URL.createObjectURL === 'function' &&
|
|
891
|
+
typeof Blob !== 'undefined';
|
|
892
|
+
|
|
893
|
+
it.skipIf(!hasURLSupport)('should create blob URL from worker code', async () => {
|
|
894
|
+
const { createWorkerBlobUrl, revokeWorkerBlobUrl } =
|
|
895
|
+
await import('../src/workers/signing-worker.js');
|
|
896
|
+
|
|
897
|
+
const url = createWorkerBlobUrl();
|
|
898
|
+
expect(url).toMatch(/^blob:/);
|
|
899
|
+
|
|
900
|
+
// Cleanup
|
|
901
|
+
revokeWorkerBlobUrl(url);
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
it('should generate valid JavaScript code', () => {
|
|
905
|
+
const code = generateWorkerCode();
|
|
906
|
+
|
|
907
|
+
// Check it's valid JS by looking for syntax elements
|
|
908
|
+
expect(code).toContain('function');
|
|
909
|
+
expect(code).toContain('self.onmessage');
|
|
910
|
+
expect(code).toContain('self.postMessage');
|
|
911
|
+
expect(code).toContain('switch');
|
|
912
|
+
expect(code).toContain('case');
|
|
913
|
+
expect(code).toContain('try');
|
|
914
|
+
expect(code).toContain('catch');
|
|
915
|
+
});
|
|
916
|
+
});
|
|
917
|
+
|
|
918
|
+
describe('ParallelSigningResult', () => {
|
|
919
|
+
it('should represent successful batch result', () => {
|
|
920
|
+
const signatures = new Map<number, SigningResultMessage>();
|
|
921
|
+
signatures.set(0, {
|
|
922
|
+
type: 'result',
|
|
923
|
+
taskId: 'task-0',
|
|
924
|
+
signature: new Uint8Array(64),
|
|
925
|
+
inputIndex: 0,
|
|
926
|
+
publicKey: new Uint8Array(33),
|
|
927
|
+
signatureType: SignatureType.ECDSA,
|
|
928
|
+
});
|
|
929
|
+
signatures.set(1, {
|
|
930
|
+
type: 'result',
|
|
931
|
+
taskId: 'task-1',
|
|
932
|
+
signature: new Uint8Array(64),
|
|
933
|
+
inputIndex: 1,
|
|
934
|
+
publicKey: new Uint8Array(33),
|
|
935
|
+
signatureType: SignatureType.Schnorr,
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
const result = {
|
|
939
|
+
success: true,
|
|
940
|
+
signatures,
|
|
941
|
+
errors: new Map<number, string>(),
|
|
942
|
+
durationMs: 150,
|
|
943
|
+
};
|
|
944
|
+
|
|
945
|
+
expect(result.success).toBe(true);
|
|
946
|
+
expect(result.signatures.size).toBe(2);
|
|
947
|
+
expect(result.errors.size).toBe(0);
|
|
948
|
+
expect(result.durationMs).toBe(150);
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
it('should represent partial failure result', () => {
|
|
952
|
+
const signatures = new Map<number, SigningResultMessage>();
|
|
953
|
+
signatures.set(0, {
|
|
954
|
+
type: 'result',
|
|
955
|
+
taskId: 'task-0',
|
|
956
|
+
signature: new Uint8Array(64),
|
|
957
|
+
inputIndex: 0,
|
|
958
|
+
publicKey: new Uint8Array(33),
|
|
959
|
+
signatureType: SignatureType.ECDSA,
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
const errors = new Map<number, string>();
|
|
963
|
+
errors.set(1, 'Invalid private key');
|
|
964
|
+
errors.set(2, 'Signing timeout');
|
|
965
|
+
|
|
966
|
+
const result = {
|
|
967
|
+
success: false,
|
|
968
|
+
signatures,
|
|
969
|
+
errors,
|
|
970
|
+
durationMs: 5000,
|
|
971
|
+
};
|
|
972
|
+
|
|
973
|
+
expect(result.success).toBe(false);
|
|
974
|
+
expect(result.signatures.size).toBe(1);
|
|
975
|
+
expect(result.errors.size).toBe(2);
|
|
976
|
+
expect(result.errors.get(1)).toBe('Invalid private key');
|
|
977
|
+
expect(result.errors.get(2)).toBe('Signing timeout');
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
it('should represent empty batch result', () => {
|
|
981
|
+
const result = {
|
|
982
|
+
success: true,
|
|
983
|
+
signatures: new Map<number, SigningResultMessage>(),
|
|
984
|
+
errors: new Map<number, string>(),
|
|
985
|
+
durationMs: 0,
|
|
986
|
+
};
|
|
987
|
+
|
|
988
|
+
expect(result.success).toBe(true);
|
|
989
|
+
expect(result.signatures.size).toBe(0);
|
|
990
|
+
expect(result.errors.size).toBe(0);
|
|
991
|
+
});
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
describe('WorkerPoolConfig', () => {
|
|
995
|
+
it('should accept minimal config', () => {
|
|
996
|
+
const config = {};
|
|
997
|
+
expect(config).toBeDefined();
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
it('should accept full config', () => {
|
|
1001
|
+
const config = {
|
|
1002
|
+
workerCount: 8,
|
|
1003
|
+
taskTimeoutMs: 60000,
|
|
1004
|
+
maxKeyHoldTimeMs: 10000,
|
|
1005
|
+
verifySignatures: false,
|
|
1006
|
+
preserveWorkers: true,
|
|
1007
|
+
};
|
|
1008
|
+
|
|
1009
|
+
expect(config.workerCount).toBe(8);
|
|
1010
|
+
expect(config.taskTimeoutMs).toBe(60000);
|
|
1011
|
+
expect(config.maxKeyHoldTimeMs).toBe(10000);
|
|
1012
|
+
expect(config.verifySignatures).toBe(false);
|
|
1013
|
+
expect(config.preserveWorkers).toBe(true);
|
|
1014
|
+
});
|
|
1015
|
+
|
|
1016
|
+
it('should have reasonable defaults', () => {
|
|
1017
|
+
// Verify default values are sensible
|
|
1018
|
+
const defaults = {
|
|
1019
|
+
workerCount: 4,
|
|
1020
|
+
taskTimeoutMs: 30000,
|
|
1021
|
+
maxKeyHoldTimeMs: 5000,
|
|
1022
|
+
verifySignatures: true,
|
|
1023
|
+
preserveWorkers: false,
|
|
1024
|
+
};
|
|
1025
|
+
|
|
1026
|
+
expect(defaults.workerCount).toBeGreaterThan(0);
|
|
1027
|
+
expect(defaults.taskTimeoutMs).toBeGreaterThan(defaults.maxKeyHoldTimeMs);
|
|
1028
|
+
expect(defaults.verifySignatures).toBe(true); // Safe default
|
|
1029
|
+
expect(defaults.preserveWorkers).toBe(false); // Secure default
|
|
1030
|
+
});
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
describe('WorkerState Transitions', () => {
|
|
1034
|
+
it('should have valid initial state (Initializing)', () => {
|
|
1035
|
+
expect(WorkerState.Initializing).toBe(0);
|
|
1036
|
+
});
|
|
1037
|
+
|
|
1038
|
+
it('should transition Initializing -> Idle', () => {
|
|
1039
|
+
let state: WorkerState = WorkerState.Initializing;
|
|
1040
|
+
// After worker sends 'ready' message
|
|
1041
|
+
state = WorkerState.Idle;
|
|
1042
|
+
expect(state).toBe(WorkerState.Idle);
|
|
1043
|
+
});
|
|
1044
|
+
|
|
1045
|
+
it('should transition Idle -> Busy', () => {
|
|
1046
|
+
let state: WorkerState = WorkerState.Idle;
|
|
1047
|
+
// When task is assigned
|
|
1048
|
+
state = WorkerState.Busy;
|
|
1049
|
+
expect(state).toBe(WorkerState.Busy);
|
|
1050
|
+
});
|
|
1051
|
+
|
|
1052
|
+
it('should transition Busy -> Idle', () => {
|
|
1053
|
+
let state: WorkerState = WorkerState.Busy;
|
|
1054
|
+
// When task completes
|
|
1055
|
+
state = WorkerState.Idle;
|
|
1056
|
+
expect(state).toBe(WorkerState.Idle);
|
|
1057
|
+
});
|
|
1058
|
+
|
|
1059
|
+
it('should transition to ShuttingDown from any state', () => {
|
|
1060
|
+
const states = [WorkerState.Initializing, WorkerState.Idle, WorkerState.Busy];
|
|
1061
|
+
|
|
1062
|
+
for (const initialState of states) {
|
|
1063
|
+
let state: WorkerState = initialState;
|
|
1064
|
+
state = WorkerState.ShuttingDown;
|
|
1065
|
+
expect(state).toBe(WorkerState.ShuttingDown);
|
|
1066
|
+
}
|
|
1067
|
+
});
|
|
1068
|
+
|
|
1069
|
+
it('should transition ShuttingDown -> Terminated', () => {
|
|
1070
|
+
let state: WorkerState = WorkerState.ShuttingDown;
|
|
1071
|
+
// After worker acknowledges shutdown
|
|
1072
|
+
state = WorkerState.Terminated;
|
|
1073
|
+
expect(state).toBe(WorkerState.Terminated);
|
|
1074
|
+
});
|
|
1075
|
+
|
|
1076
|
+
it('should not transition from Terminated', () => {
|
|
1077
|
+
const state = WorkerState.Terminated;
|
|
1078
|
+
// Terminated is final state
|
|
1079
|
+
expect(state).toBe(WorkerState.Terminated);
|
|
1080
|
+
});
|
|
1081
|
+
});
|
|
1082
|
+
|
|
1083
|
+
describe('Batch Signing Scenarios', () => {
|
|
1084
|
+
it('should handle single input signing', () => {
|
|
1085
|
+
const tasks: SigningTask[] = [
|
|
1086
|
+
{
|
|
1087
|
+
taskId: 'single',
|
|
1088
|
+
inputIndex: 0,
|
|
1089
|
+
hash: new Uint8Array(32).fill(0x11),
|
|
1090
|
+
signatureType: SignatureType.ECDSA,
|
|
1091
|
+
sighashType: 0x01,
|
|
1092
|
+
},
|
|
1093
|
+
];
|
|
1094
|
+
|
|
1095
|
+
expect(tasks.length).toBe(1);
|
|
1096
|
+
expect(tasks[0].inputIndex).toBe(0);
|
|
1097
|
+
});
|
|
1098
|
+
|
|
1099
|
+
it('should handle multi-input ECDSA signing', () => {
|
|
1100
|
+
const tasks: SigningTask[] = [];
|
|
1101
|
+
for (let i = 0; i < 10; i++) {
|
|
1102
|
+
tasks.push({
|
|
1103
|
+
taskId: `ecdsa-${i}`,
|
|
1104
|
+
inputIndex: i,
|
|
1105
|
+
hash: new Uint8Array(32).fill(i),
|
|
1106
|
+
signatureType: SignatureType.ECDSA,
|
|
1107
|
+
sighashType: 0x01,
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
expect(tasks.length).toBe(10);
|
|
1112
|
+
expect(tasks.every((t) => t.signatureType === SignatureType.ECDSA)).toBe(true);
|
|
1113
|
+
});
|
|
1114
|
+
|
|
1115
|
+
it('should handle multi-input Schnorr signing', () => {
|
|
1116
|
+
const tasks: SigningTask[] = [];
|
|
1117
|
+
for (let i = 0; i < 10; i++) {
|
|
1118
|
+
tasks.push({
|
|
1119
|
+
taskId: `schnorr-${i}`,
|
|
1120
|
+
inputIndex: i,
|
|
1121
|
+
hash: new Uint8Array(32).fill(i + 100),
|
|
1122
|
+
signatureType: SignatureType.Schnorr,
|
|
1123
|
+
sighashType: 0x00,
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
expect(tasks.length).toBe(10);
|
|
1128
|
+
expect(tasks.every((t) => t.signatureType === SignatureType.Schnorr)).toBe(true);
|
|
1129
|
+
});
|
|
1130
|
+
|
|
1131
|
+
it('should handle mixed ECDSA and Schnorr signing', () => {
|
|
1132
|
+
const tasks: SigningTask[] = [
|
|
1133
|
+
{
|
|
1134
|
+
taskId: 'ecdsa-0',
|
|
1135
|
+
inputIndex: 0,
|
|
1136
|
+
hash: new Uint8Array(32),
|
|
1137
|
+
signatureType: SignatureType.ECDSA,
|
|
1138
|
+
sighashType: 0x01,
|
|
1139
|
+
},
|
|
1140
|
+
{
|
|
1141
|
+
taskId: 'schnorr-1',
|
|
1142
|
+
inputIndex: 1,
|
|
1143
|
+
hash: new Uint8Array(32),
|
|
1144
|
+
signatureType: SignatureType.Schnorr,
|
|
1145
|
+
sighashType: 0x00,
|
|
1146
|
+
},
|
|
1147
|
+
{
|
|
1148
|
+
taskId: 'ecdsa-2',
|
|
1149
|
+
inputIndex: 2,
|
|
1150
|
+
hash: new Uint8Array(32),
|
|
1151
|
+
signatureType: SignatureType.ECDSA,
|
|
1152
|
+
sighashType: 0x01,
|
|
1153
|
+
},
|
|
1154
|
+
];
|
|
1155
|
+
|
|
1156
|
+
const ecdsaCount = tasks.filter((t) => t.signatureType === SignatureType.ECDSA).length;
|
|
1157
|
+
const schnorrCount = tasks.filter((t) => t.signatureType === SignatureType.Schnorr).length;
|
|
1158
|
+
|
|
1159
|
+
expect(ecdsaCount).toBe(2);
|
|
1160
|
+
expect(schnorrCount).toBe(1);
|
|
1161
|
+
});
|
|
1162
|
+
|
|
1163
|
+
it('should handle Taproot key-path and script-path in same batch', () => {
|
|
1164
|
+
const tasks: SigningTask[] = [
|
|
1165
|
+
{
|
|
1166
|
+
taskId: 'keypath-0',
|
|
1167
|
+
inputIndex: 0,
|
|
1168
|
+
hash: new Uint8Array(32),
|
|
1169
|
+
signatureType: SignatureType.Schnorr,
|
|
1170
|
+
sighashType: 0x00,
|
|
1171
|
+
// No leafHash = key-path
|
|
1172
|
+
},
|
|
1173
|
+
{
|
|
1174
|
+
taskId: 'scriptpath-1',
|
|
1175
|
+
inputIndex: 1,
|
|
1176
|
+
hash: new Uint8Array(32),
|
|
1177
|
+
signatureType: SignatureType.Schnorr,
|
|
1178
|
+
sighashType: 0x00,
|
|
1179
|
+
leafHash: new Uint8Array(32).fill(0xab), // Has leafHash = script-path
|
|
1180
|
+
},
|
|
1181
|
+
];
|
|
1182
|
+
|
|
1183
|
+
const keyPathTasks = tasks.filter((t) => !t.leafHash);
|
|
1184
|
+
const scriptPathTasks = tasks.filter((t) => !!t.leafHash);
|
|
1185
|
+
|
|
1186
|
+
expect(keyPathTasks.length).toBe(1);
|
|
1187
|
+
expect(scriptPathTasks.length).toBe(1);
|
|
1188
|
+
});
|
|
1189
|
+
});
|
|
1190
|
+
|
|
1191
|
+
describe('Large Scale Scenarios', () => {
|
|
1192
|
+
it('should handle 100 inputs', () => {
|
|
1193
|
+
const tasks: SigningTask[] = [];
|
|
1194
|
+
for (let i = 0; i < 100; i++) {
|
|
1195
|
+
tasks.push({
|
|
1196
|
+
taskId: `task-${i}`,
|
|
1197
|
+
inputIndex: i,
|
|
1198
|
+
hash: new Uint8Array(32),
|
|
1199
|
+
signatureType: i % 2 === 0 ? SignatureType.ECDSA : SignatureType.Schnorr,
|
|
1200
|
+
sighashType: i % 2 === 0 ? 0x01 : 0x00,
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
expect(tasks.length).toBe(100);
|
|
1205
|
+
});
|
|
1206
|
+
|
|
1207
|
+
it('should handle 1000 inputs', () => {
|
|
1208
|
+
const tasks: SigningTask[] = [];
|
|
1209
|
+
for (let i = 0; i < 1000; i++) {
|
|
1210
|
+
tasks.push({
|
|
1211
|
+
taskId: `task-${i}`,
|
|
1212
|
+
inputIndex: i,
|
|
1213
|
+
hash: new Uint8Array(32),
|
|
1214
|
+
signatureType: SignatureType.ECDSA,
|
|
1215
|
+
sighashType: 0x01,
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
expect(tasks.length).toBe(1000);
|
|
1220
|
+
});
|
|
1221
|
+
|
|
1222
|
+
it('should generate unique task IDs for large batches', () => {
|
|
1223
|
+
const taskIds = new Set<string>();
|
|
1224
|
+
for (let i = 0; i < 10000; i++) {
|
|
1225
|
+
const taskId = `batch-${Date.now()}-${i}-${Math.random().toString(36).slice(2)}`;
|
|
1226
|
+
expect(taskIds.has(taskId)).toBe(false);
|
|
1227
|
+
taskIds.add(taskId);
|
|
1228
|
+
}
|
|
1229
|
+
expect(taskIds.size).toBe(10000);
|
|
1230
|
+
});
|
|
1231
|
+
});
|
|
1232
|
+
|
|
1233
|
+
describe('ECC Bundle', () => {
|
|
1234
|
+
// Helper to get noble secp module from the bundle
|
|
1235
|
+
// The bundle exports nobleBundle with { secp, sha256, hmac }
|
|
1236
|
+
// where secp already has hashes configured
|
|
1237
|
+
async function getNobleSecp() {
|
|
1238
|
+
const { ECC_BUNDLE } = await import('../src/workers/ecc-bundle.js');
|
|
1239
|
+
|
|
1240
|
+
const fn = new Function(ECC_BUNDLE + '; return nobleBundle;');
|
|
1241
|
+
const bundle = fn();
|
|
1242
|
+
|
|
1243
|
+
// Return the secp module which has hashes already configured
|
|
1244
|
+
return bundle.secp;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
it('should export bundled ECC code', async () => {
|
|
1248
|
+
const { ECC_BUNDLE, ECC_BUNDLE_SIZE } = await import('../src/workers/ecc-bundle.js');
|
|
1249
|
+
|
|
1250
|
+
expect(ECC_BUNDLE).toBeDefined();
|
|
1251
|
+
expect(typeof ECC_BUNDLE).toBe('string');
|
|
1252
|
+
expect(ECC_BUNDLE_SIZE).toBeGreaterThan(0);
|
|
1253
|
+
expect(ECC_BUNDLE.length).toBe(ECC_BUNDLE_SIZE);
|
|
1254
|
+
});
|
|
1255
|
+
|
|
1256
|
+
it('should contain noble-secp256k1 IIFE', async () => {
|
|
1257
|
+
const { ECC_BUNDLE } = await import('../src/workers/ecc-bundle.js');
|
|
1258
|
+
|
|
1259
|
+
// Should be an IIFE that creates nobleBundle global
|
|
1260
|
+
expect(ECC_BUNDLE).toContain('nobleBundle');
|
|
1261
|
+
expect(ECC_BUNDLE).toContain('sign');
|
|
1262
|
+
expect(ECC_BUNDLE).toContain('schnorr');
|
|
1263
|
+
});
|
|
1264
|
+
|
|
1265
|
+
it('should be executable and return valid module structure', async () => {
|
|
1266
|
+
const { ECC_BUNDLE } = await import('../src/workers/ecc-bundle.js');
|
|
1267
|
+
|
|
1268
|
+
// Execute the bundle and get the module
|
|
1269
|
+
const fn = new Function(ECC_BUNDLE + '; return nobleBundle;');
|
|
1270
|
+
const bundle = fn();
|
|
1271
|
+
|
|
1272
|
+
// Verify the bundle has the expected structure
|
|
1273
|
+
expect(bundle).toBeDefined();
|
|
1274
|
+
expect(typeof bundle.secp).toBe('object');
|
|
1275
|
+
expect(typeof bundle.sha256).toBe('function');
|
|
1276
|
+
expect(typeof bundle.hmac).toBe('function');
|
|
1277
|
+
|
|
1278
|
+
// Verify the secp module has the expected structure
|
|
1279
|
+
const secp = bundle.secp;
|
|
1280
|
+
expect(typeof secp.sign).toBe('function');
|
|
1281
|
+
expect(typeof secp.verify).toBe('function');
|
|
1282
|
+
expect(typeof secp.getPublicKey).toBe('function');
|
|
1283
|
+
expect(typeof secp.schnorr).toBe('object');
|
|
1284
|
+
expect(typeof secp.schnorr.sign).toBe('function');
|
|
1285
|
+
expect(typeof secp.schnorr.verify).toBe('function');
|
|
1286
|
+
expect(typeof secp.schnorr.getPublicKey).toBe('function');
|
|
1287
|
+
expect(typeof secp.hashes).toBe('object');
|
|
1288
|
+
});
|
|
1289
|
+
|
|
1290
|
+
it('should create valid ECDSA signatures', async () => {
|
|
1291
|
+
const secp = await getNobleSecp();
|
|
1292
|
+
|
|
1293
|
+
// Create a test private key (valid non-zero 32 bytes)
|
|
1294
|
+
const privateKey = new Uint8Array(32);
|
|
1295
|
+
privateKey[31] = 0x01; // Smallest valid private key
|
|
1296
|
+
|
|
1297
|
+
// Create a test hash
|
|
1298
|
+
const hash = new Uint8Array(32).fill(0xab);
|
|
1299
|
+
|
|
1300
|
+
// Sign with ECDSA - returns Uint8Array directly (64 bytes compact format)
|
|
1301
|
+
const sig = secp.sign(hash, privateKey, { lowS: true });
|
|
1302
|
+
expect(sig).toBeDefined();
|
|
1303
|
+
expect(sig).toBeInstanceOf(Uint8Array);
|
|
1304
|
+
expect(sig.length).toBe(64);
|
|
1305
|
+
|
|
1306
|
+
// Verify the signature
|
|
1307
|
+
const pubKey = secp.getPublicKey(privateKey);
|
|
1308
|
+
const isValid = secp.verify(sig, hash, pubKey);
|
|
1309
|
+
expect(isValid).toBe(true);
|
|
1310
|
+
});
|
|
1311
|
+
|
|
1312
|
+
it('should create valid Schnorr signatures', async () => {
|
|
1313
|
+
const secp = await getNobleSecp();
|
|
1314
|
+
|
|
1315
|
+
// Create a test private key
|
|
1316
|
+
const privateKey = new Uint8Array(32);
|
|
1317
|
+
privateKey[31] = 0x02;
|
|
1318
|
+
|
|
1319
|
+
// Create a test hash
|
|
1320
|
+
const hash = new Uint8Array(32).fill(0xcd);
|
|
1321
|
+
|
|
1322
|
+
// Sign with Schnorr (BIP340)
|
|
1323
|
+
const sig = secp.schnorr.sign(hash, privateKey);
|
|
1324
|
+
expect(sig).toBeDefined();
|
|
1325
|
+
expect(sig.length).toBe(64);
|
|
1326
|
+
|
|
1327
|
+
// Verify the signature
|
|
1328
|
+
const pubKey = secp.schnorr.getPublicKey(privateKey);
|
|
1329
|
+
const isValid = secp.schnorr.verify(sig, hash, pubKey);
|
|
1330
|
+
expect(isValid).toBe(true);
|
|
1331
|
+
});
|
|
1332
|
+
|
|
1333
|
+
it('should verify ECDSA signature with wrong key fails', async () => {
|
|
1334
|
+
const secp = await getNobleSecp();
|
|
1335
|
+
|
|
1336
|
+
const privateKey1 = new Uint8Array(32);
|
|
1337
|
+
privateKey1[31] = 0x01;
|
|
1338
|
+
|
|
1339
|
+
const privateKey2 = new Uint8Array(32);
|
|
1340
|
+
privateKey2[31] = 0x02;
|
|
1341
|
+
|
|
1342
|
+
const hash = new Uint8Array(32).fill(0xef);
|
|
1343
|
+
|
|
1344
|
+
// Sign with key1 - returns Uint8Array directly
|
|
1345
|
+
const sig = secp.sign(hash, privateKey1, { lowS: true });
|
|
1346
|
+
|
|
1347
|
+
// Verify with key2's pubkey should fail
|
|
1348
|
+
const pubKey2 = secp.getPublicKey(privateKey2);
|
|
1349
|
+
const isValid = secp.verify(sig, hash, pubKey2);
|
|
1350
|
+
expect(isValid).toBe(false);
|
|
1351
|
+
});
|
|
1352
|
+
|
|
1353
|
+
it('should verify Schnorr signature with wrong key fails', async () => {
|
|
1354
|
+
const secp = await getNobleSecp();
|
|
1355
|
+
|
|
1356
|
+
const privateKey1 = new Uint8Array(32);
|
|
1357
|
+
privateKey1[31] = 0x03;
|
|
1358
|
+
|
|
1359
|
+
const privateKey2 = new Uint8Array(32);
|
|
1360
|
+
privateKey2[31] = 0x04;
|
|
1361
|
+
|
|
1362
|
+
const hash = new Uint8Array(32).fill(0x12);
|
|
1363
|
+
|
|
1364
|
+
// Sign with key1
|
|
1365
|
+
const sig = secp.schnorr.sign(hash, privateKey1);
|
|
1366
|
+
|
|
1367
|
+
// Verify with key2's pubkey should fail
|
|
1368
|
+
const pubKey2 = secp.schnorr.getPublicKey(privateKey2);
|
|
1369
|
+
const isValid = secp.schnorr.verify(sig, hash, pubKey2);
|
|
1370
|
+
expect(isValid).toBe(false);
|
|
1371
|
+
});
|
|
1372
|
+
|
|
1373
|
+
it('should have reasonable bundle size (< 50KB)', async () => {
|
|
1374
|
+
const { ECC_BUNDLE_SIZE } = await import('../src/workers/ecc-bundle.js');
|
|
1375
|
+
|
|
1376
|
+
// Bundle should be reasonably small (minified noble-secp256k1 is ~12KB)
|
|
1377
|
+
expect(ECC_BUNDLE_SIZE).toBeLessThan(50000);
|
|
1378
|
+
expect(ECC_BUNDLE_SIZE).toBeGreaterThan(5000);
|
|
1379
|
+
});
|
|
1380
|
+
|
|
1381
|
+
it('should be embedded in worker code', () => {
|
|
1382
|
+
const code = generateWorkerCode();
|
|
1383
|
+
|
|
1384
|
+
// Worker code should contain the bundled ECC library
|
|
1385
|
+
expect(code).toContain('eccBundle');
|
|
1386
|
+
expect(code).toContain('nobleBundle');
|
|
1387
|
+
expect(code).toContain('eccModule');
|
|
1388
|
+
expect(code).toContain('eccLib');
|
|
1389
|
+
});
|
|
1390
|
+
});
|