walletpair-sdk 1.0.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 (95) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +415 -0
  3. package/dist/ble/framing.d.ts +23 -0
  4. package/dist/ble/framing.d.ts.map +1 -0
  5. package/dist/ble/framing.js +83 -0
  6. package/dist/ble/framing.js.map +1 -0
  7. package/dist/ble/index.d.ts +9 -0
  8. package/dist/ble/index.d.ts.map +1 -0
  9. package/dist/ble/index.js +9 -0
  10. package/dist/ble/index.js.map +1 -0
  11. package/dist/ble/web-ble-transport.d.ts +29 -0
  12. package/dist/ble/web-ble-transport.d.ts.map +1 -0
  13. package/dist/ble/web-ble-transport.js +93 -0
  14. package/dist/ble/web-ble-transport.js.map +1 -0
  15. package/dist/crypto.d.ts +102 -0
  16. package/dist/crypto.d.ts.map +1 -0
  17. package/dist/crypto.js +279 -0
  18. package/dist/crypto.js.map +1 -0
  19. package/dist/dapp-session.d.ts +106 -0
  20. package/dist/dapp-session.d.ts.map +1 -0
  21. package/dist/dapp-session.js +918 -0
  22. package/dist/dapp-session.js.map +1 -0
  23. package/dist/emitter.d.ts +16 -0
  24. package/dist/emitter.d.ts.map +1 -0
  25. package/dist/emitter.js +41 -0
  26. package/dist/emitter.js.map +1 -0
  27. package/dist/evm/eip1193.d.ts +83 -0
  28. package/dist/evm/eip1193.d.ts.map +1 -0
  29. package/dist/evm/eip1193.js +270 -0
  30. package/dist/evm/eip1193.js.map +1 -0
  31. package/dist/evm/index.d.ts +8 -0
  32. package/dist/evm/index.d.ts.map +1 -0
  33. package/dist/evm/index.js +8 -0
  34. package/dist/evm/index.js.map +1 -0
  35. package/dist/evm/wagmi.d.ts +118 -0
  36. package/dist/evm/wagmi.d.ts.map +1 -0
  37. package/dist/evm/wagmi.js +205 -0
  38. package/dist/evm/wagmi.js.map +1 -0
  39. package/dist/index.d.ts +22 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +24 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/types.d.ts +225 -0
  44. package/dist/types.d.ts.map +1 -0
  45. package/dist/types.js +31 -0
  46. package/dist/types.js.map +1 -0
  47. package/dist/wallet-session.d.ts +107 -0
  48. package/dist/wallet-session.d.ts.map +1 -0
  49. package/dist/wallet-session.js +794 -0
  50. package/dist/wallet-session.js.map +1 -0
  51. package/dist/ws-transport.d.ts +29 -0
  52. package/dist/ws-transport.d.ts.map +1 -0
  53. package/dist/ws-transport.js +79 -0
  54. package/dist/ws-transport.js.map +1 -0
  55. package/package.json +55 -0
  56. package/src/__tests__/adversarial/crypto-attacks.test.ts +557 -0
  57. package/src/__tests__/adversarial/malicious-dapp.test.ts +505 -0
  58. package/src/__tests__/adversarial/malicious-relay.test.ts +528 -0
  59. package/src/__tests__/adversarial/malicious-wallet.test.ts +467 -0
  60. package/src/__tests__/spec-compliance/canonical-json.test.ts +227 -0
  61. package/src/__tests__/spec-compliance/crypto-vectors.test.ts +321 -0
  62. package/src/__tests__/spec-compliance/message-format.test.ts +356 -0
  63. package/src/__tests__/spec-compliance/sequence-numbers.test.ts +300 -0
  64. package/src/__tests__/spec-compliance/state-machine.test.ts +364 -0
  65. package/src/ble/framing.test.ts +196 -0
  66. package/src/ble/framing.ts +100 -0
  67. package/src/ble/index.ts +18 -0
  68. package/src/ble/web-ble-transport.test.ts +192 -0
  69. package/src/ble/web-ble-transport.ts +116 -0
  70. package/src/ble/web-bluetooth.d.ts +47 -0
  71. package/src/canonical-json.test.ts +612 -0
  72. package/src/crypto-directional.test.ts +263 -0
  73. package/src/crypto-hardening.test.ts +529 -0
  74. package/src/crypto.test.ts +635 -0
  75. package/src/crypto.ts +405 -0
  76. package/src/dapp-session.test.ts +647 -0
  77. package/src/dapp-session.ts +1004 -0
  78. package/src/emitter.test.ts +169 -0
  79. package/src/emitter.ts +45 -0
  80. package/src/evm/eip1193.test.ts +365 -0
  81. package/src/evm/eip1193.ts +346 -0
  82. package/src/evm/index.ts +19 -0
  83. package/src/evm/wagmi.test.ts +396 -0
  84. package/src/evm/wagmi.ts +321 -0
  85. package/src/index.ts +86 -0
  86. package/src/integration.test.ts +385 -0
  87. package/src/security.test.ts +430 -0
  88. package/src/sequence-validation.test.ts +1185 -0
  89. package/src/test-helpers.ts +216 -0
  90. package/src/types.test.ts +82 -0
  91. package/src/types.ts +305 -0
  92. package/src/wallet-session.test.ts +683 -0
  93. package/src/wallet-session.ts +922 -0
  94. package/src/ws-transport.test.ts +231 -0
  95. package/src/ws-transport.ts +92 -0
@@ -0,0 +1,430 @@
1
+ /**
2
+ * Security tests for WalletPair SDK.
3
+ *
4
+ * Covers: replay detection, AAD tampering, ciphertext tampering, wrong key,
5
+ * sequence overflow, mandatory encryption enforcement, and key isolation
6
+ * across sessions.
7
+ */
8
+
9
+ import { describe, it, expect, vi } from 'vitest';
10
+ import { DAppSession } from './dapp-session.js';
11
+ import { WalletSession } from './wallet-session.js';
12
+ import { makeJoinBody, MockTransport, MockRelay } from './test-helpers.js';
13
+ import {
14
+ generateX25519KeyPair,
15
+ generateChannelId,
16
+ computeSharedSecret,
17
+ deriveSessionKey,
18
+ sealPayload,
19
+ unsealPayload,
20
+ b64urlEncode,
21
+ b64urlDecode,
22
+ bytesToHex,
23
+ buildPairingUri,
24
+ } from './crypto.js';
25
+ import { deriveDirectionalSessionKeys } from './crypto.js';
26
+ import type { SessionCryptoContext } from './crypto.js';
27
+ import type { ProtocolMessage } from './types.js';
28
+
29
+ function wait(ms = 50): Promise<void> {
30
+ return new Promise((r) => setTimeout(r, ms));
31
+ }
32
+
33
+ // ---------------------------------------------------------------------------
34
+ // Helpers to set up sessions with manual control
35
+ // ---------------------------------------------------------------------------
36
+
37
+ function setupDAppManual() {
38
+ const transport = new MockTransport();
39
+ const session = new DAppSession({ transport, meta: { name: 'Test', description: 'Test dApp', url: 'https://test.com', icon: 'https://test.com/icon.png' } });
40
+ const walletKp = generateX25519KeyPair();
41
+ return { transport, session, walletKp };
42
+ }
43
+
44
+ async function connectDAppManual(ctx: ReturnType<typeof setupDAppManual>) {
45
+ const { transport, session, walletKp } = ctx;
46
+ await session.createPairing();
47
+
48
+ transport.receive({
49
+ v: 1, t: 'join', ch: session.channelId,
50
+ ts: Date.now(), from: walletKp.publicKeyB64,
51
+ body: makeJoinBody(session.channelId, transport.sent[0]!.from!, walletKp),
52
+ } as ProtocolMessage);
53
+
54
+ transport.receive({
55
+ v: 1, t: 'ready', ch: session.channelId,
56
+ ts: Date.now(), from: '_adapter',
57
+ body: { state: 'connected', reconnect: false, remote: walletKp.publicKeyB64 },
58
+ } as ProtocolMessage);
59
+
60
+ // Derive the wallet's send key (walletToDappKey) which is what
61
+ // the DAppSession expects to receive (its recvKey).
62
+ const recvKey = (session as any).recvKey as Uint8Array;
63
+ const dappPubB64 = transport.sent[0]!.from!;
64
+ return { recvKey, dappPubB64 };
65
+ }
66
+
67
+ function setupWalletManual() {
68
+ const transport = new MockTransport();
69
+ const dappKp = generateX25519KeyPair();
70
+ const channelId = generateChannelId();
71
+ const session = new WalletSession({
72
+ transport,
73
+ meta: { name: 'Test Wallet', description: 'Test wallet', url: 'https://wallet.test', icon: 'https://wallet.test/icon.png' },
74
+ capabilities: { methods: ['wallet_getAccounts'], events: [], chains: ['eip155:1'] },
75
+ });
76
+ return { transport, session, dappKp, channelId };
77
+ }
78
+
79
+ async function connectWalletManual(ctx: ReturnType<typeof setupWalletManual>) {
80
+ const { transport, session, dappKp, channelId } = ctx;
81
+ const uri = buildPairingUri({
82
+ channelId,
83
+ pubkeyB64: dappKp.publicKeyB64,
84
+ relayUrl: 'ws://localhost/v1',
85
+ name: 'Test dApp',
86
+ url: 'https://dapp.test',
87
+ icon: 'https://dapp.test/icon.png',
88
+ });
89
+ await session.joinFromUri(uri);
90
+
91
+ transport.receive({
92
+ v: 1, t: 'ready', ch: channelId,
93
+ ts: Date.now(), from: '_adapter',
94
+ body: { state: 'connected', reconnect: false, remote: dappKp.publicKeyB64 },
95
+ } as ProtocolMessage);
96
+
97
+ // The wallet's recvKey is dappToWalletKey
98
+ const recvKey = (session as any).recvKey as Uint8Array;
99
+ const walletPubB64 = transport.sent.find(m => m.t === 'join')!.from!;
100
+ return { recvKey, walletPubB64 };
101
+ }
102
+
103
+ // ---------------------------------------------------------------------------
104
+ // Tests: Replay detection
105
+ // ---------------------------------------------------------------------------
106
+
107
+ describe('Security: Replay detection', () => {
108
+
109
+ it('same sequence number is rejected', async () => {
110
+ const ctx = setupDAppManual();
111
+ const { transport, session, walletKp } = ctx;
112
+ const { recvKey } = await connectDAppManual(ctx);
113
+
114
+ // First request -> seq=0 response accepted
115
+ const p0 = session.request('wallet_getAccounts');
116
+ await wait(20);
117
+ const req0 = transport.sent.find(m => m.t === 'req') as any;
118
+ const req0Id = req0.body.id;
119
+
120
+ transport.receive({
121
+ v: 1, t: 'res', ch: session.channelId,
122
+ ts: Date.now(), from: walletKp.publicKeyB64,
123
+ body: { id: req0Id, sealed: sealPayload(recvKey, session.channelId, 0, { _ok: true, _result: ['0xa'] },
124
+ { type: 'res', from: walletKp.publicKeyB64, id: req0Id }) },
125
+ } as ProtocolMessage);
126
+ expect(await p0).toEqual(['0xa']);
127
+
128
+ // Second request -> seq=0 again (replay) must be rejected
129
+ const p1 = session.request('wallet_getAccounts');
130
+ await wait(20);
131
+ const req1 = transport.sent.filter(m => m.t === 'req')[1] as any;
132
+ const req1Id = req1.body.id;
133
+
134
+ transport.receive({
135
+ v: 1, t: 'res', ch: session.channelId,
136
+ ts: Date.now(), from: walletKp.publicKeyB64,
137
+ body: { id: req1Id, sealed: sealPayload(recvKey, session.channelId, 0, { _ok: true, _result: ['replay'] },
138
+ { type: 'res', from: walletKp.publicKeyB64, id: req1Id }) },
139
+ } as ProtocolMessage);
140
+ await expect(p1).rejects.toThrow('Replay detected');
141
+ });
142
+
143
+ it('lower sequence number is rejected', async () => {
144
+ const ctx = setupDAppManual();
145
+ const { transport, session, walletKp } = ctx;
146
+ const { recvKey } = await connectDAppManual(ctx);
147
+
148
+ // seq=5 accepted
149
+ const p0 = session.request('wallet_getAccounts');
150
+ await wait(20);
151
+ const req0 = transport.sent.find(m => m.t === 'req') as any;
152
+ const r0id = req0.body.id;
153
+ transport.receive({
154
+ v: 1, t: 'res', ch: session.channelId,
155
+ ts: Date.now(), from: walletKp.publicKeyB64,
156
+ body: { id: r0id, sealed: sealPayload(recvKey, session.channelId, 5, { _ok: true, _result: 'ok' },
157
+ { type: 'res', from: walletKp.publicKeyB64, id: r0id }) },
158
+ } as ProtocolMessage);
159
+ expect(await p0).toBe('ok');
160
+
161
+ // seq=3 (lower) must be rejected
162
+ const p1 = session.request('wallet_getAccounts');
163
+ await wait(20);
164
+ const req1 = transport.sent.filter(m => m.t === 'req')[1] as any;
165
+ const r1id = req1.body.id;
166
+ transport.receive({
167
+ v: 1, t: 'res', ch: session.channelId,
168
+ ts: Date.now(), from: walletKp.publicKeyB64,
169
+ body: { id: r1id, sealed: sealPayload(recvKey, session.channelId, 3, { _ok: true, _result: 'stale' },
170
+ { type: 'res', from: walletKp.publicKeyB64, id: r1id }) },
171
+ } as ProtocolMessage);
172
+ await expect(p1).rejects.toThrow('Replay detected');
173
+ });
174
+
175
+ it('higher sequence number is accepted', async () => {
176
+ const ctx = setupDAppManual();
177
+ const { transport, session, walletKp } = ctx;
178
+ const { recvKey } = await connectDAppManual(ctx);
179
+
180
+ // seq=0 accepted
181
+ const p0 = session.request('wallet_getAccounts');
182
+ await wait(20);
183
+ const req0 = transport.sent.find(m => m.t === 'req') as any;
184
+ const r0id = req0.body.id;
185
+ transport.receive({
186
+ v: 1, t: 'res', ch: session.channelId,
187
+ ts: Date.now(), from: walletKp.publicKeyB64,
188
+ body: { id: r0id, sealed: sealPayload(recvKey, session.channelId, 0, { _ok: true, _result: 'first' },
189
+ { type: 'res', from: walletKp.publicKeyB64, id: r0id }) },
190
+ } as ProtocolMessage);
191
+ expect(await p0).toBe('first');
192
+
193
+ // seq=10 (higher) accepted
194
+ const p1 = session.request('wallet_getAccounts');
195
+ await wait(20);
196
+ const req1 = transport.sent.filter(m => m.t === 'req')[1] as any;
197
+ const r1id = req1.body.id;
198
+ transport.receive({
199
+ v: 1, t: 'res', ch: session.channelId,
200
+ ts: Date.now(), from: walletKp.publicKeyB64,
201
+ body: { id: r1id, sealed: sealPayload(recvKey, session.channelId, 10, { _ok: true, _result: 'second' },
202
+ { type: 'res', from: walletKp.publicKeyB64, id: r1id }) },
203
+ } as ProtocolMessage);
204
+ expect(await p1).toBe('second');
205
+ });
206
+ });
207
+
208
+ // ---------------------------------------------------------------------------
209
+ // Tests: AAD tampering
210
+ // ---------------------------------------------------------------------------
211
+
212
+ describe('Security: AAD tampering', () => {
213
+
214
+ it('tampered AAD (wrong id) causes decryption failure', () => {
215
+ const key = new Uint8Array(32);
216
+ crypto.getRandomValues(key);
217
+ const channelId = generateChannelId();
218
+
219
+ const hdr = { type: 'req' as const, from: 'dapp', id: 'req-1' };
220
+ const sealed = sealPayload(key, channelId, 0, { foo: 'bar' }, hdr);
221
+
222
+ const tamperedHdr = { ...hdr, id: 'req-999' };
223
+ expect(() => unsealPayload(key, channelId, sealed, tamperedHdr)).toThrow();
224
+ });
225
+
226
+ it('tampered AAD (wrong from) causes decryption failure', () => {
227
+ const key = new Uint8Array(32);
228
+ crypto.getRandomValues(key);
229
+ const channelId = generateChannelId();
230
+
231
+ const hdr = { type: 'req' as const, from: 'dapp', id: 'req-1' };
232
+ const sealed = sealPayload(key, channelId, 0, {}, hdr);
233
+
234
+ const tamperedHdr = { ...hdr, from: 'evil-relay' };
235
+ expect(() => unsealPayload(key, channelId, sealed, tamperedHdr)).toThrow();
236
+ });
237
+ });
238
+
239
+ // ---------------------------------------------------------------------------
240
+ // Tests: Ciphertext and key tampering
241
+ // ---------------------------------------------------------------------------
242
+
243
+ describe('Security: Ciphertext and key tampering', () => {
244
+
245
+ it('tampered ciphertext causes decryption failure', () => {
246
+ const key = new Uint8Array(32);
247
+ crypto.getRandomValues(key);
248
+ const channelId = generateChannelId();
249
+
250
+ const hdr = { type: 'req' as const, from: 'dapp', id: 'req-1' };
251
+ const sealed = sealPayload(key, channelId, 0, { secret: true }, hdr);
252
+
253
+ // Decode, flip a byte in the ciphertext, re-encode
254
+ const bytes = b64urlDecode(sealed);
255
+ bytes[10] = bytes[10]! ^ 0xff;
256
+ const tampered = b64urlEncode(bytes);
257
+
258
+ expect(() => unsealPayload(key, channelId, tampered, hdr)).toThrow();
259
+ });
260
+
261
+ it('wrong key causes decryption failure', () => {
262
+ const key = new Uint8Array(32);
263
+ crypto.getRandomValues(key);
264
+ const wrongKey = new Uint8Array(32);
265
+ crypto.getRandomValues(wrongKey);
266
+ const channelId = generateChannelId();
267
+
268
+ const sealed = sealPayload(key, channelId, 0, { data: 'test' });
269
+ expect(() => unsealPayload(wrongKey, channelId, sealed)).toThrow();
270
+ });
271
+ });
272
+
273
+ // ---------------------------------------------------------------------------
274
+ // Tests: Sequence overflow closes session
275
+ // ---------------------------------------------------------------------------
276
+
277
+ describe('Security: Sequence overflow', () => {
278
+
279
+ it('DAppSession: overflow at MAX_SEND_SEQ causes session close', async () => {
280
+ const ctx = setupDAppManual();
281
+ const { session } = ctx;
282
+ await connectDAppManual(ctx);
283
+
284
+ (session as any).sendSeq = 2 ** 31;
285
+
286
+ const errorHandler = vi.fn();
287
+ session.on('error', errorHandler);
288
+
289
+ await expect(session.request('wallet_getAccounts')).rejects.toThrow('Send sequence overflow');
290
+ expect(errorHandler).toHaveBeenCalled();
291
+ expect(session.phase).toBe('closed');
292
+ });
293
+
294
+ it('WalletSession: overflow at MAX_SEND_SEQ causes session close via pushEvent', async () => {
295
+ const ctx = setupWalletManual();
296
+ const { session } = ctx;
297
+ await connectWalletManual(ctx);
298
+
299
+ (session as any).sendSeq = 2 ** 31 - 1;
300
+
301
+ const errorHandler = vi.fn();
302
+ session.on('error', errorHandler);
303
+
304
+ // Last allowed
305
+ session.pushEvent('accountsChanged', { accounts: ['0xa'] });
306
+ expect(session.phase).toBe('connected');
307
+
308
+ // Overflow
309
+ session.pushEvent('accountsChanged', { accounts: ['0xb'] });
310
+ expect(errorHandler).toHaveBeenCalled();
311
+ expect(session.phase).toBe('closed');
312
+ });
313
+ });
314
+
315
+ // ---------------------------------------------------------------------------
316
+ // Tests: Mandatory encryption enforcement
317
+ // ---------------------------------------------------------------------------
318
+
319
+ describe('Security: Mandatory encryption', () => {
320
+
321
+ it('DAppSession rejects unsealed responses', async () => {
322
+ const ctx = setupDAppManual();
323
+ const { transport, session, walletKp } = ctx;
324
+ await connectDAppManual(ctx);
325
+
326
+ const p = session.request('wallet_getAccounts');
327
+ await wait(20);
328
+ const req = transport.sent.find(m => m.t === 'req') as any;
329
+ const reqId = req.body.id;
330
+
331
+ // Send a response without sealed field
332
+ transport.receive({
333
+ v: 1, t: 'res', ch: session.channelId,
334
+ ts: Date.now(), from: walletKp.publicKeyB64,
335
+ body: { id: reqId },
336
+ // no sealed field in body
337
+ } as ProtocolMessage);
338
+
339
+ await expect(p).rejects.toThrow('Response must be encrypted');
340
+ });
341
+
342
+ it('WalletSession rejects unsealed requests', async () => {
343
+ const ctx = setupWalletManual();
344
+ const { transport, session, dappKp, channelId } = ctx;
345
+ await connectWalletManual(ctx);
346
+
347
+ const requestHandler = vi.fn();
348
+ session.on('request', requestHandler);
349
+
350
+ // Send a request without sealed field in body
351
+ transport.receive({
352
+ v: 1, t: 'req', ch: channelId,
353
+ ts: Date.now(), from: dappKp.publicKeyB64,
354
+ body: { id: 'req-unseal' },
355
+ // no sealed field
356
+ } as ProtocolMessage);
357
+
358
+ // The request handler should NOT be called
359
+ expect(requestHandler).not.toHaveBeenCalled();
360
+
361
+ // Wallet should have sent a rejection response
362
+ const rejectionMsg = transport.sent.find(m => m.t === 'res' && (m as any).body?.id === 'req-unseal') as any;
363
+ expect(rejectionMsg).toBeTruthy();
364
+ // ok no longer exists on wire body
365
+ });
366
+
367
+ it('DAppSession drops unsealed events', async () => {
368
+ const ctx = setupDAppManual();
369
+ const { transport, session, walletKp } = ctx;
370
+ await connectDAppManual(ctx);
371
+
372
+ const eventHandler = vi.fn();
373
+ session.on('event', eventHandler);
374
+
375
+ // Send an event without sealed field in body
376
+ transport.receive({
377
+ v: 1, t: 'evt', ch: session.channelId,
378
+ ts: Date.now(), from: walletKp.publicKeyB64,
379
+ body: { id: 'evt-1' },
380
+ // no sealed field
381
+ } as ProtocolMessage);
382
+
383
+ await wait(20);
384
+ expect(eventHandler).not.toHaveBeenCalled();
385
+ });
386
+ });
387
+
388
+ // ---------------------------------------------------------------------------
389
+ // Tests: Key isolation across sessions
390
+ // ---------------------------------------------------------------------------
391
+
392
+ describe('Security: Key isolation across sessions', () => {
393
+
394
+ it('session key changes if wallet pubkey changes (no key reuse)', async () => {
395
+ const dappKp = generateX25519KeyPair();
396
+ const channelId = generateChannelId();
397
+
398
+ // First wallet
399
+ const wallet1 = generateX25519KeyPair();
400
+ const shared1 = computeSharedSecret(dappKp.privateKey, wallet1.publicKey);
401
+ const root1 = deriveSessionKey(shared1, channelId);
402
+ const ctx1: SessionCryptoContext = {
403
+ dappPubKeyB64: dappKp.publicKeyB64,
404
+ walletPubKeyB64: wallet1.publicKeyB64,
405
+ capabilities: null,
406
+ walletMeta: null,
407
+ dappName: 'App',
408
+ };
409
+ const keys1 = deriveDirectionalSessionKeys(root1, channelId, ctx1);
410
+
411
+ // Second wallet (different pubkey)
412
+ const wallet2 = generateX25519KeyPair();
413
+ const shared2 = computeSharedSecret(dappKp.privateKey, wallet2.publicKey);
414
+ const root2 = deriveSessionKey(shared2, channelId);
415
+ const ctx2: SessionCryptoContext = {
416
+ dappPubKeyB64: dappKp.publicKeyB64,
417
+ walletPubKeyB64: wallet2.publicKeyB64,
418
+ capabilities: null,
419
+ walletMeta: null,
420
+ dappName: 'App',
421
+ };
422
+ const keys2 = deriveDirectionalSessionKeys(root2, channelId, ctx2);
423
+
424
+ // All keys must differ
425
+ expect(bytesToHex(keys1.dappToWalletKey)).not.toBe(bytesToHex(keys2.dappToWalletKey));
426
+ expect(bytesToHex(keys1.walletToDappKey)).not.toBe(bytesToHex(keys2.walletToDappKey));
427
+ expect(bytesToHex(keys1.rootKey)).not.toBe(bytesToHex(keys2.rootKey));
428
+ expect(bytesToHex(keys1.transcriptHash)).not.toBe(bytesToHex(keys2.transcriptHash));
429
+ });
430
+ });