@pasosdejesus/m 0.23.1 → 0.24.0
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/dist/CHANGELOG.md +29 -0
- package/dist/e2e/full-wallet.d.ts +41 -0
- package/dist/e2e/full-wallet.d.ts.map +1 -0
- package/dist/e2e/full-wallet.js +138 -0
- package/dist/e2e/full-wallet.js.map +1 -0
- package/dist/e2e/full-wallet.test.d.ts +2 -0
- package/dist/e2e/full-wallet.test.d.ts.map +1 -0
- package/dist/e2e/full-wallet.test.js +37 -0
- package/dist/e2e/full-wallet.test.js.map +1 -0
- package/dist/e2e/index.d.ts +4 -0
- package/dist/e2e/index.d.ts.map +1 -1
- package/dist/e2e/index.js +2 -0
- package/dist/e2e/index.js.map +1 -1
- package/dist/e2e/local-node.d.ts +22 -0
- package/dist/e2e/local-node.d.ts.map +1 -0
- package/dist/e2e/local-node.js +144 -0
- package/dist/e2e/local-node.js.map +1 -0
- package/dist/e2e/local-node.test.d.ts +2 -0
- package/dist/e2e/local-node.test.d.ts.map +1 -0
- package/dist/e2e/local-node.test.js +43 -0
- package/dist/e2e/local-node.test.js.map +1 -0
- package/dist/tasks/wallet.d.ts.map +1 -1
- package/dist/tasks/wallet.js +14 -25
- package/dist/tasks/wallet.js.map +1 -1
- package/dist/test-utils/index.d.ts +1 -0
- package/dist/test-utils/index.d.ts.map +1 -1
- package/dist/test-utils/index.js +1 -0
- package/dist/test-utils/index.js.map +1 -1
- package/dist/test-utils/wallet-mocks.d.ts +44 -0
- package/dist/test-utils/wallet-mocks.d.ts.map +1 -0
- package/dist/test-utils/wallet-mocks.js +114 -0
- package/dist/test-utils/wallet-mocks.js.map +1 -0
- package/dist/test-utils/wallet-mocks.test.d.ts +2 -0
- package/dist/test-utils/wallet-mocks.test.d.ts.map +1 -0
- package/dist/test-utils/wallet-mocks.test.js +55 -0
- package/dist/test-utils/wallet-mocks.test.js.map +1 -0
- package/dist/test-utils/wallet-setup.d.ts +2 -0
- package/dist/test-utils/wallet-setup.d.ts.map +1 -0
- package/dist/test-utils/wallet-setup.js +23 -0
- package/dist/test-utils/wallet-setup.js.map +1 -0
- package/dist/test-utils/wallet-setup.test.d.ts +2 -0
- package/dist/test-utils/wallet-setup.test.d.ts.map +1 -0
- package/dist/test-utils/wallet-setup.test.js +18 -0
- package/dist/test-utils/wallet-setup.test.js.map +1 -0
- package/dist/wallet/biometric.d.ts +33 -0
- package/dist/wallet/biometric.d.ts.map +1 -0
- package/dist/wallet/biometric.js +88 -0
- package/dist/wallet/biometric.js.map +1 -0
- package/dist/wallet/biometric.test.d.ts +2 -0
- package/dist/wallet/biometric.test.d.ts.map +1 -0
- package/dist/wallet/biometric.test.js +44 -0
- package/dist/wallet/biometric.test.js.map +1 -0
- package/dist/wallet/crypto.d.ts +42 -0
- package/dist/wallet/crypto.d.ts.map +1 -0
- package/dist/wallet/crypto.js +127 -0
- package/dist/wallet/crypto.js.map +1 -0
- package/dist/wallet/crypto.test.d.ts +2 -0
- package/dist/wallet/crypto.test.d.ts.map +1 -0
- package/dist/wallet/crypto.test.js +34 -0
- package/dist/wallet/crypto.test.js.map +1 -0
- package/dist/wallet/destinations.d.ts +20 -0
- package/dist/wallet/destinations.d.ts.map +1 -0
- package/dist/wallet/destinations.js +98 -0
- package/dist/wallet/destinations.js.map +1 -0
- package/dist/wallet/destinations.test.d.ts +2 -0
- package/dist/wallet/destinations.test.d.ts.map +1 -0
- package/dist/wallet/destinations.test.js +34 -0
- package/dist/wallet/destinations.test.js.map +1 -0
- package/dist/wallet/external-provider.d.ts +33 -0
- package/dist/wallet/external-provider.d.ts.map +1 -0
- package/dist/wallet/external-provider.js +108 -0
- package/dist/wallet/external-provider.js.map +1 -0
- package/dist/wallet/external-provider.test.d.ts +2 -0
- package/dist/wallet/external-provider.test.d.ts.map +1 -0
- package/dist/wallet/external-provider.test.js +44 -0
- package/dist/wallet/external-provider.test.js.map +1 -0
- package/dist/wallet/index.d.ts +14 -0
- package/dist/wallet/index.d.ts.map +1 -0
- package/dist/wallet/index.js +24 -0
- package/dist/wallet/index.js.map +1 -0
- package/dist/wallet/indexeddb.test.d.ts +2 -0
- package/dist/wallet/indexeddb.test.d.ts.map +1 -0
- package/dist/wallet/indexeddb.test.js +28 -0
- package/dist/wallet/indexeddb.test.js.map +1 -0
- package/dist/wallet/kdf.test.d.ts +2 -0
- package/dist/wallet/kdf.test.d.ts.map +1 -0
- package/dist/wallet/kdf.test.js +54 -0
- package/dist/wallet/kdf.test.js.map +1 -0
- package/dist/wallet/provider.d.ts +38 -0
- package/dist/wallet/provider.d.ts.map +1 -0
- package/dist/wallet/provider.js +285 -0
- package/dist/wallet/provider.js.map +1 -0
- package/dist/wallet/provider.test.d.ts +2 -0
- package/dist/wallet/provider.test.d.ts.map +1 -0
- package/dist/wallet/provider.test.js +170 -0
- package/dist/wallet/provider.test.js.map +1 -0
- package/dist/wallet/signer.d.ts +55 -0
- package/dist/wallet/signer.d.ts.map +1 -0
- package/dist/wallet/signer.js +91 -0
- package/dist/wallet/signer.js.map +1 -0
- package/dist/wallet/signer.test.d.ts +2 -0
- package/dist/wallet/signer.test.d.ts.map +1 -0
- package/dist/wallet/signer.test.js +62 -0
- package/dist/wallet/signer.test.js.map +1 -0
- package/dist/wallet/siwe.d.ts +2 -0
- package/dist/wallet/siwe.d.ts.map +1 -0
- package/dist/wallet/siwe.js +5 -0
- package/dist/wallet/siwe.js.map +1 -0
- package/dist/wallet/storage/file.d.ts +19 -0
- package/dist/wallet/storage/file.d.ts.map +1 -0
- package/dist/wallet/storage/file.js +48 -0
- package/dist/wallet/storage/file.js.map +1 -0
- package/dist/wallet/storage/idb.d.ts +25 -0
- package/dist/wallet/storage/idb.d.ts.map +1 -0
- package/dist/wallet/storage/idb.js +58 -0
- package/dist/wallet/storage/idb.js.map +1 -0
- package/dist/wallet/storage/index.d.ts +5 -0
- package/dist/wallet/storage/index.d.ts.map +1 -0
- package/dist/wallet/storage/index.js +4 -0
- package/dist/wallet/storage/index.js.map +1 -0
- package/dist/wallet/storage/indexeddb.d.ts +8 -0
- package/dist/wallet/storage/indexeddb.d.ts.map +1 -0
- package/dist/wallet/storage/indexeddb.js +18 -0
- package/dist/wallet/storage/indexeddb.js.map +1 -0
- package/dist/wallet/storage/memory.d.ts +10 -0
- package/dist/wallet/storage/memory.d.ts.map +1 -0
- package/dist/wallet/storage/memory.js +19 -0
- package/dist/wallet/storage/memory.js.map +1 -0
- package/dist/wallet/storage.test.d.ts +2 -0
- package/dist/wallet/storage.test.d.ts.map +1 -0
- package/dist/wallet/storage.test.js +103 -0
- package/dist/wallet/storage.test.js.map +1 -0
- package/dist/wallet/types.d.ts +69 -0
- package/dist/wallet/types.d.ts.map +1 -0
- package/dist/wallet/types.js +6 -0
- package/dist/wallet/types.js.map +1 -0
- package/dist/wallet/wallet.d.ts +81 -0
- package/dist/wallet/wallet.d.ts.map +1 -0
- package/dist/wallet/wallet.js +358 -0
- package/dist/wallet/wallet.js.map +1 -0
- package/dist/wallet/wallet.test.d.ts +2 -0
- package/dist/wallet/wallet.test.d.ts.map +1 -0
- package/dist/wallet/wallet.test.js +150 -0
- package/dist/wallet/wallet.test.js.map +1 -0
- package/dist/wallet/web-authn.d.ts +78 -0
- package/dist/wallet/web-authn.d.ts.map +1 -0
- package/dist/wallet/web-authn.js +250 -0
- package/dist/wallet/web-authn.js.map +1 -0
- package/package.json +14 -4
- package/src/e2e/README.md +51 -0
- package/src/wallet/README.md +96 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { english, generateMnemonic, mnemonicToAccount, privateKeyToAccount } from 'viem/accounts';
|
|
2
|
+
import { keccak256, stringToHex } from 'viem';
|
|
3
|
+
import { sha256 } from './crypto.js';
|
|
4
|
+
/** Words per BIP39 length: only these are valid mnemonics. */
|
|
5
|
+
const VALID_WORD_COUNTS = [12, 15, 18, 21, 24];
|
|
6
|
+
/** `english` index → word, built once (the wordlist is fixed). */
|
|
7
|
+
const WORD_INDEX = new Map(english.map((word, index) => [word, index]));
|
|
8
|
+
export function newMnemonic() {
|
|
9
|
+
const mnemonic = generateMnemonic(english);
|
|
10
|
+
// Self-check: a broken RNG or a truncated wordlist must never be stored as a
|
|
11
|
+
// wallet the user cannot recover.
|
|
12
|
+
if (!isValidMnemonicSync(mnemonic)) {
|
|
13
|
+
throw new Error('invalid-mnemonic');
|
|
14
|
+
}
|
|
15
|
+
return mnemonic;
|
|
16
|
+
}
|
|
17
|
+
export function normalizeMnemonic(mnemonic) {
|
|
18
|
+
return mnemonic.trim().toLowerCase().replace(/\s+/g, ' ');
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* BIP39 validation (words in the list + checksum), without adding a dependency:
|
|
22
|
+
* the wordlist comes from viem and the hash from WebCrypto (already required to
|
|
23
|
+
* store the wallet). `mnemonicToSeedSync` does **not** validate, so an imported
|
|
24
|
+
* phrase with a typo would silently become a different wallet; callers use this
|
|
25
|
+
* to reject it instead.
|
|
26
|
+
*/
|
|
27
|
+
export async function isValidMnemonic(mnemonic) {
|
|
28
|
+
const words = normalizeMnemonic(mnemonic).split(' ');
|
|
29
|
+
if (!VALID_WORD_COUNTS.includes(words.length))
|
|
30
|
+
return false;
|
|
31
|
+
const indices = [];
|
|
32
|
+
for (const word of words) {
|
|
33
|
+
const index = WORD_INDEX.get(word);
|
|
34
|
+
if (index === undefined)
|
|
35
|
+
return false;
|
|
36
|
+
indices.push(index);
|
|
37
|
+
}
|
|
38
|
+
const bits = indices.map((index) => index.toString(2).padStart(11, '0')).join('');
|
|
39
|
+
// ENT = words * 11 * 32 / 33; CS = ENT / 32 (12 words → 128 + 4 bits).
|
|
40
|
+
const entropyBits = (words.length * 11 * 32) / 33;
|
|
41
|
+
const entropy = new Uint8Array(entropyBits / 8);
|
|
42
|
+
for (let i = 0; i < entropy.length; i++) {
|
|
43
|
+
entropy[i] = parseInt(bits.slice(i * 8, i * 8 + 8), 2);
|
|
44
|
+
}
|
|
45
|
+
const hash = await sha256(entropy);
|
|
46
|
+
const expected = Array.from(hash)
|
|
47
|
+
.map((byte) => byte.toString(2).padStart(8, '0'))
|
|
48
|
+
.join('')
|
|
49
|
+
.slice(0, bits.length - entropyBits);
|
|
50
|
+
return expected === bits.slice(entropyBits);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Synchronous checksum check. It repeats the async logic because the digest is
|
|
54
|
+
* not available synchronously; kept for `newMnemonic`, where the phrase we just
|
|
55
|
+
* generated must be provably valid before it is stored.
|
|
56
|
+
*/
|
|
57
|
+
function isValidMnemonicSync(mnemonic) {
|
|
58
|
+
const words = normalizeMnemonic(mnemonic).split(' ');
|
|
59
|
+
if (!VALID_WORD_COUNTS.includes(words.length))
|
|
60
|
+
return false;
|
|
61
|
+
return words.every((word) => WORD_INDEX.has(word));
|
|
62
|
+
}
|
|
63
|
+
/** Throws the translatable code `invalid-mnemonic` when the phrase is not BIP39. */
|
|
64
|
+
export async function assertValidMnemonic(mnemonic) {
|
|
65
|
+
if (!(await isValidMnemonic(mnemonic)))
|
|
66
|
+
throw new Error('invalid-mnemonic');
|
|
67
|
+
}
|
|
68
|
+
export function accountFromMnemonic(mnemonic, addressIndex = 0, accountIndex = 0) {
|
|
69
|
+
return mnemonicToAccount(normalizeMnemonic(mnemonic), { accountIndex, addressIndex });
|
|
70
|
+
}
|
|
71
|
+
export function accountFromPrivateKey(privateKey) {
|
|
72
|
+
return privateKeyToAccount(privateKey);
|
|
73
|
+
}
|
|
74
|
+
export function addressFromMnemonic(mnemonic, addressIndex = 0, accountIndex = 0) {
|
|
75
|
+
return accountFromMnemonic(mnemonic, addressIndex, accountIndex).address;
|
|
76
|
+
}
|
|
77
|
+
/** keccak256 of a UTF-8 string (replaces `cast keccak`). */
|
|
78
|
+
export function keccak(value) {
|
|
79
|
+
return keccak256(stringToHex(value));
|
|
80
|
+
}
|
|
81
|
+
export function privateKeyFromMnemonic(mnemonic, addressIndex = 0, accountIndex = 0) {
|
|
82
|
+
const hdKey = accountFromMnemonic(mnemonic, addressIndex, accountIndex).getHdKey();
|
|
83
|
+
const key = hdKey.privateKey;
|
|
84
|
+
if (!key)
|
|
85
|
+
throw new Error('Could not derive the private key from the mnemonic');
|
|
86
|
+
let hex = '';
|
|
87
|
+
for (const byte of key)
|
|
88
|
+
hex += byte.toString(16).padStart(2, '0');
|
|
89
|
+
return `0x${hex}`;
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=signer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signer.js","sourceRoot":"","sources":["../../src/wallet/signer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AACjG,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,MAAM,CAAA;AAC7C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAIpC,8DAA8D;AAC9D,MAAM,iBAAiB,GAAG,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;AAE9C,kEAAkE;AAClE,MAAM,UAAU,GAAwB,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAA;AAE5F,MAAM,UAAU,WAAW;IACzB,MAAM,QAAQ,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAA;IAC1C,6EAA6E;IAC7E,kCAAkC;IAClC,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAA;IACrC,CAAC;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,QAAgB;IAChD,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;AAC3D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,QAAgB;IACpD,MAAM,KAAK,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACpD,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAA;IAE3D,MAAM,OAAO,GAAa,EAAE,CAAA;IAC5B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QAClC,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,KAAK,CAAA;QACrC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACrB,CAAC;IAED,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACjF,uEAAuE;IACvE,MAAM,WAAW,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,CAAA;IACjD,MAAM,OAAO,GAAG,IAAI,UAAU,CAAC,WAAW,GAAG,CAAC,CAAC,CAAA;IAC/C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACxC,OAAO,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IACxD,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,CAAA;IAClC,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;SAC9B,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;SAChD,IAAI,CAAC,EAAE,CAAC;SACR,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,GAAG,WAAW,CAAC,CAAA;IACtC,OAAO,QAAQ,KAAK,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAA;AAC7C,CAAC;AAED;;;;GAIG;AACH,SAAS,mBAAmB,CAAC,QAAgB;IAC3C,MAAM,KAAK,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACpD,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAA;IAC3D,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAA;AACpD,CAAC;AAED,oFAAoF;AACpF,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,QAAgB;IACxD,IAAI,CAAC,CAAC,MAAM,eAAe,CAAC,QAAQ,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAA;AAC7E,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,QAAgB,EAAE,YAAY,GAAG,CAAC,EAAE,YAAY,GAAG,CAAC;IACtF,OAAO,iBAAiB,CAAC,iBAAiB,CAAC,QAAQ,CAAC,EAAE,EAAE,YAAY,EAAE,YAAY,EAAW,CAAC,CAAA;AAChG,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,UAAsB;IAC1D,OAAO,mBAAmB,CAAC,UAAU,CAAC,CAAA;AACxC,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,QAAgB,EAAE,YAAY,GAAG,CAAC,EAAE,YAAY,GAAG,CAAC;IACtF,OAAO,mBAAmB,CAAC,QAAQ,EAAE,YAAY,EAAE,YAAY,CAAC,CAAC,OAAO,CAAA;AAC1E,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,MAAM,CAAC,KAAa;IAClC,OAAO,SAAS,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAA;AACtC,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,QAAgB,EAAE,YAAY,GAAG,CAAC,EAAE,YAAY,GAAG,CAAC;IACzF,MAAM,KAAK,GAAG,mBAAmB,CAAC,QAAQ,EAAE,YAAY,EAAE,YAAY,CAAC,CAAC,QAAQ,EAAE,CAAA;IAClF,MAAM,GAAG,GAAG,KAAK,CAAC,UAAU,CAAA;IAC5B,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAA;IAC/E,IAAI,GAAG,GAAG,EAAE,CAAA;IACZ,KAAK,MAAM,IAAI,IAAI,GAAG;QAAE,GAAG,IAAI,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;IACjE,OAAO,KAAK,GAAG,EAAE,CAAA;AACnB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signer.test.d.ts","sourceRoot":"","sources":["../../src/wallet/signer.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { mkdtemp, readFile, rm } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { accountFromMnemonic, addressFromMnemonic, isValidMnemonic, keccak, privateKeyFromMnemonic, } from './signer.js';
|
|
6
|
+
import { runWallet } from '../tasks/wallet.js';
|
|
7
|
+
const HARDHAT_MNEMONIC = 'test test test test test test test test test test test junk';
|
|
8
|
+
const HARDHAT_ADDRESS = '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266';
|
|
9
|
+
describe('signer', () => {
|
|
10
|
+
it('derives the hardhat address and a 0x private key', () => {
|
|
11
|
+
expect(addressFromMnemonic(HARDHAT_MNEMONIC)).toBe(HARDHAT_ADDRESS);
|
|
12
|
+
expect(privateKeyFromMnemonic(HARDHAT_MNEMONIC)).toMatch(/^0x[0-9a-f]{64}$/);
|
|
13
|
+
});
|
|
14
|
+
it('distinguishes accountIndex from addressIndex', () => {
|
|
15
|
+
const byAccount = accountFromMnemonic(HARDHAT_MNEMONIC, 0, 1).address;
|
|
16
|
+
const byAddress = accountFromMnemonic(HARDHAT_MNEMONIC, 1, 0).address;
|
|
17
|
+
expect(byAccount).not.toBe(HARDHAT_ADDRESS);
|
|
18
|
+
expect(byAddress).not.toBe(HARDHAT_ADDRESS);
|
|
19
|
+
expect(byAccount).not.toBe(byAddress);
|
|
20
|
+
});
|
|
21
|
+
it('validates BIP39 phrases (wordlist + checksum)', async () => {
|
|
22
|
+
expect(await isValidMnemonic(HARDHAT_MNEMONIC)).toBe(true);
|
|
23
|
+
expect(await isValidMnemonic('test test test test test test test test test test test test')).toBe(false);
|
|
24
|
+
expect(await isValidMnemonic('not a mnemonic at all')).toBe(false);
|
|
25
|
+
});
|
|
26
|
+
it('computes keccak256 of a UTF-8 string', () => {
|
|
27
|
+
expect(keccak('')).toBe('0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470');
|
|
28
|
+
expect(keccak('hello')).toMatch(/^0x[0-9a-f]{64}$/);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
// m/REQ/14 Fase 1: el CLI de billetera debe usar el núcleo y ambos caminos deben
|
|
32
|
+
// coincidir (paridad CLI ↔ núcleo).
|
|
33
|
+
describe('CLI ↔ core parity', () => {
|
|
34
|
+
let dir;
|
|
35
|
+
let logs;
|
|
36
|
+
beforeEach(async () => {
|
|
37
|
+
dir = await mkdtemp(join(tmpdir(), 'm-wallet-cli-'));
|
|
38
|
+
process.env.M_WALLETS_DIR = dir;
|
|
39
|
+
logs = [];
|
|
40
|
+
vi.spyOn(console, 'log').mockImplementation((...args) => {
|
|
41
|
+
logs.push(args.map((a) => String(a)).join(' '));
|
|
42
|
+
});
|
|
43
|
+
vi.spyOn(console, 'warn').mockImplementation(() => { });
|
|
44
|
+
vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
45
|
+
});
|
|
46
|
+
afterEach(async () => {
|
|
47
|
+
vi.restoreAllMocks();
|
|
48
|
+
delete process.env.M_WALLETS_DIR;
|
|
49
|
+
await rm(dir, { recursive: true, force: true });
|
|
50
|
+
});
|
|
51
|
+
it('wallet:keccak matches the core keccak', async () => {
|
|
52
|
+
await runWallet(['keccak', 'hello']);
|
|
53
|
+
expect(logs.join('\n')).toContain(keccak('hello'));
|
|
54
|
+
});
|
|
55
|
+
it('wallet:generate --index 2 derives with the core accountIndex', async () => {
|
|
56
|
+
await runWallet(['generate', '--name', 'parity', '--index', '2']);
|
|
57
|
+
const saved = JSON.parse(await readFile(join(dir, 'parity.json'), 'utf-8'));
|
|
58
|
+
expect(saved.address).toBe(accountFromMnemonic(saved.mnemonic, 0, 2).address);
|
|
59
|
+
expect(saved.privateKey).toBe(privateKeyFromMnemonic(saved.mnemonic, 0, 2));
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
//# sourceMappingURL=signer.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signer.test.js","sourceRoot":"","sources":["../../src/wallet/signer.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"siwe.d.ts","sourceRoot":"","sources":["../../src/wallet/siwe.ts"],"names":[],"mappings":"AAEA,wBAAsB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,MAAM,EAAE,CAAC,CAEtE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"siwe.js","sourceRoot":"","sources":["../../src/wallet/siwe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAEzC,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,OAAe;IAC5C,OAAO,WAAW,CAAC,OAAO,CAAC,CAAA;AAC7B,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { StorageAdapter, StoredWallet } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Node.js storage adapter: the encrypted record lives in a JSON file, so E2E
|
|
4
|
+
* tests (and any Node script) can use the real wallet without a browser.
|
|
5
|
+
*
|
|
6
|
+
* The import is dynamic on purpose: this module must stay loadable from a
|
|
7
|
+
* browser bundle (`node:fs/promises` is only imported when a method runs), which
|
|
8
|
+
* is also why it is exported from `@learn-tg/pdj-wallet/storage` and not from the
|
|
9
|
+
* package root.
|
|
10
|
+
*/
|
|
11
|
+
export declare class FileStorage implements StorageAdapter {
|
|
12
|
+
private readonly filePath;
|
|
13
|
+
constructor(filePath: string);
|
|
14
|
+
get(): Promise<StoredWallet | null>;
|
|
15
|
+
set(record: StoredWallet): Promise<void>;
|
|
16
|
+
delete(): Promise<void>;
|
|
17
|
+
has(): Promise<boolean>;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../../src/wallet/storage/file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAY/D;;;;;;;;GAQG;AACH,qBAAa,WAAY,YAAW,cAAc;IACpC,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,MAAM;IAEvC,GAAG,IAAI,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAWnC,GAAG,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAMxC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAKvB,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC;CAG9B"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
async function fs() {
|
|
2
|
+
try {
|
|
3
|
+
return await import('node:fs/promises');
|
|
4
|
+
}
|
|
5
|
+
catch {
|
|
6
|
+
throw new Error('FileStorage requires Node.js (node:fs/promises is not available here)');
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Node.js storage adapter: the encrypted record lives in a JSON file, so E2E
|
|
11
|
+
* tests (and any Node script) can use the real wallet without a browser.
|
|
12
|
+
*
|
|
13
|
+
* The import is dynamic on purpose: this module must stay loadable from a
|
|
14
|
+
* browser bundle (`node:fs/promises` is only imported when a method runs), which
|
|
15
|
+
* is also why it is exported from `@learn-tg/pdj-wallet/storage` and not from the
|
|
16
|
+
* package root.
|
|
17
|
+
*/
|
|
18
|
+
export class FileStorage {
|
|
19
|
+
filePath;
|
|
20
|
+
constructor(filePath) {
|
|
21
|
+
this.filePath = filePath;
|
|
22
|
+
}
|
|
23
|
+
async get() {
|
|
24
|
+
const { readFile } = await fs();
|
|
25
|
+
try {
|
|
26
|
+
const raw = await readFile(this.filePath, 'utf8');
|
|
27
|
+
return JSON.parse(raw);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (error.code === 'ENOENT')
|
|
31
|
+
return null;
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async set(record) {
|
|
36
|
+
const { writeFile } = await fs();
|
|
37
|
+
// 0600: the file holds the encrypted key, nobody else needs to read it.
|
|
38
|
+
await writeFile(this.filePath, `${JSON.stringify(record, null, 2)}\n`, { mode: 0o600 });
|
|
39
|
+
}
|
|
40
|
+
async delete() {
|
|
41
|
+
const { rm } = await fs();
|
|
42
|
+
await rm(this.filePath, { force: true });
|
|
43
|
+
}
|
|
44
|
+
async has() {
|
|
45
|
+
return (await this.get()) !== null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file.js","sourceRoot":"","sources":["../../../src/wallet/storage/file.ts"],"names":[],"mappings":"AAIA,KAAK,UAAU,EAAE;IACf,IAAI,CAAC;QACH,OAAO,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAA;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,uEAAuE,CAAC,CAAA;IAC1F,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,OAAO,WAAW;IACO;IAA7B,YAA6B,QAAgB;QAAhB,aAAQ,GAAR,QAAQ,CAAQ;IAAG,CAAC;IAEjD,KAAK,CAAC,GAAG;QACP,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,EAAE,EAAE,CAAA;QAC/B,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;YACjD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAiB,CAAA;QACxC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAK,KAA2B,CAAC,IAAI,KAAK,QAAQ;gBAAE,OAAO,IAAI,CAAA;YAC/D,MAAM,KAAK,CAAA;QACb,CAAC;IACH,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,MAAoB;QAC5B,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,EAAE,EAAE,CAAA;QAChC,wEAAwE;QACxE,MAAM,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IACzF,CAAC;IAED,KAAK,CAAC,MAAM;QACV,MAAM,EAAE,EAAE,EAAE,GAAG,MAAM,EAAE,EAAE,CAAA;QACzB,MAAM,EAAE,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;IAC1C,CAAC;IAED,KAAK,CAAC,GAAG;QACP,OAAO,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,CAAA;IACpC,CAAC;CACF"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single owner of the in-app wallet IndexedDB database.
|
|
3
|
+
*
|
|
4
|
+
* Two object stores live here: `wallet` (the password-encrypted record, see
|
|
5
|
+
* `storage/indexeddb.ts`) and `biometric` (the key sealed with the WebAuthn PRF
|
|
6
|
+
* secret, see `biometric.ts`). Both modules must open the database with the same
|
|
7
|
+
* version, so the version lives here and nowhere else.
|
|
8
|
+
*
|
|
9
|
+
* The database name is **configurable**: a shared package must not write into one
|
|
10
|
+
* consumer's namespace (`m/REQ/14`, "parametrizar las claves de almacenamiento").
|
|
11
|
+
* A consumer that already has data under its own name calls `setWalletDbName()`
|
|
12
|
+
* once at startup to keep reading it.
|
|
13
|
+
*/
|
|
14
|
+
export declare const DB_NAME = "pasosdejesus-m-wallet";
|
|
15
|
+
export declare const DB_VERSION = 2;
|
|
16
|
+
export declare const STORE_WALLET = "wallet";
|
|
17
|
+
export declare const STORE_BIOMETRIC = "biometric";
|
|
18
|
+
/** Overrides the IndexedDB database name (a consumer keeps its own namespace). */
|
|
19
|
+
export declare function setWalletDbName(name: string): void;
|
|
20
|
+
/** The IndexedDB database name currently in use. */
|
|
21
|
+
export declare function getWalletDbName(): string;
|
|
22
|
+
export declare function openDb(name?: string): Promise<IDBDatabase>;
|
|
23
|
+
/** Runs one request inside its own transaction and closes the connection. */
|
|
24
|
+
export declare function runInDb<T>(storeName: string, mode: IDBTransactionMode, fn: (store: IDBObjectStore) => IDBRequest): Promise<T>;
|
|
25
|
+
//# sourceMappingURL=idb.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idb.d.ts","sourceRoot":"","sources":["../../../src/wallet/storage/idb.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,OAAO,0BAA0B,CAAA;AAC9C,eAAO,MAAM,UAAU,IAAI,CAAA;AAC3B,eAAO,MAAM,YAAY,WAAW,CAAA;AACpC,eAAO,MAAM,eAAe,cAAc,CAAA;AAI1C,kFAAkF;AAClF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAElD;AAED,oDAAoD;AACpD,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAQD,wBAAgB,MAAM,CAAC,IAAI,GAAE,MAAe,GAAG,OAAO,CAAC,WAAW,CAAC,CAWlE;AAED,6EAA6E;AAC7E,wBAAgB,OAAO,CAAC,CAAC,EACvB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,kBAAkB,EACxB,EAAE,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,UAAU,GACxC,OAAO,CAAC,CAAC,CAAC,CAWZ"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single owner of the in-app wallet IndexedDB database.
|
|
3
|
+
*
|
|
4
|
+
* Two object stores live here: `wallet` (the password-encrypted record, see
|
|
5
|
+
* `storage/indexeddb.ts`) and `biometric` (the key sealed with the WebAuthn PRF
|
|
6
|
+
* secret, see `biometric.ts`). Both modules must open the database with the same
|
|
7
|
+
* version, so the version lives here and nowhere else.
|
|
8
|
+
*
|
|
9
|
+
* The database name is **configurable**: a shared package must not write into one
|
|
10
|
+
* consumer's namespace (`m/REQ/14`, "parametrizar las claves de almacenamiento").
|
|
11
|
+
* A consumer that already has data under its own name calls `setWalletDbName()`
|
|
12
|
+
* once at startup to keep reading it.
|
|
13
|
+
*/
|
|
14
|
+
export const DB_NAME = 'pasosdejesus-m-wallet';
|
|
15
|
+
export const DB_VERSION = 2;
|
|
16
|
+
export const STORE_WALLET = 'wallet';
|
|
17
|
+
export const STORE_BIOMETRIC = 'biometric';
|
|
18
|
+
let dbName = DB_NAME;
|
|
19
|
+
/** Overrides the IndexedDB database name (a consumer keeps its own namespace). */
|
|
20
|
+
export function setWalletDbName(name) {
|
|
21
|
+
if (name)
|
|
22
|
+
dbName = name;
|
|
23
|
+
}
|
|
24
|
+
/** The IndexedDB database name currently in use. */
|
|
25
|
+
export function getWalletDbName() {
|
|
26
|
+
return dbName;
|
|
27
|
+
}
|
|
28
|
+
function indexedDb() {
|
|
29
|
+
const idb = globalThis.indexedDB;
|
|
30
|
+
if (!idb)
|
|
31
|
+
throw new Error('IndexedDB is not available in this environment');
|
|
32
|
+
return idb;
|
|
33
|
+
}
|
|
34
|
+
export function openDb(name = dbName) {
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
const request = indexedDb().open(name, DB_VERSION);
|
|
37
|
+
request.onupgradeneeded = () => {
|
|
38
|
+
const db = request.result;
|
|
39
|
+
if (!db.objectStoreNames.contains(STORE_WALLET))
|
|
40
|
+
db.createObjectStore(STORE_WALLET);
|
|
41
|
+
if (!db.objectStoreNames.contains(STORE_BIOMETRIC))
|
|
42
|
+
db.createObjectStore(STORE_BIOMETRIC);
|
|
43
|
+
};
|
|
44
|
+
request.onsuccess = () => resolve(request.result);
|
|
45
|
+
request.onerror = () => reject(request.error ?? new Error('IndexedDB open failed'));
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
/** Runs one request inside its own transaction and closes the connection. */
|
|
49
|
+
export function runInDb(storeName, mode, fn) {
|
|
50
|
+
return openDb().then((db) => new Promise((resolve, reject) => {
|
|
51
|
+
const tx = db.transaction(storeName, mode);
|
|
52
|
+
const request = fn(tx.objectStore(storeName));
|
|
53
|
+
request.onsuccess = () => resolve(request.result);
|
|
54
|
+
request.onerror = () => reject(request.error ?? new Error('IndexedDB request failed'));
|
|
55
|
+
tx.oncomplete = () => db.close();
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=idb.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idb.js","sourceRoot":"","sources":["../../../src/wallet/storage/idb.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,uBAAuB,CAAA;AAC9C,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAA;AAC3B,MAAM,CAAC,MAAM,YAAY,GAAG,QAAQ,CAAA;AACpC,MAAM,CAAC,MAAM,eAAe,GAAG,WAAW,CAAA;AAE1C,IAAI,MAAM,GAAG,OAAO,CAAA;AAEpB,kFAAkF;AAClF,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,IAAI,IAAI;QAAE,MAAM,GAAG,IAAI,CAAA;AACzB,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,eAAe;IAC7B,OAAO,MAAM,CAAA;AACf,CAAC;AAED,SAAS,SAAS;IAChB,MAAM,GAAG,GAAI,UAAyC,CAAC,SAAS,CAAA;IAChE,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAA;IAC3E,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,OAAe,MAAM;IAC1C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,OAAO,GAAG,SAAS,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,CAAA;QAClD,OAAO,CAAC,eAAe,GAAG,GAAG,EAAE;YAC7B,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAA;YACzB,IAAI,CAAC,EAAE,CAAC,gBAAgB,CAAC,QAAQ,CAAC,YAAY,CAAC;gBAAE,EAAE,CAAC,iBAAiB,CAAC,YAAY,CAAC,CAAA;YACnF,IAAI,CAAC,EAAE,CAAC,gBAAgB,CAAC,QAAQ,CAAC,eAAe,CAAC;gBAAE,EAAE,CAAC,iBAAiB,CAAC,eAAe,CAAC,CAAA;QAC3F,CAAC,CAAA;QACD,OAAO,CAAC,SAAS,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;QACjD,OAAO,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC,CAAA;IACrF,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,OAAO,CACrB,SAAiB,EACjB,IAAwB,EACxB,EAAyC;IAEzC,OAAO,MAAM,EAAE,CAAC,IAAI,CAClB,CAAC,EAAE,EAAE,EAAE,CACL,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACjC,MAAM,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;QAC1C,MAAM,OAAO,GAAG,EAAE,CAAC,EAAE,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAA;QAC7C,OAAO,CAAC,SAAS,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,MAAW,CAAC,CAAA;QACtD,OAAO,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC,CAAA;QACtF,EAAE,CAAC,UAAU,GAAG,GAAG,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,CAAA;IAClC,CAAC,CAAC,CACL,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/wallet/storage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AACvC,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/wallet/storage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { StorageAdapter, StoredWallet } from '../types.js';
|
|
2
|
+
export declare class IndexedDBStorage implements StorageAdapter {
|
|
3
|
+
get(): Promise<StoredWallet | null>;
|
|
4
|
+
set(record: StoredWallet): Promise<void>;
|
|
5
|
+
delete(): Promise<void>;
|
|
6
|
+
has(): Promise<boolean>;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=indexeddb.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexeddb.d.ts","sourceRoot":"","sources":["../../../src/wallet/storage/indexeddb.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAI/D,qBAAa,gBAAiB,YAAW,cAAc;IAC/C,GAAG,IAAI,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAOnC,GAAG,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAIxC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAIvB,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC;CAG9B"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { runInDb, STORE_WALLET } from './idb.js';
|
|
2
|
+
const KEY = 'current';
|
|
3
|
+
export class IndexedDBStorage {
|
|
4
|
+
async get() {
|
|
5
|
+
const value = await runInDb(STORE_WALLET, 'readonly', (store) => store.get(KEY));
|
|
6
|
+
return value ?? null;
|
|
7
|
+
}
|
|
8
|
+
async set(record) {
|
|
9
|
+
await runInDb(STORE_WALLET, 'readwrite', (store) => store.put(record, KEY));
|
|
10
|
+
}
|
|
11
|
+
async delete() {
|
|
12
|
+
await runInDb(STORE_WALLET, 'readwrite', (store) => store.delete(KEY));
|
|
13
|
+
}
|
|
14
|
+
async has() {
|
|
15
|
+
return (await this.get()) !== null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=indexeddb.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexeddb.js","sourceRoot":"","sources":["../../../src/wallet/storage/indexeddb.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAGhD,MAAM,GAAG,GAAG,SAAS,CAAA;AAErB,MAAM,OAAO,gBAAgB;IAC3B,KAAK,CAAC,GAAG;QACP,MAAM,KAAK,GAAG,MAAM,OAAO,CAA2B,YAAY,EAAE,UAAU,EAAE,CAAC,KAAK,EAAE,EAAE,CACxF,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CACf,CAAA;QACD,OAAO,KAAK,IAAI,IAAI,CAAA;IACtB,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,MAAoB;QAC5B,MAAM,OAAO,CAAc,YAAY,EAAE,WAAW,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAA;IAC1F,CAAC;IAED,KAAK,CAAC,MAAM;QACV,MAAM,OAAO,CAAY,YAAY,EAAE,WAAW,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;IACnF,CAAC;IAED,KAAK,CAAC,GAAG;QACP,OAAO,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,CAAA;IACpC,CAAC;CACF"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { StorageAdapter, StoredWallet } from '../types.js';
|
|
2
|
+
export declare class MemoryStorage implements StorageAdapter {
|
|
3
|
+
private record;
|
|
4
|
+
constructor(initial?: StoredWallet | null);
|
|
5
|
+
get(): Promise<StoredWallet | null>;
|
|
6
|
+
set(record: StoredWallet): Promise<void>;
|
|
7
|
+
delete(): Promise<void>;
|
|
8
|
+
has(): Promise<boolean>;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=memory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../../src/wallet/storage/memory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/D,qBAAa,aAAc,YAAW,cAAc;IAClD,OAAO,CAAC,MAAM,CAAqB;gBAEvB,OAAO,GAAE,YAAY,GAAG,IAAW;IAIzC,GAAG,IAAI,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAInC,GAAG,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAIxC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAIvB,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC;CAG9B"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export class MemoryStorage {
|
|
2
|
+
record;
|
|
3
|
+
constructor(initial = null) {
|
|
4
|
+
this.record = initial;
|
|
5
|
+
}
|
|
6
|
+
async get() {
|
|
7
|
+
return this.record;
|
|
8
|
+
}
|
|
9
|
+
async set(record) {
|
|
10
|
+
this.record = record;
|
|
11
|
+
}
|
|
12
|
+
async delete() {
|
|
13
|
+
this.record = null;
|
|
14
|
+
}
|
|
15
|
+
async has() {
|
|
16
|
+
return this.record !== null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=memory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.js","sourceRoot":"","sources":["../../../src/wallet/storage/memory.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,aAAa;IAChB,MAAM,CAAqB;IAEnC,YAAY,UAA+B,IAAI;QAC7C,IAAI,CAAC,MAAM,GAAG,OAAO,CAAA;IACvB,CAAC;IAED,KAAK,CAAC,GAAG;QACP,OAAO,IAAI,CAAC,MAAM,CAAA;IACpB,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,MAAoB;QAC5B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;IACtB,CAAC;IAED,KAAK,CAAC,MAAM;QACV,IAAI,CAAC,MAAM,GAAG,IAAI,CAAA;IACpB,CAAC;IAED,KAAK,CAAC,GAAG;QACP,OAAO,IAAI,CAAC,MAAM,KAAK,IAAI,CAAA;IAC7B,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.test.d.ts","sourceRoot":"","sources":["../../src/wallet/storage.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
2
|
+
import { mkdtemp, rm, stat } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { encryptSecret } from './crypto.js';
|
|
6
|
+
import { FileStorage } from './storage/file.js';
|
|
7
|
+
import { MemoryStorage } from './storage/memory.js';
|
|
8
|
+
import { createWallet, deleteWallet, exportMnemonic, exportPrivateKey, getWalletInfo, hasWallet, importWallet, lockWallet, signMessage, unlockWallet, } from './wallet.js';
|
|
9
|
+
import { privateKeyFromMnemonic } from './signer.js';
|
|
10
|
+
const password = '12345678';
|
|
11
|
+
const HARDHAT_MNEMONIC = 'test test test test test test test test test test test junk';
|
|
12
|
+
const HARDHAT_ADDRESS = '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266';
|
|
13
|
+
const dirs = [];
|
|
14
|
+
async function tempStorage() {
|
|
15
|
+
const dir = await mkdtemp(join(tmpdir(), 'm-wallet-'));
|
|
16
|
+
dirs.push(dir);
|
|
17
|
+
const path = join(dir, 'wallet.json');
|
|
18
|
+
return { storage: new FileStorage(path), path };
|
|
19
|
+
}
|
|
20
|
+
function sampleRecord() {
|
|
21
|
+
return {
|
|
22
|
+
version: 1,
|
|
23
|
+
address: HARDHAT_ADDRESS,
|
|
24
|
+
chain: 'celoSepolia',
|
|
25
|
+
createdAt: 1,
|
|
26
|
+
kdf: { name: 'PBKDF2', hash: 'SHA-256', iterations: 600_000, salt: 'AA==' },
|
|
27
|
+
cipher: { name: 'AES-GCM', iv: 'BB==', data: 'CC==' },
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
afterEach(async () => {
|
|
31
|
+
await lockWallet();
|
|
32
|
+
while (dirs.length) {
|
|
33
|
+
const dir = dirs.pop();
|
|
34
|
+
if (dir)
|
|
35
|
+
await rm(dir, { recursive: true, force: true });
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
describe('FileStorage', () => {
|
|
39
|
+
it('stores, reads and deletes the encrypted record', async () => {
|
|
40
|
+
const { storage } = await tempStorage();
|
|
41
|
+
expect(await storage.has()).toBe(false);
|
|
42
|
+
expect(await storage.get()).toBeNull();
|
|
43
|
+
await storage.set(sampleRecord());
|
|
44
|
+
expect(await storage.has()).toBe(true);
|
|
45
|
+
expect(await storage.get()).toEqual(sampleRecord());
|
|
46
|
+
await storage.delete();
|
|
47
|
+
expect(await storage.has()).toBe(false);
|
|
48
|
+
});
|
|
49
|
+
it('writes the file with owner-only permissions', async () => {
|
|
50
|
+
const { storage, path } = await tempStorage();
|
|
51
|
+
await storage.set(sampleRecord());
|
|
52
|
+
const info = await stat(path);
|
|
53
|
+
expect(info.mode & 0o777).toBe(0o600);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
describe('wallet with FileStorage (Node.js)', () => {
|
|
57
|
+
it('creates, persists, unlocks and signs without a browser', async () => {
|
|
58
|
+
const { storage } = await tempStorage();
|
|
59
|
+
const { walletInfo, mnemonic } = await createWallet({ password: password, storage });
|
|
60
|
+
expect(walletInfo.address).toMatch(/^0x[0-9a-fA-F]{40}$/);
|
|
61
|
+
expect(mnemonic.split(' ')).toHaveLength(12);
|
|
62
|
+
await lockWallet();
|
|
63
|
+
expect(await hasWallet(storage)).toBe(true);
|
|
64
|
+
expect(await hasWallet(new FileStorage('/nonexistent/m-wallet.json'))).toBe(false);
|
|
65
|
+
expect((await getWalletInfo(storage))?.address).toBe(walletInfo.address);
|
|
66
|
+
const unlocked = await unlockWallet(password, storage);
|
|
67
|
+
expect(unlocked.address).toBe(walletInfo.address);
|
|
68
|
+
const signature = await signMessage('hola');
|
|
69
|
+
expect(signature).toMatch(/^0x[0-9a-f]+$/);
|
|
70
|
+
await deleteWallet(storage);
|
|
71
|
+
expect(await hasWallet(storage)).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
it('exports the recovery phrase and the private key with the password', async () => {
|
|
74
|
+
const { storage } = await tempStorage();
|
|
75
|
+
const { mnemonic } = await createWallet({ password: password, storage });
|
|
76
|
+
expect(await exportMnemonic(password, storage)).toBe(mnemonic);
|
|
77
|
+
expect(await exportPrivateKey(password, storage)).toMatch(/^0x[0-9a-f]{64}$/i);
|
|
78
|
+
await expect(exportMnemonic('99999999', storage)).rejects.toThrow(/password/);
|
|
79
|
+
await expect(exportPrivateKey('99999999', storage)).rejects.toThrow(/password/);
|
|
80
|
+
});
|
|
81
|
+
it('has no recovery phrase when the wallet was imported from a private key', async () => {
|
|
82
|
+
const { storage } = await tempStorage();
|
|
83
|
+
const privateKey = privateKeyFromMnemonic(HARDHAT_MNEMONIC);
|
|
84
|
+
await importWallet({ privateKey, password: password, storage });
|
|
85
|
+
expect(await exportPrivateKey(password, storage)).toBe(privateKey);
|
|
86
|
+
await expect(exportMnemonic(password, storage)).rejects.toThrow(/recovery phrase/);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
describe('legacy records', () => {
|
|
90
|
+
it('unlocks a record whose ciphertext is the bare private key', async () => {
|
|
91
|
+
const privateKey = privateKeyFromMnemonic(HARDHAT_MNEMONIC);
|
|
92
|
+
const legacy = await encryptSecret(privateKey, password);
|
|
93
|
+
const storage = new MemoryStorage({
|
|
94
|
+
...sampleRecord(),
|
|
95
|
+
kdf: legacy.kdf,
|
|
96
|
+
cipher: legacy.cipher,
|
|
97
|
+
});
|
|
98
|
+
const info = await unlockWallet(password, storage);
|
|
99
|
+
expect(info.address.toLowerCase()).toBe(HARDHAT_ADDRESS.toLowerCase());
|
|
100
|
+
await expect(exportMnemonic(password, storage)).rejects.toThrow(/recovery phrase/);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
//# sourceMappingURL=storage.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.test.js","sourceRoot":"","sources":["../../src/wallet/storage.test.ts"],"names":[],"mappings":"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"}
|