@bsv/sdk 1.9.24 → 1.9.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/cjs/package.json +1 -1
  2. package/dist/cjs/src/primitives/AESGCM.js +160 -76
  3. package/dist/cjs/src/primitives/AESGCM.js.map +1 -1
  4. package/dist/cjs/src/primitives/Point.js +41 -18
  5. package/dist/cjs/src/primitives/Point.js.map +1 -1
  6. package/dist/cjs/src/primitives/SymmetricKey.js +20 -19
  7. package/dist/cjs/src/primitives/SymmetricKey.js.map +1 -1
  8. package/dist/cjs/src/primitives/hex.js +1 -3
  9. package/dist/cjs/src/primitives/hex.js.map +1 -1
  10. package/dist/cjs/src/primitives/utils.js +10 -0
  11. package/dist/cjs/src/primitives/utils.js.map +1 -1
  12. package/dist/cjs/src/totp/totp.js +3 -1
  13. package/dist/cjs/src/totp/totp.js.map +1 -1
  14. package/dist/cjs/src/wallet/ProtoWallet.js +4 -2
  15. package/dist/cjs/src/wallet/ProtoWallet.js.map +1 -1
  16. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  17. package/dist/esm/src/primitives/AESGCM.js +158 -75
  18. package/dist/esm/src/primitives/AESGCM.js.map +1 -1
  19. package/dist/esm/src/primitives/Point.js +41 -18
  20. package/dist/esm/src/primitives/Point.js.map +1 -1
  21. package/dist/esm/src/primitives/SymmetricKey.js +20 -19
  22. package/dist/esm/src/primitives/SymmetricKey.js.map +1 -1
  23. package/dist/esm/src/primitives/hex.js +1 -3
  24. package/dist/esm/src/primitives/hex.js.map +1 -1
  25. package/dist/esm/src/primitives/utils.js +9 -0
  26. package/dist/esm/src/primitives/utils.js.map +1 -1
  27. package/dist/esm/src/totp/totp.js +3 -1
  28. package/dist/esm/src/totp/totp.js.map +1 -1
  29. package/dist/esm/src/wallet/ProtoWallet.js +4 -2
  30. package/dist/esm/src/wallet/ProtoWallet.js.map +1 -1
  31. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  32. package/dist/types/src/primitives/AESGCM.d.ts +59 -9
  33. package/dist/types/src/primitives/AESGCM.d.ts.map +1 -1
  34. package/dist/types/src/primitives/Point.d.ts +1 -0
  35. package/dist/types/src/primitives/Point.d.ts.map +1 -1
  36. package/dist/types/src/primitives/SymmetricKey.d.ts.map +1 -1
  37. package/dist/types/src/primitives/hex.d.ts.map +1 -1
  38. package/dist/types/src/primitives/utils.d.ts +1 -0
  39. package/dist/types/src/primitives/utils.d.ts.map +1 -1
  40. package/dist/types/src/totp/totp.d.ts.map +1 -1
  41. package/dist/types/src/wallet/ProtoWallet.d.ts.map +1 -1
  42. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  43. package/dist/umd/bundle.js +3 -3
  44. package/dist/umd/bundle.js.map +1 -1
  45. package/docs/reference/primitives.md +206 -60
  46. package/package.json +1 -1
  47. package/src/primitives/AESGCM.ts +225 -103
  48. package/src/primitives/Point.ts +67 -20
  49. package/src/primitives/SymmetricKey.ts +28 -20
  50. package/src/primitives/__tests/AESGCM.test.ts +254 -354
  51. package/src/primitives/__tests/ECDSA.test.ts +27 -0
  52. package/src/primitives/__tests/Point.test.ts +52 -0
  53. package/src/primitives/__tests/utils.test.ts +24 -1
  54. package/src/primitives/hex.ts +1 -3
  55. package/src/primitives/utils.ts +10 -0
  56. package/src/totp/__tests/totp.test.ts +21 -0
  57. package/src/totp/totp.ts +9 -1
  58. package/src/wallet/ProtoWallet.ts +8 -3
  59. package/src/wallet/__tests/ProtoWallet.test.ts +55 -34
@@ -202,33 +202,73 @@ export const getBytes = function (numericValue: number): number[] {
202
202
  ]
203
203
  }
204
204
 
205
- const createZeroBlock = function (length: number): number[] {
206
- return new Array(length).fill(0)
205
+ export const getBytes64 = function (numericValue: number): number[] {
206
+ if (numericValue < 0 || numericValue > Number.MAX_SAFE_INTEGER) {
207
+ throw new Error('getBytes64: value out of range')
208
+ }
209
+
210
+ const hi = Math.floor(numericValue / 0x100000000)
211
+ const lo = numericValue >>> 0
212
+
213
+ return [
214
+ (hi >>> 24) & 0xFF,
215
+ (hi >>> 16) & 0xFF,
216
+ (hi >>> 8) & 0xFF,
217
+ hi & 0xFF,
218
+ (lo >>> 24) & 0xFF,
219
+ (lo >>> 16) & 0xFF,
220
+ (lo >>> 8) & 0xFF,
221
+ lo & 0xFF
222
+ ]
207
223
  }
208
224
 
209
- const R = [0xe1].concat(createZeroBlock(15))
225
+ type Bytes = Uint8Array
210
226
 
211
- export const exclusiveOR = function (block0: number[], block1: number[]): number[] {
227
+ const createZeroBlock = function (length: number): Bytes {
228
+ // Uint8Array is already zero-filled
229
+ return new Uint8Array(length)
230
+ }
231
+
232
+ // R = 0xe1 || 15 zero bytes
233
+ const R: Bytes = (() => {
234
+ const r = new Uint8Array(16)
235
+ r[0] = 0xe1
236
+ return r
237
+ })()
238
+
239
+ const concatBytes = (...arrays: Bytes[]): Bytes => {
240
+ let total = 0
241
+ for (const a of arrays) total += a.length
242
+
243
+ const out = new Uint8Array(total)
244
+ let offset = 0
245
+ for (const a of arrays) {
246
+ out.set(a, offset)
247
+ offset += a.length
248
+ }
249
+ return out
250
+ }
251
+
252
+ export const exclusiveOR = function (block0: Bytes, block1: Bytes): Bytes {
212
253
  const len = block0.length
213
- const result = new Array(len)
254
+ const result = new Uint8Array(len)
214
255
  for (let i = 0; i < len; i++) {
215
- result[i] = block0[i] ^ block1[i]
256
+ result[i] = block0[i] ^ (block1[i] ?? 0)
216
257
  }
217
258
  return result
218
259
  }
219
260
 
220
- const xorInto = function (target: number[], block: number[]): void {
261
+ const xorInto = function (target: Bytes, block: Bytes): void {
221
262
  for (let i = 0; i < target.length; i++) {
222
- target[i] ^= block[i]
263
+ target[i] ^= block[i] ?? 0
223
264
  }
224
265
  }
225
266
 
226
- export const rightShift = function (block: number[]): number[] {
227
- let i: number
267
+ export const rightShift = function (block: Bytes): Bytes {
228
268
  let carry = 0
229
269
  let oldCarry = 0
230
270
 
231
- for (i = 0; i < block.length; i++) {
271
+ for (let i = 0; i < block.length; i++) {
232
272
  oldCarry = carry
233
273
  carry = block[i] & 0x01
234
274
  block[i] = block[i] >> 1
@@ -241,7 +281,7 @@ export const rightShift = function (block: number[]): number[] {
241
281
  return block
242
282
  }
243
283
 
244
- export const multiply = function (block0: number[], block1: number[]): number[] {
284
+ export const multiply = function (block0: Bytes, block1: Bytes): Bytes {
245
285
  const v = block1.slice()
246
286
  const z = createZeroBlock(16)
247
287
 
@@ -264,16 +304,14 @@ export const multiply = function (block0: number[], block1: number[]): number[]
264
304
  }
265
305
 
266
306
  export const incrementLeastSignificantThirtyTwoBits = function (
267
- block: number[]
268
- ): number[] {
269
- let i
307
+ block: Bytes
308
+ ): Bytes {
270
309
  const result = block.slice()
271
- for (i = 15; i !== 11; i--) {
272
- result[i] = result[i] + 1
273
310
 
274
- if (result[i] === 256) {
275
- result[i] = 0
276
- } else {
311
+ for (let i = 15; i > 11; i--) {
312
+ result[i] = (result[i] + 1) & 0xff // wrap explicitly
313
+
314
+ if (result[i] !== 0) {
277
315
  break
278
316
  }
279
317
  }
@@ -281,11 +319,12 @@ export const incrementLeastSignificantThirtyTwoBits = function (
281
319
  return result
282
320
  }
283
321
 
284
- export function ghash (input: number[], hashSubKey: number[]): number[] {
322
+ export function ghash (input: Bytes, hashSubKey: Bytes): Bytes {
285
323
  let result = createZeroBlock(16)
324
+ const block = new Uint8Array(16)
286
325
 
287
326
  for (let i = 0; i < input.length; i += 16) {
288
- const block = result.slice()
327
+ block.set(result)
289
328
  for (let j = 0; j < 16; j++) {
290
329
  block[j] ^= input[i + j] ?? 0
291
330
  }
@@ -296,14 +335,14 @@ export function ghash (input: number[], hashSubKey: number[]): number[] {
296
335
  }
297
336
 
298
337
  function gctr (
299
- input: number[],
300
- initialCounterBlock: number[],
301
- key: number[]
302
- ): number[] {
303
- if (input.length === 0) return []
304
-
305
- const output = new Array(input.length)
306
- let counterBlock = initialCounterBlock
338
+ input: Bytes,
339
+ initialCounterBlock: Bytes,
340
+ key: Bytes
341
+ ): Bytes {
342
+ if (input.length === 0) return new Uint8Array(0)
343
+
344
+ const output = new Uint8Array(input.length)
345
+ let counterBlock = initialCounterBlock.slice()
307
346
  let pos = 0
308
347
  const n = Math.ceil(input.length / 16)
309
348
 
@@ -323,12 +362,97 @@ function gctr (
323
362
  return output
324
363
  }
325
364
 
365
+ function buildAuthInput (cipherText: Bytes): Bytes {
366
+ const aadLenBits = 0
367
+ const ctLenBits = cipherText.length * 8
368
+
369
+ let padLen: number
370
+ if (cipherText.length === 0) {
371
+ padLen = 16
372
+ } else if (cipherText.length % 16 === 0) {
373
+ padLen = 0
374
+ } else {
375
+ padLen = 16 - (cipherText.length % 16)
376
+ }
377
+
378
+ const total =
379
+ 16 +
380
+ cipherText.length +
381
+ padLen +
382
+ 16
383
+
384
+ const out = new Uint8Array(total)
385
+ let offset = 0
386
+
387
+ offset += 16
388
+
389
+ out.set(cipherText, offset)
390
+ offset += cipherText.length
391
+
392
+ offset += padLen
393
+
394
+ const aadLen = getBytes64(aadLenBits)
395
+ out.set(aadLen, offset)
396
+ offset += 8
397
+
398
+ const ctLen = getBytes64(ctLenBits)
399
+ out.set(ctLen, offset)
400
+
401
+ return out
402
+ }
403
+
404
+ /**
405
+ * SECURITY NOTE – NON-STANDARD AES-GCM PADDING
406
+ *
407
+ * This implementation intentionally deviates from NIST SP 800-38D’s AES-GCM
408
+ * specification in how the GHASH input is formed when the additional
409
+ * authenticated data (AAD) or ciphertext length is zero.
410
+ *
411
+ * In the standard, AAD and ciphertext are each padded with the minimum number
412
+ * of zero bytes required to reach a multiple of 16 bytes; when the length is
413
+ * already a multiple of 16 (including the case length = 0), no padding block
414
+ * is added. In this implementation, when AAD.length === 0 or ciphertext.length
415
+ * === 0, an extra 16-byte block of zeros is appended before the length fields
416
+ * are processed. The same formatting logic is used symmetrically in both
417
+ * AESGCM (encryption) and AESGCMDecrypt (decryption).
418
+ *
419
+ * As a result:
420
+ * - Authentication tags produced here are NOT compatible with tags produced
421
+ * by standards-compliant AES-GCM implementations in the cases where AAD
422
+ * or ciphertext are empty.
423
+ * - Ciphertexts generated by this code must be decrypted by this exact
424
+ * implementation (or one that reproduces the same GHASH formatting), and
425
+ * must not be mixed with ciphertexts produced by a strictly standard
426
+ * AES-GCM library.
427
+ *
428
+ * Cryptographic impact: this change alters only the encoding of the message
429
+ * that is input to GHASH; it does not change the block cipher, key derivation,
430
+ * IV handling, or the basic “encrypt-then-MAC over (AAD, ciphertext, lengths)”
431
+ * structure of AES-GCM. Under the usual assumptions that AES is a secure block
432
+ * cipher and GHASH with a secret subkey is a secure polynomial MAC, this
433
+ * variant continues to provide confidentiality and integrity for data encrypted
434
+ * and decrypted consistently with this implementation. We are not aware of any
435
+ * attack that exploits the presence of this extra zero block when AAD or
436
+ * ciphertext are empty.
437
+ *
438
+ * However, this padding behavior is non-compliant with NIST SP 800-38D and has
439
+ * not been analyzed as extensively as standard AES-GCM. Code that requires
440
+ * strict standards compliance or interoperability with external AES-GCM
441
+ * implementations SHOULD NOT use this module as-is. Any future migration to a
442
+ * fully compliant AES-GCM encoding will require a compatibility strategy, as
443
+ * existing ciphertexts produced by this implementation will otherwise become
444
+ * undecryptable.
445
+ *
446
+ * This non-standard padding behavior is retained intentionally for backward
447
+ * compatibility: existing ciphertexts in production were generated with this
448
+ * encoding, and changing it would render previously encrypted data
449
+ * undecryptable by newer versions of the library.
450
+ */
326
451
  export function AESGCM (
327
- plainText: number[],
328
- additionalAuthenticatedData: number[],
329
- initializationVector: number[],
330
- key: number[]
331
- ): { result: number[], authenticationTag: number[] } {
452
+ plainText: Bytes,
453
+ initializationVector: Bytes,
454
+ key: Bytes
455
+ ): { result: Bytes, authenticationTag: Bytes } {
332
456
  if (initializationVector.length === 0) {
333
457
  throw new Error('Initialization vector must not be empty')
334
458
  }
@@ -337,60 +461,54 @@ export function AESGCM (
337
461
  throw new Error('Key must not be empty')
338
462
  }
339
463
 
340
- let preCounterBlock
341
- let plainTag
342
- const hashSubKey = AES(createZeroBlock(16), key)
343
- preCounterBlock = [...initializationVector]
464
+ const hashSubKey = new Uint8Array(AES(createZeroBlock(16), key))
465
+
466
+ let preCounterBlock: Bytes
467
+
344
468
  if (initializationVector.length === 12) {
345
- preCounterBlock = preCounterBlock.concat(createZeroBlock(3)).concat([0x01])
469
+ preCounterBlock = concatBytes(initializationVector, createZeroBlock(3), new Uint8Array([0x01]))
346
470
  } else {
347
- if (initializationVector.length % 16 !== 0) {
348
- preCounterBlock = preCounterBlock.concat(
349
- createZeroBlock(16 - (initializationVector.length % 16))
471
+ let ivPadded = initializationVector
472
+ if (ivPadded.length % 16 !== 0) {
473
+ ivPadded = concatBytes(
474
+ ivPadded,
475
+ createZeroBlock(16 - (ivPadded.length % 16))
350
476
  )
351
477
  }
352
478
 
353
- preCounterBlock = preCounterBlock.concat(createZeroBlock(8))
479
+ const lenBlock = getBytes64(initializationVector.length * 8)
480
+ const s = concatBytes(
481
+ ivPadded,
482
+ createZeroBlock(8),
483
+ new Uint8Array(lenBlock)
484
+ )
354
485
 
355
- preCounterBlock = ghash(preCounterBlock.concat(createZeroBlock(4))
356
- .concat(getBytes(initializationVector.length * 8)), hashSubKey)
486
+ preCounterBlock = ghash(s, hashSubKey)
357
487
  }
358
488
 
359
- const cipherText = gctr(plainText, incrementLeastSignificantThirtyTwoBits(preCounterBlock), key)
360
-
361
- plainTag = additionalAuthenticatedData.slice()
489
+ const cipherText = gctr(
490
+ plainText,
491
+ incrementLeastSignificantThirtyTwoBits(preCounterBlock),
492
+ key
493
+ )
362
494
 
363
- if (additionalAuthenticatedData.length === 0) {
364
- plainTag = plainTag.concat(createZeroBlock(16))
365
- } else if (additionalAuthenticatedData.length % 16 !== 0) {
366
- plainTag = plainTag.concat(createZeroBlock(16 - (additionalAuthenticatedData.length % 16)))
367
- }
495
+ const authInput = buildAuthInput(cipherText)
368
496
 
369
- plainTag = plainTag.concat(cipherText)
370
-
371
- if (cipherText.length === 0) {
372
- plainTag = plainTag.concat(createZeroBlock(16))
373
- } else if (cipherText.length % 16 !== 0) {
374
- plainTag = plainTag.concat(createZeroBlock(16 - (cipherText.length % 16)))
375
- }
376
-
377
- plainTag = plainTag.concat(createZeroBlock(4))
378
- .concat(getBytes(additionalAuthenticatedData.length * 8))
379
- .concat(createZeroBlock(4)).concat(getBytes(cipherText.length * 8))
497
+ const s = ghash(authInput, hashSubKey)
498
+ const authenticationTag = gctr(s, preCounterBlock, key)
380
499
 
381
500
  return {
382
501
  result: cipherText,
383
- authenticationTag: gctr(ghash(plainTag, hashSubKey), preCounterBlock, key)
502
+ authenticationTag
384
503
  }
385
504
  }
386
505
 
387
506
  export function AESGCMDecrypt (
388
- cipherText: number[],
389
- additionalAuthenticatedData: number[],
390
- initializationVector: number[],
391
- authenticationTag: number[],
392
- key: number[]
393
- ): number[] | null {
507
+ cipherText: Bytes,
508
+ initializationVector: Bytes,
509
+ authenticationTag: Bytes,
510
+ key: Bytes
511
+ ): Bytes | null {
394
512
  if (cipherText.length === 0) {
395
513
  throw new Error('Cipher text must not be empty')
396
514
  }
@@ -403,53 +521,57 @@ export function AESGCMDecrypt (
403
521
  throw new Error('Key must not be empty')
404
522
  }
405
523
 
406
- let preCounterBlock
407
- let compareTag
408
-
409
524
  // Generate the hash subkey
410
- const hashSubKey = AES(createZeroBlock(16), key)
525
+ const hashSubKey = new Uint8Array(AES(createZeroBlock(16), key))
526
+
527
+ let preCounterBlock: Bytes
411
528
 
412
- preCounterBlock = [...initializationVector]
413
529
  if (initializationVector.length === 12) {
414
- preCounterBlock = preCounterBlock.concat(createZeroBlock(3)).concat([0x01])
530
+ preCounterBlock = concatBytes(
531
+ initializationVector,
532
+ createZeroBlock(3),
533
+ new Uint8Array([0x01])
534
+ )
415
535
  } else {
416
- if (initializationVector.length % 16 !== 0) {
417
- preCounterBlock = preCounterBlock.concat(createZeroBlock(16 - (initializationVector.length % 16)))
536
+ let ivPadded = initializationVector
537
+ if (ivPadded.length % 16 !== 0) {
538
+ ivPadded = concatBytes(
539
+ ivPadded,
540
+ createZeroBlock(16 - (ivPadded.length % 16))
541
+ )
418
542
  }
419
543
 
420
- preCounterBlock = preCounterBlock.concat(createZeroBlock(8))
544
+ const lenBlock = getBytes64(initializationVector.length * 8)
545
+ const s = concatBytes(
546
+ ivPadded,
547
+ createZeroBlock(8),
548
+ new Uint8Array(lenBlock)
549
+ )
421
550
 
422
- preCounterBlock = ghash(preCounterBlock.concat(createZeroBlock(4)).concat(getBytes(initializationVector.length * 8)), hashSubKey)
551
+ preCounterBlock = ghash(s, hashSubKey)
423
552
  }
424
553
 
425
554
  // Decrypt to obtain the plain text
426
- const plainText = gctr(cipherText, incrementLeastSignificantThirtyTwoBits(preCounterBlock), key)
555
+ const plainText = gctr(
556
+ cipherText,
557
+ incrementLeastSignificantThirtyTwoBits(preCounterBlock),
558
+ key
559
+ )
427
560
 
428
- compareTag = additionalAuthenticatedData.slice()
561
+ const authInput = buildAuthInput(cipherText)
562
+ const s = ghash(authInput, hashSubKey)
563
+ const calculatedTag = gctr(s, preCounterBlock, key)
429
564
 
430
- if (additionalAuthenticatedData.length === 0) {
431
- compareTag = compareTag.concat(createZeroBlock(16))
432
- } else if (additionalAuthenticatedData.length % 16 !== 0) {
433
- compareTag = compareTag.concat(createZeroBlock(16 - (additionalAuthenticatedData.length % 16)))
565
+ if (calculatedTag.length !== authenticationTag.length) {
566
+ return null
434
567
  }
435
568
 
436
- compareTag = compareTag.concat(cipherText)
437
-
438
- if (cipherText.length === 0) {
439
- compareTag = compareTag.concat(createZeroBlock(16))
440
- } else if (cipherText.length % 16 !== 0) {
441
- compareTag = compareTag.concat(createZeroBlock(16 - (cipherText.length % 16)))
569
+ let diff = 0
570
+ for (let i = 0; i < calculatedTag.length; i++) {
571
+ diff |= calculatedTag[i] ^ authenticationTag[i]
442
572
  }
443
573
 
444
- compareTag = compareTag.concat(createZeroBlock(4))
445
- .concat(getBytes(additionalAuthenticatedData.length * 8))
446
- .concat(createZeroBlock(4)).concat(getBytes(cipherText.length * 8))
447
-
448
- // Generate the authentication tag
449
- const calculatedTag = gctr(ghash(compareTag, hashSubKey), preCounterBlock, key)
450
-
451
- // If the calculated tag does not match the provided tag, return null - the decryption failed.
452
- if (calculatedTag.join() !== authenticationTag.join()) {
574
+ if (diff !== 0) {
453
575
  return null
454
576
  }
455
577
 
@@ -44,14 +44,17 @@ export const biModInv = (a: bigint): bigint => { // binary‑ext GCD
44
44
  export const biModSqr = (a: bigint): bigint => biModMul(a, a)
45
45
 
46
46
  export const biModPow = (base: bigint, exp: bigint): bigint => {
47
- let result = BI_ONE
47
+ let result = 1n
48
48
  base = biMod(base)
49
- let e = exp
50
- while (e > BI_ZERO) {
51
- if ((e & BI_ONE) === BI_ONE) result = biModMul(result, base)
49
+
50
+ while (exp > 0n) {
51
+ if ((exp & 1n) !== 0n) {
52
+ result = biModMul(result, base)
53
+ }
52
54
  base = biModMul(base, base)
53
- e >>= BI_ONE
55
+ exp >>= 1n
54
56
  }
57
+
55
58
  return result
56
59
  }
57
60
 
@@ -59,7 +62,12 @@ export const P_PLUS1_DIV4 = (P_BIGINT + 1n) >> 2n
59
62
 
60
63
  export const biModSqrt = (a: bigint): bigint | null => {
61
64
  const r = biModPow(a, P_PLUS1_DIV4)
62
- return biModMul(r, r) === biMod(a) ? r : null
65
+
66
+ if (biModMul(r, r) !== biMod(a)) {
67
+ return null
68
+ }
69
+
70
+ return r
63
71
  }
64
72
 
65
73
  const toBigInt = (x: BigNumber | number | number[] | string): bigint => {
@@ -220,6 +228,13 @@ export default class Point extends BasePoint {
220
228
  y: BigNumber | null
221
229
  inf: boolean
222
230
 
231
+ static _assertOnCurve (p: Point): Point {
232
+ if (!p.validate()) {
233
+ throw new Error('Invalid point')
234
+ }
235
+ return p
236
+ }
237
+
223
238
  /**
224
239
  * Creates a point object from a given Array. These numbers can represent coordinates in hex format, or points
225
240
  * in multiple established formats.
@@ -238,7 +253,6 @@ export default class Point extends BasePoint {
238
253
  */
239
254
  static fromDER (bytes: number[]): Point {
240
255
  const len = 32
241
- // uncompressed, hybrid-odd, hybrid-even
242
256
  if (
243
257
  (bytes[0] === 0x04 || bytes[0] === 0x06 || bytes[0] === 0x07) &&
244
258
  bytes.length - 1 === 2 * len
@@ -258,12 +272,14 @@ export default class Point extends BasePoint {
258
272
  bytes.slice(1 + len, 1 + 2 * len)
259
273
  )
260
274
 
261
- return res
275
+ return Point._assertOnCurve(res)
262
276
  } else if (
263
277
  (bytes[0] === 0x02 || bytes[0] === 0x03) &&
264
278
  bytes.length - 1 === len
265
279
  ) {
266
- return Point.fromX(bytes.slice(1, 1 + len), bytes[0] === 0x03)
280
+ return Point._assertOnCurve(
281
+ Point.fromX(bytes.slice(1, 1 + len), bytes[0] === 0x03)
282
+ )
267
283
  }
268
284
  throw new Error('Unknown point format')
269
285
  }
@@ -287,7 +303,7 @@ export default class Point extends BasePoint {
287
303
  */
288
304
  static fromString (str: string): Point {
289
305
  const bytes = toArray(str, 'hex')
290
- return Point.fromDER(bytes)
306
+ return Point._assertOnCurve(Point.fromDER(bytes))
291
307
  }
292
308
 
293
309
  /**
@@ -308,16 +324,22 @@ export default class Point extends BasePoint {
308
324
  static fromX (x: BigNumber | number | number[] | string, odd: boolean): Point {
309
325
  let xBigInt = toBigInt(x)
310
326
  xBigInt = biMod(xBigInt)
327
+
311
328
  const y2 = biModAdd(biModMul(biModSqr(xBigInt), xBigInt), 7n)
312
329
  const y = biModSqrt(y2)
313
- if (y === null) throw new Error('Invalid point')
330
+ if (y === null) {
331
+ throw new Error('Invalid point')
332
+ }
333
+
314
334
  let yBig = y
315
335
  if ((yBig & BI_ONE) !== (odd ? BI_ONE : BI_ZERO)) {
316
336
  yBig = biModSub(P_BIGINT, yBig)
317
337
  }
338
+
318
339
  const xBN = new BigNumber(xBigInt.toString(16), 16)
319
340
  const yBN = new BigNumber(yBig.toString(16), 16)
320
- return new Point(xBN, yBN)
341
+
342
+ return Point._assertOnCurve(new Point(xBN, yBN))
321
343
  }
322
344
 
323
345
  /**
@@ -339,33 +361,45 @@ export default class Point extends BasePoint {
339
361
  if (typeof obj === 'string') {
340
362
  obj = JSON.parse(obj)
341
363
  }
342
- const res = new Point(obj[0], obj[1], isRed)
343
- if (typeof obj[2] !== 'object') {
364
+
365
+ let res = new Point(obj[0], obj[1], isRed)
366
+ res = Point._assertOnCurve(res)
367
+
368
+ if (typeof obj[2] !== 'object' || obj[2] === null) {
344
369
  return res
345
370
  }
346
371
 
347
- const obj2point = (obj): Point => {
348
- return new Point(obj[0], obj[1], isRed)
372
+ const pre = obj[2]
373
+
374
+ const obj2point = (p): Point => {
375
+ const pt = new Point(p[0], p[1], isRed)
376
+ return Point._assertOnCurve(pt)
349
377
  }
350
378
 
351
- const pre = obj[2]
352
379
  res.precomputed = {
353
380
  beta: null,
381
+
354
382
  doubles:
355
383
  typeof pre.doubles === 'object' && pre.doubles !== null
356
384
  ? {
357
385
  step: pre.doubles.step,
358
- points: [res].concat(pre.doubles.points.map(obj2point))
386
+ points: [res].concat(
387
+ pre.doubles.points.map(obj2point)
388
+ )
359
389
  }
360
390
  : undefined,
391
+
361
392
  naf:
362
393
  typeof pre.naf === 'object' && pre.naf !== null
363
394
  ? {
364
395
  wnd: pre.naf.wnd,
365
- points: [res].concat(pre.naf.points.map(obj2point))
396
+ points: [res].concat(
397
+ pre.naf.points.map(obj2point)
398
+ )
366
399
  }
367
400
  : undefined
368
401
  }
402
+
369
403
  return res
370
404
  }
371
405
 
@@ -426,7 +460,20 @@ export default class Point extends BasePoint {
426
460
  * const isValid = aPoint.validate();
427
461
  */
428
462
  validate (): boolean {
429
- return this.curve.validate(this)
463
+ if (this.inf || this.x == null || this.y == null) return false
464
+
465
+ try {
466
+ const xBig = BigInt('0x' + this.x.fromRed().toString(16))
467
+ const yBig = BigInt('0x' + this.y.fromRed().toString(16))
468
+
469
+ // compute y² and x³ + 7 using bigint-secure field ops
470
+ const lhs = biModMul(yBig, yBig)
471
+ const rhs = biModAdd(biModMul(biModMul(xBig, xBig), xBig), 7n)
472
+
473
+ return lhs === rhs
474
+ } catch {
475
+ return false
476
+ }
430
477
  }
431
478
 
432
479
  /**