@bcts/crypto 1.0.0-alpha.8 → 1.0.0-beta.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/LICENSE +3 -2
- package/dist/chunk-DQk6qfdC.mjs +18 -0
- package/dist/index.cjs +263 -149
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +134 -73
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +134 -73
- package/dist/index.d.mts.map +1 -1
- package/dist/index.iife.js +255 -141
- package/dist/index.iife.js.map +1 -1
- package/dist/index.mjs +223 -124
- package/dist/index.mjs.map +1 -1
- package/package.json +18 -19
- package/src/argon.ts +14 -9
- package/src/ecdsa-keys.ts +11 -5
- package/src/ecdsa-signing.ts +11 -6
- package/src/ed25519-signing.ts +7 -1
- package/src/error.ts +15 -0
- package/src/hash.ts +10 -4
- package/src/index.ts +24 -18
- package/src/memzero.ts +38 -12
- package/src/public-key-encryption.ts +31 -7
- package/src/schnorr-signing.ts +7 -1
- package/src/scrypt.ts +12 -3
- package/src/symmetric-encryption.ts +7 -1
package/dist/index.cjs
CHANGED
|
@@ -1,86 +1,55 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
let
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
//#region src/error.ts
|
|
13
|
-
/**
|
|
14
|
-
* AEAD-specific error for authentication failures
|
|
15
|
-
*/
|
|
16
|
-
var AeadError = class extends Error {
|
|
17
|
-
constructor(message = "AEAD authentication failed") {
|
|
18
|
-
super(message);
|
|
19
|
-
this.name = "AeadError";
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
/**
|
|
23
|
-
* Generic crypto error type
|
|
24
|
-
*/
|
|
25
|
-
var CryptoError = class CryptoError extends Error {
|
|
26
|
-
cause;
|
|
27
|
-
constructor(message, cause) {
|
|
28
|
-
super(message);
|
|
29
|
-
this.name = "CryptoError";
|
|
30
|
-
this.cause = cause;
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __exportAll = (all, no_symbols) => {
|
|
5
|
+
let target = {};
|
|
6
|
+
for (var name in all) {
|
|
7
|
+
__defProp(target, name, {
|
|
8
|
+
get: all[name],
|
|
9
|
+
enumerable: true
|
|
10
|
+
});
|
|
31
11
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
*
|
|
35
|
-
* @param error - Optional underlying AeadError
|
|
36
|
-
* @returns A CryptoError wrapping the AEAD error
|
|
37
|
-
*/
|
|
38
|
-
static aead(error) {
|
|
39
|
-
return new CryptoError("AEAD error", error ?? new AeadError());
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Create a CryptoError for invalid parameter values.
|
|
43
|
-
*
|
|
44
|
-
* @param message - Description of the invalid parameter
|
|
45
|
-
* @returns A CryptoError describing the invalid parameter
|
|
46
|
-
*/
|
|
47
|
-
static invalidParameter(message) {
|
|
48
|
-
return new CryptoError(`Invalid parameter: ${message}`);
|
|
12
|
+
if (!no_symbols) {
|
|
13
|
+
__defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
49
14
|
}
|
|
15
|
+
return target;
|
|
50
16
|
};
|
|
51
17
|
|
|
52
18
|
//#endregion
|
|
53
|
-
|
|
19
|
+
let _noble_hashes_sha2_js = require("@noble/hashes/sha2.js");
|
|
20
|
+
let _noble_hashes_hmac_js = require("@noble/hashes/hmac.js");
|
|
21
|
+
let _noble_hashes_pbkdf2_js = require("@noble/hashes/pbkdf2.js");
|
|
22
|
+
let _noble_hashes_hkdf_js = require("@noble/hashes/hkdf.js");
|
|
23
|
+
let _noble_ciphers_chacha_js = require("@noble/ciphers/chacha.js");
|
|
24
|
+
let _noble_curves_ed25519_js = require("@noble/curves/ed25519.js");
|
|
25
|
+
let _noble_curves_secp256k1_js = require("@noble/curves/secp256k1.js");
|
|
26
|
+
let _bcts_rand = require("@bcts/rand");
|
|
27
|
+
let _noble_hashes_scrypt_js = require("@noble/hashes/scrypt.js");
|
|
28
|
+
let _noble_hashes_argon2_js = require("@noble/hashes/argon2.js");
|
|
29
|
+
|
|
30
|
+
//#region src/hash.ts
|
|
54
31
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* **IMPORTANT: This is a best-effort implementation.** Unlike the Rust reference
|
|
58
|
-
* implementation which uses `std::ptr::write_volatile()` for guaranteed volatile
|
|
59
|
-
* writes, JavaScript engines and JIT compilers can still potentially optimize
|
|
60
|
-
* away these zeroing operations. The check at the end helps prevent optimization,
|
|
61
|
-
* but it is not foolproof.
|
|
62
|
-
*
|
|
63
|
-
* For truly sensitive cryptographic operations, consider using the Web Crypto API's
|
|
64
|
-
* `crypto.subtle` with non-extractable keys when possible, as it provides stronger
|
|
65
|
-
* guarantees than what can be achieved with pure JavaScript.
|
|
32
|
+
* Copyright © 2023-2026 Blockchain Commons, LLC
|
|
33
|
+
* Copyright © 2025-2026 Parity Technologies
|
|
66
34
|
*
|
|
67
|
-
* This function attempts to prevent the compiler from optimizing away
|
|
68
|
-
* the zeroing operation by using a verification check after the zeroing loop.
|
|
69
35
|
*/
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
36
|
+
var hash_exports = /* @__PURE__ */ __exportAll({
|
|
37
|
+
CRC32_SIZE: () => 4,
|
|
38
|
+
SHA256_SIZE: () => 32,
|
|
39
|
+
SHA512_SIZE: () => 64,
|
|
40
|
+
crc32: () => crc32,
|
|
41
|
+
crc32Data: () => crc32Data,
|
|
42
|
+
crc32DataOpt: () => crc32DataOpt,
|
|
43
|
+
doubleSha256: () => doubleSha256,
|
|
44
|
+
hkdfHmacSha256: () => hkdfHmacSha256,
|
|
45
|
+
hkdfHmacSha512: () => hkdfHmacSha512,
|
|
46
|
+
hmacSha256: () => hmacSha256,
|
|
47
|
+
hmacSha512: () => hmacSha512,
|
|
48
|
+
pbkdf2HmacSha256: () => pbkdf2HmacSha256,
|
|
49
|
+
pbkdf2HmacSha512: () => pbkdf2HmacSha512,
|
|
50
|
+
sha256: () => sha256,
|
|
51
|
+
sha512: () => sha512
|
|
52
|
+
});
|
|
84
53
|
const CRC32_SIZE = 4;
|
|
85
54
|
const SHA256_SIZE = 32;
|
|
86
55
|
const SHA512_SIZE = 64;
|
|
@@ -119,7 +88,7 @@ function crc32DataOpt(data, littleEndian) {
|
|
|
119
88
|
* Calculate SHA-256 hash
|
|
120
89
|
*/
|
|
121
90
|
function sha256(data) {
|
|
122
|
-
return (0,
|
|
91
|
+
return (0, _noble_hashes_sha2_js.sha256)(data);
|
|
123
92
|
}
|
|
124
93
|
/**
|
|
125
94
|
* Calculate double SHA-256 hash (SHA-256 of SHA-256)
|
|
@@ -132,25 +101,25 @@ function doubleSha256(message) {
|
|
|
132
101
|
* Calculate SHA-512 hash
|
|
133
102
|
*/
|
|
134
103
|
function sha512(data) {
|
|
135
|
-
return (0,
|
|
104
|
+
return (0, _noble_hashes_sha2_js.sha512)(data);
|
|
136
105
|
}
|
|
137
106
|
/**
|
|
138
107
|
* Calculate HMAC-SHA-256
|
|
139
108
|
*/
|
|
140
109
|
function hmacSha256(key, message) {
|
|
141
|
-
return (0,
|
|
110
|
+
return (0, _noble_hashes_hmac_js.hmac)(_noble_hashes_sha2_js.sha256, key, message);
|
|
142
111
|
}
|
|
143
112
|
/**
|
|
144
113
|
* Calculate HMAC-SHA-512
|
|
145
114
|
*/
|
|
146
115
|
function hmacSha512(key, message) {
|
|
147
|
-
return (0,
|
|
116
|
+
return (0, _noble_hashes_hmac_js.hmac)(_noble_hashes_sha2_js.sha512, key, message);
|
|
148
117
|
}
|
|
149
118
|
/**
|
|
150
119
|
* Derive a key using PBKDF2 with HMAC-SHA-256
|
|
151
120
|
*/
|
|
152
121
|
function pbkdf2HmacSha256(password, salt, iterations, keyLen) {
|
|
153
|
-
return (0,
|
|
122
|
+
return (0, _noble_hashes_pbkdf2_js.pbkdf2)(_noble_hashes_sha2_js.sha256, password, salt, {
|
|
154
123
|
c: iterations,
|
|
155
124
|
dkLen: keyLen
|
|
156
125
|
});
|
|
@@ -159,7 +128,7 @@ function pbkdf2HmacSha256(password, salt, iterations, keyLen) {
|
|
|
159
128
|
* Derive a key using PBKDF2 with HMAC-SHA-512
|
|
160
129
|
*/
|
|
161
130
|
function pbkdf2HmacSha512(password, salt, iterations, keyLen) {
|
|
162
|
-
return (0,
|
|
131
|
+
return (0, _noble_hashes_pbkdf2_js.pbkdf2)(_noble_hashes_sha2_js.sha512, password, salt, {
|
|
163
132
|
c: iterations,
|
|
164
133
|
dkLen: keyLen
|
|
165
134
|
});
|
|
@@ -168,17 +137,112 @@ function pbkdf2HmacSha512(password, salt, iterations, keyLen) {
|
|
|
168
137
|
* Derive a key using HKDF with HMAC-SHA-256
|
|
169
138
|
*/
|
|
170
139
|
function hkdfHmacSha256(keyMaterial, salt, keyLen) {
|
|
171
|
-
return (0,
|
|
140
|
+
return (0, _noble_hashes_hkdf_js.hkdf)(_noble_hashes_sha2_js.sha256, keyMaterial, salt, void 0, keyLen);
|
|
172
141
|
}
|
|
173
142
|
/**
|
|
174
143
|
* Derive a key using HKDF with HMAC-SHA-512
|
|
175
144
|
*/
|
|
176
145
|
function hkdfHmacSha512(keyMaterial, salt, keyLen) {
|
|
177
|
-
return (0,
|
|
146
|
+
return (0, _noble_hashes_hkdf_js.hkdf)(_noble_hashes_sha2_js.sha512, keyMaterial, salt, void 0, keyLen);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
//#endregion
|
|
150
|
+
//#region src/error.ts
|
|
151
|
+
/**
|
|
152
|
+
* Copyright © 2023-2026 Blockchain Commons, LLC
|
|
153
|
+
* Copyright © 2025-2026 Parity Technologies
|
|
154
|
+
*
|
|
155
|
+
*/
|
|
156
|
+
/**
|
|
157
|
+
* AEAD-specific error for authentication failures
|
|
158
|
+
*/
|
|
159
|
+
var AeadError = class extends Error {
|
|
160
|
+
constructor(message = "AEAD authentication failed") {
|
|
161
|
+
super(message);
|
|
162
|
+
this.name = "AeadError";
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
/**
|
|
166
|
+
* Generic crypto error type
|
|
167
|
+
*/
|
|
168
|
+
var CryptoError = class CryptoError extends Error {
|
|
169
|
+
cause;
|
|
170
|
+
constructor(message, cause) {
|
|
171
|
+
super(message);
|
|
172
|
+
this.name = "CryptoError";
|
|
173
|
+
this.cause = cause;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Create a CryptoError for AEAD authentication failures.
|
|
177
|
+
*
|
|
178
|
+
* @param error - Optional underlying AeadError
|
|
179
|
+
* @returns A CryptoError wrapping the AEAD error
|
|
180
|
+
*/
|
|
181
|
+
static aead(error) {
|
|
182
|
+
return new CryptoError("AEAD error", error ?? new AeadError());
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Create a CryptoError for invalid parameter values.
|
|
186
|
+
*
|
|
187
|
+
* **TS-specific.** Rust's `bc_crypto::Error` enum has no
|
|
188
|
+
* `InvalidParameter` variant; size validation in Rust is enforced at
|
|
189
|
+
* compile time via fixed-size array references (e.g. `&[u8; 32]`) or
|
|
190
|
+
* via `panic!`/`expect(...)` for runtime checks. The TS port has no
|
|
191
|
+
* fixed-size array types, so it surfaces those same conditions through
|
|
192
|
+
* a thrown `CryptoError.invalidParameter(...)`. Catching this is
|
|
193
|
+
* equivalent to defensive guards around an `expect`-style panic on the
|
|
194
|
+
* Rust side.
|
|
195
|
+
*
|
|
196
|
+
* @param message - Description of the invalid parameter
|
|
197
|
+
* @returns A CryptoError describing the invalid parameter
|
|
198
|
+
*/
|
|
199
|
+
static invalidParameter(message) {
|
|
200
|
+
return new CryptoError(`Invalid parameter: ${message}`);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
//#endregion
|
|
205
|
+
//#region src/memzero.ts
|
|
206
|
+
/**
|
|
207
|
+
* Securely zero out a typed array.
|
|
208
|
+
*
|
|
209
|
+
* Mirrors Rust `bc_crypto::memzero<T>(s: &mut [T])`. The Rust impl uses
|
|
210
|
+
* `std::ptr::write_volatile()` to guarantee the writes survive optimization;
|
|
211
|
+
* JavaScript has no equivalent primitive, so this is **best-effort** — JIT
|
|
212
|
+
* compilers may still elide the loop, though the post-hoc verification
|
|
213
|
+
* check forces the engine to keep the writes observable.
|
|
214
|
+
*
|
|
215
|
+
* For truly sensitive cryptographic operations, consider using the Web
|
|
216
|
+
* Crypto API's `crypto.subtle` with non-extractable keys when possible, as
|
|
217
|
+
* it provides stronger guarantees than what can be achieved with pure
|
|
218
|
+
* JavaScript.
|
|
219
|
+
*
|
|
220
|
+
* Accepts any of the standard numeric typed arrays — `Uint8Array`,
|
|
221
|
+
* `Uint8ClampedArray`, `Uint16Array`, `Uint32Array`, `Int8Array`,
|
|
222
|
+
* `Int16Array`, `Int32Array`, `Float32Array`, `Float64Array` — matching
|
|
223
|
+
* Rust's generic `&mut [T]`. (`BigInt64Array` / `BigUint64Array` are
|
|
224
|
+
* excluded because their elements are `bigint`, not `number`; if that
|
|
225
|
+
* support is needed, add a dedicated overload.)
|
|
226
|
+
*/
|
|
227
|
+
function memzero(data) {
|
|
228
|
+
const len = data.length;
|
|
229
|
+
for (let i = 0; i < len; i++) data[i] = 0;
|
|
230
|
+
if (data.length > 0 && data[0] !== 0) throw new Error("memzero failed");
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Securely zero out an array of Uint8Arrays.
|
|
234
|
+
*/
|
|
235
|
+
function memzeroVecVecU8(arrays) {
|
|
236
|
+
for (const arr of arrays) memzero(arr);
|
|
178
237
|
}
|
|
179
238
|
|
|
180
239
|
//#endregion
|
|
181
240
|
//#region src/symmetric-encryption.ts
|
|
241
|
+
/**
|
|
242
|
+
* Copyright © 2023-2026 Blockchain Commons, LLC
|
|
243
|
+
* Copyright © 2025-2026 Parity Technologies
|
|
244
|
+
*
|
|
245
|
+
*/
|
|
182
246
|
const SYMMETRIC_KEY_SIZE = 32;
|
|
183
247
|
const SYMMETRIC_NONCE_SIZE = 12;
|
|
184
248
|
const SYMMETRIC_AUTH_SIZE = 16;
|
|
@@ -213,10 +277,10 @@ function aeadChaCha20Poly1305Encrypt(plaintext, key, nonce) {
|
|
|
213
277
|
* @throws {CryptoError} If key is not 32 bytes or nonce is not 12 bytes
|
|
214
278
|
*/
|
|
215
279
|
function aeadChaCha20Poly1305EncryptWithAad(plaintext, key, nonce, aad) {
|
|
216
|
-
if (key.length !==
|
|
217
|
-
if (nonce.length !==
|
|
218
|
-
const sealed = (0,
|
|
219
|
-
return [sealed.slice(0, sealed.length -
|
|
280
|
+
if (key.length !== 32) throw CryptoError.invalidParameter(`Key must be ${32} bytes`);
|
|
281
|
+
if (nonce.length !== 12) throw CryptoError.invalidParameter(`Nonce must be ${12} bytes`);
|
|
282
|
+
const sealed = (0, _noble_ciphers_chacha_js.chacha20poly1305)(key, nonce, aad).encrypt(plaintext);
|
|
283
|
+
return [sealed.slice(0, sealed.length - 16), sealed.slice(sealed.length - 16)];
|
|
220
284
|
}
|
|
221
285
|
/**
|
|
222
286
|
* Decrypt data using ChaCha20-Poly1305 AEAD cipher.
|
|
@@ -245,14 +309,14 @@ function aeadChaCha20Poly1305Decrypt(ciphertext, key, nonce, authTag) {
|
|
|
245
309
|
* @throws {CryptoError} If authentication fails (tampered data, wrong key/nonce, or AAD mismatch)
|
|
246
310
|
*/
|
|
247
311
|
function aeadChaCha20Poly1305DecryptWithAad(ciphertext, key, nonce, aad, authTag) {
|
|
248
|
-
if (key.length !==
|
|
249
|
-
if (nonce.length !==
|
|
250
|
-
if (authTag.length !==
|
|
312
|
+
if (key.length !== 32) throw CryptoError.invalidParameter(`Key must be ${32} bytes`);
|
|
313
|
+
if (nonce.length !== 12) throw CryptoError.invalidParameter(`Nonce must be ${12} bytes`);
|
|
314
|
+
if (authTag.length !== 16) throw CryptoError.invalidParameter(`Auth tag must be ${16} bytes`);
|
|
251
315
|
const sealed = new Uint8Array(ciphertext.length + authTag.length);
|
|
252
316
|
sealed.set(ciphertext);
|
|
253
317
|
sealed.set(authTag, ciphertext.length);
|
|
254
318
|
try {
|
|
255
|
-
return (0,
|
|
319
|
+
return (0, _noble_ciphers_chacha_js.chacha20poly1305)(key, nonce, aad).decrypt(sealed);
|
|
256
320
|
} catch (error) {
|
|
257
321
|
const aeadError = new AeadError(`Decryption failed: ${error instanceof Error ? error.message : "authentication error"}`);
|
|
258
322
|
throw CryptoError.aead(aeadError);
|
|
@@ -261,8 +325,11 @@ function aeadChaCha20Poly1305DecryptWithAad(ciphertext, key, nonce, aad, authTag
|
|
|
261
325
|
|
|
262
326
|
//#endregion
|
|
263
327
|
//#region src/public-key-encryption.ts
|
|
264
|
-
|
|
265
|
-
|
|
328
|
+
/**
|
|
329
|
+
* Copyright © 2023-2026 Blockchain Commons, LLC
|
|
330
|
+
* Copyright © 2025-2026 Parity Technologies
|
|
331
|
+
*
|
|
332
|
+
*/
|
|
266
333
|
const X25519_PRIVATE_KEY_SIZE = 32;
|
|
267
334
|
const X25519_PUBLIC_KEY_SIZE = 32;
|
|
268
335
|
/**
|
|
@@ -270,7 +337,7 @@ const X25519_PUBLIC_KEY_SIZE = 32;
|
|
|
270
337
|
* Uses HKDF with "agreement" as domain separation salt.
|
|
271
338
|
*/
|
|
272
339
|
function deriveAgreementPrivateKey(keyMaterial) {
|
|
273
|
-
return hkdfHmacSha256(keyMaterial, new TextEncoder().encode("agreement"),
|
|
340
|
+
return hkdfHmacSha256(keyMaterial, new TextEncoder().encode("agreement"), 32);
|
|
274
341
|
}
|
|
275
342
|
/**
|
|
276
343
|
* Derive a signing private key from key material.
|
|
@@ -283,35 +350,52 @@ function deriveSigningPrivateKey(keyMaterial) {
|
|
|
283
350
|
* Generate a new random X25519 private key.
|
|
284
351
|
*/
|
|
285
352
|
function x25519NewPrivateKeyUsing(rng) {
|
|
286
|
-
return rng.randomData(
|
|
353
|
+
return rng.randomData(32);
|
|
287
354
|
}
|
|
288
355
|
/**
|
|
289
356
|
* Derive an X25519 public key from a private key.
|
|
290
357
|
*/
|
|
291
358
|
function x25519PublicKeyFromPrivateKey(privateKey) {
|
|
292
|
-
if (privateKey.length !==
|
|
293
|
-
return
|
|
359
|
+
if (privateKey.length !== 32) throw new Error(`Private key must be ${32} bytes`);
|
|
360
|
+
return _noble_curves_ed25519_js.x25519.getPublicKey(privateKey);
|
|
294
361
|
}
|
|
362
|
+
const SYMMETRIC_KEY_SIZE$1 = 32;
|
|
295
363
|
/**
|
|
296
|
-
* Compute a shared
|
|
364
|
+
* Compute a shared symmetric key using X25519 key agreement (ECDH).
|
|
297
365
|
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
366
|
+
* This function performs X25519 Diffie-Hellman key agreement and then
|
|
367
|
+
* derives a symmetric key using HKDF-SHA256 with "agreement" as the salt.
|
|
368
|
+
* This matches the Rust bc-crypto implementation for cross-platform compatibility.
|
|
369
|
+
*
|
|
370
|
+
* **Low-order public key handling.** The underlying `@noble/curves` X25519
|
|
371
|
+
* implementation rejects low-order public keys (where the u-coordinate is
|
|
372
|
+
* `0`) by throwing `'invalid private or public key received'`. Rust's
|
|
373
|
+
* `x25519-dalek` (v2.0-rc.2) instead silently produces the all-zero shared
|
|
374
|
+
* secret. This means an adversarial low-order public key fed in via TS
|
|
375
|
+
* surfaces as an exception, while in Rust it would yield an HKDF-derived
|
|
376
|
+
* key from a zero shared secret. For honest inputs both implementations
|
|
377
|
+
* produce byte-identical results; the TS port's stricter behaviour is a
|
|
378
|
+
* security improvement, not a parity bug.
|
|
301
379
|
*
|
|
302
380
|
* @param x25519Private - 32-byte X25519 private key
|
|
303
381
|
* @param x25519Public - 32-byte X25519 public key from the other party
|
|
304
|
-
* @returns 32-byte
|
|
382
|
+
* @returns 32-byte derived symmetric key
|
|
305
383
|
* @throws {Error} If private key is not 32 bytes or public key is not 32 bytes
|
|
384
|
+
* @throws {Error} If the public key is low-order (`@noble/curves`-specific guard)
|
|
306
385
|
*/
|
|
307
386
|
function x25519SharedKey(x25519Private, x25519Public) {
|
|
308
|
-
if (x25519Private.length !==
|
|
309
|
-
if (x25519Public.length !==
|
|
310
|
-
return
|
|
387
|
+
if (x25519Private.length !== 32) throw new Error(`Private key must be ${32} bytes`);
|
|
388
|
+
if (x25519Public.length !== 32) throw new Error(`Public key must be ${32} bytes`);
|
|
389
|
+
return hkdfHmacSha256(_noble_curves_ed25519_js.x25519.getSharedSecret(x25519Private, x25519Public), new TextEncoder().encode("agreement"), SYMMETRIC_KEY_SIZE$1);
|
|
311
390
|
}
|
|
312
391
|
|
|
313
392
|
//#endregion
|
|
314
393
|
//#region src/ecdsa-keys.ts
|
|
394
|
+
/**
|
|
395
|
+
* Copyright © 2023-2026 Blockchain Commons, LLC
|
|
396
|
+
* Copyright © 2025-2026 Parity Technologies
|
|
397
|
+
*
|
|
398
|
+
*/
|
|
315
399
|
const ECDSA_PRIVATE_KEY_SIZE = 32;
|
|
316
400
|
const ECDSA_PUBLIC_KEY_SIZE = 33;
|
|
317
401
|
const ECDSA_UNCOMPRESSED_PUBLIC_KEY_SIZE = 65;
|
|
@@ -326,28 +410,28 @@ const SCHNORR_PUBLIC_KEY_SIZE = 32;
|
|
|
326
410
|
* the key is used.
|
|
327
411
|
*/
|
|
328
412
|
function ecdsaNewPrivateKeyUsing(rng) {
|
|
329
|
-
return rng.randomData(
|
|
413
|
+
return rng.randomData(32);
|
|
330
414
|
}
|
|
331
415
|
/**
|
|
332
416
|
* Derive a compressed ECDSA public key from a private key.
|
|
333
417
|
*/
|
|
334
418
|
function ecdsaPublicKeyFromPrivateKey(privateKey) {
|
|
335
|
-
if (privateKey.length !==
|
|
336
|
-
return
|
|
419
|
+
if (privateKey.length !== 32) throw new Error(`Private key must be ${32} bytes`);
|
|
420
|
+
return _noble_curves_secp256k1_js.secp256k1.getPublicKey(privateKey, true);
|
|
337
421
|
}
|
|
338
422
|
/**
|
|
339
423
|
* Decompress a compressed public key to uncompressed format.
|
|
340
424
|
*/
|
|
341
425
|
function ecdsaDecompressPublicKey(compressed) {
|
|
342
|
-
if (compressed.length !==
|
|
343
|
-
return
|
|
426
|
+
if (compressed.length !== 33) throw new Error(`Compressed public key must be ${33} bytes`);
|
|
427
|
+
return _noble_curves_secp256k1_js.secp256k1.Point.fromBytes(compressed).toBytes(false);
|
|
344
428
|
}
|
|
345
429
|
/**
|
|
346
430
|
* Compress an uncompressed public key.
|
|
347
431
|
*/
|
|
348
432
|
function ecdsaCompressPublicKey(uncompressed) {
|
|
349
|
-
if (uncompressed.length !==
|
|
350
|
-
return
|
|
433
|
+
if (uncompressed.length !== 65) throw new Error(`Uncompressed public key must be ${65} bytes`);
|
|
434
|
+
return _noble_curves_secp256k1_js.secp256k1.Point.fromBytes(uncompressed).toBytes(true);
|
|
351
435
|
}
|
|
352
436
|
/**
|
|
353
437
|
* Derive an ECDSA private key from key material using HKDF.
|
|
@@ -356,20 +440,25 @@ function ecdsaCompressPublicKey(uncompressed) {
|
|
|
356
440
|
* matching the Rust reference implementation behavior.
|
|
357
441
|
*/
|
|
358
442
|
function ecdsaDerivePrivateKey(keyMaterial) {
|
|
359
|
-
return hkdfHmacSha256(keyMaterial, new TextEncoder().encode("signing"),
|
|
443
|
+
return hkdfHmacSha256(keyMaterial, new TextEncoder().encode("signing"), 32);
|
|
360
444
|
}
|
|
361
445
|
/**
|
|
362
446
|
* Extract the x-only (Schnorr) public key from a private key.
|
|
363
447
|
* This is used for BIP-340 Schnorr signatures.
|
|
364
448
|
*/
|
|
365
449
|
function schnorrPublicKeyFromPrivateKey(privateKey) {
|
|
366
|
-
if (privateKey.length !==
|
|
367
|
-
return
|
|
450
|
+
if (privateKey.length !== 32) throw new Error(`Private key must be ${32} bytes`);
|
|
451
|
+
return _noble_curves_secp256k1_js.secp256k1.getPublicKey(privateKey, false).slice(1, 33);
|
|
368
452
|
}
|
|
369
453
|
|
|
370
454
|
//#endregion
|
|
371
455
|
//#region src/ecdsa-signing.ts
|
|
372
456
|
/**
|
|
457
|
+
* Copyright © 2023-2026 Blockchain Commons, LLC
|
|
458
|
+
* Copyright © 2025-2026 Parity Technologies
|
|
459
|
+
*
|
|
460
|
+
*/
|
|
461
|
+
/**
|
|
373
462
|
* Sign a message using ECDSA with secp256k1.
|
|
374
463
|
*
|
|
375
464
|
* The message is hashed with double SHA-256 before signing (Bitcoin standard).
|
|
@@ -384,9 +473,9 @@ function schnorrPublicKeyFromPrivateKey(privateKey) {
|
|
|
384
473
|
* @throws {Error} If private key is not 32 bytes
|
|
385
474
|
*/
|
|
386
475
|
function ecdsaSign(privateKey, message) {
|
|
387
|
-
if (privateKey.length !==
|
|
476
|
+
if (privateKey.length !== 32) throw new Error(`Private key must be ${32} bytes`);
|
|
388
477
|
const messageHash = doubleSha256(message);
|
|
389
|
-
return
|
|
478
|
+
return _noble_curves_secp256k1_js.secp256k1.sign(messageHash, privateKey, { prehash: false });
|
|
390
479
|
}
|
|
391
480
|
/**
|
|
392
481
|
* Verify an ECDSA signature with secp256k1.
|
|
@@ -400,11 +489,11 @@ function ecdsaSign(privateKey, message) {
|
|
|
400
489
|
* @throws {Error} If public key is not 33 bytes or signature is not 64 bytes
|
|
401
490
|
*/
|
|
402
491
|
function ecdsaVerify(publicKey, signature, message) {
|
|
403
|
-
if (publicKey.length !==
|
|
404
|
-
if (signature.length !==
|
|
492
|
+
if (publicKey.length !== 33) throw new Error(`Public key must be ${33} bytes`);
|
|
493
|
+
if (signature.length !== 64) throw new Error(`Signature must be ${64} bytes`);
|
|
405
494
|
try {
|
|
406
495
|
const messageHash = doubleSha256(message);
|
|
407
|
-
return
|
|
496
|
+
return _noble_curves_secp256k1_js.secp256k1.verify(signature, messageHash, publicKey, { prehash: false });
|
|
408
497
|
} catch {
|
|
409
498
|
return false;
|
|
410
499
|
}
|
|
@@ -412,6 +501,11 @@ function ecdsaVerify(publicKey, signature, message) {
|
|
|
412
501
|
|
|
413
502
|
//#endregion
|
|
414
503
|
//#region src/schnorr-signing.ts
|
|
504
|
+
/**
|
|
505
|
+
* Copyright © 2023-2026 Blockchain Commons, LLC
|
|
506
|
+
* Copyright © 2025-2026 Parity Technologies
|
|
507
|
+
*
|
|
508
|
+
*/
|
|
415
509
|
const SCHNORR_SIGNATURE_SIZE = 64;
|
|
416
510
|
/**
|
|
417
511
|
* Sign a message using Schnorr signature (BIP-340).
|
|
@@ -422,7 +516,7 @@ const SCHNORR_SIGNATURE_SIZE = 64;
|
|
|
422
516
|
* @returns 64-byte Schnorr signature
|
|
423
517
|
*/
|
|
424
518
|
function schnorrSign(ecdsaPrivateKey, message) {
|
|
425
|
-
return schnorrSignUsing(ecdsaPrivateKey, message, new
|
|
519
|
+
return schnorrSignUsing(ecdsaPrivateKey, message, new _bcts_rand.SecureRandomNumberGenerator());
|
|
426
520
|
}
|
|
427
521
|
/**
|
|
428
522
|
* Sign a message using Schnorr signature with a custom RNG.
|
|
@@ -445,9 +539,9 @@ function schnorrSignUsing(ecdsaPrivateKey, message, rng) {
|
|
|
445
539
|
* @returns 64-byte Schnorr signature
|
|
446
540
|
*/
|
|
447
541
|
function schnorrSignWithAuxRand(ecdsaPrivateKey, message, auxRand) {
|
|
448
|
-
if (ecdsaPrivateKey.length !==
|
|
542
|
+
if (ecdsaPrivateKey.length !== 32) throw new Error(`Private key must be ${32} bytes`);
|
|
449
543
|
if (auxRand.length !== 32) throw new Error("Auxiliary randomness must be 32 bytes");
|
|
450
|
-
return
|
|
544
|
+
return _noble_curves_secp256k1_js.schnorr.sign(message, ecdsaPrivateKey, auxRand);
|
|
451
545
|
}
|
|
452
546
|
/**
|
|
453
547
|
* Verify a Schnorr signature (BIP-340).
|
|
@@ -458,10 +552,10 @@ function schnorrSignWithAuxRand(ecdsaPrivateKey, message, auxRand) {
|
|
|
458
552
|
* @returns true if signature is valid
|
|
459
553
|
*/
|
|
460
554
|
function schnorrVerify(schnorrPublicKey, signature, message) {
|
|
461
|
-
if (schnorrPublicKey.length !==
|
|
462
|
-
if (signature.length !==
|
|
555
|
+
if (schnorrPublicKey.length !== 32) throw new Error(`Public key must be ${32} bytes`);
|
|
556
|
+
if (signature.length !== 64) throw new Error(`Signature must be ${64} bytes`);
|
|
463
557
|
try {
|
|
464
|
-
return
|
|
558
|
+
return _noble_curves_secp256k1_js.schnorr.verify(signature, message, schnorrPublicKey);
|
|
465
559
|
} catch {
|
|
466
560
|
return false;
|
|
467
561
|
}
|
|
@@ -469,6 +563,11 @@ function schnorrVerify(schnorrPublicKey, signature, message) {
|
|
|
469
563
|
|
|
470
564
|
//#endregion
|
|
471
565
|
//#region src/ed25519-signing.ts
|
|
566
|
+
/**
|
|
567
|
+
* Copyright © 2023-2026 Blockchain Commons, LLC
|
|
568
|
+
* Copyright © 2025-2026 Parity Technologies
|
|
569
|
+
*
|
|
570
|
+
*/
|
|
472
571
|
const ED25519_PUBLIC_KEY_SIZE = 32;
|
|
473
572
|
const ED25519_PRIVATE_KEY_SIZE = 32;
|
|
474
573
|
const ED25519_SIGNATURE_SIZE = 64;
|
|
@@ -476,14 +575,14 @@ const ED25519_SIGNATURE_SIZE = 64;
|
|
|
476
575
|
* Generate a new random Ed25519 private key.
|
|
477
576
|
*/
|
|
478
577
|
function ed25519NewPrivateKeyUsing(rng) {
|
|
479
|
-
return rng.randomData(
|
|
578
|
+
return rng.randomData(32);
|
|
480
579
|
}
|
|
481
580
|
/**
|
|
482
581
|
* Derive an Ed25519 public key from a private key.
|
|
483
582
|
*/
|
|
484
583
|
function ed25519PublicKeyFromPrivateKey(privateKey) {
|
|
485
|
-
if (privateKey.length !==
|
|
486
|
-
return
|
|
584
|
+
if (privateKey.length !== 32) throw new Error(`Private key must be ${32} bytes`);
|
|
585
|
+
return _noble_curves_ed25519_js.ed25519.getPublicKey(privateKey);
|
|
487
586
|
}
|
|
488
587
|
/**
|
|
489
588
|
* Sign a message using Ed25519.
|
|
@@ -497,8 +596,8 @@ function ed25519PublicKeyFromPrivateKey(privateKey) {
|
|
|
497
596
|
* @throws {Error} If private key is not 32 bytes
|
|
498
597
|
*/
|
|
499
598
|
function ed25519Sign(privateKey, message) {
|
|
500
|
-
if (privateKey.length !==
|
|
501
|
-
return
|
|
599
|
+
if (privateKey.length !== 32) throw new Error(`Private key must be ${32} bytes`);
|
|
600
|
+
return _noble_curves_ed25519_js.ed25519.sign(message, privateKey);
|
|
502
601
|
}
|
|
503
602
|
/**
|
|
504
603
|
* Verify an Ed25519 signature.
|
|
@@ -510,10 +609,10 @@ function ed25519Sign(privateKey, message) {
|
|
|
510
609
|
* @throws {Error} If public key is not 32 bytes or signature is not 64 bytes
|
|
511
610
|
*/
|
|
512
611
|
function ed25519Verify(publicKey, message, signature) {
|
|
513
|
-
if (publicKey.length !==
|
|
514
|
-
if (signature.length !==
|
|
612
|
+
if (publicKey.length !== 32) throw new Error(`Public key must be ${32} bytes`);
|
|
613
|
+
if (signature.length !== 64) throw new Error(`Signature must be ${64} bytes`);
|
|
515
614
|
try {
|
|
516
|
-
return
|
|
615
|
+
return _noble_curves_ed25519_js.ed25519.verify(signature, message, publicKey);
|
|
517
616
|
} catch {
|
|
518
617
|
return false;
|
|
519
618
|
}
|
|
@@ -522,8 +621,16 @@ function ed25519Verify(publicKey, message, signature) {
|
|
|
522
621
|
//#endregion
|
|
523
622
|
//#region src/scrypt.ts
|
|
524
623
|
/**
|
|
624
|
+
* Copyright © 2023-2026 Blockchain Commons, LLC
|
|
625
|
+
* Copyright © 2025-2026 Parity Technologies
|
|
626
|
+
*
|
|
627
|
+
*/
|
|
628
|
+
/**
|
|
525
629
|
* Derive a key using Scrypt with recommended parameters.
|
|
526
|
-
*
|
|
630
|
+
*
|
|
631
|
+
* Mirrors Rust `bc_crypto::scrypt` which calls `scrypt::Params::recommended()`.
|
|
632
|
+
* The recommended parameters per the upstream `scrypt` crate are
|
|
633
|
+
* `log_n = 17` (N = 2^17 = 131072), `r = 8`, `p = 1`.
|
|
527
634
|
*
|
|
528
635
|
* @param password - Password or passphrase
|
|
529
636
|
* @param salt - Salt value
|
|
@@ -531,7 +638,7 @@ function ed25519Verify(publicKey, message, signature) {
|
|
|
531
638
|
* @returns Derived key
|
|
532
639
|
*/
|
|
533
640
|
function scrypt(password, salt, outputLen) {
|
|
534
|
-
return scryptOpt(password, salt, outputLen,
|
|
641
|
+
return scryptOpt(password, salt, outputLen, 17, 8, 1);
|
|
535
642
|
}
|
|
536
643
|
/**
|
|
537
644
|
* Derive a key using Scrypt with custom parameters.
|
|
@@ -548,7 +655,7 @@ function scryptOpt(password, salt, outputLen, logN, r, p) {
|
|
|
548
655
|
if (logN >= 64) throw new Error("logN must be <64");
|
|
549
656
|
if (r === 0) throw new Error("r must be >0");
|
|
550
657
|
if (p === 0) throw new Error("p must be >0");
|
|
551
|
-
return (0,
|
|
658
|
+
return (0, _noble_hashes_scrypt_js.scrypt)(password, salt, {
|
|
552
659
|
N: 1 << logN,
|
|
553
660
|
r,
|
|
554
661
|
p,
|
|
@@ -559,15 +666,24 @@ function scryptOpt(password, salt, outputLen, logN, r, p) {
|
|
|
559
666
|
//#endregion
|
|
560
667
|
//#region src/argon.ts
|
|
561
668
|
/**
|
|
669
|
+
* Copyright © 2023-2026 Blockchain Commons, LLC
|
|
670
|
+
* Copyright © 2025-2026 Parity Technologies
|
|
671
|
+
*
|
|
672
|
+
*/
|
|
673
|
+
/**
|
|
562
674
|
* Derive a key using Argon2id with default parameters.
|
|
563
675
|
*
|
|
676
|
+
* Mirrors Rust `bc_crypto::argon2id` which calls `Argon2::default()`. The
|
|
677
|
+
* upstream `argon2` crate's defaults are `t = 2` iterations, `m = 19 * 1024
|
|
678
|
+
* = 19456` KiB of memory, `p = 1` lane (per `argon2-0.5.x/src/params.rs`).
|
|
679
|
+
*
|
|
564
680
|
* @param password - Password or passphrase
|
|
565
681
|
* @param salt - Salt value (must be at least 8 bytes)
|
|
566
682
|
* @param outputLen - Desired output length
|
|
567
683
|
* @returns Derived key
|
|
568
684
|
*/
|
|
569
|
-
function
|
|
570
|
-
return argon2idHashOpt(password, salt, outputLen,
|
|
685
|
+
function argon2id(password, salt, outputLen) {
|
|
686
|
+
return argon2idHashOpt(password, salt, outputLen, 2, 19456, 1);
|
|
571
687
|
}
|
|
572
688
|
/**
|
|
573
689
|
* Derive a key using Argon2id with custom parameters.
|
|
@@ -581,7 +697,7 @@ function argon2idHash(password, salt, outputLen) {
|
|
|
581
697
|
* @returns Derived key
|
|
582
698
|
*/
|
|
583
699
|
function argon2idHashOpt(password, salt, outputLen, iterations, memory, parallelism) {
|
|
584
|
-
return (0,
|
|
700
|
+
return (0, _noble_hashes_argon2_js.argon2id)(password, salt, {
|
|
585
701
|
t: iterations,
|
|
586
702
|
m: memory,
|
|
587
703
|
p: parallelism,
|
|
@@ -601,8 +717,6 @@ exports.ECDSA_UNCOMPRESSED_PUBLIC_KEY_SIZE = ECDSA_UNCOMPRESSED_PUBLIC_KEY_SIZE;
|
|
|
601
717
|
exports.ED25519_PRIVATE_KEY_SIZE = ED25519_PRIVATE_KEY_SIZE;
|
|
602
718
|
exports.ED25519_PUBLIC_KEY_SIZE = ED25519_PUBLIC_KEY_SIZE;
|
|
603
719
|
exports.ED25519_SIGNATURE_SIZE = ED25519_SIGNATURE_SIZE;
|
|
604
|
-
exports.GENERIC_PRIVATE_KEY_SIZE = GENERIC_PRIVATE_KEY_SIZE;
|
|
605
|
-
exports.GENERIC_PUBLIC_KEY_SIZE = GENERIC_PUBLIC_KEY_SIZE;
|
|
606
720
|
exports.SCHNORR_PUBLIC_KEY_SIZE = SCHNORR_PUBLIC_KEY_SIZE;
|
|
607
721
|
exports.SCHNORR_SIGNATURE_SIZE = SCHNORR_SIGNATURE_SIZE;
|
|
608
722
|
exports.SHA256_SIZE = SHA256_SIZE;
|
|
@@ -616,11 +730,7 @@ exports.aeadChaCha20Poly1305Decrypt = aeadChaCha20Poly1305Decrypt;
|
|
|
616
730
|
exports.aeadChaCha20Poly1305DecryptWithAad = aeadChaCha20Poly1305DecryptWithAad;
|
|
617
731
|
exports.aeadChaCha20Poly1305Encrypt = aeadChaCha20Poly1305Encrypt;
|
|
618
732
|
exports.aeadChaCha20Poly1305EncryptWithAad = aeadChaCha20Poly1305EncryptWithAad;
|
|
619
|
-
exports.
|
|
620
|
-
exports.argon2idHashOpt = argon2idHashOpt;
|
|
621
|
-
exports.crc32 = crc32;
|
|
622
|
-
exports.crc32Data = crc32Data;
|
|
623
|
-
exports.crc32DataOpt = crc32DataOpt;
|
|
733
|
+
exports.argon2id = argon2id;
|
|
624
734
|
exports.deriveAgreementPrivateKey = deriveAgreementPrivateKey;
|
|
625
735
|
exports.deriveSigningPrivateKey = deriveSigningPrivateKey;
|
|
626
736
|
exports.doubleSha256 = doubleSha256;
|
|
@@ -635,14 +745,18 @@ exports.ed25519NewPrivateKeyUsing = ed25519NewPrivateKeyUsing;
|
|
|
635
745
|
exports.ed25519PublicKeyFromPrivateKey = ed25519PublicKeyFromPrivateKey;
|
|
636
746
|
exports.ed25519Sign = ed25519Sign;
|
|
637
747
|
exports.ed25519Verify = ed25519Verify;
|
|
748
|
+
Object.defineProperty(exports, 'hash', {
|
|
749
|
+
enumerable: true,
|
|
750
|
+
get: function () {
|
|
751
|
+
return hash_exports;
|
|
752
|
+
}
|
|
753
|
+
});
|
|
638
754
|
exports.hkdfHmacSha256 = hkdfHmacSha256;
|
|
639
|
-
exports.hkdfHmacSha512 = hkdfHmacSha512;
|
|
640
755
|
exports.hmacSha256 = hmacSha256;
|
|
641
756
|
exports.hmacSha512 = hmacSha512;
|
|
642
757
|
exports.memzero = memzero;
|
|
643
758
|
exports.memzeroVecVecU8 = memzeroVecVecU8;
|
|
644
759
|
exports.pbkdf2HmacSha256 = pbkdf2HmacSha256;
|
|
645
|
-
exports.pbkdf2HmacSha512 = pbkdf2HmacSha512;
|
|
646
760
|
exports.schnorrPublicKeyFromPrivateKey = schnorrPublicKeyFromPrivateKey;
|
|
647
761
|
exports.schnorrSign = schnorrSign;
|
|
648
762
|
exports.schnorrSignUsing = schnorrSignUsing;
|