ufsecp 3.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/ufsecp.js +468 -0
- package/package.json +27 -0
- package/prebuilds/darwin-arm64/libufsecp.dylib +0 -0
- package/prebuilds/linux-arm64/libufsecp.so +0 -0
- package/prebuilds/linux-arm64/libufsecp.so.3 +0 -0
- package/prebuilds/linux-arm64/libufsecp.so.3.10.0 +0 -0
- package/prebuilds/linux-x64/libufsecp.so +0 -0
- package/prebuilds/linux-x64/libufsecp.so.3 +0 -0
- package/prebuilds/linux-x64/libufsecp.so.3.10.0 +0 -0
- package/prebuilds/win32-x64/ufsecp.dll +0 -0
package/lib/ufsecp.js
ADDED
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UltrafastSecp256k1 — Node.js FFI binding (ufsecp stable C ABI v1).
|
|
3
|
+
*
|
|
4
|
+
* High-performance secp256k1 elliptic curve cryptography with dual-layer
|
|
5
|
+
* constant-time architecture. Context-based API.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* const { Ufsecp } = require('ufsecp');
|
|
9
|
+
* const ctx = new Ufsecp();
|
|
10
|
+
* const pub = ctx.pubkeyCreate(Buffer.alloc(32, 0).fill(1, 31));
|
|
11
|
+
* ctx.destroy();
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
'use strict';
|
|
15
|
+
|
|
16
|
+
const ffi = require('ffi-napi');
|
|
17
|
+
const ref = require('ref-napi');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const os = require('os');
|
|
20
|
+
|
|
21
|
+
const voidPtr = ref.refType(ref.types.void);
|
|
22
|
+
const voidPtrPtr = ref.refType(voidPtr);
|
|
23
|
+
const uint8Ptr = ref.refType(ref.types.uint8);
|
|
24
|
+
const int32Ptr = ref.refType(ref.types.int32);
|
|
25
|
+
const sizeTPtr = ref.refType(ref.types.size_t);
|
|
26
|
+
|
|
27
|
+
// ── Error codes ────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
const UFSECP_OK = 0;
|
|
30
|
+
const UFSECP_ERR_NULL_ARG = 1;
|
|
31
|
+
const UFSECP_ERR_BAD_KEY = 2;
|
|
32
|
+
const UFSECP_ERR_BAD_PUBKEY = 3;
|
|
33
|
+
const UFSECP_ERR_BAD_SIG = 4;
|
|
34
|
+
const UFSECP_ERR_BAD_INPUT = 5;
|
|
35
|
+
const UFSECP_ERR_VERIFY_FAIL = 6;
|
|
36
|
+
const UFSECP_ERR_ARITH = 7;
|
|
37
|
+
const UFSECP_ERR_SELFTEST = 8;
|
|
38
|
+
const UFSECP_ERR_INTERNAL = 9;
|
|
39
|
+
const UFSECP_ERR_BUF_SMALL = 10;
|
|
40
|
+
|
|
41
|
+
const ERROR_NAMES = {
|
|
42
|
+
[UFSECP_ERR_NULL_ARG]: 'null argument',
|
|
43
|
+
[UFSECP_ERR_BAD_KEY]: 'invalid private key',
|
|
44
|
+
[UFSECP_ERR_BAD_PUBKEY]: 'invalid public key',
|
|
45
|
+
[UFSECP_ERR_BAD_SIG]: 'invalid signature',
|
|
46
|
+
[UFSECP_ERR_BAD_INPUT]: 'bad input',
|
|
47
|
+
[UFSECP_ERR_VERIFY_FAIL]: 'verification failed',
|
|
48
|
+
[UFSECP_ERR_ARITH]: 'arithmetic error',
|
|
49
|
+
[UFSECP_ERR_SELFTEST]: 'selftest failed',
|
|
50
|
+
[UFSECP_ERR_INTERNAL]: 'internal error',
|
|
51
|
+
[UFSECP_ERR_BUF_SMALL]: 'buffer too small',
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
class UfsecpError extends Error {
|
|
55
|
+
constructor(op, code) {
|
|
56
|
+
super(`ufsecp ${op} failed: ${ERROR_NAMES[code] || `unknown (${code})`}`);
|
|
57
|
+
this.name = 'UfsecpError';
|
|
58
|
+
this.code = code;
|
|
59
|
+
this.operation = op;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ── Library resolution ─────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
function findLibrary() {
|
|
66
|
+
const plat = os.platform();
|
|
67
|
+
const name = plat === 'win32' ? 'ufsecp.dll'
|
|
68
|
+
: plat === 'darwin' ? 'libufsecp.dylib'
|
|
69
|
+
: 'libufsecp.so';
|
|
70
|
+
|
|
71
|
+
// 1. UFSECP_LIB env var
|
|
72
|
+
const env = process.env.UFSECP_LIB;
|
|
73
|
+
if (env) return env;
|
|
74
|
+
|
|
75
|
+
// 2. Next to this file
|
|
76
|
+
const here = path.join(__dirname, name);
|
|
77
|
+
try { require('fs').accessSync(here); return here; } catch {}
|
|
78
|
+
|
|
79
|
+
// 3. prebuilds/<platform>-<arch>/
|
|
80
|
+
const prebuilt = path.join(__dirname, '..', 'prebuilds',
|
|
81
|
+
`${plat}-${os.arch()}`, name);
|
|
82
|
+
try { require('fs').accessSync(prebuilt); return prebuilt; } catch {}
|
|
83
|
+
|
|
84
|
+
// 4. System default
|
|
85
|
+
return name;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ── FFI binding ────────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
const LIB_SPEC = {
|
|
91
|
+
// Context
|
|
92
|
+
ufsecp_ctx_create: ['int', [voidPtrPtr]],
|
|
93
|
+
ufsecp_ctx_destroy: ['void', [voidPtr]],
|
|
94
|
+
ufsecp_ctx_clone: ['int', [voidPtr, voidPtrPtr]],
|
|
95
|
+
// Version
|
|
96
|
+
ufsecp_version: ['uint32', []],
|
|
97
|
+
ufsecp_abi_version: ['uint32', []],
|
|
98
|
+
ufsecp_version_string:['string', []],
|
|
99
|
+
ufsecp_error_str: ['string', ['int']],
|
|
100
|
+
ufsecp_last_error: ['int', [voidPtr]],
|
|
101
|
+
ufsecp_last_error_msg:['string', [voidPtr]],
|
|
102
|
+
// Key ops
|
|
103
|
+
ufsecp_pubkey_create: ['int', [voidPtr, uint8Ptr, uint8Ptr]],
|
|
104
|
+
ufsecp_pubkey_create_uncompressed:['int', [voidPtr, uint8Ptr, uint8Ptr]],
|
|
105
|
+
ufsecp_pubkey_parse: ['int', [voidPtr, uint8Ptr, 'size_t', uint8Ptr]],
|
|
106
|
+
ufsecp_pubkey_xonly: ['int', [voidPtr, uint8Ptr, uint8Ptr]],
|
|
107
|
+
ufsecp_seckey_verify: ['int', [voidPtr, uint8Ptr]],
|
|
108
|
+
ufsecp_seckey_negate: ['int', [voidPtr, uint8Ptr]],
|
|
109
|
+
ufsecp_seckey_tweak_add: ['int', [voidPtr, uint8Ptr, uint8Ptr]],
|
|
110
|
+
ufsecp_seckey_tweak_mul: ['int', [voidPtr, uint8Ptr, uint8Ptr]],
|
|
111
|
+
// ECDSA
|
|
112
|
+
ufsecp_ecdsa_sign: ['int', [voidPtr, uint8Ptr, uint8Ptr, uint8Ptr]],
|
|
113
|
+
ufsecp_ecdsa_verify: ['int', [voidPtr, uint8Ptr, uint8Ptr, uint8Ptr]],
|
|
114
|
+
ufsecp_ecdsa_sig_to_der: ['int', [voidPtr, uint8Ptr, uint8Ptr, sizeTPtr]],
|
|
115
|
+
ufsecp_ecdsa_sig_from_der: ['int', [voidPtr, uint8Ptr, 'size_t', uint8Ptr]],
|
|
116
|
+
// Recovery
|
|
117
|
+
ufsecp_ecdsa_sign_recoverable: ['int', [voidPtr, uint8Ptr, uint8Ptr, uint8Ptr, int32Ptr]],
|
|
118
|
+
ufsecp_ecdsa_recover: ['int', [voidPtr, uint8Ptr, uint8Ptr, 'int', uint8Ptr]],
|
|
119
|
+
// Schnorr
|
|
120
|
+
ufsecp_schnorr_sign: ['int', [voidPtr, uint8Ptr, uint8Ptr, uint8Ptr, uint8Ptr]],
|
|
121
|
+
ufsecp_schnorr_verify: ['int', [voidPtr, uint8Ptr, uint8Ptr, uint8Ptr]],
|
|
122
|
+
// ECDH
|
|
123
|
+
ufsecp_ecdh: ['int', [voidPtr, uint8Ptr, uint8Ptr, uint8Ptr]],
|
|
124
|
+
ufsecp_ecdh_xonly: ['int', [voidPtr, uint8Ptr, uint8Ptr, uint8Ptr]],
|
|
125
|
+
ufsecp_ecdh_raw: ['int', [voidPtr, uint8Ptr, uint8Ptr, uint8Ptr]],
|
|
126
|
+
// Hashing
|
|
127
|
+
ufsecp_sha256: ['int', [uint8Ptr, 'size_t', uint8Ptr]],
|
|
128
|
+
ufsecp_hash160: ['int', [uint8Ptr, 'size_t', uint8Ptr]],
|
|
129
|
+
ufsecp_tagged_hash: ['int', ['string', uint8Ptr, 'size_t', uint8Ptr]],
|
|
130
|
+
// Addresses
|
|
131
|
+
ufsecp_addr_p2pkh: ['int', [voidPtr, uint8Ptr, 'int', uint8Ptr, sizeTPtr]],
|
|
132
|
+
ufsecp_addr_p2wpkh: ['int', [voidPtr, uint8Ptr, 'int', uint8Ptr, sizeTPtr]],
|
|
133
|
+
ufsecp_addr_p2tr: ['int', [voidPtr, uint8Ptr, 'int', uint8Ptr, sizeTPtr]],
|
|
134
|
+
// WIF
|
|
135
|
+
ufsecp_wif_encode: ['int', [voidPtr, uint8Ptr, 'int', 'int', uint8Ptr, sizeTPtr]],
|
|
136
|
+
ufsecp_wif_decode: ['int', [voidPtr, 'string', uint8Ptr, int32Ptr, int32Ptr]],
|
|
137
|
+
// BIP-32
|
|
138
|
+
ufsecp_bip32_master: ['int', [voidPtr, uint8Ptr, 'size_t', uint8Ptr]],
|
|
139
|
+
ufsecp_bip32_derive: ['int', [voidPtr, uint8Ptr, 'uint32', uint8Ptr]],
|
|
140
|
+
ufsecp_bip32_derive_path: ['int', [voidPtr, uint8Ptr, 'string', uint8Ptr]],
|
|
141
|
+
ufsecp_bip32_privkey: ['int', [voidPtr, uint8Ptr, uint8Ptr]],
|
|
142
|
+
ufsecp_bip32_pubkey: ['int', [voidPtr, uint8Ptr, uint8Ptr]],
|
|
143
|
+
// Taproot
|
|
144
|
+
ufsecp_taproot_output_key: ['int', [voidPtr, uint8Ptr, uint8Ptr, uint8Ptr, int32Ptr]],
|
|
145
|
+
ufsecp_taproot_tweak_seckey: ['int', [voidPtr, uint8Ptr, uint8Ptr, uint8Ptr]],
|
|
146
|
+
ufsecp_taproot_verify: ['int', [voidPtr, uint8Ptr, 'int', uint8Ptr, uint8Ptr, 'size_t']],
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// ── Constants ──────────────────────────────────────────────────────────
|
|
150
|
+
|
|
151
|
+
const NET_MAINNET = 0;
|
|
152
|
+
const NET_TESTNET = 1;
|
|
153
|
+
|
|
154
|
+
// ── Ufsecp class ───────────────────────────────────────────────────────
|
|
155
|
+
|
|
156
|
+
class Ufsecp {
|
|
157
|
+
/**
|
|
158
|
+
* @param {string} [libPath] Path to the ufsecp shared library.
|
|
159
|
+
*/
|
|
160
|
+
constructor(libPath) {
|
|
161
|
+
this._lib = ffi.Library(libPath || findLibrary(), LIB_SPEC);
|
|
162
|
+
const pp = ref.alloc(voidPtr);
|
|
163
|
+
const rc = this._lib.ufsecp_ctx_create(pp);
|
|
164
|
+
if (rc !== UFSECP_OK) throw new UfsecpError('ctx_create', rc);
|
|
165
|
+
this._ctx = pp.deref();
|
|
166
|
+
this._destroyed = false;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Explicitly destroy the context. Safe to call multiple times. */
|
|
170
|
+
destroy() {
|
|
171
|
+
if (!this._destroyed && this._ctx) {
|
|
172
|
+
this._lib.ufsecp_ctx_destroy(this._ctx);
|
|
173
|
+
this._ctx = null;
|
|
174
|
+
this._destroyed = true;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ── Version ──────────────────────────────────────────────────────────
|
|
179
|
+
|
|
180
|
+
version() { return this._lib.ufsecp_version(); }
|
|
181
|
+
abiVersion() { return this._lib.ufsecp_abi_version(); }
|
|
182
|
+
versionString() { return this._lib.ufsecp_version_string(); }
|
|
183
|
+
lastError() { this._alive(); return this._lib.ufsecp_last_error(this._ctx); }
|
|
184
|
+
lastErrorMsg() { this._alive(); return this._lib.ufsecp_last_error_msg(this._ctx); }
|
|
185
|
+
|
|
186
|
+
// ── Key operations ────────────────────────────────────────────────────
|
|
187
|
+
|
|
188
|
+
/** Compressed public key (33 bytes). */
|
|
189
|
+
pubkeyCreate(privkey) {
|
|
190
|
+
_chk(privkey, 32, 'privkey'); this._alive();
|
|
191
|
+
const out = Buffer.alloc(33);
|
|
192
|
+
this._throw(this._lib.ufsecp_pubkey_create(this._ctx, privkey, out), 'pubkey_create');
|
|
193
|
+
return out;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Uncompressed public key (65 bytes). */
|
|
197
|
+
pubkeyCreateUncompressed(privkey) {
|
|
198
|
+
_chk(privkey, 32, 'privkey'); this._alive();
|
|
199
|
+
const out = Buffer.alloc(65);
|
|
200
|
+
this._throw(this._lib.ufsecp_pubkey_create_uncompressed(this._ctx, privkey, out), 'pubkey_create_uncompressed');
|
|
201
|
+
return out;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Parse compressed/uncompressed → compressed 33 bytes. */
|
|
205
|
+
pubkeyParse(pubkey) {
|
|
206
|
+
this._alive();
|
|
207
|
+
const out = Buffer.alloc(33);
|
|
208
|
+
this._throw(this._lib.ufsecp_pubkey_parse(this._ctx, pubkey, pubkey.length, out), 'pubkey_parse');
|
|
209
|
+
return out;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** X-only (32 bytes, BIP-340) from private key. */
|
|
213
|
+
pubkeyXonly(privkey) {
|
|
214
|
+
_chk(privkey, 32, 'privkey'); this._alive();
|
|
215
|
+
const out = Buffer.alloc(32);
|
|
216
|
+
this._throw(this._lib.ufsecp_pubkey_xonly(this._ctx, privkey, out), 'pubkey_xonly');
|
|
217
|
+
return out;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
seckeyVerify(privkey) { _chk(privkey, 32, 'privkey'); this._alive(); return this._lib.ufsecp_seckey_verify(this._ctx, privkey) === UFSECP_OK; }
|
|
221
|
+
|
|
222
|
+
seckeyNegate(privkey) {
|
|
223
|
+
_chk(privkey, 32, 'privkey'); this._alive();
|
|
224
|
+
const buf = Buffer.from(privkey);
|
|
225
|
+
this._throw(this._lib.ufsecp_seckey_negate(this._ctx, buf), 'seckey_negate');
|
|
226
|
+
return buf;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
seckeyTweakAdd(privkey, tweak) {
|
|
230
|
+
_chk(privkey, 32, 'privkey'); _chk(tweak, 32, 'tweak'); this._alive();
|
|
231
|
+
const buf = Buffer.from(privkey);
|
|
232
|
+
this._throw(this._lib.ufsecp_seckey_tweak_add(this._ctx, buf, tweak), 'seckey_tweak_add');
|
|
233
|
+
return buf;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
seckeyTweakMul(privkey, tweak) {
|
|
237
|
+
_chk(privkey, 32, 'privkey'); _chk(tweak, 32, 'tweak'); this._alive();
|
|
238
|
+
const buf = Buffer.from(privkey);
|
|
239
|
+
this._throw(this._lib.ufsecp_seckey_tweak_mul(this._ctx, buf, tweak), 'seckey_tweak_mul');
|
|
240
|
+
return buf;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ── ECDSA ─────────────────────────────────────────────────────────────
|
|
244
|
+
|
|
245
|
+
ecdsaSign(msgHash, privkey) {
|
|
246
|
+
_chk(msgHash, 32, 'msgHash'); _chk(privkey, 32, 'privkey'); this._alive();
|
|
247
|
+
const sig = Buffer.alloc(64);
|
|
248
|
+
this._throw(this._lib.ufsecp_ecdsa_sign(this._ctx, msgHash, privkey, sig), 'ecdsa_sign');
|
|
249
|
+
return sig;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
ecdsaVerify(msgHash, sig, pubkey) {
|
|
253
|
+
_chk(msgHash, 32, 'msgHash'); _chk(sig, 64, 'sig'); _chk(pubkey, 33, 'pubkey'); this._alive();
|
|
254
|
+
return this._lib.ufsecp_ecdsa_verify(this._ctx, msgHash, sig, pubkey) === UFSECP_OK;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
ecdsaSigToDer(sig) {
|
|
258
|
+
_chk(sig, 64, 'sig'); this._alive();
|
|
259
|
+
const der = Buffer.alloc(72);
|
|
260
|
+
const len = ref.alloc('size_t', 72);
|
|
261
|
+
this._throw(this._lib.ufsecp_ecdsa_sig_to_der(this._ctx, sig, der, len), 'ecdsa_sig_to_der');
|
|
262
|
+
return der.subarray(0, len.deref());
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
ecdsaSigFromDer(der) {
|
|
266
|
+
this._alive();
|
|
267
|
+
const sig = Buffer.alloc(64);
|
|
268
|
+
this._throw(this._lib.ufsecp_ecdsa_sig_from_der(this._ctx, der, der.length, sig), 'ecdsa_sig_from_der');
|
|
269
|
+
return sig;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ── Recovery ──────────────────────────────────────────────────────────
|
|
273
|
+
|
|
274
|
+
ecdsaSignRecoverable(msgHash, privkey) {
|
|
275
|
+
_chk(msgHash, 32, 'msgHash'); _chk(privkey, 32, 'privkey'); this._alive();
|
|
276
|
+
const sig = Buffer.alloc(64);
|
|
277
|
+
const recid = ref.alloc('int');
|
|
278
|
+
this._throw(this._lib.ufsecp_ecdsa_sign_recoverable(this._ctx, msgHash, privkey, sig, recid), 'ecdsa_sign_recoverable');
|
|
279
|
+
return { signature: sig, recoveryId: recid.deref() };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
ecdsaRecover(msgHash, sig, recid) {
|
|
283
|
+
_chk(msgHash, 32, 'msgHash'); _chk(sig, 64, 'sig'); this._alive();
|
|
284
|
+
const pub = Buffer.alloc(33);
|
|
285
|
+
this._throw(this._lib.ufsecp_ecdsa_recover(this._ctx, msgHash, sig, recid, pub), 'ecdsa_recover');
|
|
286
|
+
return pub;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ── Schnorr ───────────────────────────────────────────────────────────
|
|
290
|
+
|
|
291
|
+
schnorrSign(msg, privkey, auxRand) {
|
|
292
|
+
_chk(msg, 32, 'msg'); _chk(privkey, 32, 'privkey'); _chk(auxRand, 32, 'auxRand'); this._alive();
|
|
293
|
+
const sig = Buffer.alloc(64);
|
|
294
|
+
this._throw(this._lib.ufsecp_schnorr_sign(this._ctx, msg, privkey, auxRand, sig), 'schnorr_sign');
|
|
295
|
+
return sig;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
schnorrVerify(msg, sig, pubkeyX) {
|
|
299
|
+
_chk(msg, 32, 'msg'); _chk(sig, 64, 'sig'); _chk(pubkeyX, 32, 'pubkeyX'); this._alive();
|
|
300
|
+
return this._lib.ufsecp_schnorr_verify(this._ctx, msg, sig, pubkeyX) === UFSECP_OK;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// ── ECDH ──────────────────────────────────────────────────────────────
|
|
304
|
+
|
|
305
|
+
ecdh(privkey, pubkey) {
|
|
306
|
+
_chk(privkey, 32, 'privkey'); _chk(pubkey, 33, 'pubkey'); this._alive();
|
|
307
|
+
const out = Buffer.alloc(32);
|
|
308
|
+
this._throw(this._lib.ufsecp_ecdh(this._ctx, privkey, pubkey, out), 'ecdh');
|
|
309
|
+
return out;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
ecdhXonly(privkey, pubkey) {
|
|
313
|
+
_chk(privkey, 32, 'privkey'); _chk(pubkey, 33, 'pubkey'); this._alive();
|
|
314
|
+
const out = Buffer.alloc(32);
|
|
315
|
+
this._throw(this._lib.ufsecp_ecdh_xonly(this._ctx, privkey, pubkey, out), 'ecdh_xonly');
|
|
316
|
+
return out;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
ecdhRaw(privkey, pubkey) {
|
|
320
|
+
_chk(privkey, 32, 'privkey'); _chk(pubkey, 33, 'pubkey'); this._alive();
|
|
321
|
+
const out = Buffer.alloc(32);
|
|
322
|
+
this._throw(this._lib.ufsecp_ecdh_raw(this._ctx, privkey, pubkey, out), 'ecdh_raw');
|
|
323
|
+
return out;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// ── Hashing ───────────────────────────────────────────────────────────
|
|
327
|
+
|
|
328
|
+
sha256(data) {
|
|
329
|
+
const out = Buffer.alloc(32);
|
|
330
|
+
this._throw(this._lib.ufsecp_sha256(data, data.length, out), 'sha256');
|
|
331
|
+
return out;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
hash160(data) {
|
|
335
|
+
const out = Buffer.alloc(20);
|
|
336
|
+
this._throw(this._lib.ufsecp_hash160(data, data.length, out), 'hash160');
|
|
337
|
+
return out;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
taggedHash(tag, data) {
|
|
341
|
+
const out = Buffer.alloc(32);
|
|
342
|
+
this._throw(this._lib.ufsecp_tagged_hash(tag, data, data.length, out), 'tagged_hash');
|
|
343
|
+
return out;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ── Addresses ─────────────────────────────────────────────────────────
|
|
347
|
+
|
|
348
|
+
addrP2PKH(pubkey, network = NET_MAINNET) {
|
|
349
|
+
_chk(pubkey, 33, 'pubkey'); return this._getAddr('ufsecp_addr_p2pkh', pubkey, network);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
addrP2WPKH(pubkey, network = NET_MAINNET) {
|
|
353
|
+
_chk(pubkey, 33, 'pubkey'); return this._getAddr('ufsecp_addr_p2wpkh', pubkey, network);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
addrP2TR(xonlyKey, network = NET_MAINNET) {
|
|
357
|
+
_chk(xonlyKey, 32, 'xonlyKey'); return this._getAddr('ufsecp_addr_p2tr', xonlyKey, network);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ── WIF ───────────────────────────────────────────────────────────────
|
|
361
|
+
|
|
362
|
+
wifEncode(privkey, compressed = true, network = NET_MAINNET) {
|
|
363
|
+
_chk(privkey, 32, 'privkey'); this._alive();
|
|
364
|
+
const buf = Buffer.alloc(128);
|
|
365
|
+
const len = ref.alloc('size_t', 128);
|
|
366
|
+
this._throw(this._lib.ufsecp_wif_encode(this._ctx, privkey, compressed ? 1 : 0, network, buf, len), 'wif_encode');
|
|
367
|
+
return buf.toString('utf8', 0, len.deref());
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
wifDecode(wif) {
|
|
371
|
+
this._alive();
|
|
372
|
+
const key = Buffer.alloc(32);
|
|
373
|
+
const comp = ref.alloc('int');
|
|
374
|
+
const net = ref.alloc('int');
|
|
375
|
+
this._throw(this._lib.ufsecp_wif_decode(this._ctx, wif, key, comp, net), 'wif_decode');
|
|
376
|
+
return { privkey: key, compressed: comp.deref() === 1, network: net.deref() };
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// ── BIP-32 ────────────────────────────────────────────────────────────
|
|
380
|
+
|
|
381
|
+
bip32Master(seed) {
|
|
382
|
+
this._alive();
|
|
383
|
+
if (seed.length < 16 || seed.length > 64) throw new RangeError('Seed must be 16-64 bytes');
|
|
384
|
+
const key = Buffer.alloc(82);
|
|
385
|
+
this._throw(this._lib.ufsecp_bip32_master(this._ctx, seed, seed.length, key), 'bip32_master');
|
|
386
|
+
return key;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
bip32Derive(parent, index) {
|
|
390
|
+
_chk(parent, 82, 'parent'); this._alive();
|
|
391
|
+
const child = Buffer.alloc(82);
|
|
392
|
+
this._throw(this._lib.ufsecp_bip32_derive(this._ctx, parent, index >>> 0, child), 'bip32_derive');
|
|
393
|
+
return child;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
bip32DerivePath(master, path) {
|
|
397
|
+
_chk(master, 82, 'master'); this._alive();
|
|
398
|
+
const key = Buffer.alloc(82);
|
|
399
|
+
this._throw(this._lib.ufsecp_bip32_derive_path(this._ctx, master, path, key), 'bip32_derive_path');
|
|
400
|
+
return key;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
bip32Privkey(key) {
|
|
404
|
+
_chk(key, 82, 'key'); this._alive();
|
|
405
|
+
const priv = Buffer.alloc(32);
|
|
406
|
+
this._throw(this._lib.ufsecp_bip32_privkey(this._ctx, key, priv), 'bip32_privkey');
|
|
407
|
+
return priv;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
bip32Pubkey(key) {
|
|
411
|
+
_chk(key, 82, 'key'); this._alive();
|
|
412
|
+
const pub = Buffer.alloc(33);
|
|
413
|
+
this._throw(this._lib.ufsecp_bip32_pubkey(this._ctx, key, pub), 'bip32_pubkey');
|
|
414
|
+
return pub;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// ── Taproot ───────────────────────────────────────────────────────────
|
|
418
|
+
|
|
419
|
+
taprootOutputKey(internalKeyX, merkleRoot = null) {
|
|
420
|
+
_chk(internalKeyX, 32, 'internalKeyX'); this._alive();
|
|
421
|
+
const out = Buffer.alloc(32);
|
|
422
|
+
const parity = ref.alloc('int');
|
|
423
|
+
this._throw(this._lib.ufsecp_taproot_output_key(this._ctx, internalKeyX, merkleRoot, out, parity), 'taproot_output_key');
|
|
424
|
+
return { outputKeyX: out, parity: parity.deref() };
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
taprootTweakSeckey(privkey, merkleRoot = null) {
|
|
428
|
+
_chk(privkey, 32, 'privkey'); this._alive();
|
|
429
|
+
const out = Buffer.alloc(32);
|
|
430
|
+
this._throw(this._lib.ufsecp_taproot_tweak_seckey(this._ctx, privkey, merkleRoot, out), 'taproot_tweak_seckey');
|
|
431
|
+
return out;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
taprootVerify(outputKeyX, parity, internalKeyX, merkleRoot = null) {
|
|
435
|
+
_chk(outputKeyX, 32, 'outputKeyX'); _chk(internalKeyX, 32, 'internalKeyX'); this._alive();
|
|
436
|
+
const mrLen = merkleRoot ? merkleRoot.length : 0;
|
|
437
|
+
return this._lib.ufsecp_taproot_verify(this._ctx, outputKeyX, parity, internalKeyX, merkleRoot, mrLen) === UFSECP_OK;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// ── Internal ──────────────────────────────────────────────────────────
|
|
441
|
+
|
|
442
|
+
_alive() {
|
|
443
|
+
if (this._destroyed) throw new Error('UfsecpContext already destroyed');
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
_throw(rc, op) {
|
|
447
|
+
if (rc !== UFSECP_OK) throw new UfsecpError(op, rc);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
_getAddr(fnName, key, network) {
|
|
451
|
+
this._alive();
|
|
452
|
+
const buf = Buffer.alloc(128);
|
|
453
|
+
const len = ref.alloc('size_t', 128);
|
|
454
|
+
this._throw(this._lib[fnName](this._ctx, key, network, buf, len), 'address');
|
|
455
|
+
return buf.toString('utf8', 0, len.deref());
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function _chk(buf, expected, name) {
|
|
460
|
+
if (!Buffer.isBuffer(buf) && !(buf instanceof Uint8Array)) {
|
|
461
|
+
throw new TypeError(`${name} must be a Buffer`);
|
|
462
|
+
}
|
|
463
|
+
if (buf.length !== expected) {
|
|
464
|
+
throw new RangeError(`${name} must be ${expected} bytes, got ${buf.length}`);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
module.exports = { Ufsecp, UfsecpError, NET_MAINNET, NET_TESTNET };
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ufsecp",
|
|
3
|
+
"version": "3.10.0",
|
|
4
|
+
"description": "Node.js bindings for UltrafastSecp256k1 — high-performance secp256k1 ECC (ufsecp C ABI v1)",
|
|
5
|
+
"main": "lib/ufsecp.js",
|
|
6
|
+
"files": ["lib/ufsecp.js", "prebuilds/", "README.md"],
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "node test/test.js"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"ffi-napi": "^4.0.3",
|
|
12
|
+
"ref-napi": "^3.0.3"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"secp256k1", "ecdsa", "schnorr", "bitcoin", "cryptography",
|
|
16
|
+
"elliptic-curve", "taproot", "bip32", "ecdh", "ufsecp"
|
|
17
|
+
],
|
|
18
|
+
"license": "AGPL-3.0-only",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/AvraSasmo/UltrafastSecp256k1"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=16.0.0"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://github.com/AvraSasmo/UltrafastSecp256k1"
|
|
27
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|