@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,150 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
+
import { verifyMessage } from 'viem';
|
|
3
|
+
import { createWallet, deleteWallet, getWalletInfo, hasWallet, importWallet, isUnlocked, isValidPassword, lockWallet, signMessage, setWalletChain, unlockWallet, } from './wallet.js';
|
|
4
|
+
import { MemoryStorage } from './storage/memory.js';
|
|
5
|
+
import { KDF_ITERATIONS_FLOOR, encryptSecret } from './crypto.js';
|
|
6
|
+
import { privateKeyFromMnemonic, isValidMnemonic } from './signer.js';
|
|
7
|
+
import { signSIWE } from './siwe.js';
|
|
8
|
+
const HARDHAT_MNEMONIC = 'test test test test test test test test test test test junk';
|
|
9
|
+
const HARDHAT_ADDRESS = '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266';
|
|
10
|
+
const password = '12345678';
|
|
11
|
+
describe('wallet', () => {
|
|
12
|
+
let storage;
|
|
13
|
+
beforeEach(async () => {
|
|
14
|
+
storage = new MemoryStorage();
|
|
15
|
+
await lockWallet();
|
|
16
|
+
});
|
|
17
|
+
it('creates a wallet with a fresh mnemonic and stores it encrypted', async () => {
|
|
18
|
+
const { walletInfo, mnemonic } = await createWallet({ password: password, storage });
|
|
19
|
+
expect(mnemonic.split(' ')).toHaveLength(12);
|
|
20
|
+
expect(walletInfo.address).toMatch(/^0x[0-9a-fA-F]{40}$/);
|
|
21
|
+
expect(walletInfo.chain).toBe('celoSepolia');
|
|
22
|
+
expect(await hasWallet(storage)).toBe(true);
|
|
23
|
+
expect(isUnlocked()).toBe(true);
|
|
24
|
+
const record = await storage.get();
|
|
25
|
+
expect(record?.cipher.data).not.toContain(mnemonic);
|
|
26
|
+
expect(record?.address).toBe(walletInfo.address);
|
|
27
|
+
});
|
|
28
|
+
it('rejects a password shorter than 8 characters', async () => {
|
|
29
|
+
await expect(createWallet({ password: '123', storage })).rejects.toThrow(/at least 8 characters/i);
|
|
30
|
+
await expect(createWallet({ password: 'short', storage })).rejects.toThrow(/at least 8 characters/i);
|
|
31
|
+
await expect(createWallet({ password: '123456', storage })).rejects.toThrow(/at least 8 characters/i);
|
|
32
|
+
});
|
|
33
|
+
it('upgrades the work factor of an old record when it is unlocked', async () => {
|
|
34
|
+
const legacy = new MemoryStorage();
|
|
35
|
+
const secret = await encryptSecret(JSON.stringify({ v: 1, privateKey: privateKeyFromMnemonic(HARDHAT_MNEMONIC) }), password, 1000);
|
|
36
|
+
await legacy.set({
|
|
37
|
+
version: 1,
|
|
38
|
+
address: HARDHAT_ADDRESS,
|
|
39
|
+
chain: 'celoSepolia',
|
|
40
|
+
createdAt: Date.now(),
|
|
41
|
+
kdf: secret.kdf,
|
|
42
|
+
cipher: secret.cipher,
|
|
43
|
+
});
|
|
44
|
+
expect((await unlockWallet(password, legacy)).address).toBe(HARDHAT_ADDRESS);
|
|
45
|
+
const upgraded = await legacy.get();
|
|
46
|
+
expect(upgraded.kdf.iterations).toBeGreaterThanOrEqual(KDF_ITERATIONS_FLOOR);
|
|
47
|
+
await lockWallet();
|
|
48
|
+
expect((await unlockWallet(password, legacy)).address).toBe(HARDHAT_ADDRESS);
|
|
49
|
+
});
|
|
50
|
+
it('accepts a passphrase of 8+ characters and unlocks with it', async () => {
|
|
51
|
+
const { walletInfo } = await createWallet({ password: 'correct horse', storage });
|
|
52
|
+
expect(walletInfo.address).toMatch(/^0x[a-fA-F0-9]{40}$/);
|
|
53
|
+
await lockWallet();
|
|
54
|
+
const unlocked = await unlockWallet('correct horse', storage);
|
|
55
|
+
expect(unlocked.address).toBe(walletInfo.address);
|
|
56
|
+
});
|
|
57
|
+
it('isValidPassword accepts 8+ character secrets only', () => {
|
|
58
|
+
expect(isValidPassword('12345678')).toBe(true);
|
|
59
|
+
expect(isValidPassword('correct horse')).toBe(true);
|
|
60
|
+
expect(isValidPassword('123456')).toBe(false);
|
|
61
|
+
expect(isValidPassword('short')).toBe(false);
|
|
62
|
+
expect(isValidPassword(' short ')).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
it('imports a known mnemonic and derives the expected address', async () => {
|
|
65
|
+
const info = await importWallet({ mnemonic: HARDHAT_MNEMONIC, password: password, storage });
|
|
66
|
+
expect(info.address).toBe(HARDHAT_ADDRESS);
|
|
67
|
+
await lockWallet();
|
|
68
|
+
expect(await hasWallet(storage)).toBe(true);
|
|
69
|
+
const unlocked = await unlockWallet(password, storage);
|
|
70
|
+
expect(unlocked.address).toBe(HARDHAT_ADDRESS);
|
|
71
|
+
});
|
|
72
|
+
it('rejects a mnemonic that is not BIP39', async () => {
|
|
73
|
+
const typo = 'test test test test test test test test test test test tset';
|
|
74
|
+
await expect(importWallet({ mnemonic: typo, password: password, storage })).rejects.toThrow(/invalid-mnemonic/);
|
|
75
|
+
expect(await hasWallet(storage)).toBe(false);
|
|
76
|
+
const badChecksum = 'test test test test test test test test test test test test';
|
|
77
|
+
await expect(importWallet({ mnemonic: badChecksum, password: password, storage })).rejects.toThrow(/invalid-mnemonic/);
|
|
78
|
+
await expect(importWallet({ mnemonic: HARDHAT_MNEMONIC.split(' ').slice(0, 11).join(' '), password: password, storage })).rejects.toThrow(/invalid-mnemonic/);
|
|
79
|
+
});
|
|
80
|
+
it('accepts the phrases it generates and a valid 24-word phrase', async () => {
|
|
81
|
+
const { mnemonic } = await createWallet({ password: password, storage });
|
|
82
|
+
expect(await isValidMnemonic(mnemonic)).toBe(true);
|
|
83
|
+
expect(await isValidMnemonic(` ${HARDHAT_MNEMONIC.toUpperCase()} `)).toBe(true);
|
|
84
|
+
expect(await isValidMnemonic('abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art')).toBe(true);
|
|
85
|
+
expect(await isValidMnemonic('not a mnemonic at all')).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
it('reports the wallet info without unlocking', async () => {
|
|
88
|
+
await importWallet({ mnemonic: HARDHAT_MNEMONIC, password: password, storage });
|
|
89
|
+
await lockWallet();
|
|
90
|
+
const info = await getWalletInfo(storage);
|
|
91
|
+
expect(info?.address).toBe(HARDHAT_ADDRESS);
|
|
92
|
+
expect(isUnlocked()).toBe(false);
|
|
93
|
+
});
|
|
94
|
+
it('rejects a wrong password when unlocking', async () => {
|
|
95
|
+
await importWallet({ mnemonic: HARDHAT_MNEMONIC, password: password, storage });
|
|
96
|
+
await lockWallet();
|
|
97
|
+
await expect(unlockWallet('99999999', storage)).rejects.toThrow(/Wrong password/i);
|
|
98
|
+
});
|
|
99
|
+
it('signs messages only while unlocked', async () => {
|
|
100
|
+
await importWallet({ mnemonic: HARDHAT_MNEMONIC, password: password, storage });
|
|
101
|
+
const signature = await signMessage('hello');
|
|
102
|
+
expect(signature).toMatch(/^0x[0-9a-f]{130}$/i);
|
|
103
|
+
expect(await verifyMessage({ address: HARDHAT_ADDRESS, message: 'hello', signature })).toBe(true);
|
|
104
|
+
await lockWallet();
|
|
105
|
+
await expect(signMessage('hello')).rejects.toThrow(/locked/i);
|
|
106
|
+
});
|
|
107
|
+
it('signs SIWE messages that verify against the wallet address', async () => {
|
|
108
|
+
await importWallet({ mnemonic: HARDHAT_MNEMONIC, password: password, storage });
|
|
109
|
+
const message = [
|
|
110
|
+
'learn.tg:9001 wants you to sign in with your Ethereum account:',
|
|
111
|
+
HARDHAT_ADDRESS,
|
|
112
|
+
'',
|
|
113
|
+
'Sign in to Learn Through Games.',
|
|
114
|
+
'',
|
|
115
|
+
'URI: https://learn.tg:9001',
|
|
116
|
+
'Version: 1',
|
|
117
|
+
'Chain ID: 11142220',
|
|
118
|
+
'Nonce: 12345678',
|
|
119
|
+
'Issued At: 2026-09-15T00:00:00.000Z',
|
|
120
|
+
].join('\n');
|
|
121
|
+
const signature = await signSIWE(message);
|
|
122
|
+
expect(await verifyMessage({ address: HARDHAT_ADDRESS, message, signature })).toBe(true);
|
|
123
|
+
});
|
|
124
|
+
it('switches the declared chain without invalidating the secret', async () => {
|
|
125
|
+
await importWallet({ mnemonic: HARDHAT_MNEMONIC, password: password, storage });
|
|
126
|
+
expect((await getWalletInfo(storage))?.chain).toBe('celoSepolia');
|
|
127
|
+
const info = await setWalletChain('celo', storage);
|
|
128
|
+
expect(info.chain).toBe('celo');
|
|
129
|
+
expect((await getWalletInfo(storage))?.chain).toBe('celo');
|
|
130
|
+
expect(await signMessage('hola')).toMatch(/^0x/);
|
|
131
|
+
await lockWallet();
|
|
132
|
+
const unlocked = await unlockWallet(password, storage);
|
|
133
|
+
expect(unlocked.address).toBe(HARDHAT_ADDRESS);
|
|
134
|
+
expect(unlocked.chain).toBe('celo');
|
|
135
|
+
});
|
|
136
|
+
it('rejects an unsupported chain and a switch with no wallet', async () => {
|
|
137
|
+
await importWallet({ mnemonic: HARDHAT_MNEMONIC, password: password, storage });
|
|
138
|
+
await expect(setWalletChain('optimism', storage)).rejects.toThrow(/unsupported chain/i);
|
|
139
|
+
await expect(setWalletChain('celo', new MemoryStorage())).rejects.toThrow(/no in-app wallet/i);
|
|
140
|
+
});
|
|
141
|
+
it('deletes the wallet and locks it', async () => {
|
|
142
|
+
await importWallet({ mnemonic: HARDHAT_MNEMONIC, password: password, storage });
|
|
143
|
+
await deleteWallet(storage);
|
|
144
|
+
expect(await hasWallet(storage)).toBe(false);
|
|
145
|
+
expect(await getWalletInfo(storage)).toBeNull();
|
|
146
|
+
expect(isUnlocked()).toBe(false);
|
|
147
|
+
await expect(unlockWallet(password, storage)).rejects.toThrow(/no in-app wallet/i);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
//# sourceMappingURL=wallet.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wallet.test.js","sourceRoot":"","sources":["../../src/wallet/wallet.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,MAAM,CAAA;AACpC,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,SAAS,EACT,YAAY,EACZ,UAAU,EACV,eAAe,EACf,UAAU,EACV,WAAW,EACX,cAAc,EACd,YAAY,GACb,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,OAAO,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AACjE,OAAO,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AACrE,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAEpC,MAAM,gBAAgB,GAAG,6DAA6D,CAAA;AACtF,MAAM,eAAe,GAAG,4CAA4C,CAAA;AACpE,MAAM,QAAQ,GAAG,UAAU,CAAA;AAE3B,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE;IACtB,IAAI,OAAsB,CAAA;IAE1B,UAAU,CAAC,KAAK,IAAI,EAAE;QACpB,OAAO,GAAG,IAAI,aAAa,EAAE,CAAA;QAC7B,MAAM,UAAU,EAAE,CAAA;IACpB,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gEAAgE,EAAE,KAAK,IAAI,EAAE;QAC9E,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,MAAM,YAAY,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAA;QACpF,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC,EAAE,CAAC,CAAA;QAC5C,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,qBAAqB,CAAC,CAAA;QACzD,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;QAC5C,MAAM,CAAC,MAAM,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC3C,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC/B,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,CAAA;QAClC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAA;QACnD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAA;IAClD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,8CAA8C,EAAE,KAAK,IAAI,EAAE;QAC5D,MAAM,MAAM,CAAC,YAAY,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CACtE,wBAAwB,CACzB,CAAA;QACD,MAAM,MAAM,CAAC,YAAY,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CACxE,wBAAwB,CACzB,CAAA;QACD,MAAM,MAAM,CAAC,YAAY,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CACzE,wBAAwB,CACzB,CAAA;IACH,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,+DAA+D,EAAE,KAAK,IAAI,EAAE;QAC7E,MAAM,MAAM,GAAG,IAAI,aAAa,EAAE,CAAA;QAClC,MAAM,MAAM,GAAG,MAAM,aAAa,CAChC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,UAAU,EAAE,sBAAsB,CAAC,gBAAgB,CAAC,EAAE,CAAC,EAC9E,QAAQ,EACR,IAAI,CACL,CAAA;QACD,MAAM,MAAM,CAAC,GAAG,CAAC;YACf,OAAO,EAAE,CAAC;YACV,OAAO,EAAE,eAAe;YACxB,KAAK,EAAE,aAAa;YACpB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;YACrB,GAAG,EAAE,MAAM,CAAC,GAAG;YACf,MAAM,EAAE,MAAM,CAAC,MAAM;SACtB,CAAC,CAAA;QAEF,MAAM,CAAC,CAAC,MAAM,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAA;QAE5E,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,GAAG,EAAE,CAAA;QACnC,MAAM,CAAC,QAAS,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,sBAAsB,CAAC,oBAAoB,CAAC,CAAA;QAC7E,MAAM,UAAU,EAAE,CAAA;QAClB,MAAM,CAAC,CAAC,MAAM,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAA;IAC9E,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,2DAA2D,EAAE,KAAK,IAAI,EAAE;QACzE,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,YAAY,CAAC,EAAE,QAAQ,EAAE,eAAe,EAAE,OAAO,EAAE,CAAC,CAAA;QACjF,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,qBAAqB,CAAC,CAAA;QACzD,MAAM,UAAU,EAAE,CAAA;QAClB,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,eAAe,EAAE,OAAO,CAAC,CAAA;QAC7D,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAA;IACnD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC9C,MAAM,CAAC,eAAe,CAAC,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACnD,MAAM,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAC7C,MAAM,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAC5C,MAAM,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAClD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,2DAA2D,EAAE,KAAK,IAAI,EAAE;QACzE,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,EAAE,QAAQ,EAAE,gBAAgB,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAA;QAC5F,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAA;QAC1C,MAAM,UAAU,EAAE,CAAA;QAClB,MAAM,CAAC,MAAM,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC3C,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;QACtD,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAA;IAChD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,sCAAsC,EAAE,KAAK,IAAI,EAAE;QACpD,MAAM,IAAI,GAAG,6DAA6D,CAAA;QAC1E,MAAM,MAAM,CAAC,YAAY,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAA;QAC/G,MAAM,CAAC,MAAM,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAE5C,MAAM,WAAW,GAAG,6DAA6D,CAAA;QACjF,MAAM,MAAM,CAAC,YAAY,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAA;QAEtH,MAAM,MAAM,CACV,YAAY,CAAC,EAAE,QAAQ,EAAE,gBAAgB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAC5G,CAAC,OAAO,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAA;IACvC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,6DAA6D,EAAE,KAAK,IAAI,EAAE;QAC3E,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,YAAY,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAA;QACxE,MAAM,CAAC,MAAM,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAClD,MAAM,CAAC,MAAM,eAAe,CAAC,KAAK,gBAAgB,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACjF,MAAM,CAAC,MAAM,eAAe,CAAC,6LAA6L,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACvO,MAAM,CAAC,MAAM,eAAe,CAAC,uBAAuB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACpE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,2CAA2C,EAAE,KAAK,IAAI,EAAE;QACzD,MAAM,YAAY,CAAC,EAAE,QAAQ,EAAE,gBAAgB,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAA;QAC/E,MAAM,UAAU,EAAE,CAAA;QAClB,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,OAAO,CAAC,CAAA;QACzC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAA;QAC3C,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAClC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,yCAAyC,EAAE,KAAK,IAAI,EAAE;QACvD,MAAM,YAAY,CAAC,EAAE,QAAQ,EAAE,gBAAgB,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAA;QAC/E,MAAM,UAAU,EAAE,CAAA;QAClB,MAAM,MAAM,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAA;IACpF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,oCAAoC,EAAE,KAAK,IAAI,EAAE;QAClD,MAAM,YAAY,CAAC,EAAE,QAAQ,EAAE,gBAAgB,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAA;QAC/E,MAAM,SAAS,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,CAAA;QAC5C,MAAM,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAA;QAC/C,MAAM,CAAC,MAAM,aAAa,CAAC,EAAE,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACjG,MAAM,UAAU,EAAE,CAAA;QAClB,MAAM,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;IAC/D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4DAA4D,EAAE,KAAK,IAAI,EAAE;QAC1E,MAAM,YAAY,CAAC,EAAE,QAAQ,EAAE,gBAAgB,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAA;QAC/E,MAAM,OAAO,GAAG;YACd,gEAAgE;YAChE,eAAe;YACf,EAAE;YACF,iCAAiC;YACjC,EAAE;YACF,4BAA4B;YAC5B,YAAY;YACZ,oBAAoB;YACpB,iBAAiB;YACjB,qCAAqC;SACtC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACZ,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAA;QACzC,MAAM,CAAC,MAAM,aAAa,CAAC,EAAE,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC1F,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,6DAA6D,EAAE,KAAK,IAAI,EAAE;QAC3E,MAAM,YAAY,CAAC,EAAE,QAAQ,EAAE,gBAAgB,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAA;QAC/E,MAAM,CAAC,CAAC,MAAM,aAAa,CAAC,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;QAEjE,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QAClD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAC/B,MAAM,CAAC,CAAC,MAAM,aAAa,CAAC,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAC1D,MAAM,CAAC,MAAM,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;QAEhD,MAAM,UAAU,EAAE,CAAA;QAClB,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;QACtD,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAA;QAC9C,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IACrC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,0DAA0D,EAAE,KAAK,IAAI,EAAE;QACxE,MAAM,YAAY,CAAC,EAAE,QAAQ,EAAE,gBAAgB,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAA;QAC/E,MAAM,MAAM,CAAC,cAAc,CAAC,UAAmB,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAA;QAChG,MAAM,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,IAAI,aAAa,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAA;IAChG,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,iCAAiC,EAAE,KAAK,IAAI,EAAE;QAC/C,MAAM,YAAY,CAAC,EAAE,QAAQ,EAAE,gBAAgB,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAA;QAC/E,MAAM,YAAY,CAAC,OAAO,CAAC,CAAA;QAC3B,MAAM,CAAC,MAAM,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAC5C,MAAM,CAAC,MAAM,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAA;QAC/C,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAChC,MAAM,MAAM,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAA;IACpF,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/** Overrides the WebAuthn relying-party display name. */
|
|
2
|
+
export declare function setRpName(name: string): void;
|
|
3
|
+
/**
|
|
4
|
+
* Ventana de gracia de la verificación del usuario (decisión del operador,
|
|
5
|
+
* 2026-09-20). Una vez que el usuario verificó con el dispositivo (gesto L1 o
|
|
6
|
+
* desbloqueo L2), un movimiento de fondos **a un destino ya conocido** no vuelve a
|
|
7
|
+
* pedir el gesto dentro de esta ventana. Es deliberadamente corta: 15 minutos.
|
|
8
|
+
*
|
|
9
|
+
* Además, un **destino nuevo siempre exige el gesto** (ver `destinations.ts`),
|
|
10
|
+
* aunque la ventana esté abierta: eso frena el drenaje automatizado/hostil a una
|
|
11
|
+
* dirección que el usuario nunca usó.
|
|
12
|
+
*
|
|
13
|
+
* Compromiso explícito: en un dispositivo desbloqueado y robado, dentro de la
|
|
14
|
+
* ventana se puede mover dinero a direcciones ya usadas sin gesto. La clave/PIN
|
|
15
|
+
* nunca se guarda y los 12 palabras siguen siendo la recuperación.
|
|
16
|
+
*/
|
|
17
|
+
export declare const USER_VERIFICATION_GRACE_MS: number;
|
|
18
|
+
/** Records that the user just verified (called on a successful assertion/PRF). */
|
|
19
|
+
export declare function markUserVerified(now?: number): void;
|
|
20
|
+
/** Forgets the verification (called when the wallet locks or is deleted). */
|
|
21
|
+
export declare function clearUserVerification(): void;
|
|
22
|
+
/** Whether a user verification happened within the grace window. */
|
|
23
|
+
export declare function hasRecentUserVerification(windowMs?: number, now?: number): boolean;
|
|
24
|
+
export interface PlatformSupport {
|
|
25
|
+
/** `navigator.credentials` with WebAuthn is present at all. */
|
|
26
|
+
webauthn: boolean;
|
|
27
|
+
/** The device can verify the user (Face ID, fingerprint, device password). */
|
|
28
|
+
userVerifying: boolean;
|
|
29
|
+
/** Whether the engine advertises the `prf` extension (may be unknown). */
|
|
30
|
+
prf: boolean | null;
|
|
31
|
+
}
|
|
32
|
+
export declare function toArrayBuffer(bytes: Uint8Array): ArrayBuffer;
|
|
33
|
+
/** Reports what the device can do, never throwing. */
|
|
34
|
+
export declare function detectPlatformSupport(): Promise<PlatformSupport>;
|
|
35
|
+
export interface PrfCredential {
|
|
36
|
+
credentialId: string;
|
|
37
|
+
/** The authenticator accepted the `prf` extension for this credential. */
|
|
38
|
+
prfEnabled: boolean;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Creates a discoverable platform credential with the `prf` extension. The
|
|
42
|
+
* credential is stored by the password manager / device, exactly like a passkey,
|
|
43
|
+
* and can be removed by the user from there.
|
|
44
|
+
*/
|
|
45
|
+
export declare function createPrfCredential(userName: string): Promise<PrfCredential>;
|
|
46
|
+
/**
|
|
47
|
+
* Evaluates the PRF for `salt` and returns its 32-byte secret, asking the user to
|
|
48
|
+
* verify (Face ID / fingerprint / device password). Returns `null` when the
|
|
49
|
+
* authenticator does not provide a result instead of throwing.
|
|
50
|
+
*/
|
|
51
|
+
export declare function evaluatePrf(credentialId: string, salt: Uint8Array): Promise<Uint8Array | null>;
|
|
52
|
+
/**
|
|
53
|
+
* Layer L1: asks the user to verify **with the device** (Face ID / fingerprint /
|
|
54
|
+
* device password) before a sensitive operation, and returns the assertion. It does not
|
|
55
|
+
* need the `prf` extension: any user-verified assertion is enough. Throws
|
|
56
|
+
* `no-webauthn` when there is no platform authenticator and `NotAllowedError` when
|
|
57
|
+
* the user cancels.
|
|
58
|
+
*/
|
|
59
|
+
export declare function assertUserVerification(credentialId: string, { timeoutMs }?: {
|
|
60
|
+
timeoutMs?: number;
|
|
61
|
+
}): Promise<void>;
|
|
62
|
+
/** HKDF(PRF secret) → AES-GCM key that wraps the wallet key. */ export declare function deriveWrappingKey(prfSecret: Uint8Array, hkdfSalt: Uint8Array): Promise<CryptoKey>;
|
|
63
|
+
/**
|
|
64
|
+
* Tells the authenticator that a passkey is gone (R-#246 §14 item 1, adopted from
|
|
65
|
+
* Rabby: `signalUnknownCredential`). Without it, disabling the biometric unlock or
|
|
66
|
+
* deleting the wallet left the passkey orphaned in the OS list until the user
|
|
67
|
+
* removed it by hand.
|
|
68
|
+
*
|
|
69
|
+
* Best effort: the API only exists in Chrome 132+ and must never break the caller.
|
|
70
|
+
*/
|
|
71
|
+
export declare function signalUnknownCredential(credentialId: string, rpId?: string): Promise<void>;
|
|
72
|
+
/**
|
|
73
|
+
* True when a WebAuthn/signing failure means "the user cancelled" (R-#246 §14
|
|
74
|
+
* item 4). We used to match only `NotAllowedError`, so an abort surfaced as an
|
|
75
|
+
* unknown error.
|
|
76
|
+
*/
|
|
77
|
+
export declare function isUserCancelledError(error: unknown): boolean;
|
|
78
|
+
//# sourceMappingURL=web-authn.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-authn.d.ts","sourceRoot":"","sources":["../../src/wallet/web-authn.ts"],"names":[],"mappings":"AAeA,yDAAyD;AACzD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAE5C;AAID;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,0BAA0B,QAAiB,CAAA;AAIxD,kFAAkF;AAClF,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAmB,GAAG,IAAI,CAE/D;AAED,6EAA6E;AAC7E,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED,oEAAoE;AACpE,wBAAgB,yBAAyB,CACvC,QAAQ,GAAE,MAAmC,EAC7C,GAAG,GAAE,MAAmB,GACvB,OAAO,CAET;AAED,MAAM,WAAW,eAAe;IAC9B,+DAA+D;IAC/D,QAAQ,EAAE,OAAO,CAAA;IACjB,8EAA8E;IAC9E,aAAa,EAAE,OAAO,CAAA;IACtB,0EAA0E;IAC1E,GAAG,EAAE,OAAO,GAAG,IAAI,CAAA;CACpB;AASD,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,WAAW,CAE5D;AAUD,sDAAsD;AACtD,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,eAAe,CAAC,CAmCtE;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAA;IACpB,0EAA0E;IAC1E,UAAU,EAAE,OAAO,CAAA;CACpB;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAsClF;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CA2BpG;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC1C,YAAY,EAAE,MAAM,EACpB,EAAE,SAAsB,EAAE,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACtD,OAAO,CAAC,IAAI,CAAC,CAqBf;AAED,gEAAgE,CAAA,wBAAsB,iBAAiB,CACrG,SAAS,EAAE,UAAU,EACrB,QAAQ,EAAE,UAAU,GACnB,OAAO,CAAC,SAAS,CAAC,CAkBpB;AAQD;;;;;;;GAOG;AACH,wBAAsB,uBAAuB,CAC3C,YAAY,EAAE,MAAM,EACpB,IAAI,GAAE,MAAyB,GAC9B,OAAO,CAAC,IAAI,CAAC,CAef;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAK5D"}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebAuthn helpers for the in-app wallet (layers L1/L2 of
|
|
3
|
+
* https://github.com/pasosdeJesus/learn.tg/issues/246).
|
|
4
|
+
*
|
|
5
|
+
* Measured on real phones (2026-09-18): `PublicKeyCredential` and the `prf`
|
|
6
|
+
* extension exist in Chrome/Brave for Android and in Safari (18+), and do **not**
|
|
7
|
+
* exist in the in-app browsers of Rabby, MetaMask, OneKey and OKX. Every function
|
|
8
|
+
* here is therefore optional and must degrade silently: nothing may throw just
|
|
9
|
+
* because the device has no platform authenticator.
|
|
10
|
+
*/
|
|
11
|
+
import { fromBase64, toBase64 } from './crypto.js';
|
|
12
|
+
const TIMEOUT_MS = 120_000;
|
|
13
|
+
/** Relying-party display name (a consumer overrides it with `setRpName`). */
|
|
14
|
+
let rpName = 'Pasos de Jesús';
|
|
15
|
+
/** Overrides the WebAuthn relying-party display name. */
|
|
16
|
+
export function setRpName(name) {
|
|
17
|
+
if (name)
|
|
18
|
+
rpName = name;
|
|
19
|
+
}
|
|
20
|
+
/** HKDF info string: separates this use of a PRF secret from any other. */
|
|
21
|
+
const HKDF_INFO = 'learn.tg/pdj-wallet/prf/v1';
|
|
22
|
+
/**
|
|
23
|
+
* Ventana de gracia de la verificación del usuario (decisión del operador,
|
|
24
|
+
* 2026-09-20). Una vez que el usuario verificó con el dispositivo (gesto L1 o
|
|
25
|
+
* desbloqueo L2), un movimiento de fondos **a un destino ya conocido** no vuelve a
|
|
26
|
+
* pedir el gesto dentro de esta ventana. Es deliberadamente corta: 15 minutos.
|
|
27
|
+
*
|
|
28
|
+
* Además, un **destino nuevo siempre exige el gesto** (ver `destinations.ts`),
|
|
29
|
+
* aunque la ventana esté abierta: eso frena el drenaje automatizado/hostil a una
|
|
30
|
+
* dirección que el usuario nunca usó.
|
|
31
|
+
*
|
|
32
|
+
* Compromiso explícito: en un dispositivo desbloqueado y robado, dentro de la
|
|
33
|
+
* ventana se puede mover dinero a direcciones ya usadas sin gesto. La clave/PIN
|
|
34
|
+
* nunca se guarda y los 12 palabras siguen siendo la recuperación.
|
|
35
|
+
*/
|
|
36
|
+
export const USER_VERIFICATION_GRACE_MS = 15 * 60 * 1000;
|
|
37
|
+
let lastUserVerificationAt = 0;
|
|
38
|
+
/** Records that the user just verified (called on a successful assertion/PRF). */
|
|
39
|
+
export function markUserVerified(now = Date.now()) {
|
|
40
|
+
lastUserVerificationAt = now;
|
|
41
|
+
}
|
|
42
|
+
/** Forgets the verification (called when the wallet locks or is deleted). */
|
|
43
|
+
export function clearUserVerification() {
|
|
44
|
+
lastUserVerificationAt = 0;
|
|
45
|
+
}
|
|
46
|
+
/** Whether a user verification happened within the grace window. */
|
|
47
|
+
export function hasRecentUserVerification(windowMs = USER_VERIFICATION_GRACE_MS, now = Date.now()) {
|
|
48
|
+
return lastUserVerificationAt > 0 && now - lastUserVerificationAt < windowMs;
|
|
49
|
+
}
|
|
50
|
+
function credentials() {
|
|
51
|
+
const c = globalThis.navigator;
|
|
52
|
+
return c?.credentials ?? null;
|
|
53
|
+
}
|
|
54
|
+
export function toArrayBuffer(bytes) {
|
|
55
|
+
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
56
|
+
}
|
|
57
|
+
function randomBytes(length) {
|
|
58
|
+
const cryptoObj = globalThis.crypto;
|
|
59
|
+
if (!cryptoObj?.getRandomValues)
|
|
60
|
+
throw new Error('crypto.getRandomValues is not available');
|
|
61
|
+
const bytes = new Uint8Array(length);
|
|
62
|
+
cryptoObj.getRandomValues(bytes);
|
|
63
|
+
return bytes;
|
|
64
|
+
}
|
|
65
|
+
/** Reports what the device can do, never throwing. */
|
|
66
|
+
export async function detectPlatformSupport() {
|
|
67
|
+
const hasPublicKey = typeof globalThis.PublicKeyCredential !== 'undefined';
|
|
68
|
+
const container = credentials();
|
|
69
|
+
if (!hasPublicKey || !container?.create || !container.get) {
|
|
70
|
+
return { webauthn: false, userVerifying: false, prf: null };
|
|
71
|
+
}
|
|
72
|
+
let userVerifying = false;
|
|
73
|
+
const ask = PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable;
|
|
74
|
+
if (typeof ask === 'function') {
|
|
75
|
+
try {
|
|
76
|
+
userVerifying = await ask();
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
userVerifying = false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
let prf = null;
|
|
83
|
+
const caps = PublicKeyCredential.getClientCapabilities;
|
|
84
|
+
if (typeof caps === 'function') {
|
|
85
|
+
try {
|
|
86
|
+
const result = await caps();
|
|
87
|
+
// Chrome 133+ exposes 'extension:prf'; older builds expose 'extension:hmacCreateSecret'.
|
|
88
|
+
const value = result['extension:prf'] ?? result['extension:hmacCreateSecret'];
|
|
89
|
+
if (typeof value === 'boolean')
|
|
90
|
+
prf = value;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
prf = null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return { webauthn: true, userVerifying, prf };
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Creates a discoverable platform credential with the `prf` extension. The
|
|
100
|
+
* credential is stored by the password manager / device, exactly like a passkey,
|
|
101
|
+
* and can be removed by the user from there.
|
|
102
|
+
*/
|
|
103
|
+
export async function createPrfCredential(userName) {
|
|
104
|
+
const container = credentials();
|
|
105
|
+
if (!container?.create)
|
|
106
|
+
throw new Error('WebAuthn is not available on this device');
|
|
107
|
+
const credential = (await container.create({
|
|
108
|
+
publicKey: {
|
|
109
|
+
challenge: toArrayBuffer(randomBytes(32)),
|
|
110
|
+
rp: { name: rpName },
|
|
111
|
+
user: {
|
|
112
|
+
id: toArrayBuffer(randomBytes(16)),
|
|
113
|
+
name: userName,
|
|
114
|
+
displayName: userName,
|
|
115
|
+
},
|
|
116
|
+
pubKeyCredParams: [
|
|
117
|
+
{ type: 'public-key', alg: -7 },
|
|
118
|
+
{ type: 'public-key', alg: -257 },
|
|
119
|
+
],
|
|
120
|
+
authenticatorSelection: {
|
|
121
|
+
authenticatorAttachment: 'platform',
|
|
122
|
+
residentKey: 'required',
|
|
123
|
+
userVerification: 'required',
|
|
124
|
+
},
|
|
125
|
+
timeout: TIMEOUT_MS,
|
|
126
|
+
attestation: 'none',
|
|
127
|
+
extensions: { prf: {} },
|
|
128
|
+
},
|
|
129
|
+
}));
|
|
130
|
+
if (!credential)
|
|
131
|
+
throw new Error('The browser did not return a credential');
|
|
132
|
+
const results = credential.getClientExtensionResults();
|
|
133
|
+
return {
|
|
134
|
+
credentialId: toBase64(new Uint8Array(credential.rawId)),
|
|
135
|
+
prfEnabled: results?.prf?.enabled === true,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Evaluates the PRF for `salt` and returns its 32-byte secret, asking the user to
|
|
140
|
+
* verify (Face ID / fingerprint / device password). Returns `null` when the
|
|
141
|
+
* authenticator does not provide a result instead of throwing.
|
|
142
|
+
*/
|
|
143
|
+
export async function evaluatePrf(credentialId, salt) {
|
|
144
|
+
const container = credentials();
|
|
145
|
+
if (!container?.get)
|
|
146
|
+
throw new Error('WebAuthn is not available on this device');
|
|
147
|
+
const assertion = (await container.get({
|
|
148
|
+
publicKey: {
|
|
149
|
+
challenge: toArrayBuffer(randomBytes(32)),
|
|
150
|
+
allowCredentials: [
|
|
151
|
+
{
|
|
152
|
+
type: 'public-key',
|
|
153
|
+
id: toArrayBuffer(fromBase64(credentialId)),
|
|
154
|
+
transports: ['internal', 'hybrid'],
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
userVerification: 'required',
|
|
158
|
+
timeout: TIMEOUT_MS,
|
|
159
|
+
extensions: { prf: { eval: { first: toArrayBuffer(salt) } } },
|
|
160
|
+
},
|
|
161
|
+
}));
|
|
162
|
+
if (!assertion)
|
|
163
|
+
return null;
|
|
164
|
+
markUserVerified();
|
|
165
|
+
const results = assertion.getClientExtensionResults();
|
|
166
|
+
const first = results?.prf?.results?.first;
|
|
167
|
+
return first ? new Uint8Array(first) : null;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Layer L1: asks the user to verify **with the device** (Face ID / fingerprint /
|
|
171
|
+
* device password) before a sensitive operation, and returns the assertion. It does not
|
|
172
|
+
* need the `prf` extension: any user-verified assertion is enough. Throws
|
|
173
|
+
* `no-webauthn` when there is no platform authenticator and `NotAllowedError` when
|
|
174
|
+
* the user cancels.
|
|
175
|
+
*/
|
|
176
|
+
export async function assertUserVerification(credentialId, { timeoutMs = TIMEOUT_MS } = {}) {
|
|
177
|
+
const container = credentials();
|
|
178
|
+
if (!container?.get)
|
|
179
|
+
throw new Error('no-webauthn');
|
|
180
|
+
const assertion = (await container.get({
|
|
181
|
+
publicKey: {
|
|
182
|
+
challenge: toArrayBuffer(randomBytes(32)),
|
|
183
|
+
allowCredentials: [
|
|
184
|
+
{
|
|
185
|
+
type: 'public-key',
|
|
186
|
+
id: toArrayBuffer(fromBase64(credentialId)),
|
|
187
|
+
transports: ['internal', 'hybrid'],
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
userVerification: 'required',
|
|
191
|
+
timeout: timeoutMs,
|
|
192
|
+
},
|
|
193
|
+
}));
|
|
194
|
+
if (!assertion)
|
|
195
|
+
throw new Error('auth-failed');
|
|
196
|
+
markUserVerified();
|
|
197
|
+
}
|
|
198
|
+
/** HKDF(PRF secret) → AES-GCM key that wraps the wallet key. */ export async function deriveWrappingKey(prfSecret, hkdfSalt) {
|
|
199
|
+
const subtle = globalThis.crypto?.subtle;
|
|
200
|
+
if (!subtle)
|
|
201
|
+
throw new Error('Web Crypto (crypto.subtle) is not available');
|
|
202
|
+
const material = await subtle.importKey('raw', toArrayBuffer(prfSecret), 'HKDF', false, [
|
|
203
|
+
'deriveKey',
|
|
204
|
+
]);
|
|
205
|
+
return subtle.deriveKey({
|
|
206
|
+
name: 'HKDF',
|
|
207
|
+
hash: 'SHA-256',
|
|
208
|
+
salt: toArrayBuffer(hkdfSalt),
|
|
209
|
+
info: toArrayBuffer(new TextEncoder().encode(HKDF_INFO)),
|
|
210
|
+
}, material, { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt']);
|
|
211
|
+
}
|
|
212
|
+
/** rpId used by the passkeys of this wallet (the page's host). */
|
|
213
|
+
function relyingPartyId() {
|
|
214
|
+
const location = globalThis.location;
|
|
215
|
+
return location?.hostname || 'localhost';
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Tells the authenticator that a passkey is gone (R-#246 §14 item 1, adopted from
|
|
219
|
+
* Rabby: `signalUnknownCredential`). Without it, disabling the biometric unlock or
|
|
220
|
+
* deleting the wallet left the passkey orphaned in the OS list until the user
|
|
221
|
+
* removed it by hand.
|
|
222
|
+
*
|
|
223
|
+
* Best effort: the API only exists in Chrome 132+ and must never break the caller.
|
|
224
|
+
*/
|
|
225
|
+
export async function signalUnknownCredential(credentialId, rpId = relyingPartyId()) {
|
|
226
|
+
if (!credentialId)
|
|
227
|
+
return;
|
|
228
|
+
const signal = globalThis.PublicKeyCredential?.signalUnknownCredential;
|
|
229
|
+
if (typeof signal !== 'function')
|
|
230
|
+
return;
|
|
231
|
+
try {
|
|
232
|
+
await signal({ rpId, credentialId });
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
// el passkey queda huérfano, como antes de este cambio
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* True when a WebAuthn/signing failure means "the user cancelled" (R-#246 §14
|
|
240
|
+
* item 4). We used to match only `NotAllowedError`, so an abort surfaced as an
|
|
241
|
+
* unknown error.
|
|
242
|
+
*/
|
|
243
|
+
export function isUserCancelledError(error) {
|
|
244
|
+
const name = error?.name?.toLowerCase() ?? '';
|
|
245
|
+
if (name === 'notallowederror' || name === 'aborterror')
|
|
246
|
+
return true;
|
|
247
|
+
const message = error instanceof Error ? error.message : String(error ?? '');
|
|
248
|
+
return /cancel|abort|notallowed/i.test(message);
|
|
249
|
+
}
|
|
250
|
+
//# sourceMappingURL=web-authn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-authn.js","sourceRoot":"","sources":["../../src/wallet/web-authn.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pasosdejesus/m",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"description": "Motor para crear y gestionar proyectos al estilo Pasos de Jesus.",
|
|
5
5
|
"author": "",
|
|
6
6
|
"license": "ISC",
|
|
@@ -103,6 +103,14 @@
|
|
|
103
103
|
"./e2e": {
|
|
104
104
|
"import": "./dist/e2e/index.js",
|
|
105
105
|
"types": "./dist/e2e/index.d.ts"
|
|
106
|
+
},
|
|
107
|
+
"./wallet": {
|
|
108
|
+
"import": "./dist/wallet/index.js",
|
|
109
|
+
"types": "./dist/wallet/index.d.ts"
|
|
110
|
+
},
|
|
111
|
+
"./wallet/storage": {
|
|
112
|
+
"import": "./dist/wallet/storage/index.js",
|
|
113
|
+
"types": "./dist/wallet/storage/index.d.ts"
|
|
106
114
|
}
|
|
107
115
|
},
|
|
108
116
|
"bin": {
|
|
@@ -118,7 +126,8 @@
|
|
|
118
126
|
"src/shadcn_components/README.md",
|
|
119
127
|
"src/test-utils/README.md",
|
|
120
128
|
"src/blockchain/README.md",
|
|
121
|
-
"src/e2e/README.md"
|
|
129
|
+
"src/e2e/README.md",
|
|
130
|
+
"src/wallet/README.md"
|
|
122
131
|
],
|
|
123
132
|
"dependencies": {
|
|
124
133
|
"dotenv": "^17.4.2",
|
|
@@ -127,8 +136,8 @@
|
|
|
127
136
|
"picocolors": "^1.1.1"
|
|
128
137
|
},
|
|
129
138
|
"optionalDependencies": {
|
|
130
|
-
"@pasosdejesus/
|
|
131
|
-
"@pasosdejesus/
|
|
139
|
+
"@pasosdejesus/contract-test": "0.1.4",
|
|
140
|
+
"@pasosdejesus/edr": "0.1.2"
|
|
132
141
|
},
|
|
133
142
|
"devDependencies": {
|
|
134
143
|
"@radix-ui/react-accordion": "^1.2.12",
|
|
@@ -161,6 +170,7 @@
|
|
|
161
170
|
"autoprefixer": "^10.5.0",
|
|
162
171
|
"class-variance-authority": "^0.7.1",
|
|
163
172
|
"clsx": "^2.1.1",
|
|
173
|
+
"fake-indexeddb": "^6.2.5",
|
|
164
174
|
"jsdom": "^29.1.1",
|
|
165
175
|
"kysely": "^0.28.17",
|
|
166
176
|
"kysely-codegen": "^0.20.0",
|
package/src/e2e/README.md
CHANGED
|
@@ -100,6 +100,57 @@ const page = await newPage(browser, address, timeout, true)
|
|
|
100
100
|
// GoodDollarClaimButton, wallet interactions now work
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
+
### Real-signature wallet (`m/REQ/14`)
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
installRealSigner(page, { privateKey, name?, typedDataName? }): Promise<RealSigner>
|
|
107
|
+
injectEIP6963(page, { address, privateKey?, chainId?, walletName? }): Promise<void> // real signer + realSigning
|
|
108
|
+
setupFullWalletMock(page, { privateKey, address?, chainId?, rpcUrl?, ... }): Promise<FullWalletMock>
|
|
109
|
+
fullWalletMockCode(opts): string
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
`injectEIP6963` with `privateKey` makes `personal_sign`/`eth_signTypedData_v4` return real
|
|
113
|
+
EIP-191/EIP-712 signatures; without it, the mock keeps the fake `0xabab…`. `setupFullWalletMock`
|
|
114
|
+
adds real `eth_sendTransaction`: with `rpcUrl` (opt-in) it signs in Node and broadcasts the raw
|
|
115
|
+
transaction, and forwards reads (`eth_call`, `eth_getBalance`, `eth_getTransactionReceipt`, …) to
|
|
116
|
+
that RPC. **It spends real funds**, so point it at a test network.
|
|
117
|
+
|
|
118
|
+
```js
|
|
119
|
+
// Real signature, fake broadcast (no rpcUrl)
|
|
120
|
+
await setupFullWalletMock(page, { privateKey: process.env.PRIVATE_KEY })
|
|
121
|
+
|
|
122
|
+
// Real signature + real broadcast against Celo Sepolia (spends funds)
|
|
123
|
+
await setupFullWalletMock(page, { privateKey: process.env.PRIVATE_KEY, rpcUrl: process.env.RPC_URL })
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Local EVM node (`m/REQ/14`)
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
startLocalRpc({ chainId?, accounts? }): Promise<LocalRpc> // { url, chainId, accounts, close() }
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Hermetic E2E: a JSON-RPC node over `@pasosdejesus/edr` (optional dependency, loaded
|
|
133
|
+
dynamically). The wallet signs for real and the node executes and confirms — no external
|
|
134
|
+
network, no funds. Works from viem in Node **and** from a page with `setupFullWalletMock`.
|
|
135
|
+
|
|
136
|
+
A runnable fixture lives in `packages/m/e2e/` (a minimal donation page + spec):
|
|
137
|
+
|
|
138
|
+
```sh
|
|
139
|
+
cd packages/m
|
|
140
|
+
pnpm add -D puppeteer-core # consumer devDependency, not bundled with m
|
|
141
|
+
CHROME_PATH=/usr/local/bin/chromium node e2e/specs/donate.spec.mjs
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Lessons that cost time here (see also learn.tg's `doc/e2e-testing.md`):
|
|
145
|
+
|
|
146
|
+
- **Chrome needs headroom**: this VM already runs `ulimit -d` at 12G; if a cold Chrome
|
|
147
|
+
crashes, raise it (`/bin/ksh -c 'ulimit -d 7340032; exec …'`).
|
|
148
|
+
- **Avoid `page.click` + `waitForFunction`** for a click handler that awaits the wallet:
|
|
149
|
+
that pair hit `Runtime.callFunctionOn timed out` (protocolTimeout). Do the click **and**
|
|
150
|
+
the wait inside one `page.evaluate(async () => { … })` (the spec does exactly that).
|
|
151
|
+
- **Receipt hashes**: the local node re-signs, so `setupFullWalletMock` re-keys the receipt
|
|
152
|
+
under `keccak256(rawTransaction)` (the hash viem waits for).
|
|
153
|
+
|
|
103
154
|
## Environment Variables
|
|
104
155
|
|
|
105
156
|
See [doc/e2e.md](../../../../doc/e2e.md) for full reference.
|