pwc-sdk-wallet 0.8.2 → 0.8.3
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 +3 -0
- package/dist/keyrings/SolanaKeyring.d.ts +50 -5
- package/dist/keyrings/SolanaKeyring.js +142 -20
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1881,6 +1881,9 @@ console.log('New account address:', newAccount.address);
|
|
|
1881
1881
|
// Add a new Solana account
|
|
1882
1882
|
const newSolanaAccount = await vault.addNewSolanaAccount();
|
|
1883
1883
|
console.log('New Solana account:', newSolanaAccount.address);
|
|
1884
|
+
|
|
1885
|
+
// Note: All Solana accounts are automatically persisted when you encrypt and save the vault.
|
|
1886
|
+
// When you load the vault later, all previously added accounts will be restored.
|
|
1884
1887
|
```
|
|
1885
1888
|
|
|
1886
1889
|
#### Add New TRON Account
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import { Keypair } from '@solana/web3.js';
|
|
2
|
+
/**
|
|
3
|
+
* Serialized keyring data structure
|
|
4
|
+
*/
|
|
5
|
+
export interface SerializedSolanaKeyring {
|
|
6
|
+
type: 'Solana';
|
|
7
|
+
mnemonic: string;
|
|
8
|
+
accounts: string[];
|
|
9
|
+
}
|
|
2
10
|
/**
|
|
3
11
|
* Solana keyring for managing multiple Solana accounts
|
|
4
12
|
* derived from a single mnemonic phrase using BIP-44 derivation paths.
|
|
@@ -9,12 +17,25 @@ export declare class SolanaKeyring {
|
|
|
9
17
|
private keypairs;
|
|
10
18
|
accounts: string[];
|
|
11
19
|
private mnemonic;
|
|
20
|
+
private seed;
|
|
21
|
+
private static seedCache;
|
|
22
|
+
private static readonly ERROR_INVALID_MNEMONIC;
|
|
23
|
+
private static readonly ERROR_INVALID_INDEX;
|
|
24
|
+
private static readonly ERROR_DUPLICATE_ACCOUNT;
|
|
25
|
+
private static readonly ERROR_ADDRESS_NOT_FOUND;
|
|
26
|
+
private static readonly ERROR_INVALID_DESERIALIZE;
|
|
27
|
+
private static readonly ERROR_ADDRESS_MISMATCH;
|
|
12
28
|
/**
|
|
13
29
|
* Creates a new SolanaKeyring instance from a mnemonic phrase.
|
|
14
30
|
* @param mnemonic - The mnemonic phrase (12, 15, 18, 21, or 24 words)
|
|
15
31
|
* @throws Error if the mnemonic is invalid
|
|
16
32
|
*/
|
|
17
33
|
constructor(mnemonic: string);
|
|
34
|
+
/**
|
|
35
|
+
* Gets or generates the seed for this keyring, using cache when available.
|
|
36
|
+
* @returns The seed buffer
|
|
37
|
+
*/
|
|
38
|
+
private getSeed;
|
|
18
39
|
/**
|
|
19
40
|
* Initializes the keyring by generating the seed and creating the first account.
|
|
20
41
|
* @param mnemonic - The mnemonic phrase to initialize from
|
|
@@ -23,21 +44,28 @@ export declare class SolanaKeyring {
|
|
|
23
44
|
/**
|
|
24
45
|
* Derives a Solana keypair at a specific index using BIP-44 derivation.
|
|
25
46
|
* @param seed - The seed buffer to derive from
|
|
26
|
-
* @param index - The account index to derive (0-based)
|
|
47
|
+
* @param index - The account index to derive (0-based, must be non-negative integer)
|
|
27
48
|
* @returns The derived Solana keypair
|
|
49
|
+
* @throws Error if index is invalid
|
|
28
50
|
*/
|
|
29
|
-
private deriveKeypair;
|
|
51
|
+
private static deriveKeypair;
|
|
30
52
|
/**
|
|
31
53
|
* Adds a new Solana account derived from the mnemonic at the next available index.
|
|
32
54
|
* @returns Promise resolving to the public key address of the newly created account
|
|
33
55
|
* @throws Error if account derivation fails or duplicate address is generated
|
|
34
56
|
*/
|
|
35
57
|
addNewAccount(): Promise<string>;
|
|
58
|
+
/**
|
|
59
|
+
* Validates a Solana address format.
|
|
60
|
+
* @param address - The address to validate
|
|
61
|
+
* @returns True if the address is valid, false otherwise
|
|
62
|
+
*/
|
|
63
|
+
static isValidAddress(address: string): boolean;
|
|
36
64
|
/**
|
|
37
65
|
* Gets the private key for a given address managed by this keyring.
|
|
38
66
|
* @param address - The account's public key address to get the private key for
|
|
39
67
|
* @returns Promise resolving to the private key as a hex string
|
|
40
|
-
* @throws Error if the address is not found in this keyring
|
|
68
|
+
* @throws Error if the address is not found in this keyring or is invalid
|
|
41
69
|
*/
|
|
42
70
|
getPrivateKeyForAddress(address: string): Promise<string>;
|
|
43
71
|
/**
|
|
@@ -47,9 +75,9 @@ export declare class SolanaKeyring {
|
|
|
47
75
|
getMnemonic(): string;
|
|
48
76
|
/**
|
|
49
77
|
* Serializes the keyring data for encryption and storage.
|
|
50
|
-
* @returns An object containing the keyring type and
|
|
78
|
+
* @returns An object containing the keyring type, mnemonic, and accounts
|
|
51
79
|
*/
|
|
52
|
-
serialize():
|
|
80
|
+
serialize(): SerializedSolanaKeyring;
|
|
53
81
|
/**
|
|
54
82
|
* Deserializes data into a SolanaKeyring instance.
|
|
55
83
|
* @param data - The serialized keyring data
|
|
@@ -61,6 +89,7 @@ export declare class SolanaKeyring {
|
|
|
61
89
|
* Gets the Solana keypair for a given address.
|
|
62
90
|
* @param address - The account's public key address
|
|
63
91
|
* @returns The Solana keypair or undefined if not found
|
|
92
|
+
* @throws Error if the address format is invalid
|
|
64
93
|
*/
|
|
65
94
|
getKeypair(address: string): Keypair | undefined;
|
|
66
95
|
/**
|
|
@@ -68,4 +97,20 @@ export declare class SolanaKeyring {
|
|
|
68
97
|
* @returns Array of all Solana keypairs
|
|
69
98
|
*/
|
|
70
99
|
getAllKeypairs(): Keypair[];
|
|
100
|
+
/**
|
|
101
|
+
* Clears sensitive data from memory.
|
|
102
|
+
* This should be called when the keyring is no longer needed to help prevent memory dumps.
|
|
103
|
+
* Note: This does not clear the seed cache, only instance-specific data.
|
|
104
|
+
*/
|
|
105
|
+
clearSensitiveData(): void;
|
|
106
|
+
/**
|
|
107
|
+
* Clears the seed cache to free up memory.
|
|
108
|
+
* Call this when you want to clear sensitive data from memory.
|
|
109
|
+
*/
|
|
110
|
+
static clearSeedCache(): void;
|
|
111
|
+
/**
|
|
112
|
+
* Gets the cache size for debugging purposes.
|
|
113
|
+
* @returns Number of cached seeds
|
|
114
|
+
*/
|
|
115
|
+
static getSeedCacheSize(): number;
|
|
71
116
|
}
|
|
@@ -39,6 +39,7 @@ const ed25519_hd_key_1 = require("ed25519-hd-key");
|
|
|
39
39
|
const bip39 = __importStar(require("bip39"));
|
|
40
40
|
const buffer_1 = require("buffer");
|
|
41
41
|
const chains_1 = require("../config/chains");
|
|
42
|
+
const EncryptionService_1 = require("../crypto/EncryptionService");
|
|
42
43
|
/**
|
|
43
44
|
* Solana keyring for managing multiple Solana accounts
|
|
44
45
|
* derived from a single mnemonic phrase using BIP-44 derivation paths.
|
|
@@ -54,29 +55,52 @@ class SolanaKeyring {
|
|
|
54
55
|
this.type = 'Solana';
|
|
55
56
|
this.keypairs = new Map();
|
|
56
57
|
this.accounts = [];
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
this.seed = null;
|
|
59
|
+
if (!EncryptionService_1.EncryptionService.validateMnemonic(mnemonic)) {
|
|
60
|
+
throw new Error(SolanaKeyring.ERROR_INVALID_MNEMONIC);
|
|
59
61
|
}
|
|
60
62
|
this.mnemonic = mnemonic;
|
|
61
63
|
this.initializeFromMnemonic(mnemonic);
|
|
62
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Gets or generates the seed for this keyring, using cache when available.
|
|
67
|
+
* @returns The seed buffer
|
|
68
|
+
*/
|
|
69
|
+
getSeed() {
|
|
70
|
+
if (this.seed) {
|
|
71
|
+
return this.seed;
|
|
72
|
+
}
|
|
73
|
+
// Check cache first
|
|
74
|
+
if (SolanaKeyring.seedCache.has(this.mnemonic)) {
|
|
75
|
+
this.seed = SolanaKeyring.seedCache.get(this.mnemonic);
|
|
76
|
+
return this.seed;
|
|
77
|
+
}
|
|
78
|
+
// Generate and cache seed
|
|
79
|
+
this.seed = bip39.mnemonicToSeedSync(this.mnemonic);
|
|
80
|
+
SolanaKeyring.seedCache.set(this.mnemonic, this.seed);
|
|
81
|
+
return this.seed;
|
|
82
|
+
}
|
|
63
83
|
/**
|
|
64
84
|
* Initializes the keyring by generating the seed and creating the first account.
|
|
65
85
|
* @param mnemonic - The mnemonic phrase to initialize from
|
|
66
86
|
*/
|
|
67
87
|
initializeFromMnemonic(mnemonic) {
|
|
68
|
-
const seed =
|
|
69
|
-
const firstKeypair =
|
|
88
|
+
const seed = this.getSeed();
|
|
89
|
+
const firstKeypair = SolanaKeyring.deriveKeypair(seed, 0);
|
|
70
90
|
this.keypairs.set(firstKeypair.publicKey.toString(), firstKeypair);
|
|
71
91
|
this.accounts.push(firstKeypair.publicKey.toString());
|
|
72
92
|
}
|
|
73
93
|
/**
|
|
74
94
|
* Derives a Solana keypair at a specific index using BIP-44 derivation.
|
|
75
95
|
* @param seed - The seed buffer to derive from
|
|
76
|
-
* @param index - The account index to derive (0-based)
|
|
96
|
+
* @param index - The account index to derive (0-based, must be non-negative integer)
|
|
77
97
|
* @returns The derived Solana keypair
|
|
98
|
+
* @throws Error if index is invalid
|
|
78
99
|
*/
|
|
79
|
-
deriveKeypair(seed, index) {
|
|
100
|
+
static deriveKeypair(seed, index) {
|
|
101
|
+
if (!Number.isInteger(index) || index < 0) {
|
|
102
|
+
throw new Error(SolanaKeyring.ERROR_INVALID_INDEX);
|
|
103
|
+
}
|
|
80
104
|
const path = `${chains_1.DERIVATION_PATHS.SOLANA.slice(0, -1)}${index}'`;
|
|
81
105
|
const derivedSeed = (0, ed25519_hd_key_1.derivePath)(path, seed.toString('hex')).key;
|
|
82
106
|
return web3_js_1.Keypair.fromSeed(derivedSeed);
|
|
@@ -88,25 +112,46 @@ class SolanaKeyring {
|
|
|
88
112
|
*/
|
|
89
113
|
async addNewAccount() {
|
|
90
114
|
const newIndex = this.accounts.length;
|
|
91
|
-
const seed =
|
|
92
|
-
const newKeypair =
|
|
93
|
-
|
|
94
|
-
|
|
115
|
+
const seed = this.getSeed();
|
|
116
|
+
const newKeypair = SolanaKeyring.deriveKeypair(seed, newIndex);
|
|
117
|
+
const newAddress = newKeypair.publicKey.toString();
|
|
118
|
+
if (this.accounts.includes(newAddress)) {
|
|
119
|
+
throw new Error(SolanaKeyring.ERROR_DUPLICATE_ACCOUNT);
|
|
120
|
+
}
|
|
121
|
+
this.keypairs.set(newAddress, newKeypair);
|
|
122
|
+
this.accounts.push(newAddress);
|
|
123
|
+
return newAddress;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Validates a Solana address format.
|
|
127
|
+
* @param address - The address to validate
|
|
128
|
+
* @returns True if the address is valid, false otherwise
|
|
129
|
+
*/
|
|
130
|
+
static isValidAddress(address) {
|
|
131
|
+
if (!address || typeof address !== 'string') {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
new web3_js_1.PublicKey(address);
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return false;
|
|
95
140
|
}
|
|
96
|
-
this.keypairs.set(newKeypair.publicKey.toString(), newKeypair);
|
|
97
|
-
this.accounts.push(newKeypair.publicKey.toString());
|
|
98
|
-
return newKeypair.publicKey.toString();
|
|
99
141
|
}
|
|
100
142
|
/**
|
|
101
143
|
* Gets the private key for a given address managed by this keyring.
|
|
102
144
|
* @param address - The account's public key address to get the private key for
|
|
103
145
|
* @returns Promise resolving to the private key as a hex string
|
|
104
|
-
* @throws Error if the address is not found in this keyring
|
|
146
|
+
* @throws Error if the address is not found in this keyring or is invalid
|
|
105
147
|
*/
|
|
106
148
|
async getPrivateKeyForAddress(address) {
|
|
149
|
+
if (!SolanaKeyring.isValidAddress(address)) {
|
|
150
|
+
throw new Error(`Invalid Solana address format: ${address}`);
|
|
151
|
+
}
|
|
107
152
|
const keypair = this.keypairs.get(address);
|
|
108
153
|
if (!keypair) {
|
|
109
|
-
throw new Error(
|
|
154
|
+
throw new Error(SolanaKeyring.ERROR_ADDRESS_NOT_FOUND);
|
|
110
155
|
}
|
|
111
156
|
return buffer_1.Buffer.from(keypair.secretKey).toString('hex');
|
|
112
157
|
}
|
|
@@ -119,12 +164,13 @@ class SolanaKeyring {
|
|
|
119
164
|
}
|
|
120
165
|
/**
|
|
121
166
|
* Serializes the keyring data for encryption and storage.
|
|
122
|
-
* @returns An object containing the keyring type and
|
|
167
|
+
* @returns An object containing the keyring type, mnemonic, and accounts
|
|
123
168
|
*/
|
|
124
169
|
serialize() {
|
|
125
170
|
return {
|
|
126
171
|
type: this.type,
|
|
127
|
-
mnemonic: this.mnemonic
|
|
172
|
+
mnemonic: this.mnemonic,
|
|
173
|
+
accounts: [...this.accounts] // Return a copy to prevent external modification
|
|
128
174
|
};
|
|
129
175
|
}
|
|
130
176
|
/**
|
|
@@ -135,17 +181,53 @@ class SolanaKeyring {
|
|
|
135
181
|
*/
|
|
136
182
|
static async deserialize(data) {
|
|
137
183
|
if (data.type !== 'Solana' || !data.mnemonic) {
|
|
138
|
-
throw new Error(
|
|
184
|
+
throw new Error(SolanaKeyring.ERROR_INVALID_DESERIALIZE);
|
|
139
185
|
}
|
|
140
|
-
//
|
|
141
|
-
|
|
186
|
+
// Validate mnemonic before creating keyring
|
|
187
|
+
if (!EncryptionService_1.EncryptionService.validateMnemonic(data.mnemonic)) {
|
|
188
|
+
throw new Error(`${SolanaKeyring.ERROR_INVALID_DESERIALIZE}: Invalid mnemonic`);
|
|
189
|
+
}
|
|
190
|
+
// Validate accounts array if present
|
|
191
|
+
if (data.accounts && !Array.isArray(data.accounts)) {
|
|
192
|
+
throw new Error(`${SolanaKeyring.ERROR_INVALID_DESERIALIZE}: Accounts must be an array`);
|
|
193
|
+
}
|
|
194
|
+
const keyring = new SolanaKeyring(data.mnemonic);
|
|
195
|
+
// If accounts were persisted, restore them. Otherwise, keep the first account from initialization.
|
|
196
|
+
if (data.accounts && data.accounts.length > 0) {
|
|
197
|
+
// Clear the first account that was created during initialization
|
|
198
|
+
keyring.accounts = [];
|
|
199
|
+
keyring.keypairs.clear();
|
|
200
|
+
// Restore all accounts by deriving keypairs for each index
|
|
201
|
+
// Use getSeed() to leverage caching
|
|
202
|
+
const seed = keyring.getSeed();
|
|
203
|
+
for (let i = 0; i < data.accounts.length; i++) {
|
|
204
|
+
const savedAddress = data.accounts[i];
|
|
205
|
+
// Validate saved address format
|
|
206
|
+
if (!SolanaKeyring.isValidAddress(savedAddress)) {
|
|
207
|
+
throw new Error(`${SolanaKeyring.ERROR_INVALID_DESERIALIZE}: Invalid address at index ${i}: ${savedAddress}`);
|
|
208
|
+
}
|
|
209
|
+
const keypair = SolanaKeyring.deriveKeypair(seed, i);
|
|
210
|
+
const derivedAddress = keypair.publicKey.toString();
|
|
211
|
+
// Verify that the derived address matches the saved address
|
|
212
|
+
if (derivedAddress !== savedAddress) {
|
|
213
|
+
throw new Error(`${SolanaKeyring.ERROR_ADDRESS_MISMATCH} ${i}. Expected ${savedAddress}, got ${derivedAddress}.`);
|
|
214
|
+
}
|
|
215
|
+
keyring.keypairs.set(derivedAddress, keypair);
|
|
216
|
+
keyring.accounts.push(derivedAddress);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return keyring;
|
|
142
220
|
}
|
|
143
221
|
/**
|
|
144
222
|
* Gets the Solana keypair for a given address.
|
|
145
223
|
* @param address - The account's public key address
|
|
146
224
|
* @returns The Solana keypair or undefined if not found
|
|
225
|
+
* @throws Error if the address format is invalid
|
|
147
226
|
*/
|
|
148
227
|
getKeypair(address) {
|
|
228
|
+
if (!SolanaKeyring.isValidAddress(address)) {
|
|
229
|
+
throw new Error(`Invalid Solana address format: ${address}`);
|
|
230
|
+
}
|
|
149
231
|
return this.keypairs.get(address);
|
|
150
232
|
}
|
|
151
233
|
/**
|
|
@@ -155,5 +237,45 @@ class SolanaKeyring {
|
|
|
155
237
|
getAllKeypairs() {
|
|
156
238
|
return Array.from(this.keypairs.values());
|
|
157
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* Clears sensitive data from memory.
|
|
242
|
+
* This should be called when the keyring is no longer needed to help prevent memory dumps.
|
|
243
|
+
* Note: This does not clear the seed cache, only instance-specific data.
|
|
244
|
+
*/
|
|
245
|
+
clearSensitiveData() {
|
|
246
|
+
// Clear keypairs from memory
|
|
247
|
+
this.keypairs.forEach((keypair) => {
|
|
248
|
+
// Attempt to zero out secret key (though JavaScript doesn't guarantee this)
|
|
249
|
+
if (keypair.secretKey) {
|
|
250
|
+
keypair.secretKey.fill(0);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
this.keypairs.clear();
|
|
254
|
+
this.seed = null;
|
|
255
|
+
// Note: We don't clear accounts array as it contains public addresses (not sensitive)
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Clears the seed cache to free up memory.
|
|
259
|
+
* Call this when you want to clear sensitive data from memory.
|
|
260
|
+
*/
|
|
261
|
+
static clearSeedCache() {
|
|
262
|
+
SolanaKeyring.seedCache.clear();
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Gets the cache size for debugging purposes.
|
|
266
|
+
* @returns Number of cached seeds
|
|
267
|
+
*/
|
|
268
|
+
static getSeedCacheSize() {
|
|
269
|
+
return SolanaKeyring.seedCache.size;
|
|
270
|
+
}
|
|
158
271
|
}
|
|
159
272
|
exports.SolanaKeyring = SolanaKeyring;
|
|
273
|
+
// Cache to avoid recreating seed
|
|
274
|
+
SolanaKeyring.seedCache = new Map();
|
|
275
|
+
// Error message constants
|
|
276
|
+
SolanaKeyring.ERROR_INVALID_MNEMONIC = 'Invalid mnemonic';
|
|
277
|
+
SolanaKeyring.ERROR_INVALID_INDEX = 'Account index must be a non-negative integer';
|
|
278
|
+
SolanaKeyring.ERROR_DUPLICATE_ACCOUNT = 'Duplicate account derived. Please check derivation path logic.';
|
|
279
|
+
SolanaKeyring.ERROR_ADDRESS_NOT_FOUND = 'Address not found in this keyring.';
|
|
280
|
+
SolanaKeyring.ERROR_INVALID_DESERIALIZE = 'Invalid data for SolanaKeyring deserialization.';
|
|
281
|
+
SolanaKeyring.ERROR_ADDRESS_MISMATCH = 'Account address mismatch at index';
|
package/package.json
CHANGED