@btc-vision/bitcoin 7.0.0-beta.0 → 7.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/README.md +112 -13
  2. package/benchmark-compare/BENCHMARK.md +74 -59
  3. package/benchmark-compare/compare.bench.ts +249 -96
  4. package/benchmark-compare/harness.ts +23 -25
  5. package/benchmark-compare/package.json +1 -0
  6. package/browser/address.d.ts +4 -4
  7. package/browser/address.d.ts.map +1 -1
  8. package/browser/chunks/{psbt-parallel-B-dfm5GZ.js → psbt-parallel-jZ6QcCnM.js} +3128 -2731
  9. package/browser/index.d.ts +1 -1
  10. package/browser/index.d.ts.map +1 -1
  11. package/browser/index.js +603 -585
  12. package/browser/io/base58check.d.ts +1 -25
  13. package/browser/io/base58check.d.ts.map +1 -1
  14. package/browser/io/base64.d.ts.map +1 -1
  15. package/browser/networks.d.ts +1 -0
  16. package/browser/networks.d.ts.map +1 -1
  17. package/browser/payments/bip341.d.ts +17 -0
  18. package/browser/payments/bip341.d.ts.map +1 -1
  19. package/browser/payments/index.d.ts +3 -2
  20. package/browser/payments/index.d.ts.map +1 -1
  21. package/browser/payments/p2mr.d.ts +169 -0
  22. package/browser/payments/p2mr.d.ts.map +1 -0
  23. package/browser/payments/types.d.ts +11 -1
  24. package/browser/payments/types.d.ts.map +1 -1
  25. package/browser/psbt/bip371.d.ts +30 -0
  26. package/browser/psbt/bip371.d.ts.map +1 -1
  27. package/browser/psbt/psbtutils.d.ts +1 -0
  28. package/browser/psbt/psbtutils.d.ts.map +1 -1
  29. package/browser/psbt.d.ts.map +1 -1
  30. package/browser/workers/index.js +9 -9
  31. package/build/address.d.ts +4 -4
  32. package/build/address.d.ts.map +1 -1
  33. package/build/address.js +11 -1
  34. package/build/address.js.map +1 -1
  35. package/build/index.d.ts +1 -1
  36. package/build/index.d.ts.map +1 -1
  37. package/build/index.js.map +1 -1
  38. package/build/io/base58check.d.ts +1 -25
  39. package/build/io/base58check.d.ts.map +1 -1
  40. package/build/io/base58check.js +1 -31
  41. package/build/io/base58check.js.map +1 -1
  42. package/build/io/base64.d.ts.map +1 -1
  43. package/build/io/base64.js +3 -0
  44. package/build/io/base64.js.map +1 -1
  45. package/build/networks.d.ts +1 -0
  46. package/build/networks.d.ts.map +1 -1
  47. package/build/networks.js +12 -0
  48. package/build/networks.js.map +1 -1
  49. package/build/payments/bip341.d.ts +17 -0
  50. package/build/payments/bip341.d.ts.map +1 -1
  51. package/build/payments/bip341.js +32 -1
  52. package/build/payments/bip341.js.map +1 -1
  53. package/build/payments/index.d.ts +3 -2
  54. package/build/payments/index.d.ts.map +1 -1
  55. package/build/payments/index.js +2 -1
  56. package/build/payments/index.js.map +1 -1
  57. package/build/payments/p2mr.d.ts +178 -0
  58. package/build/payments/p2mr.d.ts.map +1 -0
  59. package/build/payments/p2mr.js +555 -0
  60. package/build/payments/p2mr.js.map +1 -0
  61. package/build/payments/types.d.ts +11 -1
  62. package/build/payments/types.d.ts.map +1 -1
  63. package/build/payments/types.js +1 -0
  64. package/build/payments/types.js.map +1 -1
  65. package/build/psbt/bip371.d.ts +30 -0
  66. package/build/psbt/bip371.d.ts.map +1 -1
  67. package/build/psbt/bip371.js +80 -15
  68. package/build/psbt/bip371.js.map +1 -1
  69. package/build/psbt/psbtutils.d.ts +1 -0
  70. package/build/psbt/psbtutils.d.ts.map +1 -1
  71. package/build/psbt/psbtutils.js +2 -0
  72. package/build/psbt/psbtutils.js.map +1 -1
  73. package/build/psbt.d.ts.map +1 -1
  74. package/build/psbt.js +3 -2
  75. package/build/psbt.js.map +1 -1
  76. package/build/pubkey.js +1 -1
  77. package/build/pubkey.js.map +1 -1
  78. package/build/tsconfig.build.tsbuildinfo +1 -1
  79. package/documentation/README.md +122 -0
  80. package/documentation/address.md +820 -0
  81. package/documentation/block.md +679 -0
  82. package/documentation/crypto.md +461 -0
  83. package/documentation/ecc.md +584 -0
  84. package/documentation/errors.md +656 -0
  85. package/documentation/io.md +942 -0
  86. package/documentation/networks.md +625 -0
  87. package/documentation/p2mr.md +380 -0
  88. package/documentation/payments.md +1485 -0
  89. package/documentation/psbt.md +1400 -0
  90. package/documentation/script.md +730 -0
  91. package/documentation/taproot.md +670 -0
  92. package/documentation/transaction.md +943 -0
  93. package/documentation/types.md +587 -0
  94. package/documentation/workers.md +1007 -0
  95. package/eslint.config.js +3 -0
  96. package/package.json +17 -14
  97. package/src/address.ts +22 -10
  98. package/src/index.ts +1 -0
  99. package/src/io/base58check.ts +1 -35
  100. package/src/io/base64.ts +5 -0
  101. package/src/networks.ts +13 -0
  102. package/src/payments/bip341.ts +36 -1
  103. package/src/payments/index.ts +4 -0
  104. package/src/payments/p2mr.ts +660 -0
  105. package/src/payments/types.ts +12 -0
  106. package/src/psbt/bip371.ts +84 -13
  107. package/src/psbt/psbtutils.ts +2 -0
  108. package/src/psbt.ts +4 -2
  109. package/src/pubkey.ts +1 -1
  110. package/test/bitcoin.core.spec.ts +1 -1
  111. package/test/fixtures/p2mr.json +270 -0
  112. package/test/integration/taproot.spec.ts +7 -3
  113. package/test/opnetTestnet.spec.ts +302 -0
  114. package/test/payments.spec.ts +3 -1
  115. package/test/psbt.spec.ts +297 -2
  116. package/test/tsconfig.json +2 -2
@@ -0,0 +1,584 @@
1
+ # ECC (Elliptic Curve Cryptography) Support
2
+
3
+ The ECC module provides dependency injection for the secp256k1 elliptic curve library required by Bitcoin operations including Taproot (BIP 340/BIP 341), ECDSA signing, and public key derivation. Rather than hard-coding a specific cryptographic library, the module lets you plug in any backend that satisfies the `CryptoBackend` interface.
4
+
5
+ ## Overview
6
+
7
+ | Property | Value |
8
+ |----------|-------|
9
+ | Curve | secp256k1 |
10
+ | Module path | `@btc-vision/bitcoin` (re-exported from `@btc-vision/ecpair`) |
11
+ | Pattern | Dependency injection via singleton context |
12
+ | Canonical interface | `CryptoBackend` |
13
+ | Legacy alias | `EccLib` (deprecated) |
14
+ | Available backends | `NobleBackend` (pure JS), `LegacyBackend` (wraps `tiny-secp256k1`) — provided by `@btc-vision/ecpair` |
15
+
16
+ ### Architecture
17
+
18
+ ```
19
+ @btc-vision/bitcoin
20
+ src/ecc/
21
+ types.ts - Re-exports CryptoBackend, EccLib, XOnlyPointAddTweakResult (Parity is internal only)
22
+ context.ts - EccContext class, initEccLib(), getEccLib()
23
+ index.ts - Barrel exports
24
+ src/pubkey.ts - Public key utilities (toXOnly, decompressPublicKey, pubkeysMatch)
25
+ ```
26
+
27
+ ---
28
+
29
+ ## Installation and Quick Start
30
+
31
+ ```typescript
32
+ import { initEccLib, getEccLib, EccContext } from '@btc-vision/bitcoin';
33
+ import { createNobleBackend } from '@btc-vision/ecpair';
34
+
35
+ // 1. Initialize once at application startup
36
+ initEccLib(createNobleBackend());
37
+
38
+ // 2. Use anywhere in your codebase
39
+ const ecc = getEccLib();
40
+ const isValid = ecc.isPoint(somePublicKey);
41
+
42
+ // 3. (Optional) Clear when done, e.g. in test teardown
43
+ initEccLib(undefined);
44
+ ```
45
+
46
+ ---
47
+
48
+ ## Class: EccContext
49
+
50
+ The `EccContext` class manages the ECC library instance using a private singleton pattern. The constructor is private; all interaction happens through static methods.
51
+
52
+ ```typescript
53
+ class EccContext {
54
+ // Private constructor - cannot be instantiated directly
55
+ private constructor(lib: CryptoBackend);
56
+
57
+ // The underlying CryptoBackend instance
58
+ get lib(): CryptoBackend;
59
+
60
+ // Static lifecycle methods
61
+ static init(lib: CryptoBackend): EccContext;
62
+ static get(): EccContext;
63
+ static clear(): void;
64
+ static isInitialized(): boolean;
65
+ }
66
+ ```
67
+
68
+ ### EccContext.init()
69
+
70
+ Initializes the ECC context with the provided `CryptoBackend`. The library is verified via `verifyCryptoBackend()` before being stored. If the same library instance is already initialized, verification is skipped and the existing context is returned.
71
+
72
+ ```typescript
73
+ import { EccContext } from '@btc-vision/bitcoin';
74
+ import { createNobleBackend } from '@btc-vision/ecpair';
75
+
76
+ const context = EccContext.init(createNobleBackend());
77
+ // context.lib is now the verified NobleBackend instance
78
+ ```
79
+
80
+ | Parameter | Type | Description |
81
+ |-----------|------|-------------|
82
+ | `lib` | `CryptoBackend` | The secp256k1 backend to initialize |
83
+ | **Returns** | `EccContext` | The initialized context instance |
84
+ | **Throws** | `Error` | If the backend fails known-answer verification tests |
85
+
86
+ ### EccContext.get()
87
+
88
+ Returns the initialized `EccContext` instance. Throws if the context has not been initialized.
89
+
90
+ ```typescript
91
+ import { EccContext } from '@btc-vision/bitcoin';
92
+
93
+ const context = EccContext.get();
94
+ const tweaked = context.lib.xOnlyPointAddTweak(pubkey, tweak);
95
+ ```
96
+
97
+ | Parameter | Type | Description |
98
+ |-----------|------|-------------|
99
+ | **Returns** | `EccContext` | The current context instance |
100
+ | **Throws** | `Error` | `'ECC library not initialized. Call EccContext.init() or initEccLib() first.'` |
101
+
102
+ ### EccContext.clear()
103
+
104
+ Clears the singleton instance, resetting the context to an uninitialized state. Useful for test teardown or reinitializing with a different backend.
105
+
106
+ ```typescript
107
+ EccContext.clear();
108
+ // EccContext.isInitialized() === false
109
+ ```
110
+
111
+ ### EccContext.isInitialized()
112
+
113
+ Returns `true` if the ECC context has been initialized, `false` otherwise.
114
+
115
+ ```typescript
116
+ if (!EccContext.isInitialized()) {
117
+ EccContext.init(createNobleBackend());
118
+ }
119
+ ```
120
+
121
+ ---
122
+
123
+ ## Convenience Functions
124
+
125
+ ### initEccLib()
126
+
127
+ A convenience wrapper around `EccContext.init()` and `EccContext.clear()`. Pass a `CryptoBackend` to initialize, or `undefined` to clear.
128
+
129
+ ```typescript
130
+ import { initEccLib } from '@btc-vision/bitcoin';
131
+ import { createNobleBackend } from '@btc-vision/ecpair';
132
+
133
+ // Initialize
134
+ initEccLib(createNobleBackend());
135
+
136
+ // Clear
137
+ initEccLib(undefined);
138
+ ```
139
+
140
+ | Parameter | Type | Description |
141
+ |-----------|------|-------------|
142
+ | `eccLib` | `CryptoBackend \| undefined` | Backend to set, or `undefined` to clear |
143
+ | **Returns** | `void` | |
144
+ | **Throws** | `Error` | If the backend fails verification |
145
+
146
+ ### getEccLib()
147
+
148
+ A convenience wrapper around `EccContext.get().lib`. Returns the raw `CryptoBackend` instance directly.
149
+
150
+ ```typescript
151
+ import { getEccLib } from '@btc-vision/bitcoin';
152
+
153
+ const ecc = getEccLib();
154
+ const pubkey = ecc.pointFromScalar(privateKey);
155
+ ```
156
+
157
+ | Parameter | Type | Description |
158
+ |-----------|------|-------------|
159
+ | **Returns** | `CryptoBackend` | The initialized backend |
160
+ | **Throws** | `Error` | If the ECC library has not been initialized |
161
+
162
+ ---
163
+
164
+ ## CryptoBackend Interface
165
+
166
+ The `CryptoBackend` interface (from `@btc-vision/ecpair`) defines the low-level secp256k1 operations required by the library. Any object implementing all required methods can be used as a backend.
167
+
168
+ ```typescript
169
+ interface CryptoBackend {
170
+ // Validation
171
+ isPrivate(d: Uint8Array): boolean;
172
+ isPoint(p: Uint8Array): boolean;
173
+ isXOnlyPoint(p: Uint8Array): boolean;
174
+
175
+ // Point operations
176
+ pointFromScalar(d: PrivateKey, compressed?: boolean): PublicKey | null;
177
+ pointCompress(p: PublicKey, compressed?: boolean): PublicKey;
178
+ pointAddScalar(p: PublicKey, tweak: Bytes32, compressed?: boolean): PublicKey | null;
179
+ xOnlyPointAddTweak(p: XOnlyPublicKey, tweak: Bytes32): XOnlyPointAddTweakResult | null;
180
+
181
+ // Scalar operations
182
+ privateAdd(d: PrivateKey, tweak: Bytes32): PrivateKey | null;
183
+ privateNegate(d: PrivateKey): PrivateKey;
184
+
185
+ // ECDSA
186
+ sign(hash: MessageHash, privateKey: PrivateKey, extraEntropy?: Uint8Array): Signature;
187
+ verify(hash: MessageHash, publicKey: PublicKey, signature: Signature): boolean;
188
+
189
+ // Schnorr (BIP 340) - optional
190
+ signSchnorr?(hash: MessageHash, privateKey: PrivateKey, extraEntropy?: Uint8Array): SchnorrSignature;
191
+ verifySchnorr?(hash: MessageHash, publicKey: XOnlyPublicKey, signature: SchnorrSignature): boolean;
192
+ }
193
+ ```
194
+
195
+ ### Required Methods
196
+
197
+ | Method | Parameters | Returns | Description |
198
+ |--------|-----------|---------|-------------|
199
+ | `isPrivate` | `d: Uint8Array` | `boolean` | Returns `true` if `d` is a valid 32-byte secp256k1 private key (in range `[1, n)`) |
200
+ | `isPoint` | `p: Uint8Array` | `boolean` | Returns `true` if `p` is a valid SEC1-encoded secp256k1 point (33 or 65 bytes) |
201
+ | `isXOnlyPoint` | `p: Uint8Array` | `boolean` | Returns `true` if `p` is a valid 32-byte x-only public key on the curve |
202
+ | `pointFromScalar` | `d: PrivateKey, compressed?: boolean` | `PublicKey \| null` | Derives the public key for a private key scalar. Returns `null` if `d` is invalid |
203
+ | `pointCompress` | `p: PublicKey, compressed?: boolean` | `PublicKey` | Re-encodes a public key in compressed (33 bytes) or uncompressed (65 bytes) form |
204
+ | `pointAddScalar` | `p: PublicKey, tweak: Bytes32, compressed?: boolean` | `PublicKey \| null` | Adds a scalar tweak to a public key point (`P + tweak*G`). Returns `null` if result is point at infinity |
205
+ | `xOnlyPointAddTweak` | `p: XOnlyPublicKey, tweak: Bytes32` | `XOnlyPointAddTweakResult \| null` | Adds a scalar tweak to an x-only public key, returning the result with Y parity. Returns `null` on failure |
206
+ | `privateAdd` | `d: PrivateKey, tweak: Bytes32` | `PrivateKey \| null` | Adds two scalars modulo the curve order (`(d + tweak) mod n`). Returns `null` if result is zero |
207
+ | `privateNegate` | `d: PrivateKey` | `PrivateKey` | Negates a private key scalar modulo the curve order (`n - d`) |
208
+ | `sign` | `hash: MessageHash, privateKey: PrivateKey, extraEntropy?: Uint8Array` | `Signature` | Produces a DER-encoded ECDSA signature (8-73 bytes) |
209
+ | `verify` | `hash: MessageHash, publicKey: PublicKey, signature: Signature` | `boolean` | Verifies a DER-encoded ECDSA signature |
210
+
211
+ ### Optional Methods (Schnorr / BIP 340)
212
+
213
+ | Method | Parameters | Returns | Description |
214
+ |--------|-----------|---------|-------------|
215
+ | `signSchnorr?` | `hash: MessageHash, privateKey: PrivateKey, extraEntropy?: Uint8Array` | `SchnorrSignature` | Produces a 64-byte BIP 340 Schnorr signature. Omitting causes `signSchnorr` to throw at runtime |
216
+ | `verifySchnorr?` | `hash: MessageHash, publicKey: XOnlyPublicKey, signature: SchnorrSignature` | `boolean` | Verifies a 64-byte BIP 340 Schnorr signature. Omitting causes `verifySchnorr` to throw at runtime |
217
+
218
+ ---
219
+
220
+ ## Type Definitions
221
+
222
+ ### EccLib (Deprecated Alias)
223
+
224
+ ```typescript
225
+ // @deprecated - Use CryptoBackend instead
226
+ type EccLib = CryptoBackend;
227
+ ```
228
+
229
+ `EccLib` is a legacy type alias for `CryptoBackend`, kept for backward compatibility. New code should use `CryptoBackend` directly.
230
+
231
+ ### XOnlyPointAddTweakResult
232
+
233
+ Returned by `CryptoBackend.xOnlyPointAddTweak()` on success. Contains the resulting x-only public key and the parity of its Y coordinate.
234
+
235
+ ```typescript
236
+ interface XOnlyPointAddTweakResult {
237
+ /** Parity of the resulting point's Y coordinate. */
238
+ readonly parity: Parity;
239
+ /** 32-byte x-only public key of the tweaked point. */
240
+ readonly xOnlyPubkey: XOnlyPublicKey;
241
+ }
242
+ ```
243
+
244
+ ### Parity
245
+
246
+ ```typescript
247
+ type Parity = 0 | 1;
248
+ ```
249
+
250
+ Y-coordinate parity of a point on the secp256k1 curve: `0` means even Y, `1` means odd Y. Used in Taproot key tweaking and BIP 340.
251
+
252
+ > **Note:** `Parity` is NOT re-exported from the main `@btc-vision/bitcoin` entry point. It is defined in `@btc-vision/ecpair` and used internally. Use the literal type `0 | 1` in consumer code.
253
+
254
+ ### Branded Types (from `@btc-vision/ecpair`)
255
+
256
+ The library uses branded types to prevent accidental misuse of structurally identical `Uint8Array` values.
257
+
258
+ | Type | Underlying | Description |
259
+ |------|-----------|-------------|
260
+ | `PrivateKey` | `Uint8Array` (32 bytes) | Valid secp256k1 scalar in range `[1, n)` |
261
+ | `PublicKey` | `Uint8Array` (33 or 65 bytes) | SEC1-encoded secp256k1 point |
262
+ | `XOnlyPublicKey` | `Uint8Array` (32 bytes) | BIP 340 x-only public key |
263
+ | `Bytes32` | `Uint8Array` (32 bytes) | Generic 32-byte array |
264
+ | `MessageHash` | `Uint8Array` (32 bytes) | 32-byte hash to be signed |
265
+ | `Signature` | `Uint8Array` (8-73 bytes) | DER-encoded ECDSA signature |
266
+ | `SchnorrSignature` | `Uint8Array` (64 bytes) | BIP 340 Schnorr signature |
267
+
268
+ ---
269
+
270
+ ## Public Key Utilities (`pubkey.ts`)
271
+
272
+ The `pubkey.ts` module provides utility functions for manipulating Bitcoin public keys, including conversion between compressed, uncompressed, hybrid, and x-only formats.
273
+
274
+ ```typescript
275
+ import { toXOnly, decompressPublicKey, pubkeysMatch } from '@btc-vision/bitcoin';
276
+ ```
277
+
278
+ ### toXOnly()
279
+
280
+ Converts a public key to 32-byte x-only format by stripping the prefix byte. If the input is already 32 bytes (x-only), it is returned as-is. For 33-byte (compressed) or 65-byte (uncompressed) keys, the first byte is stripped and only the 32-byte x-coordinate is returned.
281
+
282
+ ```typescript
283
+ const toXOnly = (pubKey: PublicKey | XOnlyPublicKey): XOnlyPublicKey;
284
+ ```
285
+
286
+ ```typescript
287
+ import { toXOnly } from '@btc-vision/bitcoin';
288
+
289
+ // From a 33-byte compressed public key (0x02 or 0x03 prefix)
290
+ const compressed = new Uint8Array(33); // 0x02 + 32 bytes x
291
+ const xOnly = toXOnly(compressed);
292
+ // xOnly.length === 32 (just the x-coordinate)
293
+
294
+ // Already x-only (32 bytes) - returned unchanged
295
+ const already = new Uint8Array(32);
296
+ const same = toXOnly(already);
297
+ // same === already
298
+ ```
299
+
300
+ | Parameter | Type | Description |
301
+ |-----------|------|-------------|
302
+ | `pubKey` | `PublicKey \| XOnlyPublicKey` | 32, 33, or 65-byte public key |
303
+ | **Returns** | `XOnlyPublicKey` | 32-byte x-only public key |
304
+
305
+ ### UncompressedPublicKey Interface
306
+
307
+ Returned by `decompressPublicKey()`. Contains both the hybrid and standard uncompressed encodings of a public key.
308
+
309
+ ```typescript
310
+ interface UncompressedPublicKey {
311
+ /** 65-byte hybrid public key (prefix 0x06 for even Y, 0x07 for odd Y) */
312
+ hybrid: Uint8Array;
313
+ /** 65-byte uncompressed public key (prefix 0x04) */
314
+ uncompressed: Uint8Array;
315
+ }
316
+ ```
317
+
318
+ ### decompressPublicKey()
319
+
320
+ Converts a compressed (33-byte) or uncompressed (65-byte) public key to both its hybrid form (prefix `0x06`/`0x07`) and standard uncompressed form (prefix `0x04`). Returns `undefined` for 32-byte x-only keys or invalid lengths.
321
+
322
+ ```typescript
323
+ function decompressPublicKey(realPubKey: PublicKey): UncompressedPublicKey | undefined;
324
+ ```
325
+
326
+ ```typescript
327
+ import { decompressPublicKey } from '@btc-vision/bitcoin';
328
+
329
+ const compressed = getCompressedPubKey(); // 33-byte key with 0x02 or 0x03 prefix
330
+ const result = decompressPublicKey(compressed);
331
+
332
+ if (result) {
333
+ console.log(result.uncompressed.length); // 65, prefix 0x04
334
+ console.log(result.hybrid.length); // 65, prefix 0x06 or 0x07
335
+ console.log(result.hybrid[0]); // 0x06 (even Y) or 0x07 (odd Y)
336
+ }
337
+
338
+ // x-only keys return undefined
339
+ const xonly = new Uint8Array(32);
340
+ decompressPublicKey(xonly); // undefined
341
+ ```
342
+
343
+ | Parameter | Type | Description |
344
+ |-----------|------|-------------|
345
+ | `realPubKey` | `PublicKey` | 33-byte compressed or 65-byte uncompressed key |
346
+ | **Returns** | `UncompressedPublicKey \| undefined` | Both hybrid and uncompressed forms, or `undefined` for x-only / invalid lengths |
347
+ | **Throws** | `Error` | `'Invalid secp256k1 public key bytes. Cannot parse.'` if the key bytes are not a valid curve point |
348
+
349
+ ### Hybrid Key Prefix Encoding
350
+
351
+ | Y Parity | Standard Prefix | Hybrid Prefix |
352
+ |-----------|----------------|---------------|
353
+ | Even | `0x02` (compressed) / `0x04` (uncompressed) | `0x06` |
354
+ | Odd | `0x03` (compressed) / `0x04` (uncompressed) | `0x07` |
355
+
356
+ ### pubkeysMatch()
357
+
358
+ Compares two public key `Uint8Array` values for equality. For 65-byte keys, hybrid prefixes (`0x06`/`0x07`) are treated as equivalent to the uncompressed prefix (`0x04`), so a hybrid-encoded key will match its uncompressed counterpart.
359
+
360
+ ```typescript
361
+ function pubkeysMatch(a: Uint8Array, b: Uint8Array): boolean;
362
+ ```
363
+
364
+ ```typescript
365
+ import { pubkeysMatch } from '@btc-vision/bitcoin';
366
+
367
+ // Exact match
368
+ pubkeysMatch(keyA, keyA); // true
369
+
370
+ // Hybrid vs uncompressed match (same x,y but different prefix byte)
371
+ const uncompressed = new Uint8Array([0x04, ...xBytes, ...yBytes]);
372
+ const hybrid = new Uint8Array([0x06, ...xBytes, ...yBytes]);
373
+ pubkeysMatch(uncompressed, hybrid); // true
374
+
375
+ // Different keys
376
+ pubkeysMatch(keyA, keyB); // false
377
+ ```
378
+
379
+ | Parameter | Type | Description |
380
+ |-----------|------|-------------|
381
+ | `a` | `Uint8Array` | First public key |
382
+ | `b` | `Uint8Array` | Second public key |
383
+ | **Returns** | `boolean` | `true` if keys represent the same point |
384
+
385
+ ### bigIntTo32Bytes()
386
+
387
+ Internal utility that converts a `bigint` to a zero-padded 32-byte `Uint8Array`. Used by `decompressPublicKey()` to serialize point coordinates.
388
+
389
+ > **Note:** This function is NOT re-exported from the main `@btc-vision/bitcoin` entry point. It is an internal utility in `src/pubkey.ts`.
390
+
391
+ ```typescript
392
+ function bigIntTo32Bytes(num: bigint): Uint8Array;
393
+ ```
394
+
395
+ ---
396
+
397
+ ## Backend Setup
398
+
399
+ ### Option 1: Noble Backend (Recommended)
400
+
401
+ Pure JavaScript implementation backed by `@noble/curves/secp256k1`. No native dependencies, works in all environments.
402
+
403
+ ```typescript
404
+ import { initEccLib } from '@btc-vision/bitcoin';
405
+ import { createNobleBackend } from '@btc-vision/ecpair';
406
+
407
+ initEccLib(createNobleBackend());
408
+ ```
409
+
410
+ ### Option 2: Legacy Backend (tiny-secp256k1)
411
+
412
+ Wraps an existing `tiny-secp256k1` installation (or any compatible library) via the `TinySecp256k1Interface` adapter.
413
+
414
+ ```typescript
415
+ import { initEccLib } from '@btc-vision/bitcoin';
416
+ import { createLegacyBackend } from '@btc-vision/ecpair';
417
+ import * as tinysecp from 'tiny-secp256k1';
418
+
419
+ initEccLib(createLegacyBackend(tinysecp));
420
+ ```
421
+
422
+ ### Option 3: Custom Backend
423
+
424
+ Any object implementing all required `CryptoBackend` methods can be used. The library verifies the backend against known-answer test vectors during initialization.
425
+
426
+ ```typescript
427
+ import { initEccLib } from '@btc-vision/bitcoin';
428
+ import type { CryptoBackend } from '@btc-vision/ecpair';
429
+
430
+ const customBackend: CryptoBackend = {
431
+ isPrivate(d) { /* ... */ },
432
+ isPoint(p) { /* ... */ },
433
+ isXOnlyPoint(p) { /* ... */ },
434
+ pointFromScalar(d, compressed) { /* ... */ },
435
+ pointCompress(p, compressed) { /* ... */ },
436
+ pointAddScalar(p, tweak, compressed) { /* ... */ },
437
+ xOnlyPointAddTweak(p, tweak) { /* ... */ },
438
+ privateAdd(d, tweak) { /* ... */ },
439
+ privateNegate(d) { /* ... */ },
440
+ sign(hash, privateKey, extraEntropy) { /* ... */ },
441
+ verify(hash, publicKey, signature) { /* ... */ },
442
+ // Optional Schnorr methods
443
+ signSchnorr(hash, privateKey, extraEntropy) { /* ... */ },
444
+ verifySchnorr(hash, publicKey, signature) { /* ... */ },
445
+ };
446
+
447
+ initEccLib(customBackend); // Runs verifyCryptoBackend() internally
448
+ ```
449
+
450
+ ### Backend Comparison
451
+
452
+ | Aspect | `NobleBackend` | `LegacyBackend` |
453
+ |--------|---------------|-----------------|
454
+ | Package | `@btc-vision/ecpair` (built-in) | `@btc-vision/ecpair` + `tiny-secp256k1` |
455
+ | Dependencies | `@noble/curves` (pure JS) | `tiny-secp256k1` (native/WASM) |
456
+ | Schnorr support | Always available | Depends on `tiny-secp256k1` version |
457
+ | Construction | `createNobleBackend()` | `createLegacyBackend(tinysecp)` |
458
+ | Environment | Browser, Node.js, Workers | Node.js (native), Browser (WASM build) |
459
+
460
+ ---
461
+
462
+ ## Verification
463
+
464
+ When `EccContext.init()` or `initEccLib()` is called, the backend is validated by `verifyCryptoBackend()` from `@btc-vision/ecpair`. This function runs a comprehensive suite of known-answer tests to verify correctness before the backend is accepted. If any test vector fails, an error is thrown and the backend is not stored.
465
+
466
+ ```typescript
467
+ import { verifyCryptoBackend } from '@btc-vision/ecpair';
468
+
469
+ // Manual verification (rarely needed - initEccLib does this automatically)
470
+ const backend = createNobleBackend();
471
+ verifyCryptoBackend(backend); // throws if broken
472
+ ```
473
+
474
+ ---
475
+
476
+ ## Complete Examples
477
+
478
+ ### Taproot Key Tweaking
479
+
480
+ ```typescript
481
+ import { initEccLib, getEccLib, toXOnly } from '@btc-vision/bitcoin';
482
+ import { createNobleBackend, createBytes32, createXOnlyPublicKey } from '@btc-vision/ecpair';
483
+
484
+ // Initialize the ECC library
485
+ initEccLib(createNobleBackend());
486
+ const ecc = getEccLib();
487
+
488
+ // Get x-only public key from a compressed key
489
+ const compressedPubKey = getPublicKey(); // 33-byte compressed key
490
+ const xOnlyPubKey = toXOnly(compressedPubKey);
491
+
492
+ // Tweak the x-only key (used in Taproot)
493
+ const tweak = createBytes32(computeTapTweak(xOnlyPubKey, merkleRoot));
494
+ const result = ecc.xOnlyPointAddTweak(
495
+ createXOnlyPublicKey(xOnlyPubKey),
496
+ tweak,
497
+ );
498
+
499
+ if (result) {
500
+ console.log('Tweaked key:', result.xOnlyPubkey); // 32-byte x-only
501
+ console.log('Parity:', result.parity); // 0 or 1
502
+ }
503
+ ```
504
+
505
+ ### ECDSA Signing and Verification
506
+
507
+ ```typescript
508
+ import { initEccLib, getEccLib } from '@btc-vision/bitcoin';
509
+ import {
510
+ createNobleBackend,
511
+ createMessageHash,
512
+ createPrivateKey,
513
+ } from '@btc-vision/ecpair';
514
+
515
+ initEccLib(createNobleBackend());
516
+ const ecc = getEccLib();
517
+
518
+ // Sign
519
+ const hash = createMessageHash(sha256(message));
520
+ const privKey = createPrivateKey(keyBytes);
521
+ const signature = ecc.sign(hash, privKey);
522
+
523
+ // Derive public key
524
+ const pubKey = ecc.pointFromScalar(privKey);
525
+ if (pubKey) {
526
+ // Verify
527
+ const isValid = ecc.verify(hash, pubKey, signature);
528
+ console.log('Valid:', isValid); // true
529
+ }
530
+ ```
531
+
532
+ ### Decompressing and Comparing Public Keys
533
+
534
+ ```typescript
535
+ import { decompressPublicKey, pubkeysMatch, toXOnly } from '@btc-vision/bitcoin';
536
+
537
+ // Decompress a key to get hybrid and uncompressed forms
538
+ const compressed = getCompressedPubKey(); // 33 bytes
539
+ const result = decompressPublicKey(compressed);
540
+
541
+ if (result) {
542
+ // The hybrid and uncompressed forms match (same point, different prefix)
543
+ pubkeysMatch(result.hybrid, result.uncompressed); // true
544
+
545
+ // Extract x-only from any form
546
+ const xFromCompressed = toXOnly(compressed);
547
+ // xFromCompressed is 32 bytes (the x-coordinate)
548
+ }
549
+ ```
550
+
551
+ ### Testing with Context Lifecycle
552
+
553
+ ```typescript
554
+ import { EccContext, initEccLib } from '@btc-vision/bitcoin';
555
+ import { createNobleBackend } from '@btc-vision/ecpair';
556
+
557
+ describe('my taproot tests', () => {
558
+ beforeAll(() => {
559
+ initEccLib(createNobleBackend());
560
+ });
561
+
562
+ afterAll(() => {
563
+ initEccLib(undefined); // or EccContext.clear()
564
+ });
565
+
566
+ it('should check initialization', () => {
567
+ expect(EccContext.isInitialized()).toBe(true);
568
+
569
+ const ecc = EccContext.get().lib;
570
+ expect(ecc.isPoint(validPubKey)).toBe(true);
571
+ });
572
+ });
573
+ ```
574
+
575
+ ---
576
+
577
+ ## Error Handling
578
+
579
+ | Scenario | Error Message |
580
+ |----------|--------------|
581
+ | Calling `getEccLib()` or `EccContext.get()` before initialization | `'ECC library not initialized. Call EccContext.init() or initEccLib() first.'` |
582
+ | Backend fails verification during `initEccLib()` or `EccContext.init()` | Error thrown by `verifyCryptoBackend()` with details about which test vector failed |
583
+ | `decompressPublicKey()` with invalid curve point bytes | `'Invalid secp256k1 public key bytes. Cannot parse.'` |
584
+ | `decompressPublicKey()` with unsupported key length (not 32, 33, or 65) | Logs a warning and returns `undefined` |