@btc-vision/bitcoin 7.0.0-alpha.1 → 7.0.0-alpha.10

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.
Files changed (290) hide show
  1. package/README.md +455 -155
  2. package/browser/address.d.ts +5 -1
  3. package/browser/address.d.ts.map +1 -1
  4. package/browser/branded.d.ts +3 -14
  5. package/browser/branded.d.ts.map +1 -1
  6. package/browser/chunks/psbt-parallel-BBFlkmiv.js +10717 -0
  7. package/browser/ecc/context.d.ts +22 -21
  8. package/browser/ecc/context.d.ts.map +1 -1
  9. package/browser/ecc/index.d.ts +1 -1
  10. package/browser/ecc/index.d.ts.map +1 -1
  11. package/browser/ecc/types.d.ts +10 -123
  12. package/browser/ecc/types.d.ts.map +1 -1
  13. package/browser/env.d.ts +13 -0
  14. package/browser/env.d.ts.map +1 -0
  15. package/browser/index.d.ts +6 -6
  16. package/browser/index.d.ts.map +1 -1
  17. package/browser/index.js +2602 -11786
  18. package/browser/io/hex.d.ts.map +1 -1
  19. package/browser/io/index.d.ts +0 -1
  20. package/browser/io/index.d.ts.map +1 -1
  21. package/browser/opcodes.d.ts +11 -0
  22. package/browser/opcodes.d.ts.map +1 -1
  23. package/browser/payments/p2tr.d.ts.map +1 -1
  24. package/browser/psbt/PsbtCache.d.ts +54 -0
  25. package/browser/psbt/PsbtCache.d.ts.map +1 -0
  26. package/browser/psbt/PsbtFinalizer.d.ts +21 -0
  27. package/browser/psbt/PsbtFinalizer.d.ts.map +1 -0
  28. package/browser/psbt/PsbtSigner.d.ts +32 -0
  29. package/browser/psbt/PsbtSigner.d.ts.map +1 -0
  30. package/browser/psbt/PsbtTransaction.d.ts +25 -0
  31. package/browser/psbt/PsbtTransaction.d.ts.map +1 -0
  32. package/browser/psbt/types.d.ts +4 -70
  33. package/browser/psbt/types.d.ts.map +1 -1
  34. package/browser/psbt/validation.d.ts +1 -1
  35. package/browser/psbt/validation.d.ts.map +1 -1
  36. package/browser/psbt.d.ts +26 -40
  37. package/browser/psbt.d.ts.map +1 -1
  38. package/browser/script.d.ts.map +1 -1
  39. package/browser/transaction.d.ts +4 -4
  40. package/browser/transaction.d.ts.map +1 -1
  41. package/browser/types.d.ts +5 -3
  42. package/browser/types.d.ts.map +1 -1
  43. package/browser/workers/WorkerSigningPool.d.ts +7 -0
  44. package/browser/workers/WorkerSigningPool.d.ts.map +1 -1
  45. package/browser/workers/WorkerSigningPool.node.d.ts +7 -0
  46. package/browser/workers/WorkerSigningPool.node.d.ts.map +1 -1
  47. package/browser/workers/WorkerSigningPool.sequential.d.ts +67 -0
  48. package/browser/workers/WorkerSigningPool.sequential.d.ts.map +1 -0
  49. package/browser/workers/WorkerSigningPool.worklet.d.ts +64 -0
  50. package/browser/workers/WorkerSigningPool.worklet.d.ts.map +1 -0
  51. package/browser/workers/index.browser.d.ts +16 -0
  52. package/browser/workers/index.browser.d.ts.map +1 -0
  53. package/browser/workers/index.d.ts +4 -64
  54. package/browser/workers/index.d.ts.map +1 -1
  55. package/browser/workers/index.js +28 -0
  56. package/browser/workers/index.node.d.ts +17 -0
  57. package/browser/workers/index.node.d.ts.map +1 -0
  58. package/browser/workers/index.react-native.d.ts +28 -0
  59. package/browser/workers/index.react-native.d.ts.map +1 -0
  60. package/browser/workers/index.shared.d.ts +15 -0
  61. package/browser/workers/index.shared.d.ts.map +1 -0
  62. package/browser/workers/psbt-parallel.d.ts +2 -3
  63. package/browser/workers/psbt-parallel.d.ts.map +1 -1
  64. package/browser/workers/types.d.ts +17 -0
  65. package/browser/workers/types.d.ts.map +1 -1
  66. package/build/address.d.ts +5 -1
  67. package/build/address.d.ts.map +1 -1
  68. package/build/address.js +29 -17
  69. package/build/address.js.map +1 -1
  70. package/build/bech32utils.js.map +1 -1
  71. package/build/block.js.map +1 -1
  72. package/build/branded.d.ts +3 -14
  73. package/build/branded.d.ts.map +1 -1
  74. package/build/branded.js +0 -5
  75. package/build/branded.js.map +1 -1
  76. package/build/ecc/context.d.ts +22 -21
  77. package/build/ecc/context.d.ts.map +1 -1
  78. package/build/ecc/context.js +23 -95
  79. package/build/ecc/context.js.map +1 -1
  80. package/build/ecc/index.d.ts +1 -1
  81. package/build/ecc/index.d.ts.map +1 -1
  82. package/build/ecc/types.d.ts +7 -126
  83. package/build/ecc/types.d.ts.map +1 -1
  84. package/build/ecc/types.js +4 -1
  85. package/build/ecc/types.js.map +1 -1
  86. package/build/env.d.ts +13 -0
  87. package/build/env.d.ts.map +1 -0
  88. package/build/env.js +198 -0
  89. package/build/env.js.map +1 -0
  90. package/build/index.d.ts +7 -6
  91. package/build/index.d.ts.map +1 -1
  92. package/build/index.js +7 -5
  93. package/build/index.js.map +1 -1
  94. package/build/io/hex.d.ts.map +1 -1
  95. package/build/io/hex.js +2 -1
  96. package/build/io/hex.js.map +1 -1
  97. package/build/io/index.d.ts +0 -1
  98. package/build/io/index.d.ts.map +1 -1
  99. package/build/io/index.js +0 -2
  100. package/build/io/index.js.map +1 -1
  101. package/build/opcodes.d.ts +11 -0
  102. package/build/opcodes.d.ts.map +1 -1
  103. package/build/opcodes.js +19 -4
  104. package/build/opcodes.js.map +1 -1
  105. package/build/payments/bip341.js.map +1 -1
  106. package/build/payments/embed.js.map +1 -1
  107. package/build/payments/p2ms.js.map +1 -1
  108. package/build/payments/p2pk.js.map +1 -1
  109. package/build/payments/p2pkh.js.map +1 -1
  110. package/build/payments/p2sh.js.map +1 -1
  111. package/build/payments/p2tr.d.ts.map +1 -1
  112. package/build/payments/p2tr.js +2 -3
  113. package/build/payments/p2tr.js.map +1 -1
  114. package/build/payments/p2wpkh.js.map +1 -1
  115. package/build/payments/p2wsh.js.map +1 -1
  116. package/build/psbt/PsbtCache.d.ts +54 -0
  117. package/build/psbt/PsbtCache.d.ts.map +1 -0
  118. package/build/psbt/PsbtCache.js +249 -0
  119. package/build/psbt/PsbtCache.js.map +1 -0
  120. package/build/psbt/PsbtFinalizer.d.ts +21 -0
  121. package/build/psbt/PsbtFinalizer.d.ts.map +1 -0
  122. package/build/psbt/PsbtFinalizer.js +157 -0
  123. package/build/psbt/PsbtFinalizer.js.map +1 -0
  124. package/build/psbt/PsbtSigner.d.ts +32 -0
  125. package/build/psbt/PsbtSigner.d.ts.map +1 -0
  126. package/build/psbt/PsbtSigner.js +192 -0
  127. package/build/psbt/PsbtSigner.js.map +1 -0
  128. package/build/psbt/PsbtTransaction.d.ts +25 -0
  129. package/build/psbt/PsbtTransaction.d.ts.map +1 -0
  130. package/build/psbt/PsbtTransaction.js +61 -0
  131. package/build/psbt/PsbtTransaction.js.map +1 -0
  132. package/build/psbt/types.d.ts +4 -70
  133. package/build/psbt/types.d.ts.map +1 -1
  134. package/build/psbt/validation.d.ts +1 -1
  135. package/build/psbt/validation.d.ts.map +1 -1
  136. package/build/psbt.d.ts +26 -40
  137. package/build/psbt.d.ts.map +1 -1
  138. package/build/psbt.js +177 -799
  139. package/build/psbt.js.map +1 -1
  140. package/build/script.d.ts.map +1 -1
  141. package/build/script.js +2 -2
  142. package/build/script.js.map +1 -1
  143. package/build/transaction.d.ts +4 -4
  144. package/build/transaction.d.ts.map +1 -1
  145. package/build/transaction.js +5 -4
  146. package/build/transaction.js.map +1 -1
  147. package/build/tsconfig.build.tsbuildinfo +1 -1
  148. package/build/types.d.ts +5 -3
  149. package/build/types.d.ts.map +1 -1
  150. package/build/types.js +11 -16
  151. package/build/types.js.map +1 -1
  152. package/build/workers/WorkerSigningPool.d.ts +7 -0
  153. package/build/workers/WorkerSigningPool.d.ts.map +1 -1
  154. package/build/workers/WorkerSigningPool.js +12 -1
  155. package/build/workers/WorkerSigningPool.js.map +1 -1
  156. package/build/workers/WorkerSigningPool.node.d.ts +7 -0
  157. package/build/workers/WorkerSigningPool.node.d.ts.map +1 -1
  158. package/build/workers/WorkerSigningPool.node.js +37 -5
  159. package/build/workers/WorkerSigningPool.node.js.map +1 -1
  160. package/build/workers/WorkerSigningPool.sequential.d.ts +76 -0
  161. package/build/workers/WorkerSigningPool.sequential.d.ts.map +1 -0
  162. package/build/workers/WorkerSigningPool.sequential.js +160 -0
  163. package/build/workers/WorkerSigningPool.sequential.js.map +1 -0
  164. package/build/workers/WorkerSigningPool.worklet.d.ts +79 -0
  165. package/build/workers/WorkerSigningPool.worklet.d.ts.map +1 -0
  166. package/build/workers/WorkerSigningPool.worklet.js +390 -0
  167. package/build/workers/WorkerSigningPool.worklet.js.map +1 -0
  168. package/build/workers/index.browser.d.ts +24 -0
  169. package/build/workers/index.browser.d.ts.map +1 -0
  170. package/build/workers/index.browser.js +30 -0
  171. package/build/workers/index.browser.js.map +1 -0
  172. package/build/workers/index.d.ts +6 -18
  173. package/build/workers/index.d.ts.map +1 -1
  174. package/build/workers/index.js +12 -14
  175. package/build/workers/index.js.map +1 -1
  176. package/build/workers/index.node.d.ts +38 -0
  177. package/build/workers/index.node.d.ts.map +1 -0
  178. package/build/workers/index.node.js +45 -0
  179. package/build/workers/index.node.js.map +1 -0
  180. package/build/workers/index.react-native.d.ts +28 -0
  181. package/build/workers/index.react-native.d.ts.map +1 -0
  182. package/build/workers/index.react-native.js +67 -0
  183. package/build/workers/index.react-native.js.map +1 -0
  184. package/build/workers/index.shared.d.ts +15 -0
  185. package/build/workers/index.shared.d.ts.map +1 -0
  186. package/build/workers/index.shared.js +20 -0
  187. package/build/workers/index.shared.js.map +1 -0
  188. package/build/workers/psbt-parallel.d.ts +2 -3
  189. package/build/workers/psbt-parallel.d.ts.map +1 -1
  190. package/build/workers/psbt-parallel.js +4 -4
  191. package/build/workers/psbt-parallel.js.map +1 -1
  192. package/build/workers/types.d.ts +17 -0
  193. package/build/workers/types.d.ts.map +1 -1
  194. package/package.json +46 -8
  195. package/src/address.ts +41 -18
  196. package/src/bech32utils.ts +3 -3
  197. package/src/block.ts +2 -2
  198. package/src/branded.ts +15 -13
  199. package/src/ecc/context.ts +30 -133
  200. package/src/ecc/index.ts +2 -2
  201. package/src/ecc/types.ts +7 -138
  202. package/src/env.ts +239 -0
  203. package/src/index.ts +45 -9
  204. package/src/io/hex.ts +2 -1
  205. package/src/io/index.ts +0 -3
  206. package/src/opcodes.ts +21 -4
  207. package/src/payments/bip341.ts +3 -3
  208. package/src/payments/embed.ts +1 -1
  209. package/src/payments/p2ms.ts +2 -2
  210. package/src/payments/p2pk.ts +2 -2
  211. package/src/payments/p2pkh.ts +3 -3
  212. package/src/payments/p2sh.ts +4 -4
  213. package/src/payments/p2tr.ts +9 -9
  214. package/src/payments/p2wpkh.ts +5 -5
  215. package/src/payments/p2wsh.ts +2 -2
  216. package/src/psbt/PsbtCache.ts +325 -0
  217. package/src/psbt/PsbtFinalizer.ts +213 -0
  218. package/src/psbt/PsbtSigner.ts +302 -0
  219. package/src/psbt/PsbtTransaction.ts +82 -0
  220. package/src/psbt/types.ts +4 -86
  221. package/src/psbt/validation.ts +1 -1
  222. package/src/psbt.ts +349 -1198
  223. package/src/script.ts +2 -2
  224. package/src/transaction.ts +10 -9
  225. package/src/types.ts +18 -28
  226. package/src/workers/WorkerSigningPool.node.ts +41 -5
  227. package/src/workers/WorkerSigningPool.sequential.ts +191 -0
  228. package/src/workers/WorkerSigningPool.ts +14 -1
  229. package/src/workers/WorkerSigningPool.worklet.ts +522 -0
  230. package/src/workers/index.browser.ts +34 -0
  231. package/src/workers/index.node.ts +50 -0
  232. package/src/workers/index.react-native.ts +110 -0
  233. package/src/workers/index.shared.ts +58 -0
  234. package/src/workers/index.ts +14 -65
  235. package/src/workers/psbt-parallel.ts +7 -7
  236. package/src/workers/types.ts +21 -0
  237. package/test/address.spec.ts +2 -2
  238. package/test/bitcoin.core.spec.ts +5 -2
  239. package/test/browser/payments.spec.ts +151 -0
  240. package/test/browser/psbt.spec.ts +1510 -0
  241. package/test/browser/script.spec.ts +223 -0
  242. package/test/browser/setup.ts +13 -0
  243. package/test/browser/workers-signing.spec.ts +537 -0
  244. package/test/crypto.spec.ts +2 -2
  245. package/test/env.spec.ts +418 -0
  246. package/test/fixtures/core/base58_encode_decode.json +12 -48
  247. package/test/fixtures/core/base58_keys_invalid.json +50 -150
  248. package/test/fixtures/core/sighash.json +1 -3
  249. package/test/fixtures/core/tx_valid.json +133 -501
  250. package/test/fixtures/embed.json +3 -11
  251. package/test/fixtures/p2ms.json +21 -91
  252. package/test/fixtures/p2pk.json +5 -24
  253. package/test/fixtures/p2pkh.json +7 -36
  254. package/test/fixtures/p2sh.json +8 -54
  255. package/test/fixtures/p2tr.json +2 -6
  256. package/test/fixtures/p2wpkh.json +7 -36
  257. package/test/fixtures/p2wsh.json +14 -59
  258. package/test/fixtures/psbt.json +2 -6
  259. package/test/fixtures/script.json +12 -48
  260. package/test/integration/addresses.spec.ts +11 -5
  261. package/test/integration/bip32.spec.ts +1 -1
  262. package/test/integration/cltv.spec.ts +10 -6
  263. package/test/integration/csv.spec.ts +10 -9
  264. package/test/integration/payments.spec.ts +8 -4
  265. package/test/integration/taproot.spec.ts +26 -6
  266. package/test/integration/transactions.spec.ts +22 -8
  267. package/test/payments.spec.ts +1 -1
  268. package/test/payments.utils.ts +1 -1
  269. package/test/psbt.spec.ts +250 -64
  270. package/test/script_signature.spec.ts +1 -1
  271. package/test/transaction.spec.ts +18 -5
  272. package/test/tsconfig.json +6 -20
  273. package/test/workers-pool.spec.ts +65 -23
  274. package/test/workers-sequential.spec.ts +669 -0
  275. package/test/workers-signing.spec.ts +7 -3
  276. package/test/workers-worklet.spec.ts +500 -0
  277. package/test/workers.spec.ts +6 -7
  278. package/typedoc.json +11 -1
  279. package/vite.config.browser.ts +31 -6
  280. package/vitest.config.browser.ts +68 -0
  281. package/browser/ecpair.d.ts +0 -99
  282. package/browser/io/MemoryPool.d.ts +0 -220
  283. package/browser/io/MemoryPool.d.ts.map +0 -1
  284. package/build/io/MemoryPool.d.ts +0 -220
  285. package/build/io/MemoryPool.d.ts.map +0 -1
  286. package/build/io/MemoryPool.js +0 -309
  287. package/build/io/MemoryPool.js.map +0 -1
  288. package/src/ecpair.d.ts +0 -99
  289. package/src/io/MemoryPool.ts +0 -343
  290. package/test/taproot-cache.spec.ts +0 -694
@@ -0,0 +1,522 @@
1
+ /**
2
+ * Worklet-based parallel signing pool for React Native.
3
+ *
4
+ * Uses `react-native-worklets` (Software Mansion v0.7+) to run signing
5
+ * operations in parallel across multiple worklet runtimes.
6
+ * Each runtime gets its own ECC module instance via eval of the bundled
7
+ * @noble/secp256k1 IIFE string.
8
+ *
9
+ * SECURITY ARCHITECTURE:
10
+ * - Private keys are cloned per-runtime (structuredClone semantics)
11
+ * - Keys are zeroed inside worklet AND in main thread finally block
12
+ * - Tainted runtimes (timeout) are replaced, not reused
13
+ *
14
+ * @packageDocumentation
15
+ */
16
+
17
+ import type {
18
+ ParallelSignerKeyPair,
19
+ ParallelSigningResult,
20
+ SigningResultMessage,
21
+ SigningTask,
22
+ WorkerPoolConfig,
23
+ } from './types.js';
24
+ import { SignatureType } from './types.js';
25
+
26
+ /**
27
+ * Minimal interface for react-native-worklets runtime.
28
+ * Keeps us decoupled from the actual module types.
29
+ */
30
+ interface WorkletRuntime {
31
+ readonly name: string;
32
+ }
33
+
34
+ /**
35
+ * Minimal interface for the react-native-worklets module.
36
+ */
37
+ interface WorkletsModule {
38
+ createWorkletRuntime(name: string): WorkletRuntime;
39
+ runOnRuntime<T>(runtime: WorkletRuntime, fn: () => T): Promise<T>;
40
+ }
41
+
42
+ /**
43
+ * Default configuration for the worklet pool.
44
+ */
45
+ const DEFAULT_CONFIG: Required<WorkerPoolConfig> = {
46
+ workerCount: 4,
47
+ taskTimeoutMs: 30000,
48
+ maxKeyHoldTimeMs: 5000,
49
+ verifySignatures: true,
50
+ preserveWorkers: false,
51
+ };
52
+
53
+ /**
54
+ * Internal runtime wrapper for pool management.
55
+ */
56
+ interface PooledRuntime {
57
+ readonly id: number;
58
+ runtime: WorkletRuntime;
59
+ tainted: boolean;
60
+ }
61
+
62
+ /**
63
+ * Worklet-based parallel signing pool for React Native.
64
+ *
65
+ * Mirrors the API of WorkerSigningPool (browser) but uses
66
+ * `react-native-worklets` runtimes instead of Web Workers.
67
+ * `runOnRuntime()` returns a Promise directly — no postMessage protocol.
68
+ *
69
+ * @example
70
+ * ```typescript
71
+ * import { WorkletSigningPool } from '@btc-vision/bitcoin/workers';
72
+ *
73
+ * const pool = WorkletSigningPool.getInstance();
74
+ * pool.preserveWorkers();
75
+ *
76
+ * const result = await pool.signBatch(tasks, keyPair);
77
+ * await pool.shutdown();
78
+ * ```
79
+ */
80
+ export class WorkletSigningPool {
81
+ static #instance: WorkletSigningPool | null = null;
82
+
83
+ readonly #config: Required<WorkerPoolConfig>;
84
+ readonly #runtimes: PooledRuntime[] = [];
85
+
86
+ #workletsModule: WorkletsModule | null = null;
87
+ #eccBundleCode: string | null = null;
88
+ #preserveWorkers: boolean = false;
89
+ #nextRuntimeId: number = 0;
90
+ #initialized: boolean = false;
91
+ #shuttingDown: boolean = false;
92
+
93
+ /**
94
+ * Whether Uint8Array survives worklet boundary.
95
+ * Detected during initialize(); if false, we encode as number[].
96
+ */
97
+ #uint8ArraySupported: boolean = true;
98
+
99
+ private constructor(config: WorkerPoolConfig = {}) {
100
+ this.#config = { ...DEFAULT_CONFIG, ...config };
101
+ this.#preserveWorkers = this.#config.preserveWorkers;
102
+ }
103
+
104
+ /** Number of active runtimes. */
105
+ public get workerCount(): number {
106
+ return this.#runtimes.length;
107
+ }
108
+
109
+ /** Idle runtimes (all non-tainted). */
110
+ public get idleWorkerCount(): number {
111
+ return this.#runtimes.filter((r) => !r.tainted).length;
112
+ }
113
+
114
+ /** Busy runtimes — always 0 outside of signBatch. */
115
+ public get busyWorkerCount(): number {
116
+ return 0;
117
+ }
118
+
119
+ /** Whether runtimes are preserved between batches. */
120
+ public get isPreservingWorkers(): boolean {
121
+ return this.#preserveWorkers;
122
+ }
123
+
124
+ /**
125
+ * Gets the singleton pool instance.
126
+ *
127
+ * @param config - Optional configuration (only used on first call)
128
+ */
129
+ public static getInstance(config?: WorkerPoolConfig): WorkletSigningPool {
130
+ if (!WorkletSigningPool.#instance) {
131
+ WorkletSigningPool.#instance = new WorkletSigningPool(config);
132
+ }
133
+ return WorkletSigningPool.#instance;
134
+ }
135
+
136
+ /** Resets the singleton instance (for testing). */
137
+ public static resetInstance(): void {
138
+ if (WorkletSigningPool.#instance) {
139
+ WorkletSigningPool.#instance.shutdown().catch(() => {});
140
+ WorkletSigningPool.#instance = null;
141
+ }
142
+ }
143
+
144
+ /** Enable runtime preservation between signing batches. */
145
+ public preserveWorkers(): void {
146
+ this.#preserveWorkers = true;
147
+ }
148
+
149
+ /** Disable runtime preservation. */
150
+ public releaseWorkers(): void {
151
+ this.#preserveWorkers = false;
152
+ }
153
+
154
+ /**
155
+ * Initializes the worklet pool.
156
+ *
157
+ * Dynamically imports `react-native-worklets`, creates N runtimes,
158
+ * and injects the ECC bundle into each via eval.
159
+ *
160
+ * @throws If `react-native-worklets` is not installed or eval fails
161
+ */
162
+ public async initialize(): Promise<void> {
163
+ if (this.#initialized) {
164
+ return;
165
+ }
166
+
167
+ if (this.#shuttingDown) {
168
+ throw new Error('Cannot initialize pool while shutting down');
169
+ }
170
+
171
+ // Lazy dynamic import — module loads even without the dep
172
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
173
+ const worklets: WorkletsModule = await import('react-native-worklets' as string);
174
+ this.#workletsModule = worklets;
175
+
176
+ // Load ECC bundle
177
+ const { ECC_BUNDLE } = await import('./ecc-bundle.js');
178
+ this.#eccBundleCode = ECC_BUNDLE;
179
+
180
+ // Create runtimes and inject ECC
181
+ for (let i = 0; i < this.#config.workerCount; i++) {
182
+ await this.#createRuntime();
183
+ }
184
+
185
+ // Feature-detect Uint8Array support across worklet boundary
186
+ const firstRuntime = this.#runtimes[0];
187
+ if (firstRuntime) {
188
+ try {
189
+ const result = await this.#workletsModule.runOnRuntime(
190
+ firstRuntime.runtime,
191
+ () => {
192
+ const arr = new Uint8Array([1, 2, 3]);
193
+ return arr instanceof Uint8Array;
194
+ },
195
+ );
196
+ this.#uint8ArraySupported = result;
197
+ } catch {
198
+ this.#uint8ArraySupported = false;
199
+ }
200
+ }
201
+
202
+ this.#initialized = true;
203
+ }
204
+
205
+ /**
206
+ * Signs a batch of tasks in parallel across worklet runtimes.
207
+ *
208
+ * SECURITY: Private keys are cloned per-runtime and zeroed both
209
+ * inside the worklet and in the main thread finally block.
210
+ */
211
+ public async signBatch(
212
+ tasks: readonly SigningTask[],
213
+ keyPair: ParallelSignerKeyPair,
214
+ ): Promise<ParallelSigningResult> {
215
+ const startTime = performance.now();
216
+
217
+ if (!this.#initialized) {
218
+ await this.initialize();
219
+ }
220
+
221
+ if (tasks.length === 0) {
222
+ return {
223
+ success: true,
224
+ signatures: new Map(),
225
+ errors: new Map(),
226
+ durationMs: performance.now() - startTime,
227
+ };
228
+ }
229
+
230
+ // Distribute tasks round-robin
231
+ const runtimeCount = Math.min(this.#runtimes.length, tasks.length);
232
+ const taskBatches: SigningTask[][] = Array.from({ length: runtimeCount }, () => []);
233
+
234
+ for (let i = 0; i < tasks.length; i++) {
235
+ const batch = taskBatches[i % runtimeCount];
236
+ const task = tasks[i];
237
+ if (batch && task) {
238
+ batch.push(task);
239
+ }
240
+ }
241
+
242
+ const privateKey = keyPair.getPrivateKey();
243
+
244
+ try {
245
+ const batchResults = await Promise.allSettled(
246
+ taskBatches.map((batch, index) =>
247
+ this.#signBatchOnRuntime(
248
+ batch,
249
+ privateKey,
250
+ keyPair.publicKey,
251
+ index,
252
+ ),
253
+ ),
254
+ );
255
+
256
+ const signatures = new Map<number, SigningResultMessage>();
257
+ const errors = new Map<number, string>();
258
+
259
+ for (let i = 0; i < batchResults.length; i++) {
260
+ const result = batchResults[i];
261
+ if (!result) continue;
262
+
263
+ if (result.status === 'fulfilled') {
264
+ for (const sig of result.value.signatures) {
265
+ signatures.set(sig.inputIndex, sig);
266
+ }
267
+ for (const [idx, errMsg] of result.value.errors) {
268
+ errors.set(idx, errMsg);
269
+ }
270
+ } else {
271
+ const reason = result.reason as { message?: string } | undefined;
272
+ const errorMsg = reason?.message ?? 'Worklet batch signing failed';
273
+ const failedBatch = taskBatches[i];
274
+ if (failedBatch) {
275
+ for (const task of failedBatch) {
276
+ errors.set(task.inputIndex, errorMsg);
277
+ }
278
+ }
279
+ }
280
+ }
281
+
282
+ // Cleanup runtimes if not preserving
283
+ if (!this.#preserveWorkers) {
284
+ await this.shutdown();
285
+ }
286
+
287
+ return {
288
+ success: errors.size === 0,
289
+ signatures,
290
+ errors,
291
+ durationMs: performance.now() - startTime,
292
+ };
293
+ } finally {
294
+ // SECURITY: Zero the key in main thread
295
+ privateKey.fill(0);
296
+ }
297
+ }
298
+
299
+ /** Shuts down all runtimes. */
300
+ // eslint-disable-next-line @typescript-eslint/require-await
301
+ public async shutdown(): Promise<void> {
302
+ if (this.#shuttingDown) {
303
+ return;
304
+ }
305
+
306
+ this.#shuttingDown = true;
307
+
308
+ // Worklet runtimes don't have a destroy() — clear references for GC
309
+ this.#runtimes.length = 0;
310
+ this.#workletsModule = null;
311
+ this.#eccBundleCode = null;
312
+ this.#initialized = false;
313
+ this.#shuttingDown = false;
314
+ }
315
+
316
+ public [Symbol.dispose](): void {
317
+ void this.shutdown();
318
+ }
319
+
320
+ public async [Symbol.asyncDispose](): Promise<void> {
321
+ await this.shutdown();
322
+ }
323
+
324
+ /**
325
+ * Creates a new worklet runtime and injects the ECC bundle.
326
+ */
327
+ async #createRuntime(): Promise<PooledRuntime> {
328
+ if (!this.#workletsModule || !this.#eccBundleCode) {
329
+ throw new Error('Worklets module or ECC bundle not loaded');
330
+ }
331
+
332
+ const id = this.#nextRuntimeId++;
333
+ const runtime = this.#workletsModule.createWorkletRuntime(`signing-runtime-${id}`);
334
+
335
+ // Inject ECC bundle into the worklet runtime.
336
+ // The bundle declares `var nobleBundle = (()=>{...})()` with "use strict",
337
+ // so a plain eval won't leak it to globalThis. We use `new Function` to
338
+ // execute the bundle and then explicitly assign the result.
339
+ const bundleCode = this.#eccBundleCode;
340
+ await this.#workletsModule.runOnRuntime(runtime, () => {
341
+ 'worklet';
342
+ // eslint-disable-next-line @typescript-eslint/no-implied-eval
343
+ const fn = new Function(bundleCode + '; return nobleBundle;') as () => unknown;
344
+ (globalThis as Record<string, unknown>)['nobleBundle'] = fn();
345
+ });
346
+
347
+ const pooled: PooledRuntime = { id, runtime, tainted: false };
348
+ this.#runtimes.push(pooled);
349
+ return pooled;
350
+ }
351
+
352
+ /**
353
+ * Replaces a tainted runtime with a fresh one.
354
+ */
355
+ async #replaceRuntime(pooled: PooledRuntime): Promise<void> {
356
+ const idx = this.#runtimes.indexOf(pooled);
357
+ if (idx >= 0) {
358
+ this.#runtimes.splice(idx, 1);
359
+ }
360
+
361
+ try {
362
+ await this.#createRuntime();
363
+ } catch {
364
+ // If replacement fails, pool continues with fewer runtimes
365
+ }
366
+ }
367
+
368
+ /**
369
+ * Signs a batch of tasks on a specific runtime.
370
+ */
371
+ async #signBatchOnRuntime(
372
+ tasks: readonly SigningTask[],
373
+ privateKey: Uint8Array,
374
+ publicKey: Uint8Array,
375
+ runtimeIndex: number,
376
+ ): Promise<{
377
+ signatures: SigningResultMessage[];
378
+ errors: Map<number, string>;
379
+ }> {
380
+ if (tasks.length === 0) {
381
+ return { signatures: [], errors: new Map() };
382
+ }
383
+
384
+ const pooled = this.#runtimes[runtimeIndex];
385
+ if (!pooled || pooled.tainted) {
386
+ throw new Error(`Runtime ${runtimeIndex} unavailable or tainted`);
387
+ }
388
+
389
+ if (!this.#workletsModule) {
390
+ throw new Error('Worklets module not loaded');
391
+ }
392
+
393
+ // Prepare data for worklet transfer
394
+ const useArrayEncoding = !this.#uint8ArraySupported;
395
+ const keyData: number[] | Uint8Array = useArrayEncoding
396
+ ? Array.from(privateKey)
397
+ : new Uint8Array(privateKey);
398
+
399
+ const taskData = tasks.map((t) => ({
400
+ taskId: t.taskId,
401
+ inputIndex: t.inputIndex,
402
+ hash: useArrayEncoding ? Array.from(t.hash) : new Uint8Array(t.hash),
403
+ signatureType: t.signatureType,
404
+ lowR: t.lowR,
405
+ sighashType: t.sighashType,
406
+ leafHash: t.leafHash
407
+ ? (useArrayEncoding ? Array.from(t.leafHash) : new Uint8Array(t.leafHash))
408
+ : undefined,
409
+ }));
410
+
411
+ const pubKeyData: number[] | Uint8Array = useArrayEncoding
412
+ ? Array.from(publicKey)
413
+ : new Uint8Array(publicKey);
414
+
415
+ // Dispatch to worklet runtime with timeout
416
+ const signingPromise = this.#workletsModule.runOnRuntime(
417
+ pooled.runtime,
418
+ () => {
419
+ 'worklet';
420
+ // Inside the worklet runtime, nobleBundle is available from init eval
421
+ // The bundled secp.sign() returns a compact 64-byte Uint8Array directly
422
+ // (not a Signature object). secp.schnorr.sign() also returns Uint8Array.
423
+ const eccModule = (globalThis as Record<string, unknown>)['nobleBundle'] as {
424
+ secp: {
425
+ sign(
426
+ hash: Uint8Array,
427
+ key: Uint8Array,
428
+ opts?: { lowS: boolean; prehash: boolean },
429
+ ): Uint8Array;
430
+ schnorr: {
431
+ sign(hash: Uint8Array, key: Uint8Array): Uint8Array;
432
+ };
433
+ };
434
+ };
435
+
436
+ const toU8 = (data: number[] | Uint8Array): Uint8Array =>
437
+ data instanceof Uint8Array ? data : new Uint8Array(data);
438
+
439
+ const privKey = toU8(keyData);
440
+ const results: Array<{
441
+ type: 'result';
442
+ taskId: string;
443
+ signature: number[];
444
+ inputIndex: number;
445
+ publicKey: number[];
446
+ signatureType: number;
447
+ leafHash?: number[];
448
+ }> = [];
449
+ const errors: Array<{ inputIndex: number; error: string }> = [];
450
+
451
+ try {
452
+ for (const task of taskData) {
453
+ try {
454
+ const hash = toU8(task.hash);
455
+ let signature: Uint8Array;
456
+
457
+ if (task.signatureType === 1) {
458
+ signature = eccModule.secp.schnorr.sign(hash, privKey);
459
+ } else {
460
+ // prehash: false — input is already a 32-byte hash
461
+ signature = eccModule.secp.sign(hash, privKey, {
462
+ lowS: true,
463
+ prehash: false,
464
+ });
465
+ }
466
+
467
+ const entry: (typeof results)[number] = {
468
+ type: 'result' as const,
469
+ taskId: task.taskId,
470
+ signature: Array.from(signature),
471
+ inputIndex: task.inputIndex,
472
+ publicKey: Array.from(toU8(pubKeyData)),
473
+ signatureType: task.signatureType,
474
+ };
475
+ if (task.leafHash) {
476
+ entry.leafHash = Array.from(toU8(task.leafHash));
477
+ }
478
+ results.push(entry);
479
+ } catch (err: unknown) {
480
+ const msg = err instanceof Error ? err.message : 'Signing failed';
481
+ errors.push({ inputIndex: task.inputIndex, error: msg });
482
+ }
483
+ }
484
+ } finally {
485
+ // SECURITY: Zero key inside worklet
486
+ privKey.fill(0);
487
+ }
488
+
489
+ return { results, errors };
490
+ },
491
+ );
492
+
493
+ // Timeout guard
494
+ const timeoutPromise = new Promise<never>((_resolve, reject) => {
495
+ setTimeout(() => {
496
+ pooled.tainted = true;
497
+ this.#replaceRuntime(pooled).catch(() => {});
498
+ reject(new Error(`Worklet signing timeout for ${tasks.length} tasks`));
499
+ }, this.#config.maxKeyHoldTimeMs);
500
+ });
501
+
502
+ const raw = await Promise.race([signingPromise, timeoutPromise]);
503
+
504
+ // Convert results back to proper types
505
+ const signatures: SigningResultMessage[] = raw.results.map((r) => ({
506
+ type: 'result' as const,
507
+ taskId: r.taskId,
508
+ signature: new Uint8Array(r.signature),
509
+ inputIndex: r.inputIndex,
510
+ publicKey: new Uint8Array(r.publicKey),
511
+ signatureType: r.signatureType as SignatureType,
512
+ leafHash: r.leafHash ? new Uint8Array(r.leafHash) : undefined,
513
+ }));
514
+
515
+ const errors = new Map<number, string>();
516
+ for (const e of raw.errors) {
517
+ errors.set(e.inputIndex, e.error);
518
+ }
519
+
520
+ return { signatures, errors };
521
+ }
522
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Browser worker pool entry point.
3
+ *
4
+ * Uses Web Workers for parallel signing. Never references Node.js modules
5
+ * (worker_threads, os, etc.), so it is safe for browser bundlers.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+
10
+ import type { WorkerPoolConfig, SigningPoolLike } from './types.js';
11
+
12
+ export * from './index.shared.js';
13
+
14
+ /**
15
+ * Detects the runtime environment.
16
+ *
17
+ * @returns Always 'browser' in this entry point.
18
+ */
19
+ export function detectRuntime(): 'node' | 'browser' | 'react-native' | 'unknown' {
20
+ return 'browser';
21
+ }
22
+
23
+ /**
24
+ * Creates a signing pool for the browser runtime using Web Workers.
25
+ *
26
+ * @param config - Optional pool configuration
27
+ * @returns A promise resolving to the initialized signing pool
28
+ */
29
+ export async function createSigningPool(config?: WorkerPoolConfig): Promise<SigningPoolLike> {
30
+ const { WorkerSigningPool } = await import('./WorkerSigningPool.js');
31
+ const pool = WorkerSigningPool.getInstance(config);
32
+ await pool.initialize();
33
+ return pool;
34
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Node.js worker pool entry point.
3
+ *
4
+ * Uses worker_threads for parallel signing. Provides direct access to
5
+ * NodeWorkerSigningPool without runtime detection overhead.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { NodeWorkerSigningPool } from '@btc-vision/bitcoin/workers';
10
+ *
11
+ * const pool = NodeWorkerSigningPool.getInstance({ workerCount: 4 });
12
+ * await pool.initialize();
13
+ * pool.preserveWorkers();
14
+ *
15
+ * const result = await pool.signBatch(tasks, keyPair);
16
+ *
17
+ * await pool.shutdown();
18
+ * ```
19
+ *
20
+ * @packageDocumentation
21
+ */
22
+
23
+ import type { WorkerPoolConfig, SigningPoolLike } from './types.js';
24
+
25
+ export * from './index.shared.js';
26
+
27
+ // Node.js specific exports
28
+ export { NodeWorkerSigningPool, type NodeWorkerPoolConfig } from './WorkerSigningPool.node.js';
29
+
30
+ /**
31
+ * Detects the runtime environment.
32
+ *
33
+ * @returns Always 'node' in this entry point.
34
+ */
35
+ export function detectRuntime(): 'node' | 'browser' | 'react-native' | 'unknown' {
36
+ return 'node';
37
+ }
38
+
39
+ /**
40
+ * Creates a signing pool for the Node.js runtime using worker_threads.
41
+ *
42
+ * @param config - Optional pool configuration
43
+ * @returns A promise resolving to the initialized signing pool
44
+ */
45
+ export async function createSigningPool(config?: WorkerPoolConfig): Promise<SigningPoolLike> {
46
+ const { NodeWorkerSigningPool } = await import('./WorkerSigningPool.node.js');
47
+ const pool = NodeWorkerSigningPool.getInstance(config);
48
+ await pool.initialize();
49
+ return pool;
50
+ }