Skip to content

Commit 2f72e9e

Browse files
authored
DCKW-611 Multiple keys can be registered to unlock the cloud wallet
2 parents 6622edd + 0ec0f21 commit 2f72e9e

File tree

10 files changed

+751
-32
lines changed

10 files changed

+751
-32
lines changed

docs/cloud-wallet.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,86 @@ async function example() {
176176
example();
177177

178178
```
179+
180+
## Multi-Key Authentication
181+
182+
The Cloud Wallet supports multiple authentication methods to unlock the same wallet, providing both security and convenience.
183+
184+
### Available Authentication Methods
185+
186+
1. **Mnemonic-based authentication**: The traditional recovery phrase approach
187+
2. **Biometric authentication**: Using fingerprints, facial recognition, or other biometric data
188+
3. **Future extensions**: Can be extended to support passkeys and other authentication methods
189+
190+
### How Multi-Key Authentication Works
191+
192+
The Cloud Wallet uses a key mapping system that allows a secondary key (e.g. derived from biometrics) to unlock the same master key that was originally derived from a mnemonic phrase.
193+
194+
The system uses a two-vault architecture:
195+
- KeyMappingVault: Stores encrypted master keys that can only be accessed with proper authentication
196+
- CloudWalletVault: The main vault containing wallet documents, secured by the master key
197+
198+
#### Step 1: Enroll User with Biometric Data
199+
200+
To set up biometric authentication, enroll the user with their biometric data and identifier (typically an email):
201+
202+
```ts
203+
import { enrollUserWithBiometrics } from '@docknetwork/wallet-sdk-core/lib/cloud-wallet';
204+
205+
// Biometric data would come from platform-specific biometric APIs
206+
const biometricData = await getPlatformBiometricData();
207+
const userEmail = '[email protected]';
208+
209+
// Enroll user and get master key + recovery mnemonic
210+
const { masterKey, mnemonic } = await enrollUserWithBiometrics(
211+
EDV_URL,
212+
EDV_AUTH_KEY,
213+
biometricData,
214+
userEmail
215+
);
216+
217+
// IMPORTANT: Store the mnemonic securely for recovery purposes
218+
```
219+
The enrollment process:
220+
1. Creates a unique master key and mnemonic
221+
2. Generates encryption keys from the biometric data
222+
3. Encrypts the master key with the biometric-derived keys
223+
4. Stores the encrypted master key in the KeyMappingVault, indexed by the user's email
224+
225+
#### Step 2: Authenticate with Biometrics
226+
227+
Next, when the user wants to access their wallet, they can authenticate with their biometric data:
228+
229+
```ts
230+
import {
231+
authenticateWithBiometrics,
232+
initializeCloudWalletWithBiometrics
233+
} from '@docknetwork/wallet-sdk-core/lib/cloud-wallet';
234+
235+
// Get current biometric data from platform APIs
236+
const biometricData = await getPlatformBiometricData();
237+
const userEmail = '[email protected]';
238+
239+
// Method 1: Get the master key directly
240+
const masterKey = await authenticateWithBiometrics(
241+
EDV_URL,
242+
EDV_AUTH_KEY,
243+
biometricData,
244+
userEmail
245+
);
246+
247+
// Method 2: Initialize cloud wallet in one step
248+
const cloudWallet = await initializeCloudWalletWithBiometrics(
249+
EDV_URL,
250+
EDV_AUTH_KEY,
251+
biometricData,
252+
userEmail,
253+
dataStore
254+
);
255+
```
256+
The authentication process:
257+
1. Uses biometric data and email to access the KeyMappingVault
258+
2. Finds the encrypted master key associated with the user's email
259+
3. Derives decryption keys from the provided biometric data
260+
4. Decrypts the master key
261+
5. Uses the master key to access the CloudWalletVault
Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
import {
2+
enrollUserWithBiometrics,
3+
authenticateWithBiometrics,
4+
deriveKeyMappingVaultKeys,
5+
deriveBiometricEncryptionKey,
6+
encryptMasterKey,
7+
decryptMasterKey,
8+
initializeKeyMappingVault,
9+
recoverCloudWalletMasterKey
10+
} from '@docknetwork/wallet-sdk-core/src/cloud-wallet';
11+
12+
const EDV_URL = process.env.EDV_URL || '';
13+
const EDV_AUTH_KEY = process.env.EDV_AUTH_KEY || '';
14+
15+
// Helper to create mock biometric data
16+
const createMockBiometricData = (userId = '123') => {
17+
return Buffer.from(JSON.stringify({
18+
type: 'fingerprint',
19+
id: userId,
20+
quality: 0.95,
21+
minutiae: Array(20).fill(0).map((_, i) => ({
22+
x: 100 + Math.floor(i / 4) * 20,
23+
y: 150 + (i % 4) * 20,
24+
angle: (i * 15) % 360,
25+
type: i % 3
26+
})),
27+
timestamp: Date.now()
28+
}));
29+
};
30+
31+
describe('Biometric Authentication System', () => {
32+
beforeAll(async () => {
33+
if (!EDV_URL || !EDV_AUTH_KEY) {
34+
throw new Error("Missing required environment variables: EDV_URL and EDV_AUTH_KEY");
35+
}
36+
});
37+
38+
describe('Key derivation and encryption', () => {
39+
it('should derive consistent keys from the same biometric data and email', async () => {
40+
const bioData = createMockBiometricData();
41+
const email = `user${new Date().getTime()}@example.com`;
42+
43+
const keys1 = await deriveKeyMappingVaultKeys(bioData, email);
44+
const keys2 = await deriveKeyMappingVaultKeys(bioData, email);
45+
46+
expect(keys1.hmacKey).toBe(keys2.hmacKey);
47+
expect(keys1.agreementKey).toMatchObject(keys2.agreementKey);
48+
expect(keys1.verificationKey).toMatchObject(keys2.verificationKey);
49+
});
50+
51+
it('should derive different keys for different biometric data', async () => {
52+
const bioData1 = createMockBiometricData('123');
53+
const bioData2 = createMockBiometricData('456');
54+
const email = `user${new Date().getTime()}@example.com`;
55+
56+
const keys1 = await deriveKeyMappingVaultKeys(bioData1, email);
57+
const keys2 = await deriveKeyMappingVaultKeys(bioData2, email);
58+
59+
expect(keys1.hmacKey).not.toBe(keys2.hmacKey);
60+
expect(keys1.agreementKey).not.toBe(keys2.agreementKey);
61+
expect(keys1.verificationKey).not.toBe(keys2.verificationKey);
62+
});
63+
64+
it('should encrypt and decrypt a master key correctly', async () => {
65+
const bioData = createMockBiometricData();
66+
const email = `user${new Date().getTime()}@example.com`;
67+
const masterKey = new Uint8Array(Buffer.from('test-master-key-for-encryption'));
68+
69+
const { key, iv } = await deriveBiometricEncryptionKey(bioData, email);
70+
const encryptedKey = await encryptMasterKey(masterKey, key, iv);
71+
expect(encryptedKey).not.toStrictEqual(masterKey);
72+
73+
const decryptedKey = await decryptMasterKey(encryptedKey, key, iv);
74+
expect(decryptedKey).toStrictEqual(masterKey);
75+
});
76+
77+
it('should fail decryption with wrong biometric data', async () => {
78+
const email = `user${new Date().getTime()}@example.com`;
79+
const masterKey = new Uint8Array(Buffer.from('test-master-key-for-encryption'));
80+
81+
const bioData1 = createMockBiometricData('123');
82+
const { key: key1, iv: iv1 } = await deriveBiometricEncryptionKey(bioData1, email);
83+
const encryptedKey = await encryptMasterKey(masterKey, key1, iv1);
84+
85+
const bioData2 = createMockBiometricData('456');
86+
const { key: key2, iv: iv2 } = await deriveBiometricEncryptionKey(bioData2, email);
87+
88+
await expect(decryptMasterKey(encryptedKey, key2, iv2)).rejects.toThrow('Decryption failed: Invalid key or corrupted data');
89+
});
90+
});
91+
92+
describe('Enrollment process', () => {
93+
it('should successfully enroll a user with biometrics', async () => {
94+
const bioData = createMockBiometricData();
95+
const email = `user${new Date().getTime()}@example.com`;
96+
97+
const result = await enrollUserWithBiometrics(
98+
EDV_URL,
99+
EDV_AUTH_KEY,
100+
bioData,
101+
email
102+
);
103+
104+
expect(result.masterKey).toBeDefined();
105+
expect(result.mnemonic).toBeDefined();
106+
expect(result.mnemonic.split(' ').length).toBe(12);
107+
108+
// Verify a document was inserted into the key mapping vault
109+
const edvService = await initializeKeyMappingVault(
110+
EDV_URL,
111+
EDV_AUTH_KEY,
112+
bioData,
113+
email
114+
);
115+
116+
const docs = await edvService.find({});
117+
expect(docs.documents).toBeDefined();
118+
expect(docs.documents.length).toBe(1);
119+
expect(docs.documents[0].content.id).toBeDefined();
120+
expect(docs.documents[0].content.encryptedKey).toBeDefined();
121+
});
122+
123+
it('should handle enrollment with different biometric data and identifiers', async () => {
124+
const bioData1 = createMockBiometricData('123');
125+
const bioData2 = createMockBiometricData('456');
126+
const email1 = `user1-${new Date().getTime()}@example.com`;
127+
const email2 = `user2-${new Date().getTime()}@example.com`;
128+
129+
const result1 = await enrollUserWithBiometrics(
130+
EDV_URL,
131+
EDV_AUTH_KEY,
132+
bioData1,
133+
email1
134+
);
135+
136+
const result2 = await enrollUserWithBiometrics(
137+
EDV_URL,
138+
EDV_AUTH_KEY,
139+
bioData2,
140+
email2
141+
);
142+
143+
expect(result1.masterKey).toBeDefined();
144+
expect(result2.masterKey).toBeDefined();
145+
expect(result1.masterKey).not.toBe(result2.masterKey);
146+
});
147+
148+
it('should allow recovery using mnemonic after biometric enrollment', async () => {
149+
const bioData = createMockBiometricData();
150+
const email = `user${new Date().getTime()}@example.com`;
151+
152+
const { masterKey, mnemonic } = await enrollUserWithBiometrics(
153+
EDV_URL,
154+
EDV_AUTH_KEY,
155+
bioData,
156+
email
157+
);
158+
159+
const recoveredKey = await recoverCloudWalletMasterKey(mnemonic);
160+
expect(recoveredKey).toStrictEqual(masterKey);
161+
});
162+
});
163+
164+
describe('Authentication process', () => {
165+
it('should successfully authenticate with correct biometric data', async () => {
166+
const bioData = createMockBiometricData();
167+
const email = `user${new Date().getTime()}@example.com`;
168+
169+
const { masterKey: originalMasterKey } = await enrollUserWithBiometrics(
170+
EDV_URL,
171+
EDV_AUTH_KEY,
172+
bioData,
173+
email
174+
);
175+
176+
const retrievedMasterKey = await authenticateWithBiometrics(
177+
EDV_URL,
178+
EDV_AUTH_KEY,
179+
bioData,
180+
email
181+
);
182+
183+
expect(retrievedMasterKey).toStrictEqual(originalMasterKey);
184+
});
185+
186+
it('should support multiple users with different biometric data', async () => {
187+
const bioData1 = createMockBiometricData('123');
188+
const bioData2 = createMockBiometricData('456');
189+
const email1 = `user1-${new Date().getTime()}@example.com`;
190+
const email2 = `user2-${new Date().getTime()}@example.com`;
191+
192+
const result1 = await enrollUserWithBiometrics(
193+
EDV_URL,
194+
EDV_AUTH_KEY,
195+
bioData1,
196+
email1
197+
);
198+
199+
const result2 = await enrollUserWithBiometrics(
200+
EDV_URL,
201+
EDV_AUTH_KEY,
202+
bioData2,
203+
email2
204+
);
205+
206+
expect(result1.masterKey).not.toStrictEqual(result2.masterKey);
207+
208+
const key1 = await authenticateWithBiometrics(
209+
EDV_URL,
210+
EDV_AUTH_KEY,
211+
bioData1,
212+
email1
213+
);
214+
215+
const key2 = await authenticateWithBiometrics(
216+
EDV_URL,
217+
EDV_AUTH_KEY,
218+
bioData2,
219+
email2
220+
);
221+
222+
expect(key1).toStrictEqual(result1.masterKey);
223+
expect(key2).toStrictEqual(result2.masterKey);
224+
});
225+
226+
it('should handle multiple key mappings for the same email', async () => {
227+
const bioData1 = createMockBiometricData('123');
228+
const bioData2 = createMockBiometricData('456');
229+
const email = `user${new Date().getTime()}@example.com`;
230+
const { masterKey: enrollMasterKey1 } = await enrollUserWithBiometrics(
231+
EDV_URL,
232+
EDV_AUTH_KEY,
233+
bioData1,
234+
email
235+
);
236+
237+
const { masterKey: enrollMasterKey2 } = await enrollUserWithBiometrics(
238+
EDV_URL,
239+
EDV_AUTH_KEY,
240+
bioData2,
241+
email
242+
);
243+
244+
expect(enrollMasterKey1).not.toStrictEqual(enrollMasterKey2);
245+
246+
const masterKey1 = await authenticateWithBiometrics(
247+
EDV_URL,
248+
EDV_AUTH_KEY,
249+
bioData1,
250+
email
251+
);
252+
253+
const masterKey2 = await authenticateWithBiometrics(
254+
EDV_URL,
255+
EDV_AUTH_KEY,
256+
bioData2,
257+
email
258+
);
259+
260+
expect(masterKey1).not.toStrictEqual(masterKey2);
261+
});
262+
263+
it('should fail authentication with incorrect biometric data', async () => {
264+
const bioData1 = createMockBiometricData('123');
265+
const bioData2 = createMockBiometricData('456');
266+
const email = `user${new Date().getTime()}@example.com`;
267+
268+
// Enroll with bioData1
269+
await enrollUserWithBiometrics(
270+
EDV_URL,
271+
EDV_AUTH_KEY,
272+
bioData1,
273+
email
274+
);
275+
276+
// Try to authenticate with bioData2
277+
await expect(
278+
authenticateWithBiometrics(
279+
EDV_URL,
280+
EDV_AUTH_KEY,
281+
bioData2,
282+
email
283+
)
284+
).rejects.toThrow('Authentication failed: Invalid identifier');
285+
});
286+
287+
it('should fail authentication with incorrect identifier', async () => {
288+
const bioData = createMockBiometricData();
289+
const email = `user${new Date().getTime()}@example.com`;
290+
291+
// Enroll first
292+
await enrollUserWithBiometrics(
293+
EDV_URL,
294+
EDV_AUTH_KEY,
295+
bioData,
296+
email
297+
);
298+
299+
// Try to authenticate with wrong email
300+
await expect(
301+
authenticateWithBiometrics(
302+
EDV_URL,
303+
EDV_AUTH_KEY,
304+
bioData,
305+
306+
)
307+
).rejects.toThrow('Authentication failed: Invalid identifier');
308+
});
309+
});
310+
});

0 commit comments

Comments
 (0)