hd-wallet-wasm 2.1.0 → 2.1.2
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 +1 -1
- package/dist/hd-wallet-wasi.wasm +0 -0
- package/dist/hd-wallet.js +1 -1
- package/dist/runtime/aligned.mjs +198 -79
- package/dist/runtime/epm-attestation.mjs +19 -4
- package/dist/runtime/index.d.ts +31 -13
- package/dist/runtime/index.mjs +1037 -517
- package/dist/runtime/input-validation.mjs +368 -0
- package/package.json +2 -2
package/dist/runtime/aligned.mjs
CHANGED
|
@@ -51,6 +51,7 @@ import {
|
|
|
51
51
|
Curve as AlignedCurve,
|
|
52
52
|
Error as AlignedErrorCode,
|
|
53
53
|
} from './generated/aligned/hd_wallet_aligned.mjs';
|
|
54
|
+
import { createInputValidation } from './input-validation.mjs';
|
|
54
55
|
|
|
55
56
|
// Re-export for external use
|
|
56
57
|
export { AlignedCurve, AlignedErrorCode };
|
|
@@ -90,12 +91,54 @@ const ERROR_MESSAGES = {
|
|
|
90
91
|
*/
|
|
91
92
|
export class AlignedError extends Error {
|
|
92
93
|
constructor(code, message) {
|
|
93
|
-
|
|
94
|
+
const lookup = code < 0 ? -code : code;
|
|
95
|
+
super(message || ERROR_MESSAGES[lookup] || `Error code: ${code}`);
|
|
94
96
|
this.name = 'AlignedError';
|
|
95
|
-
this.code =
|
|
97
|
+
this.code = lookup;
|
|
96
98
|
}
|
|
97
99
|
}
|
|
98
100
|
|
|
101
|
+
// Input validation shared with the main API (input-validation.mjs): every
|
|
102
|
+
// key, hash and signature is checked for type, length and (keys) validity
|
|
103
|
+
// before it is written into a request.
|
|
104
|
+
const V = createInputValidation((code, message) => new AlignedError(code, message));
|
|
105
|
+
|
|
106
|
+
// Largest batch: the request structs carry 32-bit counts, and the result
|
|
107
|
+
// buffers are allocated up front.
|
|
108
|
+
const MAX_BATCH = 0x100000;
|
|
109
|
+
|
|
110
|
+
// Allocate `size` bytes and zero them, so no field a request leaves unset is
|
|
111
|
+
// read from stale heap.
|
|
112
|
+
function allocZeroed(wasm, size) {
|
|
113
|
+
const pointer = wasm._hd_alloc(Math.max(1, size));
|
|
114
|
+
if (!pointer) {
|
|
115
|
+
throw new AlignedError(AlignedErrorCode.OUT_OF_MEMORY);
|
|
116
|
+
}
|
|
117
|
+
wasm.HEAPU8.fill(0, pointer, pointer + Math.max(1, size));
|
|
118
|
+
return pointer;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Curves the batch APIs sign and verify with; P-384 signatures do not fit the
|
|
122
|
+
// 64-byte entries and X25519 does not sign, so entries for them report
|
|
123
|
+
// NOT_SUPPORTED.
|
|
124
|
+
const BATCH_CURVES = Object.freeze([
|
|
125
|
+
AlignedCurve.SECP256K1,
|
|
126
|
+
AlignedCurve.ED25519,
|
|
127
|
+
AlignedCurve.P256,
|
|
128
|
+
AlignedCurve.P384,
|
|
129
|
+
AlignedCurve.X25519,
|
|
130
|
+
]);
|
|
131
|
+
|
|
132
|
+
// The batch exports return the number of entries written, or a negative
|
|
133
|
+
// -Error code. Only a count within [0, capacity] is success; anything else
|
|
134
|
+
// throws before a result entry is read.
|
|
135
|
+
function checkCount(result, capacity) {
|
|
136
|
+
if (!Number.isInteger(result) || result < 0 || result > capacity) {
|
|
137
|
+
throw new AlignedError(result < 0 ? result : AlignedErrorCode.INTERNAL);
|
|
138
|
+
}
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
|
|
99
142
|
// =============================================================================
|
|
100
143
|
// AlignedKeyDeriver - Batch Key Derivation
|
|
101
144
|
// =============================================================================
|
|
@@ -141,6 +184,8 @@ export class AlignedKeyDeriver {
|
|
|
141
184
|
* @returns {Array<DerivedKey>} Array of derived keys
|
|
142
185
|
*/
|
|
143
186
|
deriveBatch(baseKey, startIndex, count, hardened = false) {
|
|
187
|
+
V.uint32(startIndex, 'startIndex');
|
|
188
|
+
V.integer(count, 'count', 0, MAX_BATCH);
|
|
144
189
|
// Short-circuit: requesting zero keys returns empty array
|
|
145
190
|
if (count === 0) {
|
|
146
191
|
return [];
|
|
@@ -149,10 +194,7 @@ export class AlignedKeyDeriver {
|
|
|
149
194
|
const wasm = this.#wasm;
|
|
150
195
|
|
|
151
196
|
// Allocate request buffer
|
|
152
|
-
const requestPtr = wasm
|
|
153
|
-
if (!requestPtr) {
|
|
154
|
-
throw new AlignedError(AlignedErrorCode.OUT_OF_MEMORY);
|
|
155
|
-
}
|
|
197
|
+
const requestPtr = allocZeroed(wasm, BATCHDERIVEREQUEST_SIZE);
|
|
156
198
|
|
|
157
199
|
try {
|
|
158
200
|
// Fill request
|
|
@@ -171,15 +213,11 @@ export class AlignedKeyDeriver {
|
|
|
171
213
|
|
|
172
214
|
try {
|
|
173
215
|
// Call WASM function
|
|
174
|
-
const derivedCount = wasm._hd_aligned_derive_batch(
|
|
216
|
+
const derivedCount = checkCount(wasm._hd_aligned_derive_batch(
|
|
175
217
|
requestPtr,
|
|
176
218
|
resultsPtr,
|
|
177
219
|
count
|
|
178
|
-
);
|
|
179
|
-
|
|
180
|
-
if (derivedCount < 0) {
|
|
181
|
-
throw new AlignedError(derivedCount);
|
|
182
|
-
}
|
|
220
|
+
), count);
|
|
183
221
|
|
|
184
222
|
// Read results
|
|
185
223
|
const results = [];
|
|
@@ -203,9 +241,13 @@ export class AlignedKeyDeriver {
|
|
|
203
241
|
|
|
204
242
|
return results;
|
|
205
243
|
} finally {
|
|
244
|
+
// Entries carry derived private keys.
|
|
245
|
+
wasm._hd_secure_wipe(resultsPtr, resultsSize);
|
|
206
246
|
wasm._hd_dealloc(resultsPtr);
|
|
207
247
|
}
|
|
208
248
|
} finally {
|
|
249
|
+
// The request carries the base private key.
|
|
250
|
+
wasm._hd_secure_wipe(requestPtr, BATCHDERIVEREQUEST_SIZE);
|
|
209
251
|
wasm._hd_dealloc(requestPtr);
|
|
210
252
|
}
|
|
211
253
|
}
|
|
@@ -221,23 +263,28 @@ export class AlignedKeyDeriver {
|
|
|
221
263
|
*/
|
|
222
264
|
*streamKeys(baseKey, startIndex = 0, batchSize = 100, hardened = false) {
|
|
223
265
|
const wasm = this.#wasm;
|
|
266
|
+
V.uint32(startIndex, 'startIndex');
|
|
267
|
+
// The results buffer holds exactly batchSize entries.
|
|
268
|
+
V.integer(batchSize, 'batchSize', 1, MAX_BATCH);
|
|
224
269
|
|
|
225
270
|
// Allocate config buffer
|
|
226
|
-
const configPtr = wasm
|
|
227
|
-
if (!configPtr) {
|
|
228
|
-
throw new AlignedError(AlignedErrorCode.OUT_OF_MEMORY);
|
|
229
|
-
}
|
|
271
|
+
const configPtr = allocZeroed(wasm, STREAMDERIVECONFIG_SIZE);
|
|
230
272
|
|
|
231
|
-
// Fill config
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
273
|
+
// Fill config, create the stream, then wipe the config (it carries the
|
|
274
|
+
// base private key) whatever happens.
|
|
275
|
+
let streamHandle;
|
|
276
|
+
try {
|
|
277
|
+
const configView = StreamDeriveConfigView.fromMemory(wasm.wasmMemory, configPtr);
|
|
278
|
+
this.#fillBaseKey(configView, baseKey);
|
|
279
|
+
configView.start_index = startIndex >>> 0;
|
|
280
|
+
configView.batch_size = batchSize >>> 0;
|
|
281
|
+
configView.hardened = hardened ? 1 : 0;
|
|
237
282
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
283
|
+
streamHandle = wasm._hd_aligned_stream_create(configPtr);
|
|
284
|
+
} finally {
|
|
285
|
+
wasm._hd_secure_wipe(configPtr, STREAMDERIVECONFIG_SIZE);
|
|
286
|
+
wasm._hd_dealloc(configPtr);
|
|
287
|
+
}
|
|
241
288
|
|
|
242
289
|
if (!streamHandle) {
|
|
243
290
|
throw new AlignedError(AlignedErrorCode.INTERNAL, 'Failed to create stream');
|
|
@@ -253,13 +300,15 @@ export class AlignedKeyDeriver {
|
|
|
253
300
|
|
|
254
301
|
try {
|
|
255
302
|
while (true) {
|
|
256
|
-
|
|
303
|
+
// 0 ends the stream; a negative -Error throws instead of ending it
|
|
304
|
+
// silently.
|
|
305
|
+
const derivedCount = checkCount(wasm._hd_aligned_stream_next(
|
|
257
306
|
streamHandle,
|
|
258
307
|
resultsPtr,
|
|
259
308
|
batchSize
|
|
260
|
-
);
|
|
309
|
+
), batchSize);
|
|
261
310
|
|
|
262
|
-
if (derivedCount
|
|
311
|
+
if (derivedCount === 0) {
|
|
263
312
|
break;
|
|
264
313
|
}
|
|
265
314
|
|
|
@@ -286,6 +335,7 @@ export class AlignedKeyDeriver {
|
|
|
286
335
|
yield batch;
|
|
287
336
|
}
|
|
288
337
|
} finally {
|
|
338
|
+
wasm._hd_secure_wipe(resultsPtr, resultsSize);
|
|
289
339
|
wasm._hd_dealloc(resultsPtr);
|
|
290
340
|
wasm._hd_aligned_stream_destroy(streamHandle);
|
|
291
341
|
}
|
|
@@ -296,11 +346,18 @@ export class AlignedKeyDeriver {
|
|
|
296
346
|
* @private
|
|
297
347
|
*/
|
|
298
348
|
#fillBaseKey(view, baseKey) {
|
|
299
|
-
if (baseKey._handle) {
|
|
349
|
+
if (baseKey && baseKey._handle) {
|
|
300
350
|
// HDKey object - use conversion function
|
|
301
351
|
const keyDataPtr = this.#wasm._hd_alloc(EXTENDEDKEYDATA_SIZE);
|
|
352
|
+
if (!keyDataPtr) {
|
|
353
|
+
throw new AlignedError(AlignedErrorCode.OUT_OF_MEMORY);
|
|
354
|
+
}
|
|
302
355
|
try {
|
|
303
|
-
|
|
356
|
+
// On failure the buffer holds no key: never copy it.
|
|
357
|
+
const result = this.#wasm._hd_aligned_from_extended_key(baseKey._handle, keyDataPtr);
|
|
358
|
+
if (result !== 0) {
|
|
359
|
+
throw new AlignedError(result);
|
|
360
|
+
}
|
|
304
361
|
const keyDataView = ExtendedKeyDataView.fromMemory(
|
|
305
362
|
this.#wasm.wasmMemory, keyDataPtr
|
|
306
363
|
);
|
|
@@ -314,14 +371,27 @@ export class AlignedKeyDeriver {
|
|
|
314
371
|
view.base_key_private_key_data.set(keyDataView.private_key_data);
|
|
315
372
|
view.base_key_has_private_key = keyDataView.has_private_key;
|
|
316
373
|
} finally {
|
|
374
|
+
this.#wasm._hd_secure_wipe(keyDataPtr, EXTENDEDKEYDATA_SIZE);
|
|
317
375
|
this.#wasm._hd_dealloc(keyDataPtr);
|
|
318
376
|
}
|
|
319
|
-
} else if (baseKey.privateKey && baseKey.chainCode) {
|
|
320
|
-
// Plain object with key data
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
377
|
+
} else if (baseKey && baseKey.privateKey && baseKey.chainCode) {
|
|
378
|
+
// Plain object with key data: every field at its exact size, the keys
|
|
379
|
+
// valid for the curve (32-byte scalars, 33-byte compressed keys).
|
|
380
|
+
const curve = baseKey.curve ?? AlignedCurve.SECP256K1;
|
|
381
|
+
V.curve(curve, [AlignedCurve.SECP256K1, AlignedCurve.P256]);
|
|
382
|
+
const depth = baseKey.depth ?? 0;
|
|
383
|
+
const parentFingerprint = baseKey.parentFingerprint ?? 0;
|
|
384
|
+
const childIndex = baseKey.childIndex ?? 0;
|
|
385
|
+
V.integer(depth, 'depth', 0, 255);
|
|
386
|
+
V.uint32(parentFingerprint, 'parentFingerprint');
|
|
387
|
+
V.uint32(childIndex, 'childIndex');
|
|
388
|
+
V.bytesOfLength(baseKey.chainCode, 32, 'chainCode');
|
|
389
|
+
V.privateKey(this.#wasm, baseKey.privateKey, curve);
|
|
390
|
+
V.publicKey(this.#wasm, baseKey.publicKey, curve, 'publicKey', [33]);
|
|
391
|
+
view.base_key_curve = curve;
|
|
392
|
+
view.base_key_depth = depth;
|
|
393
|
+
view.base_key_parent_fingerprint = parentFingerprint;
|
|
394
|
+
view.base_key_child_index = childIndex;
|
|
325
395
|
view.base_key_chain_code_data.set(baseKey.chainCode);
|
|
326
396
|
view.base_key_public_key_data.set(baseKey.publicKey);
|
|
327
397
|
view.base_key_private_key_data.set(baseKey.privateKey);
|
|
@@ -371,13 +441,28 @@ export class AlignedSigner {
|
|
|
371
441
|
*/
|
|
372
442
|
signBatch(privateKey, hashes, curve = AlignedCurve.SECP256K1) {
|
|
373
443
|
const wasm = this.#wasm;
|
|
374
|
-
|
|
444
|
+
V.curve(curve, BATCH_CURVES);
|
|
445
|
+
// The array and each entry are read once (a Proxy or a getter could answer
|
|
446
|
+
// a second read differently); only the copies are used below.
|
|
447
|
+
const hashCount = Array.isArray(hashes) ? hashes.length : -1;
|
|
448
|
+
if (!Number.isInteger(hashCount) || hashCount < 0 || hashCount > MAX_BATCH) {
|
|
449
|
+
throw new AlignedError(AlignedErrorCode.INVALID_ARGUMENT, `hashes must be an array of at most ${MAX_BATCH}`);
|
|
450
|
+
}
|
|
451
|
+
const hashCopies = [];
|
|
452
|
+
for (let i = 0; i < hashCount; i++) hashCopies.push(V.snapshot(hashes[i], 'hash'));
|
|
453
|
+
// The request holds a 32-byte key: secp256k1 and P-256 keys must be
|
|
454
|
+
// 0 < d < n; Ed25519 seeds are any 32 bytes. P-384 and X25519 entries
|
|
455
|
+
// report NOT_SUPPORTED.
|
|
456
|
+
if (curve === AlignedCurve.SECP256K1 || curve === AlignedCurve.P256 || curve === AlignedCurve.ED25519) {
|
|
457
|
+
V.privateKey(wasm, privateKey, curve);
|
|
458
|
+
} else {
|
|
459
|
+
V.bytesOfLength(privateKey, 32, 'privateKey', AlignedErrorCode.INVALID_PRIVATE_KEY);
|
|
460
|
+
}
|
|
461
|
+
for (const hash of hashCopies) V.bytesOfLength(hash, 32, 'hash');
|
|
462
|
+
const count = hashCopies.length;
|
|
375
463
|
|
|
376
464
|
// Allocate request
|
|
377
|
-
const requestPtr = wasm
|
|
378
|
-
if (!requestPtr) {
|
|
379
|
-
throw new AlignedError(AlignedErrorCode.OUT_OF_MEMORY);
|
|
380
|
-
}
|
|
465
|
+
const requestPtr = allocZeroed(wasm, BATCHSIGNREQUEST_SIZE);
|
|
381
466
|
|
|
382
467
|
try {
|
|
383
468
|
// Fill request
|
|
@@ -387,38 +472,28 @@ export class AlignedSigner {
|
|
|
387
472
|
requestView.count = count;
|
|
388
473
|
|
|
389
474
|
// Allocate and fill hashes
|
|
390
|
-
const hashesPtr = wasm
|
|
391
|
-
if (!hashesPtr) {
|
|
392
|
-
throw new AlignedError(AlignedErrorCode.OUT_OF_MEMORY);
|
|
393
|
-
}
|
|
475
|
+
const hashesPtr = allocZeroed(wasm, count * HASHENTRY_SIZE);
|
|
394
476
|
|
|
395
477
|
try {
|
|
396
478
|
const hashesArray = HashEntryArrayView.fromMemory(wasm.wasmMemory, hashesPtr, count);
|
|
397
479
|
for (let i = 0; i < count; i++) {
|
|
398
480
|
const entry = hashesArray.at(i);
|
|
399
481
|
entry.index = i;
|
|
400
|
-
entry.hash_data.set(
|
|
482
|
+
entry.hash_data.set(hashCopies[i]);
|
|
401
483
|
}
|
|
402
484
|
|
|
403
485
|
// Allocate results
|
|
404
|
-
const resultsPtr = wasm
|
|
405
|
-
if (!resultsPtr) {
|
|
406
|
-
throw new AlignedError(AlignedErrorCode.OUT_OF_MEMORY);
|
|
407
|
-
}
|
|
486
|
+
const resultsPtr = allocZeroed(wasm, count * SIGNATUREENTRY_SIZE);
|
|
408
487
|
|
|
409
488
|
try {
|
|
410
489
|
// Call WASM function
|
|
411
|
-
const signedCount = wasm._hd_aligned_sign_batch(
|
|
490
|
+
const signedCount = checkCount(wasm._hd_aligned_sign_batch(
|
|
412
491
|
requestPtr,
|
|
413
492
|
hashesPtr,
|
|
414
493
|
count,
|
|
415
494
|
resultsPtr,
|
|
416
495
|
count
|
|
417
|
-
);
|
|
418
|
-
|
|
419
|
-
if (signedCount < 0) {
|
|
420
|
-
throw new AlignedError(signedCount);
|
|
421
|
-
}
|
|
496
|
+
), count);
|
|
422
497
|
|
|
423
498
|
// Read results
|
|
424
499
|
const results = [];
|
|
@@ -446,6 +521,8 @@ export class AlignedSigner {
|
|
|
446
521
|
wasm._hd_dealloc(hashesPtr);
|
|
447
522
|
}
|
|
448
523
|
} finally {
|
|
524
|
+
// The request carries the private key.
|
|
525
|
+
wasm._hd_secure_wipe(requestPtr, BATCHSIGNREQUEST_SIZE);
|
|
449
526
|
wasm._hd_dealloc(requestPtr);
|
|
450
527
|
}
|
|
451
528
|
}
|
|
@@ -460,55 +537,83 @@ export class AlignedSigner {
|
|
|
460
537
|
*/
|
|
461
538
|
verifyBatch(publicKey, entries, curve = AlignedCurve.SECP256K1) {
|
|
462
539
|
const wasm = this.#wasm;
|
|
463
|
-
|
|
540
|
+
V.curve(curve, BATCH_CURVES);
|
|
541
|
+
// The array, each entry and each field are read once (a Proxy or a getter
|
|
542
|
+
// could answer a second read differently); only the copies are used below.
|
|
543
|
+
const entryCount = Array.isArray(entries) ? entries.length : -1;
|
|
544
|
+
if (!Number.isInteger(entryCount) || entryCount < 0 || entryCount > MAX_BATCH) {
|
|
545
|
+
throw new AlignedError(AlignedErrorCode.INVALID_ARGUMENT, `entries must be an array of at most ${MAX_BATCH}`);
|
|
546
|
+
}
|
|
547
|
+
// Types are programmer errors (throw); wrong lengths or an invalid key
|
|
548
|
+
// only make the affected entries invalid.
|
|
549
|
+
const publicKeyCopy = V.snapshot(publicKey, 'publicKey');
|
|
550
|
+
const entryCopies = [];
|
|
551
|
+
for (let i = 0; i < entryCount; i++) {
|
|
552
|
+
const entry = entries[i];
|
|
553
|
+
if (entry === null || typeof entry !== 'object') {
|
|
554
|
+
throw new AlignedError(AlignedErrorCode.INVALID_ARGUMENT, 'each entry must be { hash, signature }');
|
|
555
|
+
}
|
|
556
|
+
const { hash, signature } = entry;
|
|
557
|
+
entryCopies.push({ hash: V.snapshot(hash, 'hash'), signature: V.snapshot(signature, 'signature') });
|
|
558
|
+
}
|
|
559
|
+
const count = entryCopies.length;
|
|
560
|
+
|
|
561
|
+
// The request holds a 33-byte key: a compressed secp256k1 or P-256 key,
|
|
562
|
+
// or an Ed25519 key after one padding byte.
|
|
563
|
+
let keyBytes = null;
|
|
564
|
+
if (curve === AlignedCurve.ED25519) {
|
|
565
|
+
if (publicKeyCopy.length === 32) {
|
|
566
|
+
keyBytes = new Uint8Array(33);
|
|
567
|
+
keyBytes.set(publicKeyCopy, 1);
|
|
568
|
+
} else if (publicKeyCopy.length === 33) {
|
|
569
|
+
keyBytes = publicKeyCopy;
|
|
570
|
+
}
|
|
571
|
+
} else if (curve === AlignedCurve.SECP256K1 || curve === AlignedCurve.P256) {
|
|
572
|
+
if (V.isPublicKey(wasm, publicKeyCopy, curve, [33])) keyBytes = publicKeyCopy;
|
|
573
|
+
} else {
|
|
574
|
+
// P-384 / X25519: the export reports NOT_SUPPORTED for every entry.
|
|
575
|
+
keyBytes = new Uint8Array(33);
|
|
576
|
+
}
|
|
577
|
+
const entryValid = entryCopies.map((entry) => keyBytes !== null &&
|
|
578
|
+
entry.hash.length === 32 && entry.signature.length === 64);
|
|
464
579
|
|
|
465
580
|
// Allocate request
|
|
466
|
-
const requestPtr = wasm
|
|
467
|
-
if (!requestPtr) {
|
|
468
|
-
throw new AlignedError(AlignedErrorCode.OUT_OF_MEMORY);
|
|
469
|
-
}
|
|
581
|
+
const requestPtr = allocZeroed(wasm, BATCHVERIFYREQUEST_SIZE);
|
|
470
582
|
|
|
471
583
|
try {
|
|
472
584
|
// Fill request
|
|
473
585
|
const requestView = BatchVerifyRequestView.fromMemory(wasm.wasmMemory, requestPtr);
|
|
474
|
-
requestView.public_key_data.set(
|
|
586
|
+
if (keyBytes !== null) requestView.public_key_data.set(keyBytes);
|
|
475
587
|
requestView.curve = curve;
|
|
476
588
|
requestView.count = count;
|
|
477
589
|
|
|
478
|
-
// Allocate and fill entries
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
throw new AlignedError(AlignedErrorCode.OUT_OF_MEMORY);
|
|
482
|
-
}
|
|
590
|
+
// Allocate and fill entries (an invalid entry stays all zero and its
|
|
591
|
+
// result is reported invalid below whatever the export says)
|
|
592
|
+
const entriesPtr = allocZeroed(wasm, count * VERIFYENTRY_SIZE);
|
|
483
593
|
|
|
484
594
|
try {
|
|
485
595
|
const entriesArray = VerifyEntryArrayView.fromMemory(wasm.wasmMemory, entriesPtr, count);
|
|
486
596
|
for (let i = 0; i < count; i++) {
|
|
487
597
|
const view = entriesArray.at(i);
|
|
488
598
|
view.index = i;
|
|
489
|
-
|
|
490
|
-
|
|
599
|
+
if (entryValid[i]) {
|
|
600
|
+
view.hash_data.set(entryCopies[i].hash);
|
|
601
|
+
view.signature_data.set(entryCopies[i].signature);
|
|
602
|
+
}
|
|
491
603
|
}
|
|
492
604
|
|
|
493
605
|
// Allocate results
|
|
494
|
-
const resultsPtr = wasm
|
|
495
|
-
if (!resultsPtr) {
|
|
496
|
-
throw new AlignedError(AlignedErrorCode.OUT_OF_MEMORY);
|
|
497
|
-
}
|
|
606
|
+
const resultsPtr = allocZeroed(wasm, count * VERIFYRESULTENTRY_SIZE);
|
|
498
607
|
|
|
499
608
|
try {
|
|
500
609
|
// Call WASM function
|
|
501
|
-
const verifiedCount = wasm._hd_aligned_verify_batch(
|
|
610
|
+
const verifiedCount = checkCount(wasm._hd_aligned_verify_batch(
|
|
502
611
|
requestPtr,
|
|
503
612
|
entriesPtr,
|
|
504
613
|
count,
|
|
505
614
|
resultsPtr,
|
|
506
615
|
count
|
|
507
|
-
);
|
|
508
|
-
|
|
509
|
-
if (verifiedCount < 0) {
|
|
510
|
-
throw new AlignedError(verifiedCount);
|
|
511
|
-
}
|
|
616
|
+
), count);
|
|
512
617
|
|
|
513
618
|
// Read results
|
|
514
619
|
const results = [];
|
|
@@ -518,6 +623,16 @@ export class AlignedSigner {
|
|
|
518
623
|
|
|
519
624
|
for (let i = 0; i < verifiedCount; i++) {
|
|
520
625
|
const entry = resultsArray.at(i);
|
|
626
|
+
if (!entryValid[i]) {
|
|
627
|
+
results.push({
|
|
628
|
+
index: i,
|
|
629
|
+
valid: false,
|
|
630
|
+
error: keyBytes === null
|
|
631
|
+
? AlignedErrorCode.INVALID_PUBLIC_KEY
|
|
632
|
+
: AlignedErrorCode.INVALID_SIGNATURE,
|
|
633
|
+
});
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
521
636
|
results.push({
|
|
522
637
|
index: entry.index,
|
|
523
638
|
valid: entry.error === AlignedErrorCode.OK,
|
|
@@ -588,6 +703,9 @@ export class AlignedAPI {
|
|
|
588
703
|
*/
|
|
589
704
|
keyToBytes(hdKey) {
|
|
590
705
|
const wasm = this.#wasm;
|
|
706
|
+
if (!hdKey || !hdKey._handle) {
|
|
707
|
+
throw new AlignedError(AlignedErrorCode.INVALID_ARGUMENT, 'hdKey must be a live HDKey');
|
|
708
|
+
}
|
|
591
709
|
const ptr = wasm._hd_alloc(EXTENDEDKEYDATA_SIZE);
|
|
592
710
|
if (!ptr) {
|
|
593
711
|
throw new AlignedError(AlignedErrorCode.OUT_OF_MEMORY);
|
|
@@ -600,6 +718,7 @@ export class AlignedAPI {
|
|
|
600
718
|
}
|
|
601
719
|
return new Uint8Array(wasm.HEAPU8.buffer, ptr, EXTENDEDKEYDATA_SIZE).slice();
|
|
602
720
|
} finally {
|
|
721
|
+
wasm._hd_secure_wipe(ptr, EXTENDEDKEYDATA_SIZE);
|
|
603
722
|
wasm._hd_dealloc(ptr);
|
|
604
723
|
}
|
|
605
724
|
}
|
|
@@ -224,6 +224,15 @@ export function buildEPMSigningContent(epm) {
|
|
|
224
224
|
const SECP256K1_ORDER = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n;
|
|
225
225
|
const SECP256K1_HALF_ORDER = SECP256K1_ORDER >> 1n;
|
|
226
226
|
|
|
227
|
+
// Hex from an attestation or proof is untrusted data: anything that is not an
|
|
228
|
+
// even-length hex string decodes to null, and the verification fails.
|
|
229
|
+
function hexOrNull(wallet, value) {
|
|
230
|
+
if (typeof value !== 'string' || value.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(value)) {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
return wallet.utils.decodeHex(value);
|
|
234
|
+
}
|
|
235
|
+
|
|
227
236
|
function bytesToBigInt(bytes) {
|
|
228
237
|
let value = 0n;
|
|
229
238
|
for (const b of bytes) value = (value << 8n) | BigInt(b);
|
|
@@ -337,7 +346,8 @@ export function verifyEPMSignature(wallet, epm, publicKey, options = {}) {
|
|
|
337
346
|
if (!sigHex) return false;
|
|
338
347
|
|
|
339
348
|
const content = buildEPMSigningContent(epm);
|
|
340
|
-
const sig = wallet
|
|
349
|
+
const sig = hexOrNull(wallet, sigHex);
|
|
350
|
+
if (sig === null) return false;
|
|
341
351
|
const curve = String(
|
|
342
352
|
options.curve || (publicKey && publicKey.length === 32 ? 'ed25519' : 'secp256k1'),
|
|
343
353
|
).toLowerCase();
|
|
@@ -547,9 +557,14 @@ export function buildSolanaChainProof(wallet, { address, publicKeyHex, privateKe
|
|
|
547
557
|
* @returns {boolean} True if the signature is valid for the given public key and payload
|
|
548
558
|
*/
|
|
549
559
|
export function verifyChainProof(wallet, proof) {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
560
|
+
if (proof === null || typeof proof !== 'object') {
|
|
561
|
+
throw new TypeError('proof must be a ChainProof object');
|
|
562
|
+
}
|
|
563
|
+
// A proof whose fields are not hex does not verify.
|
|
564
|
+
const pubKey = hexOrNull(wallet, proof.PUBLIC_KEY);
|
|
565
|
+
const sig = hexOrNull(wallet, proof.SIGNATURE);
|
|
566
|
+
const payload = hexOrNull(wallet, proof.SIGNED_PAYLOAD);
|
|
567
|
+
if (pubKey === null || sig === null || payload === null) return false;
|
|
553
568
|
|
|
554
569
|
const algorithm = proof.ALGORITHM;
|
|
555
570
|
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -12,8 +12,16 @@
|
|
|
12
12
|
// Import aligned API types
|
|
13
13
|
import { AlignedAPI } from './aligned.js';
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Binary input to every wallet API: a Uint8Array (Node Buffers included) of
|
|
17
|
+
* this realm. A DataView, an ArrayBuffer, another typed array, a detached or
|
|
18
|
+
* cross-realm array is refused.
|
|
19
|
+
*/
|
|
20
|
+
export type BinaryLike = Uint8Array;
|
|
21
|
+
/** A PEM string or its bytes, or DER bytes. */
|
|
22
|
+
export type X509CertificateInput = string | Uint8Array;
|
|
23
|
+
/** What an SDN plugin capability callback may return (it is copied to a Uint8Array). */
|
|
24
|
+
export type PluginBinaryLike = Uint8Array | ArrayBuffer | ArrayBufferView;
|
|
17
25
|
|
|
18
26
|
export interface SdnPluginTypeRef {
|
|
19
27
|
schemaName: string | null;
|
|
@@ -113,7 +121,7 @@ export interface SdnPluginInvocationResult {
|
|
|
113
121
|
}
|
|
114
122
|
|
|
115
123
|
export interface SdnPluginCapabilities {
|
|
116
|
-
randomBytes?: (length: number, context?: Record<string, unknown>) =>
|
|
124
|
+
randomBytes?: (length: number, context?: Record<string, unknown>) => PluginBinaryLike;
|
|
117
125
|
/**
|
|
118
126
|
* Wallet-held signer for sign_detached. For secp256k1 it receives the message
|
|
119
127
|
* (digest is null) and must return ECDSA over SHA-256(messageBytes), hashed once.
|
|
@@ -124,8 +132,8 @@ export interface SdnPluginCapabilities {
|
|
|
124
132
|
messageBytes: Uint8Array;
|
|
125
133
|
digest: Uint8Array | null;
|
|
126
134
|
}) => {
|
|
127
|
-
signature:
|
|
128
|
-
publicKey:
|
|
135
|
+
signature: PluginBinaryLike;
|
|
136
|
+
publicKey: PluginBinaryLike;
|
|
129
137
|
algorithm?: string;
|
|
130
138
|
};
|
|
131
139
|
}
|
|
@@ -818,7 +826,12 @@ export interface BitcoinTxBuilder {
|
|
|
818
826
|
|
|
819
827
|
export interface BitcoinTx {
|
|
820
828
|
addInput(txid: string, vout: number, sequence?: number): this;
|
|
821
|
-
|
|
829
|
+
/**
|
|
830
|
+
* Pay `address`: P2PKH, P2SH, P2WPKH (v0, 20 bytes), P2WSH (v0, 32 bytes)
|
|
831
|
+
* or P2TR (v1, 32 bytes) of `network` (mainnet by default). Any other
|
|
832
|
+
* address, or one of the other network, throws (BIP-350).
|
|
833
|
+
*/
|
|
834
|
+
addOutput(address: string, amount: bigint, network?: Network): this;
|
|
822
835
|
sign(inputIndex: number, privateKey: Uint8Array, redeemScript?: Uint8Array): this;
|
|
823
836
|
serialize(): Uint8Array;
|
|
824
837
|
getTxid(): string;
|
|
@@ -867,6 +880,11 @@ export interface EthereumAPI {
|
|
|
867
880
|
tx: EthereumTxBuilder;
|
|
868
881
|
}
|
|
869
882
|
|
|
883
|
+
/**
|
|
884
|
+
* `nonce` and `chainId` are required: a missing nonce or chainId, or a chainId
|
|
885
|
+
* outside 1 to 2^63 - 37 (EIP-2294), throws (an explicit nonce 0 is valid).
|
|
886
|
+
* Each field is read once.
|
|
887
|
+
*/
|
|
870
888
|
export interface EthereumTxBuilder {
|
|
871
889
|
create(params: {
|
|
872
890
|
nonce: number;
|
|
@@ -1044,7 +1062,7 @@ export interface UtilsAPI {
|
|
|
1044
1062
|
|
|
1045
1063
|
export interface X509WalletAttestationOptions {
|
|
1046
1064
|
curve: Curve;
|
|
1047
|
-
privateKey:
|
|
1065
|
+
privateKey: Uint8Array;
|
|
1048
1066
|
keyLabel?: string;
|
|
1049
1067
|
commentPrefix?: string;
|
|
1050
1068
|
}
|
|
@@ -1090,22 +1108,22 @@ export interface X509Pkcs12Bundle {
|
|
|
1090
1108
|
export interface X509API {
|
|
1091
1109
|
isAvailable(): boolean;
|
|
1092
1110
|
generatePrivateKey(curve: Curve): Uint8Array;
|
|
1093
|
-
exportPrivateKeyPem(curve: Curve, privateKey:
|
|
1111
|
+
exportPrivateKeyPem(curve: Curve, privateKey: Uint8Array): string;
|
|
1094
1112
|
importPrivateKeyPem(curve: Curve, pem: string): Uint8Array;
|
|
1095
1113
|
createSelfSignedCertificate(
|
|
1096
1114
|
options: X509CertificateOptions,
|
|
1097
1115
|
certificateCurve: Curve,
|
|
1098
|
-
certificatePrivateKey:
|
|
1116
|
+
certificatePrivateKey: Uint8Array,
|
|
1099
1117
|
outputEncoding?: X509Encoding
|
|
1100
1118
|
): string | Uint8Array;
|
|
1101
1119
|
issueCertificate(
|
|
1102
1120
|
options: X509CertificateOptions,
|
|
1103
1121
|
issuerCurve: Curve,
|
|
1104
|
-
issuerPrivateKey:
|
|
1122
|
+
issuerPrivateKey: Uint8Array,
|
|
1105
1123
|
issuerCertificate: X509CertificateInput,
|
|
1106
1124
|
issuerCertificateEncoding: X509Encoding,
|
|
1107
1125
|
subjectCurve: Curve,
|
|
1108
|
-
subjectPrivateKey:
|
|
1126
|
+
subjectPrivateKey: Uint8Array,
|
|
1109
1127
|
outputEncoding?: X509Encoding
|
|
1110
1128
|
): string | Uint8Array;
|
|
1111
1129
|
convertCertificate(
|
|
@@ -1119,12 +1137,12 @@ export interface X509API {
|
|
|
1119
1137
|
certificate: X509CertificateInput,
|
|
1120
1138
|
certificateEncoding: X509Encoding,
|
|
1121
1139
|
privateKeyCurve: Curve,
|
|
1122
|
-
privateKey:
|
|
1140
|
+
privateKey: Uint8Array,
|
|
1123
1141
|
password: string,
|
|
1124
1142
|
friendlyName?: string,
|
|
1125
1143
|
chainPem?: X509CertificateInput
|
|
1126
1144
|
): Uint8Array;
|
|
1127
|
-
importPkcs12(pkcs12Bundle:
|
|
1145
|
+
importPkcs12(pkcs12Bundle: Uint8Array, password: string): X509Pkcs12Bundle;
|
|
1128
1146
|
}
|
|
1129
1147
|
|
|
1130
1148
|
// =============================================================================
|