@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/CHANGELOG.md +4 -0
- package/README.md +2 -2
- package/mobile/out/src/CWIStyleWalletManager.d.ts.map +1 -1
- package/mobile/out/src/CWIStyleWalletManager.js +39 -4
- package/mobile/out/src/CWIStyleWalletManager.js.map +1 -1
- package/mobile/out/src/WalletPermissionsManager.d.ts.map +1 -1
- package/mobile/out/src/WalletPermissionsManager.js +10 -29
- package/mobile/out/src/WalletPermissionsManager.js.map +1 -1
- package/mobile/package-lock.json +6 -6
- package/mobile/package.json +2 -2
- package/out/src/CWIStyleWalletManager.d.ts.map +1 -1
- package/out/src/CWIStyleWalletManager.js +39 -4
- package/out/src/CWIStyleWalletManager.js.map +1 -1
- package/out/src/WalletPermissionsManager.d.ts.map +1 -1
- package/out/src/WalletPermissionsManager.js +10 -29
- package/out/src/WalletPermissionsManager.js.map +1 -1
- package/out/test/bsv-ts-sdk/LocalKVStore.test.js +6 -2
- package/out/test/bsv-ts-sdk/LocalKVStore.test.js.map +1 -1
- package/out/tsconfig.all.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/CWIStyleWalletManager.ts +61 -4
- package/src/WalletPermissionsManager.ts +10 -29
- package/test/bsv-ts-sdk/LocalKVStore.test.ts +16 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bsv/wallet-toolbox",
|
|
3
|
-
"version": "1.5.
|
|
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.
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
//
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 => {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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) {
|