@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.
- package/README.md +112 -13
- package/benchmark-compare/BENCHMARK.md +74 -59
- package/benchmark-compare/compare.bench.ts +249 -96
- package/benchmark-compare/harness.ts +23 -25
- package/benchmark-compare/package.json +1 -0
- package/browser/address.d.ts +4 -4
- package/browser/address.d.ts.map +1 -1
- package/browser/chunks/{psbt-parallel-B-dfm5GZ.js → psbt-parallel-jZ6QcCnM.js} +3128 -2731
- package/browser/index.d.ts +1 -1
- package/browser/index.d.ts.map +1 -1
- package/browser/index.js +603 -585
- package/browser/io/base58check.d.ts +1 -25
- package/browser/io/base58check.d.ts.map +1 -1
- package/browser/io/base64.d.ts.map +1 -1
- package/browser/networks.d.ts +1 -0
- package/browser/networks.d.ts.map +1 -1
- package/browser/payments/bip341.d.ts +17 -0
- package/browser/payments/bip341.d.ts.map +1 -1
- package/browser/payments/index.d.ts +3 -2
- package/browser/payments/index.d.ts.map +1 -1
- package/browser/payments/p2mr.d.ts +169 -0
- package/browser/payments/p2mr.d.ts.map +1 -0
- package/browser/payments/types.d.ts +11 -1
- package/browser/payments/types.d.ts.map +1 -1
- package/browser/psbt/bip371.d.ts +30 -0
- package/browser/psbt/bip371.d.ts.map +1 -1
- package/browser/psbt/psbtutils.d.ts +1 -0
- package/browser/psbt/psbtutils.d.ts.map +1 -1
- package/browser/psbt.d.ts.map +1 -1
- package/browser/workers/index.js +9 -9
- package/build/address.d.ts +4 -4
- package/build/address.d.ts.map +1 -1
- package/build/address.js +11 -1
- package/build/address.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js.map +1 -1
- package/build/io/base58check.d.ts +1 -25
- package/build/io/base58check.d.ts.map +1 -1
- package/build/io/base58check.js +1 -31
- package/build/io/base58check.js.map +1 -1
- package/build/io/base64.d.ts.map +1 -1
- package/build/io/base64.js +3 -0
- package/build/io/base64.js.map +1 -1
- package/build/networks.d.ts +1 -0
- package/build/networks.d.ts.map +1 -1
- package/build/networks.js +12 -0
- package/build/networks.js.map +1 -1
- package/build/payments/bip341.d.ts +17 -0
- package/build/payments/bip341.d.ts.map +1 -1
- package/build/payments/bip341.js +32 -1
- package/build/payments/bip341.js.map +1 -1
- package/build/payments/index.d.ts +3 -2
- package/build/payments/index.d.ts.map +1 -1
- package/build/payments/index.js +2 -1
- package/build/payments/index.js.map +1 -1
- package/build/payments/p2mr.d.ts +178 -0
- package/build/payments/p2mr.d.ts.map +1 -0
- package/build/payments/p2mr.js +555 -0
- package/build/payments/p2mr.js.map +1 -0
- package/build/payments/types.d.ts +11 -1
- package/build/payments/types.d.ts.map +1 -1
- package/build/payments/types.js +1 -0
- package/build/payments/types.js.map +1 -1
- package/build/psbt/bip371.d.ts +30 -0
- package/build/psbt/bip371.d.ts.map +1 -1
- package/build/psbt/bip371.js +80 -15
- package/build/psbt/bip371.js.map +1 -1
- package/build/psbt/psbtutils.d.ts +1 -0
- package/build/psbt/psbtutils.d.ts.map +1 -1
- package/build/psbt/psbtutils.js +2 -0
- package/build/psbt/psbtutils.js.map +1 -1
- package/build/psbt.d.ts.map +1 -1
- package/build/psbt.js +3 -2
- package/build/psbt.js.map +1 -1
- package/build/pubkey.js +1 -1
- package/build/pubkey.js.map +1 -1
- package/build/tsconfig.build.tsbuildinfo +1 -1
- package/documentation/README.md +122 -0
- package/documentation/address.md +820 -0
- package/documentation/block.md +679 -0
- package/documentation/crypto.md +461 -0
- package/documentation/ecc.md +584 -0
- package/documentation/errors.md +656 -0
- package/documentation/io.md +942 -0
- package/documentation/networks.md +625 -0
- package/documentation/p2mr.md +380 -0
- package/documentation/payments.md +1485 -0
- package/documentation/psbt.md +1400 -0
- package/documentation/script.md +730 -0
- package/documentation/taproot.md +670 -0
- package/documentation/transaction.md +943 -0
- package/documentation/types.md +587 -0
- package/documentation/workers.md +1007 -0
- package/eslint.config.js +3 -0
- package/package.json +17 -14
- package/src/address.ts +22 -10
- package/src/index.ts +1 -0
- package/src/io/base58check.ts +1 -35
- package/src/io/base64.ts +5 -0
- package/src/networks.ts +13 -0
- package/src/payments/bip341.ts +36 -1
- package/src/payments/index.ts +4 -0
- package/src/payments/p2mr.ts +660 -0
- package/src/payments/types.ts +12 -0
- package/src/psbt/bip371.ts +84 -13
- package/src/psbt/psbtutils.ts +2 -0
- package/src/psbt.ts +4 -2
- package/src/pubkey.ts +1 -1
- package/test/bitcoin.core.spec.ts +1 -1
- package/test/fixtures/p2mr.json +270 -0
- package/test/integration/taproot.spec.ts +7 -3
- package/test/opnetTestnet.spec.ts +302 -0
- package/test/payments.spec.ts +3 -1
- package/test/psbt.spec.ts +297 -2
- 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` |
|