@bsv/wallet-toolbox 1.5.6 → 1.5.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/wallet-toolbox",
3
- "version": "1.5.6",
3
+ "version": "1.5.8",
4
4
  "description": "BRC100 conforming wallet, wallet storage and wallet signer components",
5
5
  "main": "./out/src/index.js",
6
6
  "types": "./out/src/index.d.ts",
@@ -32,7 +32,7 @@
32
32
  "dependencies": {
33
33
  "@bsv/auth-express-middleware": "^1.2.0",
34
34
  "@bsv/payment-express-middleware": "^1.2.1",
35
- "@bsv/sdk": "^1.6.8",
35
+ "@bsv/sdk": "^1.6.11",
36
36
  "express": "^4.21.2",
37
37
  "idb": "^8.0.2",
38
38
  "knex": "^3.1.0",
@@ -70,6 +70,57 @@ import { PrivilegedKeyManager } from './sdk/PrivilegedKeyManager'
70
70
  */
71
71
  export const PBKDF2_NUM_ROUNDS = 7777
72
72
 
73
+ /**
74
+ * PBKDF-2 that prefers the browser / Node 20+ WebCrypto implementation and
75
+ * silently falls back to the existing JS code.
76
+ *
77
+ * @param passwordBytes Raw password bytes.
78
+ * @param salt Salt bytes.
79
+ * @param iterations Number of rounds.
80
+ * @param keyLen Desired key length in bytes.
81
+ * @param hash Digest algorithm (default "sha512").
82
+ * @returns Derived key bytes.
83
+ */
84
+ async function pbkdf2NativeOrJs(
85
+ passwordBytes: number[],
86
+ salt: number[],
87
+ iterations: number,
88
+ keyLen: number,
89
+ hash: 'sha256' | 'sha512' = 'sha512'
90
+ ): Promise<number[]> {
91
+ // ----- fast-path: WebCrypto (both browser & recent Node expose globalThis.crypto.subtle)
92
+ const subtle = (globalThis as any)?.crypto?.subtle as SubtleCrypto | undefined
93
+ if (subtle) {
94
+ try {
95
+ const baseKey = await subtle.importKey(
96
+ 'raw',
97
+ new Uint8Array(passwordBytes),
98
+ { name: 'PBKDF2' },
99
+ /*extractable*/ false,
100
+ ['deriveBits']
101
+ )
102
+
103
+ const bits = await subtle.deriveBits(
104
+ {
105
+ name: 'PBKDF2',
106
+ salt: new Uint8Array(salt),
107
+ iterations,
108
+ hash: hash.toUpperCase() as AlgorithmIdentifier
109
+ },
110
+ baseKey,
111
+ keyLen * 8
112
+ )
113
+ return Array.from(new Uint8Array(bits))
114
+ } catch (err) {
115
+ console.warn('[pbkdf2] WebCrypto path failed → falling back to JS implementation', err)
116
+ /* fall through */
117
+ }
118
+ }
119
+
120
+ // ----- slow-path: old JavaScript implementation
121
+ return Hash.pbkdf2(passwordBytes, salt, iterations, keyLen, hash)
122
+ }
123
+
73
124
  /**
74
125
  * Unique Identifier for the default profile (16 zero bytes).
75
126
  */
@@ -734,7 +785,7 @@ export class CWIStyleWalletManager implements WalletInterface {
734
785
  if (!this.currentUMPToken) {
735
786
  throw new Error('Provide presentation or recovery key first.')
736
787
  }
737
- const derivedPasswordKey = Hash.pbkdf2(
788
+ const derivedPasswordKey = await pbkdf2NativeOrJs(
738
789
  Utils.toArray(password, 'utf8'),
739
790
  this.currentUMPToken.passwordSalt,
740
791
  PBKDF2_NUM_ROUNDS,
@@ -777,7 +828,13 @@ export class CWIStyleWalletManager implements WalletInterface {
777
828
  const recoveryKey = Random(32)
778
829
  await this.recoveryKeySaver(recoveryKey)
779
830
  const passwordSalt = Random(32)
780
- const passwordKey = Hash.pbkdf2(Utils.toArray(password, 'utf8'), passwordSalt, PBKDF2_NUM_ROUNDS, 32, 'sha512')
831
+ const passwordKey = await pbkdf2NativeOrJs(
832
+ Utils.toArray(password, 'utf8'),
833
+ passwordSalt,
834
+ PBKDF2_NUM_ROUNDS,
835
+ 32,
836
+ 'sha512'
837
+ )
781
838
  const rootPrimaryKey = Random(32)
782
839
  const rootPrivilegedKey = Random(32)
783
840
 
@@ -1177,7 +1234,7 @@ export class CWIStyleWalletManager implements WalletInterface {
1177
1234
  }
1178
1235
 
1179
1236
  const passwordSalt = Random(32)
1180
- const newPasswordKey = Hash.pbkdf2(
1237
+ const newPasswordKey = await pbkdf2NativeOrJs(
1181
1238
  Utils.toArray(newPassword, 'utf8'),
1182
1239
  passwordSalt,
1183
1240
  PBKDF2_NUM_ROUNDS,
@@ -1615,7 +1672,7 @@ export class CWIStyleWalletManager implements WalletInterface {
1615
1672
  })
1616
1673
 
1617
1674
  // Decrypt the root privileged key using the confirmed password
1618
- const derivedPasswordKey = Hash.pbkdf2(
1675
+ const derivedPasswordKey = await pbkdf2NativeOrJs(
1619
1676
  Utils.toArray(password, 'utf8'),
1620
1677
  this.currentUMPToken!.passwordSalt,
1621
1678
  PBKDF2_NUM_ROUNDS,
@@ -568,10 +568,13 @@ export class WalletPermissionsManager implements WalletInterface {
568
568
  }
569
569
  }
570
570
 
571
- // Update the cache regardless of ephemeral status
572
- const expiry = params.expiry || Math.floor(Date.now() / 1000) + 3600 * 24 * 30
573
- const key = this.buildRequestKey(matching.request as PermissionRequest)
574
- this.cachePermission(key, expiry)
571
+ // Only cache non-ephemeral permissions
572
+ // Ephemeral permissions should not be cached as they are one-time authorizations
573
+ if (!params.ephemeral) {
574
+ const expiry = params.expiry || Math.floor(Date.now() / 1000) + 3600 * 24 * 30
575
+ const key = this.buildRequestKey(matching.request as PermissionRequest)
576
+ this.cachePermission(key, expiry)
577
+ }
575
578
  }
576
579
 
577
580
  /**
@@ -825,10 +828,6 @@ export class WalletPermissionsManager implements WalletInterface {
825
828
  reason,
826
829
  renewal: false
827
830
  })
828
- if (granted) {
829
- // Unknown expiry until grantPermission() is called; cache for now with TTL only
830
- this.cachePermission(cacheKey, 0)
831
- }
832
831
  return granted
833
832
  }
834
833
  }
@@ -891,9 +890,6 @@ export class WalletPermissionsManager implements WalletInterface {
891
890
  reason,
892
891
  renewal: false
893
892
  })
894
- if (granted) {
895
- this.cachePermission(cacheKey, 0)
896
- }
897
893
  return granted
898
894
  }
899
895
  }
@@ -975,9 +971,6 @@ export class WalletPermissionsManager implements WalletInterface {
975
971
  reason,
976
972
  renewal: false
977
973
  })
978
- if (granted) {
979
- this.cachePermission(cacheKey, 0)
980
- }
981
974
  return granted
982
975
  }
983
976
  }
@@ -1026,7 +1019,7 @@ export class WalletPermissionsManager implements WalletInterface {
1026
1019
  `Spending authorization insufficient for ${satoshis}, no user consent (seekPermission=false).`
1027
1020
  )
1028
1021
  }
1029
- const granted = await this.requestPermissionFlow({
1022
+ return await this.requestPermissionFlow({
1030
1023
  type: 'spending',
1031
1024
  originator,
1032
1025
  spending: { satoshis, lineItems },
@@ -1034,27 +1027,19 @@ export class WalletPermissionsManager implements WalletInterface {
1034
1027
  renewal: true,
1035
1028
  previousToken: token
1036
1029
  })
1037
- if (granted) {
1038
- this.cachePermission(cacheKey, 0)
1039
- }
1040
- return granted
1041
1030
  }
1042
1031
  } else {
1043
1032
  // no token
1044
1033
  if (!seekPermission) {
1045
1034
  throw new Error(`No spending authorization found, (seekPermission=false).`)
1046
1035
  }
1047
- const granted = await this.requestPermissionFlow({
1036
+ return await this.requestPermissionFlow({
1048
1037
  type: 'spending',
1049
1038
  originator,
1050
1039
  spending: { satoshis, lineItems },
1051
1040
  reason,
1052
1041
  renewal: false
1053
1042
  })
1054
- if (granted) {
1055
- this.cachePermission(cacheKey, 0)
1056
- }
1057
- return granted
1058
1043
  }
1059
1044
  }
1060
1045
 
@@ -1102,7 +1087,7 @@ export class WalletPermissionsManager implements WalletInterface {
1102
1087
  }
1103
1088
 
1104
1089
  // 3) Let ensureProtocolPermission handle the rest.
1105
- const granted = await this.ensureProtocolPermission({
1090
+ return await this.ensureProtocolPermission({
1106
1091
  originator,
1107
1092
  privileged: false,
1108
1093
  protocolID: [1, `action label ${label}`],
@@ -1111,10 +1096,6 @@ export class WalletPermissionsManager implements WalletInterface {
1111
1096
  seekPermission,
1112
1097
  usageType: 'generic'
1113
1098
  })
1114
- if (granted) {
1115
- this.cachePermission(cacheKey, 0)
1116
- }
1117
- return granted
1118
1099
  }
1119
1100
 
1120
1101
  /**
@@ -53,18 +53,22 @@ describe('LocalKVStore tests', () => {
53
53
  })
54
54
 
55
55
  test('4 promise test', async () => {
56
- jest.useFakeTimers();
57
- let resolveNewLock: () => void = () => {};
58
- const newLock = new Promise<void>(resolve => { resolveNewLock = resolve; });
59
- const t = Date.now();
60
- setTimeout(() => { resolveNewLock(); }, 1000);
61
- jest.advanceTimersByTime(1000);
62
- await newLock;
63
- const elapsed = Date.now() - t;
64
- logger(`Elapsed time: ${elapsed} ms`);
65
- expect(elapsed).toBeGreaterThanOrEqual(1000);
66
- jest.useRealTimers();
67
- });
56
+ jest.useFakeTimers()
57
+ let resolveNewLock: () => void = () => {}
58
+ const newLock = new Promise<void>(resolve => {
59
+ resolveNewLock = resolve
60
+ })
61
+ const t = Date.now()
62
+ setTimeout(() => {
63
+ resolveNewLock()
64
+ }, 1000)
65
+ jest.advanceTimersByTime(1000)
66
+ await newLock
67
+ const elapsed = Date.now() - t
68
+ logger(`Elapsed time: ${elapsed} ms`)
69
+ expect(elapsed).toBeGreaterThanOrEqual(1000)
70
+ jest.useRealTimers()
71
+ })
68
72
 
69
73
  test('5 set x 4 get set x 4 get', async () => {
70
74
  for (const { storage, wallet } of ctxs) {