@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.
Files changed (151) hide show
  1. package/dist/CHANGELOG.md +29 -0
  2. package/dist/e2e/full-wallet.d.ts +41 -0
  3. package/dist/e2e/full-wallet.d.ts.map +1 -0
  4. package/dist/e2e/full-wallet.js +138 -0
  5. package/dist/e2e/full-wallet.js.map +1 -0
  6. package/dist/e2e/full-wallet.test.d.ts +2 -0
  7. package/dist/e2e/full-wallet.test.d.ts.map +1 -0
  8. package/dist/e2e/full-wallet.test.js +37 -0
  9. package/dist/e2e/full-wallet.test.js.map +1 -0
  10. package/dist/e2e/index.d.ts +4 -0
  11. package/dist/e2e/index.d.ts.map +1 -1
  12. package/dist/e2e/index.js +2 -0
  13. package/dist/e2e/index.js.map +1 -1
  14. package/dist/e2e/local-node.d.ts +22 -0
  15. package/dist/e2e/local-node.d.ts.map +1 -0
  16. package/dist/e2e/local-node.js +144 -0
  17. package/dist/e2e/local-node.js.map +1 -0
  18. package/dist/e2e/local-node.test.d.ts +2 -0
  19. package/dist/e2e/local-node.test.d.ts.map +1 -0
  20. package/dist/e2e/local-node.test.js +43 -0
  21. package/dist/e2e/local-node.test.js.map +1 -0
  22. package/dist/tasks/wallet.d.ts.map +1 -1
  23. package/dist/tasks/wallet.js +14 -25
  24. package/dist/tasks/wallet.js.map +1 -1
  25. package/dist/test-utils/index.d.ts +1 -0
  26. package/dist/test-utils/index.d.ts.map +1 -1
  27. package/dist/test-utils/index.js +1 -0
  28. package/dist/test-utils/index.js.map +1 -1
  29. package/dist/test-utils/wallet-mocks.d.ts +44 -0
  30. package/dist/test-utils/wallet-mocks.d.ts.map +1 -0
  31. package/dist/test-utils/wallet-mocks.js +114 -0
  32. package/dist/test-utils/wallet-mocks.js.map +1 -0
  33. package/dist/test-utils/wallet-mocks.test.d.ts +2 -0
  34. package/dist/test-utils/wallet-mocks.test.d.ts.map +1 -0
  35. package/dist/test-utils/wallet-mocks.test.js +55 -0
  36. package/dist/test-utils/wallet-mocks.test.js.map +1 -0
  37. package/dist/test-utils/wallet-setup.d.ts +2 -0
  38. package/dist/test-utils/wallet-setup.d.ts.map +1 -0
  39. package/dist/test-utils/wallet-setup.js +23 -0
  40. package/dist/test-utils/wallet-setup.js.map +1 -0
  41. package/dist/test-utils/wallet-setup.test.d.ts +2 -0
  42. package/dist/test-utils/wallet-setup.test.d.ts.map +1 -0
  43. package/dist/test-utils/wallet-setup.test.js +18 -0
  44. package/dist/test-utils/wallet-setup.test.js.map +1 -0
  45. package/dist/wallet/biometric.d.ts +33 -0
  46. package/dist/wallet/biometric.d.ts.map +1 -0
  47. package/dist/wallet/biometric.js +88 -0
  48. package/dist/wallet/biometric.js.map +1 -0
  49. package/dist/wallet/biometric.test.d.ts +2 -0
  50. package/dist/wallet/biometric.test.d.ts.map +1 -0
  51. package/dist/wallet/biometric.test.js +44 -0
  52. package/dist/wallet/biometric.test.js.map +1 -0
  53. package/dist/wallet/crypto.d.ts +42 -0
  54. package/dist/wallet/crypto.d.ts.map +1 -0
  55. package/dist/wallet/crypto.js +127 -0
  56. package/dist/wallet/crypto.js.map +1 -0
  57. package/dist/wallet/crypto.test.d.ts +2 -0
  58. package/dist/wallet/crypto.test.d.ts.map +1 -0
  59. package/dist/wallet/crypto.test.js +34 -0
  60. package/dist/wallet/crypto.test.js.map +1 -0
  61. package/dist/wallet/destinations.d.ts +20 -0
  62. package/dist/wallet/destinations.d.ts.map +1 -0
  63. package/dist/wallet/destinations.js +98 -0
  64. package/dist/wallet/destinations.js.map +1 -0
  65. package/dist/wallet/destinations.test.d.ts +2 -0
  66. package/dist/wallet/destinations.test.d.ts.map +1 -0
  67. package/dist/wallet/destinations.test.js +34 -0
  68. package/dist/wallet/destinations.test.js.map +1 -0
  69. package/dist/wallet/external-provider.d.ts +33 -0
  70. package/dist/wallet/external-provider.d.ts.map +1 -0
  71. package/dist/wallet/external-provider.js +108 -0
  72. package/dist/wallet/external-provider.js.map +1 -0
  73. package/dist/wallet/external-provider.test.d.ts +2 -0
  74. package/dist/wallet/external-provider.test.d.ts.map +1 -0
  75. package/dist/wallet/external-provider.test.js +44 -0
  76. package/dist/wallet/external-provider.test.js.map +1 -0
  77. package/dist/wallet/index.d.ts +14 -0
  78. package/dist/wallet/index.d.ts.map +1 -0
  79. package/dist/wallet/index.js +24 -0
  80. package/dist/wallet/index.js.map +1 -0
  81. package/dist/wallet/indexeddb.test.d.ts +2 -0
  82. package/dist/wallet/indexeddb.test.d.ts.map +1 -0
  83. package/dist/wallet/indexeddb.test.js +28 -0
  84. package/dist/wallet/indexeddb.test.js.map +1 -0
  85. package/dist/wallet/kdf.test.d.ts +2 -0
  86. package/dist/wallet/kdf.test.d.ts.map +1 -0
  87. package/dist/wallet/kdf.test.js +54 -0
  88. package/dist/wallet/kdf.test.js.map +1 -0
  89. package/dist/wallet/provider.d.ts +38 -0
  90. package/dist/wallet/provider.d.ts.map +1 -0
  91. package/dist/wallet/provider.js +285 -0
  92. package/dist/wallet/provider.js.map +1 -0
  93. package/dist/wallet/provider.test.d.ts +2 -0
  94. package/dist/wallet/provider.test.d.ts.map +1 -0
  95. package/dist/wallet/provider.test.js +170 -0
  96. package/dist/wallet/provider.test.js.map +1 -0
  97. package/dist/wallet/signer.d.ts +55 -0
  98. package/dist/wallet/signer.d.ts.map +1 -0
  99. package/dist/wallet/signer.js +91 -0
  100. package/dist/wallet/signer.js.map +1 -0
  101. package/dist/wallet/signer.test.d.ts +2 -0
  102. package/dist/wallet/signer.test.d.ts.map +1 -0
  103. package/dist/wallet/signer.test.js +62 -0
  104. package/dist/wallet/signer.test.js.map +1 -0
  105. package/dist/wallet/siwe.d.ts +2 -0
  106. package/dist/wallet/siwe.d.ts.map +1 -0
  107. package/dist/wallet/siwe.js +5 -0
  108. package/dist/wallet/siwe.js.map +1 -0
  109. package/dist/wallet/storage/file.d.ts +19 -0
  110. package/dist/wallet/storage/file.d.ts.map +1 -0
  111. package/dist/wallet/storage/file.js +48 -0
  112. package/dist/wallet/storage/file.js.map +1 -0
  113. package/dist/wallet/storage/idb.d.ts +25 -0
  114. package/dist/wallet/storage/idb.d.ts.map +1 -0
  115. package/dist/wallet/storage/idb.js +58 -0
  116. package/dist/wallet/storage/idb.js.map +1 -0
  117. package/dist/wallet/storage/index.d.ts +5 -0
  118. package/dist/wallet/storage/index.d.ts.map +1 -0
  119. package/dist/wallet/storage/index.js +4 -0
  120. package/dist/wallet/storage/index.js.map +1 -0
  121. package/dist/wallet/storage/indexeddb.d.ts +8 -0
  122. package/dist/wallet/storage/indexeddb.d.ts.map +1 -0
  123. package/dist/wallet/storage/indexeddb.js +18 -0
  124. package/dist/wallet/storage/indexeddb.js.map +1 -0
  125. package/dist/wallet/storage/memory.d.ts +10 -0
  126. package/dist/wallet/storage/memory.d.ts.map +1 -0
  127. package/dist/wallet/storage/memory.js +19 -0
  128. package/dist/wallet/storage/memory.js.map +1 -0
  129. package/dist/wallet/storage.test.d.ts +2 -0
  130. package/dist/wallet/storage.test.d.ts.map +1 -0
  131. package/dist/wallet/storage.test.js +103 -0
  132. package/dist/wallet/storage.test.js.map +1 -0
  133. package/dist/wallet/types.d.ts +69 -0
  134. package/dist/wallet/types.d.ts.map +1 -0
  135. package/dist/wallet/types.js +6 -0
  136. package/dist/wallet/types.js.map +1 -0
  137. package/dist/wallet/wallet.d.ts +81 -0
  138. package/dist/wallet/wallet.d.ts.map +1 -0
  139. package/dist/wallet/wallet.js +358 -0
  140. package/dist/wallet/wallet.js.map +1 -0
  141. package/dist/wallet/wallet.test.d.ts +2 -0
  142. package/dist/wallet/wallet.test.d.ts.map +1 -0
  143. package/dist/wallet/wallet.test.js +150 -0
  144. package/dist/wallet/wallet.test.js.map +1 -0
  145. package/dist/wallet/web-authn.d.ts +78 -0
  146. package/dist/wallet/web-authn.d.ts.map +1 -0
  147. package/dist/wallet/web-authn.js +250 -0
  148. package/dist/wallet/web-authn.js.map +1 -0
  149. package/package.json +14 -4
  150. package/src/e2e/README.md +51 -0
  151. package/src/wallet/README.md +96 -0
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Layer L2 of https://github.com/pasosdeJesus/learn.tg/issues/246: the wallet key
3
+ * sealed with the WebAuthn **PRF** secret.
4
+ *
5
+ * The private key is never written in plaintext. The stored record only holds the
6
+ * ciphertext plus the (non-secret) salts and the credential id; the key that opens
7
+ * it can only be produced by the authenticator, after the user verifies (Face ID /
8
+ * fingerprint / device password). The password-encrypted record of `wallet.ts` is untouched
9
+ * and remains the recovery path.
10
+ */
11
+ import { fromBase64, toBase64, wipe } from './crypto.js';
12
+ import { runInDb, STORE_BIOMETRIC } from './storage/idb.js';
13
+ import { deriveWrappingKey, signalUnknownCredential, toArrayBuffer } from './web-authn.js';
14
+ const KEY = 'current';
15
+ const IV_BYTES = 12;
16
+ export const PRF_SALT_BYTES = 32;
17
+ export const HKDF_SALT_BYTES = 16;
18
+ async function subtle() {
19
+ const c = globalThis.crypto?.subtle;
20
+ if (!c)
21
+ throw new Error('Web Crypto (crypto.subtle) is not available');
22
+ return c;
23
+ }
24
+ function randomBytes(length) {
25
+ const c = globalThis.crypto;
26
+ if (!c?.getRandomValues)
27
+ throw new Error('crypto.getRandomValues is not available');
28
+ const bytes = new Uint8Array(length);
29
+ c.getRandomValues(bytes);
30
+ return bytes;
31
+ }
32
+ export async function readBiometricRecord() {
33
+ const value = await runInDb(STORE_BIOMETRIC, 'readonly', (store) => store.get(KEY));
34
+ return value ?? null;
35
+ }
36
+ export async function writeBiometricRecord(record) {
37
+ await runInDb(STORE_BIOMETRIC, 'readwrite', (store) => store.put(record, KEY));
38
+ }
39
+ export async function deleteBiometricRecord() {
40
+ await runInDb(STORE_BIOMETRIC, 'readwrite', (store) => store.delete(KEY));
41
+ }
42
+ /**
43
+ * Drops the sealed copy **and** tells the authenticator the passkey is gone
44
+ * (R-#246 §14 item 1). Used when the user turns the gesture off or deletes the
45
+ * wallet: before this, the orphan passkey stayed in the OS list.
46
+ */
47
+ export async function forgetBiometricCredential() {
48
+ const record = await readBiometricRecord().catch(() => null);
49
+ if (record?.credentialId)
50
+ await signalUnknownCredential(record.credentialId);
51
+ await deleteBiometricRecord().catch(() => undefined);
52
+ }
53
+ /** Encrypts `privateKey` with the key derived from the PRF secret of `prfSalt`. */
54
+ export async function sealWithPrfSecret(prfSecret, prfSalt, privateKey, { address, credentialId }) {
55
+ const hkdfSalt = randomBytes(HKDF_SALT_BYTES);
56
+ const iv = randomBytes(IV_BYTES);
57
+ const key = await deriveWrappingKey(prfSecret, hkdfSalt);
58
+ const data = await (await subtle()).encrypt({ name: 'AES-GCM', iv: toArrayBuffer(iv) }, key, toArrayBuffer(new TextEncoder().encode(privateKey)));
59
+ return {
60
+ v: 1,
61
+ address,
62
+ credentialId,
63
+ prfSalt: toBase64(prfSalt),
64
+ hkdfSalt: toBase64(hkdfSalt),
65
+ iv: toBase64(iv),
66
+ data: toBase64(new Uint8Array(data)),
67
+ createdAt: Date.now(),
68
+ };
69
+ }
70
+ /** Decrypts the sealed key with the PRF secret. Throws `auth-failed` on failure. */
71
+ export async function unsealWithPrfSecret(prfSecret, record) {
72
+ const key = await deriveWrappingKey(prfSecret, fromBase64(record.hkdfSalt));
73
+ try {
74
+ const plain = await (await subtle()).decrypt({ name: 'AES-GCM', iv: toArrayBuffer(fromBase64(record.iv)) }, key, toArrayBuffer(fromBase64(record.data)));
75
+ return new TextDecoder().decode(plain);
76
+ }
77
+ catch {
78
+ throw new Error('auth-failed');
79
+ }
80
+ }
81
+ export function wipePrfSecret(secret) {
82
+ wipe(secret);
83
+ }
84
+ /** Fresh salt for a `prf.eval` call; stored next to the sealed record. */
85
+ export function newPrfSalt() {
86
+ return randomBytes(PRF_SALT_BYTES);
87
+ }
88
+ //# sourceMappingURL=biometric.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"biometric.js","sourceRoot":"","sources":["../../src/wallet/biometric.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=biometric.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"biometric.test.d.ts","sourceRoot":"","sources":["../../src/wallet/biometric.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,44 @@
1
+ import { beforeAll, beforeEach, describe, expect, it } from 'vitest';
2
+ import { installIndexedDB, mockWebAuthn } from '../test-utils/wallet-mocks.js';
3
+ import { MemoryStorage } from './storage/memory.js';
4
+ import { createWallet, deleteWallet, hasBiometricUnlock, lockWallet, unlockWithBiometric, enableBiometricUnlock, disableBiometricUnlock, } from './wallet.js';
5
+ import { deleteBiometricRecord, readBiometricRecord } from './biometric.js';
6
+ const password = '12345678';
7
+ beforeAll(async () => {
8
+ await installIndexedDB();
9
+ });
10
+ describe('biometric unlock (L2) with mocked WebAuthn', () => {
11
+ let storage;
12
+ beforeEach(async () => {
13
+ storage = new MemoryStorage();
14
+ await lockWallet();
15
+ await deleteBiometricRecord().catch(() => undefined);
16
+ mockWebAuthn();
17
+ });
18
+ it('seals the key with the PRF secret and re-opens it with one gesture', async () => {
19
+ const { walletInfo } = await createWallet({ password, storage });
20
+ const { address } = await enableBiometricUnlock(password, storage);
21
+ expect(address).toBe(walletInfo.address);
22
+ expect(await hasBiometricUnlock(storage)).toBe(true);
23
+ const sealed = await readBiometricRecord();
24
+ expect(sealed?.address).toBe(walletInfo.address);
25
+ // El secreto no queda en claro en el registro.
26
+ expect(JSON.stringify(sealed)).not.toContain('privateKey');
27
+ await lockWallet();
28
+ const unlocked = await unlockWithBiometric(storage);
29
+ expect(unlocked.address).toBe(walletInfo.address);
30
+ });
31
+ it('forgets the sealed copy when the biometric unlock is disabled', async () => {
32
+ await createWallet({ password, storage });
33
+ await enableBiometricUnlock(password, storage);
34
+ await disableBiometricUnlock();
35
+ expect(await hasBiometricUnlock(storage)).toBe(false);
36
+ });
37
+ it('cleans the sealed copy when the wallet is deleted', async () => {
38
+ await createWallet({ password, storage });
39
+ await enableBiometricUnlock(password, storage);
40
+ await deleteWallet(storage);
41
+ expect(await readBiometricRecord().catch(() => null)).toBeNull();
42
+ });
43
+ });
44
+ //# sourceMappingURL=biometric.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"biometric.test.js","sourceRoot":"","sources":["../../src/wallet/biometric.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ /** Default work factor (R-#251). */
2
+ export declare const KDF_ITERATIONS = 600000;
3
+ /** Never below the historical value: an old record must not get weaker. */
4
+ export declare const KDF_ITERATIONS_FLOOR = 600000;
5
+ /** Ceiling so a slow phone cannot hang when calibrating (R-#251). */
6
+ export declare const KDF_ITERATIONS_CEILING = 5000000;
7
+ /** Target cost of one derivation on the device (R-#251). */
8
+ export declare const KDF_BUDGET_MS = 400;
9
+ /**
10
+ * Accepted secret (R-#251): NFKD + trim, so a leading/trailing space or a composed
11
+ * accent does not lock the user out of their own wallet. Idempotent.
12
+ */
13
+ export declare function normalizePassword(password: string): string;
14
+ export declare function toBase64(bytes: Uint8Array): string;
15
+ export declare function fromBase64(value: string): Uint8Array;
16
+ export interface EncryptedSecret {
17
+ kdf: {
18
+ name: 'PBKDF2';
19
+ hash: 'SHA-256';
20
+ iterations: number;
21
+ salt: string;
22
+ };
23
+ cipher: {
24
+ name: 'AES-GCM';
25
+ iv: string;
26
+ data: string;
27
+ };
28
+ }
29
+ export declare function encryptSecret(plaintext: string, password: string, iterations?: number): Promise<EncryptedSecret>;
30
+ /**
31
+ * Picks the PBKDF2 work factor for this device (R-#251): measures the floor and
32
+ * scales it to `budgetMs`, clamped to [floor, ceiling]. The count lives in the
33
+ * record, so a device that chose 2 M and another that chose 600 k keep working.
34
+ *
35
+ * `measure` is injectable so tests do not depend on the machine's speed.
36
+ */
37
+ export declare function calibrateIterations(measure?: (iterations: number) => Promise<number>, budgetMs?: number): Promise<number>;
38
+ export declare function decryptSecret(secret: EncryptedSecret, password: string): Promise<string>;
39
+ export declare function wipe(bytes: Uint8Array | null | undefined): void;
40
+ /** SHA-256 over bytes. Used by the BIP39 checksum of `isValidMnemonic`. */
41
+ export declare function sha256(bytes: Uint8Array): Promise<Uint8Array>;
42
+ //# sourceMappingURL=crypto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../src/wallet/crypto.ts"],"names":[],"mappings":"AAIA,oCAAoC;AACpC,eAAO,MAAM,cAAc,SAAoB,CAAA;AAC/C,2EAA2E;AAC3E,eAAO,MAAM,oBAAoB,SAAoB,CAAA;AACrD,qEAAqE;AACrE,eAAO,MAAM,sBAAsB,UAAY,CAAA;AAC/C,4DAA4D;AAC5D,eAAO,MAAM,aAAa,MAAM,CAAA;AAEhC;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE1D;AAsBD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAIlD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAKpD;AAwBD,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,IAAI,EAAE,SAAS,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IAC1E,MAAM,EAAE;QAAE,IAAI,EAAE,SAAS,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CACtD;AAED,wBAAsB,aAAa,CACjC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,UAAU,GAAE,MAA0B,GACrC,OAAO,CAAC,eAAe,CAAC,CAe1B;AAUD;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,GAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAqB,EACpE,QAAQ,GAAE,MAAsB,GAC/B,OAAO,CAAC,MAAM,CAAC,CAajB;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAsB9F;AAED,wBAAgB,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,CAE/D;AAED,2EAA2E;AAC3E,wBAAsB,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAGnE"}
@@ -0,0 +1,127 @@
1
+ const PBKDF2_ITERATIONS = 600_000;
2
+ const SALT_BYTES = 16;
3
+ const IV_BYTES = 12;
4
+ /** Default work factor (R-#251). */
5
+ export const KDF_ITERATIONS = PBKDF2_ITERATIONS;
6
+ /** Never below the historical value: an old record must not get weaker. */
7
+ export const KDF_ITERATIONS_FLOOR = PBKDF2_ITERATIONS;
8
+ /** Ceiling so a slow phone cannot hang when calibrating (R-#251). */
9
+ export const KDF_ITERATIONS_CEILING = 5_000_000;
10
+ /** Target cost of one derivation on the device (R-#251). */
11
+ export const KDF_BUDGET_MS = 400;
12
+ /**
13
+ * Accepted secret (R-#251): NFKD + trim, so a leading/trailing space or a composed
14
+ * accent does not lock the user out of their own wallet. Idempotent.
15
+ */
16
+ export function normalizePassword(password) {
17
+ return typeof password === 'string' ? password.normalize('NFKD').trim() : '';
18
+ }
19
+ function subtle() {
20
+ const c = globalThis.crypto;
21
+ if (!c?.subtle) {
22
+ throw new Error('Web Crypto (crypto.subtle) is not available; Node 18+ or a secure browser context is required');
23
+ }
24
+ return c.subtle;
25
+ }
26
+ function randomBytes(length) {
27
+ const c = globalThis.crypto;
28
+ if (!c?.getRandomValues) {
29
+ throw new Error('crypto.getRandomValues is not available');
30
+ }
31
+ const bytes = new Uint8Array(length);
32
+ c.getRandomValues(bytes);
33
+ return bytes;
34
+ }
35
+ export function toBase64(bytes) {
36
+ let binary = '';
37
+ for (const b of bytes)
38
+ binary += String.fromCharCode(b);
39
+ return btoa(binary);
40
+ }
41
+ export function fromBase64(value) {
42
+ const binary = atob(value);
43
+ const bytes = new Uint8Array(binary.length);
44
+ for (let i = 0; i < binary.length; i++)
45
+ bytes[i] = binary.charCodeAt(i);
46
+ return bytes;
47
+ }
48
+ async function deriveKey(password, salt, iterations) {
49
+ const material = await subtle().importKey('raw', new TextEncoder().encode(password), 'PBKDF2', false, ['deriveKey']);
50
+ return subtle().deriveKey({
51
+ name: 'PBKDF2',
52
+ salt: salt,
53
+ iterations,
54
+ hash: 'SHA-256',
55
+ }, material, { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt']);
56
+ }
57
+ export async function encryptSecret(plaintext, password, iterations = PBKDF2_ITERATIONS) {
58
+ const salt = randomBytes(SALT_BYTES);
59
+ const iv = randomBytes(IV_BYTES);
60
+ const count = Number.isFinite(iterations) && iterations > 0 ? Math.floor(iterations) : PBKDF2_ITERATIONS;
61
+ const key = await deriveKey(normalizePassword(password), salt, count);
62
+ const data = await subtle().encrypt({ name: 'AES-GCM', iv: iv }, key, new TextEncoder().encode(plaintext));
63
+ return {
64
+ kdf: { name: 'PBKDF2', hash: 'SHA-256', iterations: count, salt: toBase64(salt) },
65
+ cipher: { name: 'AES-GCM', iv: toBase64(iv), data: toBase64(new Uint8Array(data)) },
66
+ };
67
+ }
68
+ /** One derivation with a throwaway salt, used to measure the device cost. */
69
+ async function measureDerivation(iterations) {
70
+ const salt = randomBytes(SALT_BYTES);
71
+ const started = Date.now();
72
+ await deriveKey('calibration-only', salt, iterations);
73
+ return Date.now() - started;
74
+ }
75
+ /**
76
+ * Picks the PBKDF2 work factor for this device (R-#251): measures the floor and
77
+ * scales it to `budgetMs`, clamped to [floor, ceiling]. The count lives in the
78
+ * record, so a device that chose 2 M and another that chose 600 k keep working.
79
+ *
80
+ * `measure` is injectable so tests do not depend on the machine's speed.
81
+ */
82
+ export async function calibrateIterations(measure = measureDerivation, budgetMs = KDF_BUDGET_MS) {
83
+ let floorMs = 0;
84
+ try {
85
+ floorMs = await measure(KDF_ITERATIONS_FLOOR);
86
+ }
87
+ catch {
88
+ return KDF_ITERATIONS_FLOOR;
89
+ }
90
+ if (!Number.isFinite(floorMs) || floorMs <= 0)
91
+ return KDF_ITERATIONS_FLOOR;
92
+ const projected = Math.floor((KDF_ITERATIONS_FLOOR * budgetMs) / floorMs);
93
+ if (!Number.isFinite(projected) || projected < KDF_ITERATIONS_FLOOR) {
94
+ return KDF_ITERATIONS_FLOOR;
95
+ }
96
+ return Math.min(KDF_ITERATIONS_CEILING, projected);
97
+ }
98
+ export async function decryptSecret(secret, password) {
99
+ const salt = fromBase64(secret.kdf.salt);
100
+ const iv = fromBase64(secret.cipher.iv);
101
+ // R-#251: primero con la clave normalizada (NFKD + trim) y, si el registro se
102
+ // cifró con la clave tal cual la escribió el usuario, con esa misma. Así una clave
103
+ // con un espacio o un acento compuesto sigue abriendo la billetera.
104
+ const normalized = normalizePassword(password);
105
+ const candidates = normalized === password ? [password] : [normalized, password];
106
+ for (const candidate of candidates) {
107
+ const key = await deriveKey(candidate, salt, secret.kdf.iterations);
108
+ try {
109
+ const plain = await subtle().decrypt({ name: 'AES-GCM', iv: iv }, key, fromBase64(secret.cipher.data));
110
+ return new TextDecoder().decode(plain);
111
+ }
112
+ catch {
113
+ // se prueba la siguiente variante
114
+ }
115
+ }
116
+ throw new Error('Wrong password or corrupted wallet data');
117
+ }
118
+ export function wipe(bytes) {
119
+ if (bytes)
120
+ bytes.fill(0);
121
+ }
122
+ /** SHA-256 over bytes. Used by the BIP39 checksum of `isValidMnemonic`. */
123
+ export async function sha256(bytes) {
124
+ const digest = await subtle().digest('SHA-256', bytes);
125
+ return new Uint8Array(digest);
126
+ }
127
+ //# sourceMappingURL=crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/wallet/crypto.ts"],"names":[],"mappings":"AAAA,MAAM,iBAAiB,GAAG,OAAO,CAAA;AACjC,MAAM,UAAU,GAAG,EAAE,CAAA;AACrB,MAAM,QAAQ,GAAG,EAAE,CAAA;AAEnB,oCAAoC;AACpC,MAAM,CAAC,MAAM,cAAc,GAAG,iBAAiB,CAAA;AAC/C,2EAA2E;AAC3E,MAAM,CAAC,MAAM,oBAAoB,GAAG,iBAAiB,CAAA;AACrD,qEAAqE;AACrE,MAAM,CAAC,MAAM,sBAAsB,GAAG,SAAS,CAAA;AAC/C,4DAA4D;AAC5D,MAAM,CAAC,MAAM,aAAa,GAAG,GAAG,CAAA;AAEhC;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAgB;IAChD,OAAO,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;AAC9E,CAAC;AAED,SAAS,MAAM;IACb,MAAM,CAAC,GAAI,UAAkC,CAAC,MAAM,CAAA;IACpD,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CACb,+FAA+F,CAChG,CAAA;IACH,CAAC;IACD,OAAO,CAAC,CAAC,MAAM,CAAA;AACjB,CAAC;AAED,SAAS,WAAW,CAAC,MAAc;IACjC,MAAM,CAAC,GAAI,UAAkC,CAAC,MAAM,CAAA;IACpD,IAAI,CAAC,CAAC,EAAE,eAAe,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAA;IAC5D,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAA;IACpC,CAAC,CAAC,eAAe,CAAC,KAAK,CAAC,CAAA;IACxB,OAAO,KAAK,CAAA;AACd,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,KAAiB;IACxC,IAAI,MAAM,GAAG,EAAE,CAAA;IACf,KAAK,MAAM,CAAC,IAAI,KAAK;QAAE,MAAM,IAAI,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;IACvD,OAAO,IAAI,CAAC,MAAM,CAAC,CAAA;AACrB,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAA;IAC1B,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IAC3C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;IACvE,OAAO,KAAK,CAAA;AACd,CAAC;AAED,KAAK,UAAU,SAAS,CAAC,QAAgB,EAAE,IAAgB,EAAE,UAAkB;IAC7E,MAAM,QAAQ,GAAG,MAAM,MAAM,EAAE,CAAC,SAAS,CACvC,KAAK,EACL,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,QAAQ,CAA2B,EAC5D,QAAQ,EACR,KAAK,EACL,CAAC,WAAW,CAAC,CACd,CAAA;IACD,OAAO,MAAM,EAAE,CAAC,SAAS,CACvB;QACE,IAAI,EAAE,QAAQ;QACd,IAAI,EAAE,IAA8B;QACpC,UAAU;QACV,IAAI,EAAE,SAAS;KAChB,EACD,QAAQ,EACR,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,EAChC,KAAK,EACL,CAAC,SAAS,EAAE,SAAS,CAAC,CACvB,CAAA;AACH,CAAC;AAOD,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,SAAiB,EACjB,QAAgB,EAChB,aAAqB,iBAAiB;IAEtC,MAAM,IAAI,GAAG,WAAW,CAAC,UAAU,CAAC,CAAA;IACpC,MAAM,EAAE,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAA;IAChC,MAAM,KAAK,GACT,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAA;IAC5F,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,iBAAiB,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,CAAA;IACrE,MAAM,IAAI,GAAG,MAAM,MAAM,EAAE,CAAC,OAAO,CACjC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,EAA4B,EAAE,EACrD,GAAG,EACH,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,SAAS,CAA2B,CAC9D,CAAA;IACD,OAAO;QACL,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE;QACjF,MAAM,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE;KACpF,CAAA;AACH,CAAC;AAED,6EAA6E;AAC7E,KAAK,UAAU,iBAAiB,CAAC,UAAkB;IACjD,MAAM,IAAI,GAAG,WAAW,CAAC,UAAU,CAAC,CAAA;IACpC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;IAC1B,MAAM,SAAS,CAAC,kBAAkB,EAAE,IAAI,EAAE,UAAU,CAAC,CAAA;IACrD,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAA;AAC7B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,UAAmD,iBAAiB,EACpE,WAAmB,aAAa;IAEhC,IAAI,OAAO,GAAG,CAAC,CAAA;IACf,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,OAAO,CAAC,oBAAoB,CAAC,CAAA;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,oBAAoB,CAAA;IAC7B,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,OAAO,IAAI,CAAC;QAAE,OAAO,oBAAoB,CAAA;IAC1E,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,oBAAoB,GAAG,QAAQ,CAAC,GAAG,OAAO,CAAC,CAAA;IACzE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,SAAS,GAAG,oBAAoB,EAAE,CAAC;QACpE,OAAO,oBAAoB,CAAA;IAC7B,CAAC;IACD,OAAO,IAAI,CAAC,GAAG,CAAC,sBAAsB,EAAE,SAAS,CAAC,CAAA;AACpD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,MAAuB,EAAE,QAAgB;IAC3E,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IACxC,MAAM,EAAE,GAAG,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;IACvC,8EAA8E;IAC9E,mFAAmF;IACnF,oEAAoE;IACpE,MAAM,UAAU,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAA;IAC9C,MAAM,UAAU,GAAG,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAA;IAChF,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,SAAS,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;QACnE,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,MAAM,EAAE,CAAC,OAAO,CAClC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,EAA4B,EAAE,EACrD,GAAG,EACH,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAA2B,CACzD,CAAA;YACD,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QACxC,CAAC;QAAC,MAAM,CAAC;YACP,kCAAkC;QACpC,CAAC;IACH,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAA;AAC5D,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,KAAoC;IACvD,IAAI,KAAK;QAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AAC1B,CAAC;AAED,2EAA2E;AAC3E,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,KAAiB;IAC5C,MAAM,MAAM,GAAG,MAAM,MAAM,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE,KAA+B,CAAC,CAAA;IAChF,OAAO,IAAI,UAAU,CAAC,MAAM,CAAC,CAAA;AAC/B,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=crypto.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.test.d.ts","sourceRoot":"","sources":["../../src/wallet/crypto.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,34 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { KDF_ITERATIONS, decryptSecret, encryptSecret, fromBase64, toBase64 } from './crypto.js';
3
+ describe('crypto', () => {
4
+ it('round-trips a secret with the password', async () => {
5
+ const secret = await encryptSecret('0x' + 'ab'.repeat(32), '123456');
6
+ expect(secret.kdf.name).toBe('PBKDF2');
7
+ expect(secret.kdf.hash).toBe('SHA-256');
8
+ expect(secret.kdf.iterations).toBe(KDF_ITERATIONS);
9
+ expect(secret.cipher.name).toBe('AES-GCM');
10
+ expect(secret.kdf.salt).not.toBe('');
11
+ expect(secret.cipher.iv).not.toBe('');
12
+ expect(secret.cipher.data).not.toContain('ab'.repeat(32));
13
+ await expect(decryptSecret(secret, '123456')).resolves.toBe('0x' + 'ab'.repeat(32));
14
+ });
15
+ it('uses 600,000 PBKDF2 iterations', () => {
16
+ expect(KDF_ITERATIONS).toBe(600_000);
17
+ });
18
+ it('uses a fresh salt and IV per encryption', async () => {
19
+ const first = await encryptSecret('secret', '123456');
20
+ const second = await encryptSecret('secret', '123456');
21
+ expect(first.kdf.salt).not.toBe(second.kdf.salt);
22
+ expect(first.cipher.iv).not.toBe(second.cipher.iv);
23
+ expect(first.cipher.data).not.toBe(second.cipher.data);
24
+ });
25
+ it('rejects a wrong password', async () => {
26
+ const secret = await encryptSecret('secret', '123456');
27
+ await expect(decryptSecret(secret, '654321')).rejects.toThrow(/Wrong password/i);
28
+ });
29
+ it('round-trips base64 helpers', () => {
30
+ const bytes = new Uint8Array([0, 1, 2, 250, 255]);
31
+ expect(Array.from(fromBase64(toBase64(bytes)))).toEqual([0, 1, 2, 250, 255]);
32
+ });
33
+ });
34
+ //# sourceMappingURL=crypto.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.test.js","sourceRoot":"","sources":["../../src/wallet/crypto.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAC7C,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAEhG,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE;IACtB,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;QACtD,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAA;QACpE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QACtC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QACvC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAA;QAClD,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QAC1C,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACpC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACrC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAA;QACzD,MAAM,MAAM,CAAC,aAAa,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAA;IACrF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;QACxC,MAAM,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IACtC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,yCAAyC,EAAE,KAAK,IAAI,EAAE;QACvD,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;QACrD,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;QACtD,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QAChD,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;QAClD,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IACxD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,0BAA0B,EAAE,KAAK,IAAI,EAAE;QACxC,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;QACtD,MAAM,MAAM,CAAC,aAAa,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAA;IAClF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAA;QACjD,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAA;IAC9E,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Destinations this wallet has already sent funds to (R-#253).
3
+ *
4
+ * A **new** destination always requires the device gesture, even inside the grace
5
+ * window of `USER_VERIFICATION_GRACE_MS`: that stops an automated or hostile flow
6
+ * from draining the wallet to an address the user never used. Repeated transfers to
7
+ * a known address stay silent within the window.
8
+ *
9
+ * Stored per wallet address in `localStorage` (not a secret; only addresses), with
10
+ * an in-memory fallback when `localStorage` is unavailable (private mode, Node).
11
+ */
12
+ /** Overrides the storage-key prefix (a consumer keeps its own namespace). */
13
+ export declare function setDestinationsPrefix(value: string): void;
14
+ /** Whether the wallet has already sent funds to `destination`. */
15
+ export declare function isKnownDestination(wallet: string, destination: string): boolean;
16
+ /** Records `destination` for `wallet` after a successful transfer. */
17
+ export declare function rememberDestination(wallet: string, destination: string): void;
18
+ /** Forgets every destination (tests, and when the wallet is deleted). */
19
+ export declare function clearDestinations(wallet?: string): void;
20
+ //# sourceMappingURL=destinations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"destinations.d.ts","sourceRoot":"","sources":["../../src/wallet/destinations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,6EAA6E;AAC7E,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAEzD;AA+BD,kEAAkE;AAClE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAG/E;AAED,sEAAsE;AACtE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,CAc7E;AAED,yEAAyE;AACzE,wBAAgB,iBAAiB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAiBvD"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Destinations this wallet has already sent funds to (R-#253).
3
+ *
4
+ * A **new** destination always requires the device gesture, even inside the grace
5
+ * window of `USER_VERIFICATION_GRACE_MS`: that stops an automated or hostile flow
6
+ * from draining the wallet to an address the user never used. Repeated transfers to
7
+ * a known address stay silent within the window.
8
+ *
9
+ * Stored per wallet address in `localStorage` (not a secret; only addresses), with
10
+ * an in-memory fallback when `localStorage` is unavailable (private mode, Node).
11
+ */
12
+ /** Namespace of the `localStorage` keys (a consumer sets its own with `setDestinationsPrefix`). */
13
+ let prefix = 'pasosdejesus-m-wallet.destinations.';
14
+ /** Overrides the storage-key prefix (a consumer keeps its own namespace). */
15
+ export function setDestinationsPrefix(value) {
16
+ if (value)
17
+ prefix = value;
18
+ }
19
+ /** Cap so the list cannot grow without bound. */
20
+ const MAX_DESTINATIONS = 200;
21
+ const memory = new Map();
22
+ function storage() {
23
+ try {
24
+ return globalThis.localStorage ?? null;
25
+ }
26
+ catch {
27
+ return null;
28
+ }
29
+ }
30
+ function load(wallet) {
31
+ const key = wallet.toLowerCase();
32
+ const cached = memory.get(key);
33
+ if (cached)
34
+ return cached;
35
+ const set = new Set();
36
+ const raw = storage()?.getItem(prefix + key);
37
+ if (raw) {
38
+ try {
39
+ for (const item of JSON.parse(raw))
40
+ set.add(String(item).toLowerCase());
41
+ }
42
+ catch {
43
+ // Lista corrupta: se empieza de cero (sólo provoca pedir el gesto de nuevo).
44
+ }
45
+ }
46
+ memory.set(key, set);
47
+ return set;
48
+ }
49
+ /** Whether the wallet has already sent funds to `destination`. */
50
+ export function isKnownDestination(wallet, destination) {
51
+ if (!wallet || !destination)
52
+ return false;
53
+ return load(wallet).has(destination.toLowerCase());
54
+ }
55
+ /** Records `destination` for `wallet` after a successful transfer. */
56
+ export function rememberDestination(wallet, destination) {
57
+ if (!wallet || !destination)
58
+ return;
59
+ const set = load(wallet);
60
+ set.add(destination.toLowerCase());
61
+ while (set.size > MAX_DESTINATIONS) {
62
+ const first = set.values().next().value;
63
+ if (first === undefined)
64
+ break;
65
+ set.delete(first);
66
+ }
67
+ try {
68
+ storage()?.setItem(prefix + wallet.toLowerCase(), JSON.stringify([...set]));
69
+ }
70
+ catch {
71
+ // Sin persistencia: queda sólo en memoria (se pedirá el gesto tras recargar).
72
+ }
73
+ }
74
+ /** Forgets every destination (tests, and when the wallet is deleted). */
75
+ export function clearDestinations(wallet) {
76
+ if (wallet) {
77
+ memory.delete(wallet.toLowerCase());
78
+ }
79
+ else {
80
+ memory.clear();
81
+ }
82
+ const s = storage();
83
+ if (!s)
84
+ return;
85
+ try {
86
+ for (let i = s.length - 1; i >= 0; i -= 1) {
87
+ const key = s.key(i);
88
+ if (!key?.startsWith(prefix))
89
+ continue;
90
+ if (!wallet || key === prefix + wallet.toLowerCase())
91
+ s.removeItem(key);
92
+ }
93
+ }
94
+ catch {
95
+ // Nada que limpiar.
96
+ }
97
+ }
98
+ //# sourceMappingURL=destinations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"destinations.js","sourceRoot":"","sources":["../../src/wallet/destinations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,mGAAmG;AACnG,IAAI,MAAM,GAAG,qCAAqC,CAAA;AAClD,6EAA6E;AAC7E,MAAM,UAAU,qBAAqB,CAAC,KAAa;IACjD,IAAI,KAAK;QAAE,MAAM,GAAG,KAAK,CAAA;AAC3B,CAAC;AACD,iDAAiD;AACjD,MAAM,gBAAgB,GAAG,GAAG,CAAA;AAE5B,MAAM,MAAM,GAAG,IAAI,GAAG,EAAuB,CAAA;AAE7C,SAAS,OAAO;IACd,IAAI,CAAC;QACH,OAAQ,UAAyC,CAAC,YAAY,IAAI,IAAI,CAAA;IACxE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED,SAAS,IAAI,CAAC,MAAc;IAC1B,MAAM,GAAG,GAAG,MAAM,CAAC,WAAW,EAAE,CAAA;IAChC,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAC9B,IAAI,MAAM;QAAE,OAAO,MAAM,CAAA;IACzB,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAA;IAC7B,MAAM,GAAG,GAAG,OAAO,EAAE,EAAE,OAAO,CAAC,MAAM,GAAG,GAAG,CAAC,CAAA;IAC5C,IAAI,GAAG,EAAE,CAAC;QACR,IAAI,CAAC;YACH,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAa;gBAAE,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,CAAA;QACrF,CAAC;QAAC,MAAM,CAAC;YACP,6EAA6E;QAC/E,CAAC;IACH,CAAC;IACD,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;IACpB,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,kBAAkB,CAAC,MAAc,EAAE,WAAmB;IACpE,IAAI,CAAC,MAAM,IAAI,CAAC,WAAW;QAAE,OAAO,KAAK,CAAA;IACzC,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC,CAAA;AACpD,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,mBAAmB,CAAC,MAAc,EAAE,WAAmB;IACrE,IAAI,CAAC,MAAM,IAAI,CAAC,WAAW;QAAE,OAAM;IACnC,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,CAAA;IACxB,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC,CAAA;IAClC,OAAO,GAAG,CAAC,IAAI,GAAG,gBAAgB,EAAE,CAAC;QACnC,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAA;QACvC,IAAI,KAAK,KAAK,SAAS;YAAE,MAAK;QAC9B,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IACnB,CAAC;IACD,IAAI,CAAC;QACH,OAAO,EAAE,EAAE,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC,WAAW,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAA;IAC7E,CAAC;IAAC,MAAM,CAAC;QACP,8EAA8E;IAChF,CAAC;AACH,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,iBAAiB,CAAC,MAAe;IAC/C,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAA;IACrC,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,KAAK,EAAE,CAAA;IAChB,CAAC;IACD,MAAM,CAAC,GAAG,OAAO,EAAE,CAAA;IACnB,IAAI,CAAC,CAAC;QAAE,OAAM;IACd,IAAI,CAAC;QACH,KAAK,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1C,MAAM,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;YACpB,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,MAAM,CAAC;gBAAE,SAAQ;YACtC,IAAI,CAAC,MAAM,IAAI,GAAG,KAAK,MAAM,GAAG,MAAM,CAAC,WAAW,EAAE;gBAAE,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAA;QACzE,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,oBAAoB;IACtB,CAAC;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=destinations.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"destinations.test.d.ts","sourceRoot":"","sources":["../../src/wallet/destinations.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,34 @@
1
+ import { beforeEach, describe, expect, it } from 'vitest';
2
+ import { clearDestinations, isKnownDestination, rememberDestination, setDestinationsPrefix, } from './destinations.js';
3
+ const WALLET = '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266';
4
+ const OTHER = '0x70997970C51812dc3A010C7d01b50e0d17dc79C8';
5
+ describe('destinations (R-#253)', () => {
6
+ beforeEach(() => {
7
+ setDestinationsPrefix('m-wallet-test.destinations.');
8
+ clearDestinations();
9
+ });
10
+ it('treats an unseen address as new', () => {
11
+ expect(isKnownDestination(WALLET, OTHER)).toBe(false);
12
+ });
13
+ it('remembers an address, case-insensitively', () => {
14
+ rememberDestination(WALLET, OTHER);
15
+ expect(isKnownDestination(WALLET, OTHER)).toBe(true);
16
+ expect(isKnownDestination(WALLET, OTHER.toLowerCase())).toBe(true);
17
+ });
18
+ it('keeps the list per wallet', () => {
19
+ rememberDestination(WALLET, OTHER);
20
+ expect(isKnownDestination('0x0000000000000000000000000000000000000001', OTHER)).toBe(false);
21
+ });
22
+ it('is false for empty input', () => {
23
+ expect(isKnownDestination('', OTHER)).toBe(false);
24
+ expect(isKnownDestination(WALLET, '')).toBe(false);
25
+ });
26
+ it('clears one wallet without touching the others', () => {
27
+ rememberDestination(WALLET, OTHER);
28
+ rememberDestination('0x0000000000000000000000000000000000000001', OTHER);
29
+ clearDestinations(WALLET);
30
+ expect(isKnownDestination(WALLET, OTHER)).toBe(false);
31
+ expect(isKnownDestination('0x0000000000000000000000000000000000000001', OTHER)).toBe(true);
32
+ });
33
+ });
34
+ //# sourceMappingURL=destinations.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"destinations.test.js","sourceRoot":"","sources":["../../src/wallet/destinations.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AACzD,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,mBAAmB,CAAA;AAE1B,MAAM,MAAM,GAAG,4CAA4C,CAAA;AAC3D,MAAM,KAAK,GAAG,4CAA4C,CAAA;AAE1D,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,UAAU,CAAC,GAAG,EAAE;QACd,qBAAqB,CAAC,6BAA6B,CAAC,CAAA;QACpD,iBAAiB,EAAE,CAAA;IACrB,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,CAAC,kBAAkB,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACvD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,mBAAmB,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;QAClC,MAAM,CAAC,kBAAkB,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACpD,MAAM,CAAC,kBAAkB,CAAC,MAAM,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACpE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACnC,mBAAmB,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;QAClC,MAAM,CAAC,kBAAkB,CAAC,4CAA4C,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAC7F,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;QAClC,MAAM,CAAC,kBAAkB,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACjD,MAAM,CAAC,kBAAkB,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACpD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,mBAAmB,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;QAClC,mBAAmB,CAAC,4CAA4C,EAAE,KAAK,CAAC,CAAA;QACxE,iBAAiB,CAAC,MAAM,CAAC,CAAA;QACzB,MAAM,CAAC,kBAAkB,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACrD,MAAM,CAAC,kBAAkB,CAAC,4CAA4C,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC5F,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
@@ -0,0 +1,33 @@
1
+ import type { Eip1193Provider } from './types.js';
2
+ export interface AnnouncedProvider {
3
+ uuid: string;
4
+ name: string;
5
+ rdns: string;
6
+ icon?: string;
7
+ provider: Eip1193Provider;
8
+ }
9
+ export interface ExternalProviderState {
10
+ provider: Eip1193Provider | null;
11
+ announced: AnnouncedProvider[];
12
+ /** De dónde salió el proveedor elegido (`window.ethereum`, EIP-6963 o ninguno). */
13
+ source: ExternalProviderSource;
14
+ /** rdns del anuncio elegido; `null` si es `window.ethereum`. */
15
+ rdns: string | null;
16
+ }
17
+ export type ExternalProviderSource = 'window.ethereum' | 'eip6963' | null;
18
+ /** Suscribe un listener (para `useSyncExternalStore` del consumidor). */
19
+ export declare function subscribeExternalProvider(listener: () => void): () => void;
20
+ /** Snapshot actual del store (para `useSyncExternalStore`). */
21
+ export declare function getExternalProviderSnapshot(): ExternalProviderState;
22
+ /** Effective injected provider, or `null` (also callable outside React). */
23
+ export declare function getExternalProvider(): Eip1193Provider | null;
24
+ /**
25
+ * Valor para `wallet_source` cuando la sesión se inicia con una billetera inyectada:
26
+ * `eip6963:<rdns>` si llegó por anuncio, `window.ethereum` si es el objeto clásico y
27
+ * `unknown` si todavía no hay proveedor.
28
+ */
29
+ export declare function externalWalletSource(): string;
30
+ export declare function getAnnouncedProviders(): AnnouncedProvider[];
31
+ /** Test-only: forgets the listeners and the chosen provider. */
32
+ export declare function resetExternalProviderForTests(): void;
33
+ //# sourceMappingURL=external-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"external-provider.d.ts","sourceRoot":"","sources":["../../src/wallet/external-provider.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAEjD,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,eAAe,CAAA;CAC1B;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAA;IAChC,SAAS,EAAE,iBAAiB,EAAE,CAAA;IAC9B,mFAAmF;IACnF,MAAM,EAAE,sBAAsB,CAAA;IAC9B,gEAAgE;IAChE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CACpB;AAED,MAAM,MAAM,sBAAsB,GAAG,iBAAiB,GAAG,SAAS,GAAG,IAAI,CAAA;AAuEzE,yEAAyE;AACzE,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAM1E;AAED,+DAA+D;AAC/D,wBAAgB,2BAA2B,IAAI,qBAAqB,CAInE;AAED,4EAA4E;AAC5E,wBAAgB,mBAAmB,IAAI,eAAe,GAAG,IAAI,CAI5D;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAM7C;AAED,wBAAgB,qBAAqB,IAAI,iBAAiB,EAAE,CAG3D;AAED,gEAAgE;AAChE,wBAAgB,6BAA6B,IAAI,IAAI,CAIpD"}