@bsv/sdk 2.1.1 → 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.
Files changed (53) hide show
  1. package/dist/cjs/package.json +1 -1
  2. package/dist/cjs/src/identity/ContactsManager.js +44 -6
  3. package/dist/cjs/src/identity/ContactsManager.js.map +1 -1
  4. package/dist/cjs/src/identity/IdentityClient.js +106 -37
  5. package/dist/cjs/src/identity/IdentityClient.js.map +1 -1
  6. package/dist/cjs/src/overlay-tools/LookupResolver.js +85 -58
  7. package/dist/cjs/src/overlay-tools/LookupResolver.js.map +1 -1
  8. package/dist/cjs/src/primitives/Hash.js +173 -50
  9. package/dist/cjs/src/primitives/Hash.js.map +1 -1
  10. package/dist/cjs/src/primitives/SymmetricKey.js +123 -1
  11. package/dist/cjs/src/primitives/SymmetricKey.js.map +1 -1
  12. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  13. package/dist/esm/src/identity/ContactsManager.js +44 -6
  14. package/dist/esm/src/identity/ContactsManager.js.map +1 -1
  15. package/dist/esm/src/identity/IdentityClient.js +106 -37
  16. package/dist/esm/src/identity/IdentityClient.js.map +1 -1
  17. package/dist/esm/src/overlay-tools/LookupResolver.js +85 -58
  18. package/dist/esm/src/overlay-tools/LookupResolver.js.map +1 -1
  19. package/dist/esm/src/primitives/Hash.js +177 -50
  20. package/dist/esm/src/primitives/Hash.js.map +1 -1
  21. package/dist/esm/src/primitives/SymmetricKey.js +123 -1
  22. package/dist/esm/src/primitives/SymmetricKey.js.map +1 -1
  23. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  24. package/dist/types/src/identity/ContactsManager.d.ts +13 -2
  25. package/dist/types/src/identity/ContactsManager.d.ts.map +1 -1
  26. package/dist/types/src/identity/IdentityClient.d.ts +50 -24
  27. package/dist/types/src/identity/IdentityClient.d.ts.map +1 -1
  28. package/dist/types/src/overlay-tools/LookupResolver.d.ts +14 -1
  29. package/dist/types/src/overlay-tools/LookupResolver.d.ts.map +1 -1
  30. package/dist/types/src/primitives/Hash.d.ts +21 -16
  31. package/dist/types/src/primitives/Hash.d.ts.map +1 -1
  32. package/dist/types/src/primitives/SymmetricKey.d.ts.map +1 -1
  33. package/dist/types/src/wallet/Wallet.interfaces.d.ts +16 -1
  34. package/dist/types/src/wallet/Wallet.interfaces.d.ts.map +1 -1
  35. package/dist/types/src/wallet/WalletClient.d.ts +1 -1
  36. package/dist/types/src/wallet/WalletClient.d.ts.map +1 -1
  37. package/dist/types/src/wallet/substrates/window.CWI.d.ts +1 -1
  38. package/dist/types/src/wallet/substrates/window.CWI.d.ts.map +1 -1
  39. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  40. package/dist/umd/bundle.js +3 -3
  41. package/package.json +1 -1
  42. package/src/identity/ContactsManager.ts +47 -6
  43. package/src/identity/IdentityClient.ts +137 -53
  44. package/src/identity/__tests/IdentityClient.additional.test.ts +150 -1
  45. package/src/identity/__tests/IdentityClient.test.ts +4 -4
  46. package/src/overlay-tools/LookupResolver.ts +94 -57
  47. package/src/primitives/Hash.ts +232 -96
  48. package/src/primitives/SymmetricKey.ts +145 -1
  49. package/src/primitives/__tests/Hash.additional.test.ts +65 -0
  50. package/src/primitives/__tests/Hash.test.ts +6 -1
  51. package/src/wallet/Wallet.interfaces.ts +16 -1
  52. package/src/wallet/WalletClient.ts +1 -1
  53. package/src/wallet/substrates/window.CWI.ts +1 -1
@@ -349,10 +349,141 @@ function zero8 (word: string): string {
349
349
  }
350
350
  }
351
351
 
352
+ const BufferCtor =
353
+ typeof globalThis === 'undefined' ? undefined : (globalThis as any).Buffer
354
+ const CAN_USE_BUFFER =
355
+ BufferCtor != null && typeof BufferCtor.from === 'function'
356
+ const HEX_DIGITS = '0123456789abcdef'
357
+ const HEX_BYTE_STRINGS = new Array<string>(256)
358
+ for (let i = 0; i < HEX_BYTE_STRINGS.length; i++) {
359
+ HEX_BYTE_STRINGS[i] = HEX_DIGITS[(i >> 4) & 0xf] + HEX_DIGITS[i & 0xf]
360
+ }
361
+
352
362
  function bytesToHex (data: Uint8Array): string {
353
- let res = ''
354
- for (const b of data) res += (b.toString(16).padStart(2, '0'))
355
- return res
363
+ if (CAN_USE_BUFFER) {
364
+ return BufferCtor.from(data).toString('hex')
365
+ }
366
+ const out = new Array<string>(data.length)
367
+ for (let i = 0; i < data.length; i++) out[i] = HEX_BYTE_STRINGS[data[i]]
368
+ return out.join('')
369
+ }
370
+
371
+ const NODE_CRYPTO = (() => {
372
+ const processLike =
373
+ typeof globalThis === 'undefined' ? undefined : (globalThis as any).process
374
+ const getBuiltinModule = processLike?.getBuiltinModule
375
+ if (typeof getBuiltinModule === 'function') {
376
+ try {
377
+ const crypto = getBuiltinModule.call(processLike, 'node:crypto')
378
+ if (crypto != null) return crypto
379
+ } catch {
380
+ // continue to CommonJS fallback
381
+ }
382
+ }
383
+
384
+ try {
385
+ if (typeof require === 'function') {
386
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
387
+ return require('node:crypto')
388
+ }
389
+ } catch {
390
+ // node:crypto is unavailable in this runtime
391
+ }
392
+ return undefined
393
+ })()
394
+
395
+ type HashInput = Uint8Array | number[] | string
396
+
397
+ function toHashBytes (msg: HashInput, enc?: 'hex' | 'utf8'): Uint8Array {
398
+ if (msg instanceof Uint8Array) {
399
+ return msg
400
+ }
401
+ if (Array.isArray(msg)) {
402
+ return new Uint8Array(msg)
403
+ }
404
+ return Uint8Array.from(toArray(msg, enc))
405
+ }
406
+
407
+ function toHashKeyBytes (key: HashInput): Uint8Array {
408
+ return typeof key === 'string' ? toHashBytes(key, 'hex') : toHashBytes(key)
409
+ }
410
+
411
+ interface FallbackHashLike {
412
+ update: (data: Uint8Array) => unknown
413
+ digest: () => Uint8Array
414
+ }
415
+
416
+ function updateNativeOrFallback (
417
+ native: any,
418
+ fallback: FallbackHashLike | undefined,
419
+ data: Uint8Array
420
+ ): void {
421
+ if (native != null) {
422
+ native.update(data)
423
+ } else if (fallback != null) {
424
+ fallback.update(data)
425
+ }
426
+ }
427
+
428
+ function digestNativeOrFallback (
429
+ native: any,
430
+ fallback: FallbackHashLike | undefined
431
+ ): number[] {
432
+ if (native != null) return Array.from(native.digest())
433
+ if (fallback != null) return Array.from(fallback.digest())
434
+ return []
435
+ }
436
+
437
+ function digestHexNativeOrFallback (
438
+ native: any,
439
+ fallback: FallbackHashLike | undefined
440
+ ): string {
441
+ if (native != null) return native.digest('hex')
442
+ if (fallback != null) return bytesToHex(fallback.digest())
443
+ return ''
444
+ }
445
+
446
+ function createNodeHash (algorithm: string): any {
447
+ const createHash = NODE_CRYPTO?.createHash
448
+ if (typeof createHash !== 'function') return undefined
449
+ try {
450
+ return createHash(algorithm)
451
+ } catch {
452
+ return undefined
453
+ }
454
+ }
455
+
456
+ function createNodeHmac (algorithm: string, keyBytes: Uint8Array): any {
457
+ const createHmac = NODE_CRYPTO?.createHmac
458
+ if (typeof createHmac !== 'function') return undefined
459
+ try {
460
+ return createHmac(algorithm, keyBytes)
461
+ } catch {
462
+ return undefined
463
+ }
464
+ }
465
+
466
+ function digestWithNodeHash (
467
+ algorithm: string,
468
+ msg: HashInput,
469
+ enc?: 'hex' | 'utf8'
470
+ ): Uint8Array | undefined {
471
+ const hash = createNodeHash(algorithm)
472
+ if (hash == null) return undefined
473
+ hash.update(toHashBytes(msg, enc))
474
+ return hash.digest()
475
+ }
476
+
477
+ function digestWithNodeHmac (
478
+ algorithm: string,
479
+ key: HashInput,
480
+ msg: HashInput,
481
+ enc?: 'hex' | 'utf8'
482
+ ): Uint8Array | undefined {
483
+ const hmac = createNodeHmac(algorithm, toHashKeyBytes(key))
484
+ if (hmac == null) return undefined
485
+ hmac.update(toHashBytes(msg, enc))
486
+ return hmac.digest()
356
487
  }
357
488
 
358
489
  function join32 (msg, start, end, endian): number[] {
@@ -631,27 +762,27 @@ export class RIPEMD160 extends BaseHash {
631
762
  * @property k - The round constants used for each round of SHA-256
632
763
  */
633
764
  export class SHA256 {
634
- private readonly h: FastSHA256
765
+ private readonly h?: FastSHA256
766
+ private readonly native?: any
767
+
635
768
  constructor () {
636
- this.h = new FastSHA256()
769
+ this.native = createNodeHash('sha256')
770
+ if (this.native == null) {
771
+ this.h = new FastSHA256()
772
+ }
637
773
  }
638
774
 
639
- update (
640
- msg: Uint8Array | number[] | string,
641
- enc?: 'hex' | 'utf8'
642
- ): this {
643
- const data =
644
- msg instanceof Uint8Array ? msg : Uint8Array.from(toArray(msg, enc))
645
- this.h.update(data)
775
+ update (msg: HashInput, enc?: 'hex' | 'utf8'): this {
776
+ updateNativeOrFallback(this.native, this.h, toHashBytes(msg, enc))
646
777
  return this
647
778
  }
648
779
 
649
780
  digest (): number[] {
650
- return Array.from(this.h.digest())
781
+ return digestNativeOrFallback(this.native, this.h)
651
782
  }
652
783
 
653
784
  digestHex (): string {
654
- return bytesToHex(this.h.digest())
785
+ return digestHexNativeOrFallback(this.native, this.h)
655
786
  }
656
787
  }
657
788
 
@@ -755,23 +886,27 @@ export class SHA1 extends BaseHash {
755
886
  * @property k - The round constants used for each round of SHA-512.
756
887
  */
757
888
  export class SHA512 {
758
- private readonly h: FastSHA512
889
+ private readonly h?: FastSHA512
890
+ private readonly native?: any
891
+
759
892
  constructor () {
760
- this.h = new FastSHA512()
893
+ this.native = createNodeHash('sha512')
894
+ if (this.native == null) {
895
+ this.h = new FastSHA512()
896
+ }
761
897
  }
762
898
 
763
- update (msg: number[] | string, enc?: 'hex' | 'utf8'): this {
764
- const data = Uint8Array.from(toArray(msg, enc))
765
- this.h.update(data)
899
+ update (msg: HashInput, enc?: 'hex' | 'utf8'): this {
900
+ updateNativeOrFallback(this.native, this.h, toHashBytes(msg, enc))
766
901
  return this
767
902
  }
768
903
 
769
904
  digest (): number[] {
770
- return Array.from(this.h.digest())
905
+ return digestNativeOrFallback(this.native, this.h)
771
906
  }
772
907
 
773
908
  digestHex (): string {
774
- return bytesToHex(this.h.digest())
909
+ return digestHexNativeOrFallback(this.native, this.h)
775
910
  }
776
911
  }
777
912
 
@@ -788,7 +923,8 @@ export class SHA512 {
788
923
  * @property outSize - The output size of the SHA-256 hash function, in bytes. It's set to 32 bytes.
789
924
  */
790
925
  export class SHA256HMAC {
791
- private readonly h: HMAC<FastSHA256>
926
+ private readonly h?: HMAC<FastSHA256>
927
+ private readonly native?: any
792
928
  blockSize = 64
793
929
  outSize = 32
794
930
 
@@ -805,17 +941,12 @@ export class SHA256HMAC {
805
941
  * @example
806
942
  * const myHMAC = new SHA256HMAC('deadbeef');
807
943
  */
808
- constructor (key: Uint8Array | number[] | string) {
809
- const k =
810
- key instanceof Uint8Array
811
- ? key
812
- : Uint8Array.from(
813
- toArray(
814
- key,
815
- typeof key === 'string' ? 'hex' : undefined
816
- )
817
- )
818
- this.h = new HMAC(sha256Fast, k)
944
+ constructor (key: HashInput) {
945
+ const k = toHashKeyBytes(key)
946
+ this.native = createNodeHmac('sha256', k)
947
+ if (this.native == null) {
948
+ this.h = new HMAC(sha256Fast, k)
949
+ }
819
950
  }
820
951
 
821
952
  /**
@@ -829,10 +960,8 @@ export class SHA256HMAC {
829
960
  * @example
830
961
  * myHMAC.update('deadbeef', 'hex');
831
962
  */
832
- update (msg: Uint8Array | number[] | string, enc?: 'hex'): this {
833
- const data =
834
- msg instanceof Uint8Array ? msg : Uint8Array.from(toArray(msg, enc))
835
- this.h.update(data)
963
+ update (msg: HashInput, enc?: 'hex'): this {
964
+ updateNativeOrFallback(this.native, this.h, toHashBytes(msg, enc))
836
965
  return this
837
966
  }
838
967
 
@@ -846,7 +975,7 @@ export class SHA256HMAC {
846
975
  * let hashedMessage = myHMAC.digest();
847
976
  */
848
977
  digest (): number[] {
849
- return Array.from(this.h.digest())
978
+ return digestNativeOrFallback(this.native, this.h)
850
979
  }
851
980
 
852
981
  /**
@@ -859,7 +988,7 @@ export class SHA256HMAC {
859
988
  * let hashedMessage = myHMAC.digestHex();
860
989
  */
861
990
  digestHex (): string {
862
- return bytesToHex(this.h.digest())
991
+ return digestHexNativeOrFallback(this.native, this.h)
863
992
  }
864
993
  }
865
994
 
@@ -922,7 +1051,8 @@ export class SHA1HMAC {
922
1051
  * @property outSize - The output size of the SHA-512 hash function, in bytes. It's set to 64 bytes.
923
1052
  */
924
1053
  export class SHA512HMAC {
925
- private readonly h: HMAC<FastSHA512>
1054
+ private readonly h?: HMAC<FastSHA512>
1055
+ private readonly native?: any
926
1056
  blockSize = 128
927
1057
  outSize = 32
928
1058
 
@@ -939,17 +1069,12 @@ export class SHA512HMAC {
939
1069
  * @example
940
1070
  * const myHMAC = new SHA512HMAC('deadbeef');
941
1071
  */
942
- constructor (key: Uint8Array | number[] | string) {
943
- const k =
944
- key instanceof Uint8Array
945
- ? key
946
- : Uint8Array.from(
947
- toArray(
948
- key,
949
- typeof key === 'string' ? 'hex' : undefined
950
- )
951
- )
952
- this.h = new HMAC(sha512Fast, k)
1072
+ constructor (key: HashInput) {
1073
+ const k = toHashKeyBytes(key)
1074
+ this.native = createNodeHmac('sha512', k)
1075
+ if (this.native == null) {
1076
+ this.h = new HMAC(sha512Fast, k)
1077
+ }
953
1078
  }
954
1079
 
955
1080
  /**
@@ -963,10 +1088,8 @@ export class SHA512HMAC {
963
1088
  * @example
964
1089
  * myHMAC.update('deadbeef', 'hex');
965
1090
  */
966
- update (msg: Uint8Array | number[] | string, enc?: 'hex' | 'utf8'): this {
967
- const data =
968
- msg instanceof Uint8Array ? msg : Uint8Array.from(toArray(msg, enc))
969
- this.h.update(data)
1091
+ update (msg: HashInput, enc?: 'hex' | 'utf8'): this {
1092
+ updateNativeOrFallback(this.native, this.h, toHashBytes(msg, enc))
970
1093
  return this
971
1094
  }
972
1095
 
@@ -980,7 +1103,7 @@ export class SHA512HMAC {
980
1103
  * let hashedMessage = myHMAC.digest();
981
1104
  */
982
1105
  digest (): number[] {
983
- return Array.from(this.h.digest())
1106
+ return digestNativeOrFallback(this.native, this.h)
984
1107
  }
985
1108
 
986
1109
  /**
@@ -993,10 +1116,32 @@ export class SHA512HMAC {
993
1116
  * let hashedMessage = myHMAC.digestHex();
994
1117
  */
995
1118
  digestHex (): string {
996
- return bytesToHex(this.h.digest())
1119
+ return digestHexNativeOrFallback(this.native, this.h)
997
1120
  }
998
1121
  }
999
1122
 
1123
+ function sha256Bytes (msg: HashInput, enc?: 'hex' | 'utf8'): Uint8Array {
1124
+ const native = digestWithNodeHash('sha256', msg, enc)
1125
+ if (native != null) return native
1126
+ return new FastSHA256().update(toHashBytes(msg, enc)).digest()
1127
+ }
1128
+
1129
+ function sha512Bytes (
1130
+ msg: HashInput,
1131
+ enc?: 'hex' | 'utf8'
1132
+ ): Uint8Array {
1133
+ const native = digestWithNodeHash('sha512', msg, enc)
1134
+ if (native != null) return native
1135
+ return new FastSHA512().update(toHashBytes(msg, enc)).digest()
1136
+ }
1137
+
1138
+ function ripemd160Bytes (
1139
+ msg: HashInput,
1140
+ enc?: 'hex' | 'utf8'
1141
+ ): Uint8Array | undefined {
1142
+ return digestWithNodeHash('ripemd160', msg, enc)
1143
+ }
1144
+
1000
1145
  /**
1001
1146
  * Computes RIPEMD160 hash of a given message.
1002
1147
  * @function ripemd160
@@ -1012,6 +1157,8 @@ export const ripemd160 = (
1012
1157
  msg: number[] | string,
1013
1158
  enc?: 'hex' | 'utf8'
1014
1159
  ): number[] => {
1160
+ const native = ripemd160Bytes(msg, enc)
1161
+ if (native != null) return Array.from(native)
1015
1162
  return new RIPEMD160().update(msg, enc).digest()
1016
1163
  }
1017
1164
 
@@ -1044,11 +1191,8 @@ export const sha1 = (
1044
1191
  * @example
1045
1192
  * const digest = sha256('Hello, world!');
1046
1193
  */
1047
- export const sha256 = (
1048
- msg: Uint8Array | number[] | string,
1049
- enc?: 'hex' | 'utf8'
1050
- ): number[] => {
1051
- return new SHA256().update(msg, enc).digest()
1194
+ export const sha256 = (msg: HashInput, enc?: 'hex' | 'utf8'): number[] => {
1195
+ return Array.from(sha256Bytes(msg, enc))
1052
1196
  }
1053
1197
 
1054
1198
  /**
@@ -1062,11 +1206,8 @@ export const sha256 = (
1062
1206
  * @example
1063
1207
  * const digest = sha512('Hello, world!');
1064
1208
  */
1065
- export const sha512 = (
1066
- msg: number[] | string,
1067
- enc?: 'hex' | 'utf8'
1068
- ): number[] => {
1069
- return new SHA512().update(msg, enc).digest()
1209
+ export const sha512 = (msg: HashInput, enc?: 'hex' | 'utf8'): number[] => {
1210
+ return Array.from(sha512Bytes(msg, enc))
1070
1211
  }
1071
1212
 
1072
1213
  /**
@@ -1082,12 +1223,8 @@ export const sha512 = (
1082
1223
  * @example
1083
1224
  * const doubleHash = hash256('Hello, world!');
1084
1225
  */
1085
- export const hash256 = (
1086
- msg: Uint8Array | number[] | string,
1087
- enc?: 'hex' | 'utf8'
1088
- ): number[] => {
1089
- const first = new SHA256().update(msg, enc).digest()
1090
- return new SHA256().update(first).digest()
1226
+ export const hash256 = (msg: HashInput, enc?: 'hex' | 'utf8'): number[] => {
1227
+ return Array.from(sha256Bytes(sha256Bytes(msg, enc)))
1091
1228
  }
1092
1229
 
1093
1230
  /**
@@ -1102,11 +1239,10 @@ export const hash256 = (
1102
1239
  * @example
1103
1240
  * const hash = hash160('Hello, world!');
1104
1241
  */
1105
- export const hash160 = (
1106
- msg: Uint8Array | number[] | string,
1107
- enc?: 'hex' | 'utf8'
1108
- ): number[] => {
1109
- const first = new SHA256().update(msg, enc).digest()
1242
+ export const hash160 = (msg: HashInput, enc?: 'hex' | 'utf8'): number[] => {
1243
+ const first = sha256Bytes(msg, enc)
1244
+ const native = ripemd160Bytes(first)
1245
+ if (native != null) return Array.from(native)
1110
1246
  return new RIPEMD160().update(first).digest()
1111
1247
  }
1112
1248
 
@@ -1123,10 +1259,12 @@ export const hash160 = (
1123
1259
  * const digest = sha256hmac('deadbeef', 'ffff001d');
1124
1260
  */
1125
1261
  export const sha256hmac = (
1126
- key: Uint8Array | number[] | string,
1127
- msg: Uint8Array | number[] | string,
1262
+ key: HashInput,
1263
+ msg: HashInput,
1128
1264
  enc?: 'hex'
1129
1265
  ): number[] => {
1266
+ const native = digestWithNodeHmac('sha256', key, msg, enc)
1267
+ if (native != null) return Array.from(native)
1130
1268
  return new SHA256HMAC(key).update(msg, enc).digest()
1131
1269
  }
1132
1270
 
@@ -1143,10 +1281,12 @@ export const sha256hmac = (
1143
1281
  * const digest = sha512hmac('deadbeef', 'ffff001d');
1144
1282
  */
1145
1283
  export const sha512hmac = (
1146
- key: Uint8Array | number[] | string,
1147
- msg: Uint8Array | number[] | string,
1284
+ key: HashInput,
1285
+ msg: HashInput,
1148
1286
  enc?: 'hex'
1149
1287
  ): number[] => {
1288
+ const native = digestWithNodeHmac('sha512', key, msg, enc)
1289
+ if (native != null) return Array.from(native)
1150
1290
  return new SHA512HMAC(key).update(msg, enc).digest()
1151
1291
  }
1152
1292
 
@@ -1938,20 +2078,16 @@ export function pbkdf2 (
1938
2078
  if (digest !== 'sha512') {
1939
2079
  throw new Error('Only sha512 is supported in this PBKDF2 implementation')
1940
2080
  }
1941
- // Attempt to use the native Node.js implementation if available as it is
1942
- // considerably faster than the pure TypeScript fallback below. If the crypto
1943
- // module isn't present (for example in a browser build) we'll silently fall
1944
- // back to the original implementation.
1945
- try {
1946
- // eslint-disable-next-line @typescript-eslint/no-var-requires
1947
- const nodeCrypto = require('node:crypto')
1948
- if (typeof nodeCrypto.pbkdf2Sync === 'function') {
1949
- const p = Buffer.from(password)
1950
- const s = Buffer.from(salt)
1951
- return [...nodeCrypto.pbkdf2Sync(p, s, iterations, keylen, digest)]
1952
- }
1953
- } catch {
1954
- // ignore
2081
+ const pbkdf2Sync = NODE_CRYPTO?.pbkdf2Sync
2082
+ if (typeof pbkdf2Sync === 'function') {
2083
+ const out = pbkdf2Sync(
2084
+ toHashBytes(password),
2085
+ toHashBytes(salt),
2086
+ iterations,
2087
+ keylen,
2088
+ digest
2089
+ )
2090
+ return Array.from(out)
1955
2091
  }
1956
2092
  const p = Uint8Array.from(password)
1957
2093
  const s = Uint8Array.from(salt)
@@ -3,6 +3,124 @@ import { AESGCM, AESGCMDecrypt } from './AESGCM.js'
3
3
  import Random from './Random.js'
4
4
  import { toArray, encode } from './utils.js'
5
5
 
6
+ // ---------------------------------------------------------------------------
7
+ // Native AES-GCM fast-path via node:crypto / react-native-quick-crypto
8
+ //
9
+ // Resolved once at module load using the same pattern as Hash.ts. When
10
+ // `node:crypto` (or a compatible shim) is available and exposes
11
+ // `createCipheriv` / `createDecipheriv`, encrypt and decrypt will use it
12
+ // instead of the pure-TS implementation. The pure-TS path remains the
13
+ // unconditional fallback — any error in the native path causes silent
14
+ // re-execution through the pure-TS implementation.
15
+ // ---------------------------------------------------------------------------
16
+ const NODE_CRYPTO_SYM = (() => {
17
+ const processLike =
18
+ typeof globalThis === 'undefined' ? undefined : (globalThis as any).process
19
+ const getBuiltinModule = processLike?.getBuiltinModule
20
+ if (typeof getBuiltinModule === 'function') {
21
+ try {
22
+ const crypto = getBuiltinModule.call(processLike, 'node:crypto')
23
+ if (crypto != null) return crypto
24
+ } catch {
25
+ // continue to CommonJS fallback
26
+ }
27
+ }
28
+ try {
29
+ if (typeof require === 'function') {
30
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
31
+ return require('node:crypto')
32
+ }
33
+ } catch {
34
+ // node:crypto is unavailable in this runtime
35
+ }
36
+ return undefined
37
+ })()
38
+
39
+ /** True when the runtime provides a usable createCipheriv for aes-256-gcm. */
40
+ const NATIVE_AES_GCM_AVAILABLE: boolean = (() => {
41
+ if (NODE_CRYPTO_SYM == null) return false
42
+ return (
43
+ typeof NODE_CRYPTO_SYM.createCipheriv === 'function' &&
44
+ typeof NODE_CRYPTO_SYM.createDecipheriv === 'function'
45
+ )
46
+ })()
47
+
48
+ /**
49
+ * Encrypt `plaintext` with AES-256-GCM via node:crypto.
50
+ * Returns `iv (32 bytes) || ciphertext || authTag (16 bytes)` — identical
51
+ * layout to the pure-TS AESGCM path used by SymmetricKey.encrypt.
52
+ *
53
+ * Returns `null` on any failure so the caller can fall back to pure-TS.
54
+ */
55
+ function nativeEncrypt (
56
+ plaintext: Uint8Array,
57
+ iv: Uint8Array,
58
+ key: Uint8Array
59
+ ): Uint8Array | null {
60
+ try {
61
+ const cipher = NODE_CRYPTO_SYM.createCipheriv(
62
+ 'aes-256-gcm',
63
+ Buffer.from(key.buffer, key.byteOffset, key.byteLength),
64
+ Buffer.from(iv.buffer, iv.byteOffset, iv.byteLength)
65
+ )
66
+ const encrypted: Buffer = Buffer.concat([
67
+ cipher.update(Buffer.from(plaintext.buffer, plaintext.byteOffset, plaintext.byteLength)),
68
+ cipher.final()
69
+ ])
70
+ const authTag: Buffer = cipher.getAuthTag() // always 16 bytes for GCM
71
+
72
+ const out = new Uint8Array(iv.length + encrypted.length + authTag.length)
73
+ let offset = 0
74
+ out.set(iv, offset); offset += iv.length
75
+ out.set(encrypted, offset); offset += encrypted.length
76
+ out.set(authTag, offset)
77
+ return out
78
+ } catch {
79
+ return null
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Decrypt an `iv || ciphertext || authTag` bundle produced by nativeEncrypt
85
+ * (or by the pure-TS SymmetricKey.encrypt path) using node:crypto.
86
+ *
87
+ * Returns the plaintext on success, `null` on authentication failure, or
88
+ * `undefined` to signal a non-auth error so the caller can fall back.
89
+ */
90
+ function nativeDecrypt (
91
+ msgBytes: Uint8Array,
92
+ ivLength: number,
93
+ tagLength: number,
94
+ key: Uint8Array
95
+ ): Uint8Array | null | undefined {
96
+ try {
97
+ const iv = msgBytes.slice(0, ivLength)
98
+ const tagStart = msgBytes.length - tagLength
99
+ const ciphertext = msgBytes.slice(ivLength, tagStart)
100
+ const messageTag = msgBytes.slice(tagStart)
101
+
102
+ const decipher = NODE_CRYPTO_SYM.createDecipheriv(
103
+ 'aes-256-gcm',
104
+ Buffer.from(key.buffer, key.byteOffset, key.byteLength),
105
+ Buffer.from(iv.buffer, iv.byteOffset, iv.byteLength)
106
+ )
107
+ decipher.setAuthTag(Buffer.from(messageTag.buffer, messageTag.byteOffset, messageTag.byteLength))
108
+
109
+ // Decryption authenticates on final(); throws if tag is wrong.
110
+ const decrypted: Buffer = Buffer.concat([
111
+ decipher.update(Buffer.from(ciphertext.buffer, ciphertext.byteOffset, ciphertext.byteLength)),
112
+ decipher.final()
113
+ ])
114
+ return new Uint8Array(decrypted.buffer, decrypted.byteOffset, decrypted.byteLength)
115
+ } catch {
116
+ // Node throws "Unsupported state or unable to authenticate data" on auth
117
+ // failure. Treat any error as auth failure so SymmetricKey.decrypt re-throws
118
+ // its own descriptive message — pure-TS fallback would return null in the
119
+ // same scenario.
120
+ return null
121
+ }
122
+ }
123
+
6
124
  /**
7
125
  * `SymmetricKey` is a class that extends the `BigNumber` class and implements symmetric encryption and decryption methods.
8
126
  * Symmetric-Key encryption is a form of encryption where the same key is used to encrypt and decrypt the message.
@@ -45,6 +163,16 @@ export default class SymmetricKey extends BigNumber {
45
163
  const msgBytes = new Uint8Array(toArray(msg, enc))
46
164
  const keyBytes = new Uint8Array(this.toArray('be', 32))
47
165
 
166
+ // Fast path: native AES-256-GCM via node:crypto / react-native-quick-crypto.
167
+ // Falls back to pure-TS on any failure.
168
+ if (NATIVE_AES_GCM_AVAILABLE) {
169
+ const nativeResult = nativeEncrypt(msgBytes, iv, keyBytes)
170
+ if (nativeResult !== null) {
171
+ return encode(Array.from(nativeResult), enc)
172
+ }
173
+ }
174
+
175
+ // Pure-TS fallback.
48
176
  const { result, authenticationTag } = AESGCM(
49
177
  msgBytes,
50
178
  iv,
@@ -90,12 +218,28 @@ export default class SymmetricKey extends BigNumber {
90
218
  throw new Error('Ciphertext too short')
91
219
  }
92
220
 
221
+ const keyBytes = new Uint8Array(this.toArray('be', 32))
222
+
223
+ // Fast path: native AES-256-GCM via node:crypto / react-native-quick-crypto.
224
+ // Falls back to pure-TS on null/undefined return.
225
+ if (NATIVE_AES_GCM_AVAILABLE) {
226
+ const nativeResult = nativeDecrypt(msgBytes, ivLength, tagLength, keyBytes)
227
+ if (nativeResult !== undefined) {
228
+ // nativeResult is Uint8Array on success or null on auth/decryption failure.
229
+ if (nativeResult === null) {
230
+ throw new Error('Decryption failed!')
231
+ }
232
+ return encode(Array.from(nativeResult), enc)
233
+ }
234
+ // undefined means unexpected setup error — fall through to pure-TS.
235
+ }
236
+
237
+ // Pure-TS fallback.
93
238
  const iv = msgBytes.slice(0, ivLength)
94
239
  const tagStart = msgBytes.length - tagLength
95
240
  const ciphertext = msgBytes.slice(ivLength, tagStart)
96
241
  const messageTag = msgBytes.slice(tagStart)
97
242
 
98
- const keyBytes = new Uint8Array(this.toArray('be', 32))
99
243
  const result = AESGCMDecrypt(
100
244
  ciphertext,
101
245
  iv,