walletpair-sdk 1.0.2 → 1.0.5

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 (74) hide show
  1. package/README.md +13 -0
  2. package/dist/ble/framing.d.ts.map +1 -1
  3. package/dist/ble/framing.js +2 -2
  4. package/dist/ble/framing.js.map +1 -1
  5. package/dist/ble/index.d.ts +2 -2
  6. package/dist/ble/index.d.ts.map +1 -1
  7. package/dist/ble/index.js +2 -2
  8. package/dist/ble/index.js.map +1 -1
  9. package/dist/ble/web-ble-transport.d.ts +1 -1
  10. package/dist/ble/web-ble-transport.d.ts.map +1 -1
  11. package/dist/ble/web-ble-transport.js +23 -12
  12. package/dist/ble/web-ble-transport.js.map +1 -1
  13. package/dist/crypto.d.ts.map +1 -1
  14. package/dist/crypto.js +29 -12
  15. package/dist/crypto.js.map +1 -1
  16. package/dist/dapp-session.d.ts.map +1 -1
  17. package/dist/dapp-session.js +15 -5
  18. package/dist/dapp-session.js.map +1 -1
  19. package/dist/emitter.d.ts +1 -3
  20. package/dist/emitter.d.ts.map +1 -1
  21. package/dist/emitter.js +4 -2
  22. package/dist/emitter.js.map +1 -1
  23. package/dist/evm/eip1193.d.ts +2 -2
  24. package/dist/evm/eip1193.d.ts.map +1 -1
  25. package/dist/evm/eip1193.js +32 -18
  26. package/dist/evm/eip1193.js.map +1 -1
  27. package/dist/evm/index.d.ts +2 -2
  28. package/dist/evm/index.d.ts.map +1 -1
  29. package/dist/evm/index.js.map +1 -1
  30. package/dist/wallet-session.d.ts.map +1 -1
  31. package/dist/wallet-session.js +4 -3
  32. package/dist/wallet-session.js.map +1 -1
  33. package/dist/ws-transport.d.ts +3 -2
  34. package/dist/ws-transport.d.ts.map +1 -1
  35. package/dist/ws-transport.js +13 -4
  36. package/dist/ws-transport.js.map +1 -1
  37. package/package.json +20 -1
  38. package/src/__tests__/adversarial/crypto-attacks.test.ts +240 -233
  39. package/src/__tests__/adversarial/malicious-dapp.test.ts +228 -194
  40. package/src/__tests__/adversarial/malicious-relay.test.ts +292 -220
  41. package/src/__tests__/adversarial/malicious-wallet.test.ts +246 -180
  42. package/src/__tests__/spec-compliance/canonical-json.test.ts +105 -105
  43. package/src/__tests__/spec-compliance/crypto-vectors.test.ts +149 -154
  44. package/src/__tests__/spec-compliance/message-format.test.ts +180 -151
  45. package/src/__tests__/spec-compliance/sequence-numbers.test.ts +142 -149
  46. package/src/__tests__/spec-compliance/state-machine.test.ts +203 -180
  47. package/src/ble/framing.test.ts +122 -114
  48. package/src/ble/framing.ts +48 -51
  49. package/src/ble/index.ts +7 -7
  50. package/src/ble/web-ble-transport.test.ts +93 -84
  51. package/src/ble/web-ble-transport.ts +70 -57
  52. package/src/ble/web-bluetooth.d.ts +19 -19
  53. package/src/canonical-json.test.ts +301 -285
  54. package/src/crypto-directional.test.ts +155 -129
  55. package/src/crypto-hardening.test.ts +292 -283
  56. package/src/crypto.test.ts +364 -346
  57. package/src/crypto.ts +185 -175
  58. package/src/dapp-session.test.ts +522 -385
  59. package/src/dapp-session.ts +17 -11
  60. package/src/emitter.test.ts +122 -122
  61. package/src/emitter.ts +20 -18
  62. package/src/evm/eip1193.test.ts +283 -205
  63. package/src/evm/eip1193.ts +162 -138
  64. package/src/evm/index.ts +5 -5
  65. package/src/evm/wagmi.test.ts +1 -1
  66. package/src/integration.test.ts +329 -201
  67. package/src/security.test.ts +331 -238
  68. package/src/sequence-validation.test.ts +6 -9
  69. package/src/test-helpers.ts +102 -78
  70. package/src/types.test.ts +45 -50
  71. package/src/wallet-session.test.ts +611 -383
  72. package/src/wallet-session.ts +7 -9
  73. package/src/ws-transport.test.ts +141 -139
  74. package/src/ws-transport.ts +52 -41
@@ -1,27 +1,27 @@
1
- import { describe, it, expect } from 'vitest';
1
+ import { describe, expect, it } from 'vitest'
2
2
  import {
3
- b64urlEncode,
4
3
  b64urlDecode,
5
- generateX25519KeyPair,
6
- getPublicKey,
4
+ b64urlEncode,
5
+ buildPairingUri,
6
+ bytesToHex,
7
+ canonicalJson,
8
+ computeSessionFingerprint,
7
9
  computeSharedSecret,
8
- deriveSessionKey,
10
+ constantTimeEqual,
9
11
  deriveJoinEncryptionKey,
10
- computeSessionFingerprint,
11
- sealPayload,
12
- unsealPayload,
13
- sealJoin,
14
- unsealJoin,
12
+ deriveSessionKey,
15
13
  generateChannelId,
16
- buildPairingUri,
17
- parsePairingUri,
18
- bytesToHex,
14
+ generateX25519KeyPair,
15
+ getPublicKey,
19
16
  hexToBytes,
20
- constantTimeEqual,
21
- canonicalJson,
17
+ parsePairingUri,
18
+ sealJoin,
19
+ sealPayload,
22
20
  signSnapshot,
21
+ unsealJoin,
22
+ unsealPayload,
23
23
  verifySnapshot,
24
- } from './crypto.js';
24
+ } from './crypto.js'
25
25
 
26
26
  // ---------------------------------------------------------------------------
27
27
  // Base64url
@@ -29,35 +29,35 @@ import {
29
29
 
30
30
  describe('b64url', () => {
31
31
  it('round-trips arbitrary bytes', () => {
32
- const bytes = new Uint8Array([0, 1, 2, 255, 128, 64, 32, 16]);
33
- expect(b64urlDecode(b64urlEncode(bytes))).toEqual(bytes);
34
- });
32
+ const bytes = new Uint8Array([0, 1, 2, 255, 128, 64, 32, 16])
33
+ expect(b64urlDecode(b64urlEncode(bytes))).toEqual(bytes)
34
+ })
35
35
 
36
36
  it('produces no padding characters', () => {
37
- const encoded = b64urlEncode(new Uint8Array([1, 2, 3]));
38
- expect(encoded).not.toContain('=');
39
- });
37
+ const encoded = b64urlEncode(new Uint8Array([1, 2, 3]))
38
+ expect(encoded).not.toContain('=')
39
+ })
40
40
 
41
41
  it('uses URL-safe alphabet (no + or /)', () => {
42
42
  // Encode bytes that would produce + and / in standard base64
43
- const bytes = new Uint8Array(256);
44
- for (let i = 0; i < 256; i++) bytes[i] = i;
45
- const encoded = b64urlEncode(bytes);
46
- expect(encoded).not.toContain('+');
47
- expect(encoded).not.toContain('/');
48
- });
43
+ const bytes = new Uint8Array(256)
44
+ for (let i = 0; i < 256; i++) bytes[i] = i
45
+ const encoded = b64urlEncode(bytes)
46
+ expect(encoded).not.toContain('+')
47
+ expect(encoded).not.toContain('/')
48
+ })
49
49
 
50
50
  it('handles empty input', () => {
51
- expect(b64urlEncode(new Uint8Array(0))).toBe('');
52
- expect(b64urlDecode('')).toEqual(new Uint8Array(0));
53
- });
51
+ expect(b64urlEncode(new Uint8Array(0))).toBe('')
52
+ expect(b64urlDecode('')).toEqual(new Uint8Array(0))
53
+ })
54
54
 
55
55
  it('decodes known value', () => {
56
56
  // "Hello" in base64url = "SGVsbG8"
57
- const decoded = b64urlDecode('SGVsbG8');
58
- expect(new TextDecoder().decode(decoded)).toBe('Hello');
59
- });
60
- });
57
+ const decoded = b64urlDecode('SGVsbG8')
58
+ expect(new TextDecoder().decode(decoded)).toBe('Hello')
59
+ })
60
+ })
61
61
 
62
62
  // ---------------------------------------------------------------------------
63
63
  // Key generation
@@ -65,32 +65,32 @@ describe('b64url', () => {
65
65
 
66
66
  describe('generateX25519KeyPair', () => {
67
67
  it('returns 32-byte private and public keys', () => {
68
- const kp = generateX25519KeyPair();
69
- expect(kp.privateKey).toHaveLength(32);
70
- expect(kp.publicKey).toHaveLength(32);
71
- expect(typeof kp.publicKeyB64).toBe('string');
72
- expect(kp.publicKeyB64.length).toBeGreaterThan(0);
73
- });
68
+ const kp = generateX25519KeyPair()
69
+ expect(kp.privateKey).toHaveLength(32)
70
+ expect(kp.publicKey).toHaveLength(32)
71
+ expect(typeof kp.publicKeyB64).toBe('string')
72
+ expect(kp.publicKeyB64.length).toBeGreaterThan(0)
73
+ })
74
74
 
75
75
  it('generates unique key pairs', () => {
76
- const a = generateX25519KeyPair();
77
- const b = generateX25519KeyPair();
78
- expect(bytesToHex(a.privateKey)).not.toBe(bytesToHex(b.privateKey));
79
- });
76
+ const a = generateX25519KeyPair()
77
+ const b = generateX25519KeyPair()
78
+ expect(bytesToHex(a.privateKey)).not.toBe(bytesToHex(b.privateKey))
79
+ })
80
80
 
81
81
  it('publicKeyB64 decodes back to publicKey', () => {
82
- const kp = generateX25519KeyPair();
83
- expect(b64urlDecode(kp.publicKeyB64)).toEqual(kp.publicKey);
84
- });
85
- });
82
+ const kp = generateX25519KeyPair()
83
+ expect(b64urlDecode(kp.publicKeyB64)).toEqual(kp.publicKey)
84
+ })
85
+ })
86
86
 
87
87
  describe('getPublicKey', () => {
88
88
  it('derives the same public key as generateX25519KeyPair', () => {
89
- const kp = generateX25519KeyPair();
90
- const derived = getPublicKey(kp.privateKey);
91
- expect(derived).toEqual(kp.publicKey);
92
- });
93
- });
89
+ const kp = generateX25519KeyPair()
90
+ const derived = getPublicKey(kp.privateKey)
91
+ expect(derived).toEqual(kp.publicKey)
92
+ })
93
+ })
94
94
 
95
95
  // ---------------------------------------------------------------------------
96
96
  // Shared secret & session key derivation
@@ -98,55 +98,55 @@ describe('getPublicKey', () => {
98
98
 
99
99
  describe('key exchange', () => {
100
100
  it('both peers derive the same shared secret (X25519 DH)', () => {
101
- const alice = generateX25519KeyPair();
102
- const bob = generateX25519KeyPair();
101
+ const alice = generateX25519KeyPair()
102
+ const bob = generateX25519KeyPair()
103
103
 
104
- const secretA = computeSharedSecret(alice.privateKey, bob.publicKey);
105
- const secretB = computeSharedSecret(bob.privateKey, alice.publicKey);
104
+ const secretA = computeSharedSecret(alice.privateKey, bob.publicKey)
105
+ const secretB = computeSharedSecret(bob.privateKey, alice.publicKey)
106
106
 
107
- expect(secretA).toEqual(secretB);
108
- expect(secretA).toHaveLength(32);
109
- });
107
+ expect(secretA).toEqual(secretB)
108
+ expect(secretA).toHaveLength(32)
109
+ })
110
110
 
111
111
  it('both peers derive the same session key', () => {
112
- const alice = generateX25519KeyPair();
113
- const bob = generateX25519KeyPair();
114
- const channelId = generateChannelId();
112
+ const alice = generateX25519KeyPair()
113
+ const bob = generateX25519KeyPair()
114
+ const channelId = generateChannelId()
115
115
 
116
- const shared = computeSharedSecret(alice.privateKey, bob.publicKey);
117
- const skA = deriveSessionKey(shared, channelId);
118
- const skB = deriveSessionKey(computeSharedSecret(bob.privateKey, alice.publicKey), channelId);
116
+ const shared = computeSharedSecret(alice.privateKey, bob.publicKey)
117
+ const skA = deriveSessionKey(shared, channelId)
118
+ const skB = deriveSessionKey(computeSharedSecret(bob.privateKey, alice.publicKey), channelId)
119
119
 
120
- expect(skA).toEqual(skB);
121
- expect(skA).toHaveLength(32);
122
- });
120
+ expect(skA).toEqual(skB)
121
+ expect(skA).toHaveLength(32)
122
+ })
123
123
 
124
124
  it('different channel IDs produce different session keys', () => {
125
- const alice = generateX25519KeyPair();
126
- const bob = generateX25519KeyPair();
127
- const shared = computeSharedSecret(alice.privateKey, bob.publicKey);
125
+ const alice = generateX25519KeyPair()
126
+ const bob = generateX25519KeyPair()
127
+ const shared = computeSharedSecret(alice.privateKey, bob.publicKey)
128
128
 
129
- const sk1 = deriveSessionKey(shared, generateChannelId());
130
- const sk2 = deriveSessionKey(shared, generateChannelId());
129
+ const sk1 = deriveSessionKey(shared, generateChannelId())
130
+ const sk2 = deriveSessionKey(shared, generateChannelId())
131
131
 
132
- expect(bytesToHex(sk1)).not.toBe(bytesToHex(sk2));
133
- });
132
+ expect(bytesToHex(sk1)).not.toBe(bytesToHex(sk2))
133
+ })
134
134
 
135
135
  it('rejects remote public key that is not 32 bytes', () => {
136
- const alice = generateX25519KeyPair();
137
- expect(() => computeSharedSecret(alice.privateKey, new Uint8Array(31))).toThrow('32 bytes');
138
- expect(() => computeSharedSecret(alice.privateKey, new Uint8Array(33))).toThrow('32 bytes');
139
- expect(() => computeSharedSecret(alice.privateKey, new Uint8Array(0))).toThrow('32 bytes');
140
- });
136
+ const alice = generateX25519KeyPair()
137
+ expect(() => computeSharedSecret(alice.privateKey, new Uint8Array(31))).toThrow('32 bytes')
138
+ expect(() => computeSharedSecret(alice.privateKey, new Uint8Array(33))).toThrow('32 bytes')
139
+ expect(() => computeSharedSecret(alice.privateKey, new Uint8Array(0))).toThrow('32 bytes')
140
+ })
141
141
 
142
142
  it('rejects all-zero public key (low-order point)', () => {
143
- const alice = generateX25519KeyPair();
144
- const zeroKey = new Uint8Array(32); // all zeros — low-order point
143
+ const alice = generateX25519KeyPair()
144
+ const zeroKey = new Uint8Array(32) // all zeros — low-order point
145
145
  // Noble library rejects this at the X25519 level; our wrapper also
146
146
  // has an explicit all-zero check for libraries that don't.
147
- expect(() => computeSharedSecret(alice.privateKey, zeroKey)).toThrow();
148
- });
149
- });
147
+ expect(() => computeSharedSecret(alice.privateKey, zeroKey)).toThrow()
148
+ })
149
+ })
150
150
 
151
151
  // ---------------------------------------------------------------------------
152
152
  // Session fingerprint
@@ -154,154 +154,158 @@ describe('key exchange', () => {
154
154
 
155
155
  describe('computeSessionFingerprint', () => {
156
156
  it('returns a 4-digit string', () => {
157
- const channelId = generateChannelId();
158
- const kp = generateX25519KeyPair();
157
+ const channelId = generateChannelId()
158
+ const kp = generateX25519KeyPair()
159
159
 
160
- const code = computeSessionFingerprint(channelId, kp.publicKeyB64);
161
- expect(code).toMatch(/^\d{4}$/);
162
- });
160
+ const code = computeSessionFingerprint(channelId, kp.publicKeyB64)
161
+ expect(code).toMatch(/^\d{4}$/)
162
+ })
163
163
 
164
164
  it('is deterministic for same inputs', () => {
165
- const ch = '00'.repeat(32);
166
- const pubB64 = b64urlEncode(new Uint8Array(32).fill(42));
167
- expect(computeSessionFingerprint(ch, pubB64)).toBe(computeSessionFingerprint(ch, pubB64));
168
- });
165
+ const ch = '00'.repeat(32)
166
+ const pubB64 = b64urlEncode(new Uint8Array(32).fill(42))
167
+ expect(computeSessionFingerprint(ch, pubB64)).toBe(computeSessionFingerprint(ch, pubB64))
168
+ })
169
169
 
170
170
  it('different channel IDs produce different fingerprints', () => {
171
- const kp = generateX25519KeyPair();
172
- const ch1 = generateChannelId();
173
- const ch2 = generateChannelId();
171
+ const kp = generateX25519KeyPair()
172
+ const ch1 = generateChannelId()
173
+ const ch2 = generateChannelId()
174
174
  expect(computeSessionFingerprint(ch1, kp.publicKeyB64)).not.toBe(
175
175
  computeSessionFingerprint(ch2, kp.publicKeyB64),
176
- );
177
- });
176
+ )
177
+ })
178
178
 
179
179
  it('different dApp pubkeys produce different fingerprints', () => {
180
- const ch = generateChannelId();
181
- const kp1 = generateX25519KeyPair();
182
- const kp2 = generateX25519KeyPair();
180
+ const ch = generateChannelId()
181
+ const kp1 = generateX25519KeyPair()
182
+ const kp2 = generateX25519KeyPair()
183
183
  expect(computeSessionFingerprint(ch, kp1.publicKeyB64)).not.toBe(
184
184
  computeSessionFingerprint(ch, kp2.publicKeyB64),
185
- );
186
- });
185
+ )
186
+ })
187
187
 
188
188
  it('pads with leading zeros when necessary', () => {
189
189
  // We can't force a specific output, but verify format consistency
190
- const results = new Set<string>();
190
+ const results = new Set<string>()
191
191
  for (let i = 0; i < 20; i++) {
192
- const ch = generateChannelId();
193
- const kp = generateX25519KeyPair();
194
- const code = computeSessionFingerprint(ch, kp.publicKeyB64);
195
- expect(code).toHaveLength(4);
196
- results.add(code);
192
+ const ch = generateChannelId()
193
+ const kp = generateX25519KeyPair()
194
+ const code = computeSessionFingerprint(ch, kp.publicKeyB64)
195
+ expect(code).toHaveLength(4)
196
+ results.add(code)
197
197
  }
198
198
  // Very unlikely all 20 are the same
199
- expect(results.size).toBeGreaterThan(1);
200
- });
201
- });
199
+ expect(results.size).toBeGreaterThan(1)
200
+ })
201
+ })
202
202
 
203
203
  // ---------------------------------------------------------------------------
204
204
  // Seal / Unseal (encryption round-trip)
205
205
  // ---------------------------------------------------------------------------
206
206
 
207
207
  describe('seal/unseal', () => {
208
- const sessionKey = new Uint8Array(32);
209
- crypto.getRandomValues(sessionKey);
210
- const channelId = generateChannelId();
208
+ const sessionKey = new Uint8Array(32)
209
+ crypto.getRandomValues(sessionKey)
210
+ const channelId = generateChannelId()
211
211
 
212
212
  it('round-trips a simple object', () => {
213
- const data = { hello: 'world', num: 42 };
214
- const sealed = sealPayload(sessionKey, channelId, 0, data);
215
- const { seq, data: decrypted } = unsealPayload(sessionKey, channelId, sealed);
213
+ const data = { hello: 'world', num: 42 }
214
+ const sealed = sealPayload(sessionKey, channelId, 0, data)
215
+ const { seq, data: decrypted } = unsealPayload(sessionKey, channelId, sealed)
216
216
 
217
- expect(seq).toBe(0);
218
- expect(decrypted).toEqual(data);
219
- });
217
+ expect(seq).toBe(0)
218
+ expect(decrypted).toEqual(data)
219
+ })
220
220
 
221
221
  it('round-trips with different sequence numbers', () => {
222
222
  for (const seqNum of [0, 1, 100, 65535, 2 ** 31 - 1]) {
223
- const data = { seq: seqNum };
224
- const sealed = sealPayload(sessionKey, channelId, seqNum, data);
225
- const { seq, data: decrypted } = unsealPayload(sessionKey, channelId, sealed);
226
- expect(seq).toBe(seqNum);
227
- expect(decrypted).toEqual(data);
223
+ const data = { seq: seqNum }
224
+ const sealed = sealPayload(sessionKey, channelId, seqNum, data)
225
+ const { seq, data: decrypted } = unsealPayload(sessionKey, channelId, sealed)
226
+ expect(seq).toBe(seqNum)
227
+ expect(decrypted).toEqual(data)
228
228
  }
229
- });
229
+ })
230
230
 
231
231
  it('round-trips arrays, strings, numbers, null', () => {
232
232
  for (const data of [[1, 2, 3], 'hello', 42, null, { nested: { deep: true } }]) {
233
- const sealed = sealPayload(sessionKey, channelId, 0, data);
234
- const { data: decrypted } = unsealPayload(sessionKey, channelId, sealed);
235
- expect(decrypted).toEqual(data);
233
+ const sealed = sealPayload(sessionKey, channelId, 0, data)
234
+ const { data: decrypted } = unsealPayload(sessionKey, channelId, sealed)
235
+ expect(decrypted).toEqual(data)
236
236
  }
237
- });
237
+ })
238
238
 
239
239
  it('round-trips empty object', () => {
240
- const sealed = sealPayload(sessionKey, channelId, 0, {});
241
- const { data } = unsealPayload(sessionKey, channelId, sealed);
242
- expect(data).toEqual({});
243
- });
240
+ const sealed = sealPayload(sessionKey, channelId, 0, {})
241
+ const { data } = unsealPayload(sessionKey, channelId, sealed)
242
+ expect(data).toEqual({})
243
+ })
244
244
 
245
245
  it('round-trips unicode text', () => {
246
- const data = { text: '你好世界 🌍 émojis' };
247
- const sealed = sealPayload(sessionKey, channelId, 0, data);
248
- const { data: decrypted } = unsealPayload(sessionKey, channelId, sealed);
249
- expect(decrypted).toEqual(data);
250
- });
246
+ const data = { text: '你好世界 🌍 émojis' }
247
+ const sealed = sealPayload(sessionKey, channelId, 0, data)
248
+ const { data: decrypted } = unsealPayload(sessionKey, channelId, sealed)
249
+ expect(decrypted).toEqual(data)
250
+ })
251
251
 
252
252
  it('fails to decrypt with wrong session key', () => {
253
- const sealed = sealPayload(sessionKey, channelId, 0, { secret: true });
254
- const wrongKey = new Uint8Array(32);
255
- crypto.getRandomValues(wrongKey);
256
- expect(() => unsealPayload(wrongKey, channelId, sealed)).toThrow();
257
- });
253
+ const sealed = sealPayload(sessionKey, channelId, 0, { secret: true })
254
+ const wrongKey = new Uint8Array(32)
255
+ crypto.getRandomValues(wrongKey)
256
+ expect(() => unsealPayload(wrongKey, channelId, sealed)).toThrow()
257
+ })
258
258
 
259
259
  it('fails to decrypt with wrong channel ID', () => {
260
- const sealed = sealPayload(sessionKey, channelId, 0, { secret: true });
261
- const wrongCh = generateChannelId();
262
- expect(() => unsealPayload(sessionKey, wrongCh, sealed)).toThrow();
263
- });
260
+ const sealed = sealPayload(sessionKey, channelId, 0, { secret: true })
261
+ const wrongCh = generateChannelId()
262
+ expect(() => unsealPayload(sessionKey, wrongCh, sealed)).toThrow()
263
+ })
264
264
 
265
265
  it('fails to decrypt tampered ciphertext', () => {
266
- const sealed = sealPayload(sessionKey, channelId, 0, { secret: true });
267
- const bytes = b64urlDecode(sealed);
266
+ const sealed = sealPayload(sessionKey, channelId, 0, { secret: true })
267
+ const bytes = b64urlDecode(sealed)
268
268
  // Flip a byte in the ciphertext portion
269
- bytes[10]! ^= 0xff;
270
- const tampered = b64urlEncode(bytes);
271
- expect(() => unsealPayload(sessionKey, channelId, tampered)).toThrow();
272
- });
269
+ bytes[10]! ^= 0xff
270
+ const tampered = b64urlEncode(bytes)
271
+ expect(() => unsealPayload(sessionKey, channelId, tampered)).toThrow()
272
+ })
273
273
 
274
274
  it('different sequence numbers produce different ciphertexts', () => {
275
- const data = { same: 'data' };
276
- const s0 = sealPayload(sessionKey, channelId, 0, data);
277
- const s1 = sealPayload(sessionKey, channelId, 1, data);
278
- expect(s0).not.toBe(s1);
279
- });
280
- });
275
+ const data = { same: 'data' }
276
+ const s0 = sealPayload(sessionKey, channelId, 0, data)
277
+ const s1 = sealPayload(sessionKey, channelId, 1, data)
278
+ expect(s0).not.toBe(s1)
279
+ })
280
+ })
281
281
 
282
282
  describe('sealJoin/unsealJoin', () => {
283
283
  it('round-trips capabilities and metadata with a nonce-prefixed envelope', () => {
284
- const joinKey = deriveJoinEncryptionKey(new Uint8Array(32).fill(7), '11'.repeat(32));
285
- const capabilities = { methods: ['wallet_getAccounts'], events: ['accountsChanged'], chains: ['eip155:1'] };
286
- const meta = { name: 'Test Wallet' };
284
+ const joinKey = deriveJoinEncryptionKey(new Uint8Array(32).fill(7), '11'.repeat(32))
285
+ const capabilities = {
286
+ methods: ['wallet_getAccounts'],
287
+ events: ['accountsChanged'],
288
+ chains: ['eip155:1'],
289
+ }
290
+ const meta = { name: 'Test Wallet' }
287
291
 
288
- const sealed = sealJoin(joinKey, '11'.repeat(32), capabilities, meta);
289
- const envelope = b64urlDecode(sealed);
290
- expect(envelope.length).toBeGreaterThan(12 + 16);
292
+ const sealed = sealJoin(joinKey, '11'.repeat(32), capabilities, meta)
293
+ const envelope = b64urlDecode(sealed)
294
+ expect(envelope.length).toBeGreaterThan(12 + 16)
291
295
 
292
- expect(unsealJoin(joinKey, '11'.repeat(32), sealed)).toEqual({ capabilities, meta });
293
- });
296
+ expect(unsealJoin(joinKey, '11'.repeat(32), sealed)).toEqual({ capabilities, meta })
297
+ })
294
298
 
295
299
  it('uses a fresh nonce for each sealed_join encryption', () => {
296
- const joinKey = deriveJoinEncryptionKey(new Uint8Array(32).fill(9), '22'.repeat(32));
297
- const capabilities = { methods: ['wallet_getAccounts'], events: [], chains: ['eip155:1'] };
300
+ const joinKey = deriveJoinEncryptionKey(new Uint8Array(32).fill(9), '22'.repeat(32))
301
+ const capabilities = { methods: ['wallet_getAccounts'], events: [], chains: ['eip155:1'] }
298
302
 
299
- const a = sealJoin(joinKey, '22'.repeat(32), capabilities, {});
300
- const b = sealJoin(joinKey, '22'.repeat(32), capabilities, {});
303
+ const a = sealJoin(joinKey, '22'.repeat(32), capabilities, {})
304
+ const b = sealJoin(joinKey, '22'.repeat(32), capabilities, {})
301
305
 
302
- expect(a).not.toBe(b);
303
- });
304
- });
306
+ expect(a).not.toBe(b)
307
+ })
308
+ })
305
309
 
306
310
  // ---------------------------------------------------------------------------
307
311
  // Channel ID generation
@@ -309,15 +313,15 @@ describe('sealJoin/unsealJoin', () => {
309
313
 
310
314
  describe('generateChannelId', () => {
311
315
  it('returns 64 hex characters (32 bytes)', () => {
312
- const id = generateChannelId();
313
- expect(id).toMatch(/^[0-9a-f]{64}$/);
314
- });
316
+ const id = generateChannelId()
317
+ expect(id).toMatch(/^[0-9a-f]{64}$/)
318
+ })
315
319
 
316
320
  it('generates unique IDs', () => {
317
- const ids = new Set(Array.from({ length: 10 }, () => generateChannelId()));
318
- expect(ids.size).toBe(10);
319
- });
320
- });
321
+ const ids = new Set(Array.from({ length: 10 }, () => generateChannelId()))
322
+ expect(ids.size).toBe(10)
323
+ })
324
+ })
321
325
 
322
326
  // ---------------------------------------------------------------------------
323
327
  // Pairing URI
@@ -332,59 +336,69 @@ describe('buildPairingUri', () => {
332
336
  name: 'My dApp',
333
337
  url: 'https://dapp.example.com',
334
338
  icon: 'https://dapp.example.com/icon.png',
335
- });
336
- expect(uri).toContain('walletpair:?ch=abcd1234');
337
- expect(uri).toContain('&pubkey=AQID');
338
- expect(uri).toContain('&relay=');
339
- expect(uri).toContain('&name=My%20dApp');
340
- });
339
+ })
340
+ expect(uri).toContain('walletpair:?ch=abcd1234')
341
+ expect(uri).toContain('&pubkey=AQID')
342
+ expect(uri).toContain('&relay=')
343
+ expect(uri).toContain('&name=My%20dApp')
344
+ })
341
345
 
342
346
  it('omits relay when not provided', () => {
343
- const uri = buildPairingUri({ channelId: 'abcd', pubkeyB64: 'XY', name: 'Test', url: 'https://test.com', icon: 'https://test.com/icon.png' });
344
- expect(uri).not.toContain('relay');
345
- });
346
- });
347
+ const uri = buildPairingUri({
348
+ channelId: 'abcd',
349
+ pubkeyB64: 'XY',
350
+ name: 'Test',
351
+ url: 'https://test.com',
352
+ icon: 'https://test.com/icon.png',
353
+ })
354
+ expect(uri).not.toContain('relay')
355
+ })
356
+ })
347
357
 
348
358
  // Valid test fixtures for parsePairingUri
349
- const TEST_CH = 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2';
350
- const TEST_PUBKEY = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'; // 32 zero-bytes base64url (43 chars)
359
+ const TEST_CH = 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2'
360
+ const TEST_PUBKEY = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' // 32 zero-bytes base64url (43 chars)
351
361
 
352
362
  describe('parsePairingUri', () => {
353
363
  it('parses a full URI', () => {
354
- const uri = `walletpair:?ch=${TEST_CH}&pubkey=${TEST_PUBKEY}&relay=wss%3A%2F%2Frelay.example.com%2Fv1&name=Test&url=https%3A%2F%2Ftest.com&icon=https%3A%2F%2Ftest.com%2Ficon.png`;
355
- const params = parsePairingUri(uri);
356
- expect(params.ch).toBe(TEST_CH);
357
- expect(params.pubkey).toBe(TEST_PUBKEY);
358
- expect(params.relay).toBe('wss://relay.example.com/v1');
359
- expect(params.name).toBe('Test');
360
- expect(params.url).toBe('https://test.com');
361
- expect(params.icon).toBe('https://test.com/icon.png');
362
- });
364
+ const uri = `walletpair:?ch=${TEST_CH}&pubkey=${TEST_PUBKEY}&relay=wss%3A%2F%2Frelay.example.com%2Fv1&name=Test&url=https%3A%2F%2Ftest.com&icon=https%3A%2F%2Ftest.com%2Ficon.png`
365
+ const params = parsePairingUri(uri)
366
+ expect(params.ch).toBe(TEST_CH)
367
+ expect(params.pubkey).toBe(TEST_PUBKEY)
368
+ expect(params.relay).toBe('wss://relay.example.com/v1')
369
+ expect(params.name).toBe('Test')
370
+ expect(params.url).toBe('https://test.com')
371
+ expect(params.icon).toBe('https://test.com/icon.png')
372
+ })
363
373
 
364
374
  it('parses BLE URI (no relay)', () => {
365
- const uri = `walletpair:?ch=${TEST_CH}&pubkey=${TEST_PUBKEY}&name=BLE%20Wallet&url=https%3A%2F%2Fble.example.com&icon=https%3A%2F%2Fble.example.com%2Ficon.png`;
366
- const params = parsePairingUri(uri);
367
- expect(params.ch).toBe(TEST_CH);
368
- expect(params.pubkey).toBe(TEST_PUBKEY);
369
- expect(params.relay).toBeUndefined();
370
- expect(params.name).toBe('BLE Wallet');
371
- });
375
+ const uri = `walletpair:?ch=${TEST_CH}&pubkey=${TEST_PUBKEY}&name=BLE%20Wallet&url=https%3A%2F%2Fble.example.com&icon=https%3A%2F%2Fble.example.com%2Ficon.png`
376
+ const params = parsePairingUri(uri)
377
+ expect(params.ch).toBe(TEST_CH)
378
+ expect(params.pubkey).toBe(TEST_PUBKEY)
379
+ expect(params.relay).toBeUndefined()
380
+ expect(params.name).toBe('BLE Wallet')
381
+ })
372
382
 
373
383
  it('throws on missing ch', () => {
374
- expect(() => parsePairingUri(`walletpair:?pubkey=${TEST_PUBKEY}`)).toThrow('missing ch or pubkey');
375
- });
384
+ expect(() => parsePairingUri(`walletpair:?pubkey=${TEST_PUBKEY}`)).toThrow(
385
+ 'missing ch or pubkey',
386
+ )
387
+ })
376
388
 
377
389
  it('throws on missing pubkey', () => {
378
- expect(() => parsePairingUri(`walletpair:?ch=${TEST_CH}`)).toThrow('missing ch or pubkey');
379
- });
390
+ expect(() => parsePairingUri(`walletpair:?ch=${TEST_CH}`)).toThrow('missing ch or pubkey')
391
+ })
380
392
 
381
393
  it('throws on invalid ch length', () => {
382
- expect(() => parsePairingUri(`walletpair:?ch=abc123&pubkey=${TEST_PUBKEY}`)).toThrow('64 lowercase hex');
383
- });
394
+ expect(() => parsePairingUri(`walletpair:?ch=abc123&pubkey=${TEST_PUBKEY}`)).toThrow(
395
+ '64 lowercase hex',
396
+ )
397
+ })
384
398
 
385
399
  it('throws on invalid pubkey length', () => {
386
- expect(() => parsePairingUri(`walletpair:?ch=${TEST_CH}&pubkey=AQID`)).toThrow('32 bytes');
387
- });
400
+ expect(() => parsePairingUri(`walletpair:?ch=${TEST_CH}&pubkey=AQID`)).toThrow('32 bytes')
401
+ })
388
402
 
389
403
  it('round-trips with buildPairingUri', () => {
390
404
  const original = {
@@ -394,15 +408,15 @@ describe('parsePairingUri', () => {
394
408
  name: 'Test dApp',
395
409
  url: 'https://dapp.example.com',
396
410
  icon: 'https://dapp.example.com/icon.png',
397
- };
398
- const uri = buildPairingUri(original);
399
- const parsed = parsePairingUri(uri);
400
- expect(parsed.ch).toBe(original.channelId);
401
- expect(parsed.pubkey).toBe(original.pubkeyB64);
402
- expect(parsed.relay).toBe(original.relayUrl);
403
- expect(parsed.name).toBe(original.name);
404
- });
405
- });
411
+ }
412
+ const uri = buildPairingUri(original)
413
+ const parsed = parsePairingUri(uri)
414
+ expect(parsed.ch).toBe(original.channelId)
415
+ expect(parsed.pubkey).toBe(original.pubkeyB64)
416
+ expect(parsed.relay).toBe(original.relayUrl)
417
+ expect(parsed.name).toBe(original.name)
418
+ })
419
+ })
406
420
 
407
421
  // ---------------------------------------------------------------------------
408
422
  // hex helpers
@@ -421,17 +435,17 @@ describe('buildPairingUri / parsePairingUri with url and icon', () => {
421
435
  name: 'My dApp',
422
436
  url: 'https://mydapp.com',
423
437
  icon: 'https://mydapp.com/logo.png',
424
- };
425
- const uri = buildPairingUri(original);
426
- const parsed = parsePairingUri(uri);
427
-
428
- expect(parsed.ch).toBe(original.channelId);
429
- expect(parsed.pubkey).toBe(original.pubkeyB64);
430
- expect(parsed.relay).toBe(original.relayUrl);
431
- expect(parsed.name).toBe(original.name);
432
- expect(parsed.url).toBe(original.url);
433
- expect(parsed.icon).toBe(original.icon);
434
- });
438
+ }
439
+ const uri = buildPairingUri(original)
440
+ const parsed = parsePairingUri(uri)
441
+
442
+ expect(parsed.ch).toBe(original.channelId)
443
+ expect(parsed.pubkey).toBe(original.pubkeyB64)
444
+ expect(parsed.relay).toBe(original.relayUrl)
445
+ expect(parsed.name).toBe(original.name)
446
+ expect(parsed.url).toBe(original.url)
447
+ expect(parsed.icon).toBe(original.icon)
448
+ })
435
449
 
436
450
  it('round-trips url and icon containing special characters', () => {
437
451
  const original = {
@@ -440,25 +454,24 @@ describe('buildPairingUri / parsePairingUri with url and icon', () => {
440
454
  name: 'Test',
441
455
  url: 'https://example.com/path?q=1&b=2',
442
456
  icon: 'https://cdn.example.com/icons/logo.png?size=64&format=webp',
443
- };
444
- const uri = buildPairingUri(original);
445
- const parsed = parsePairingUri(uri);
457
+ }
458
+ const uri = buildPairingUri(original)
459
+ const parsed = parsePairingUri(uri)
446
460
 
447
- expect(parsed.url).toBe(original.url);
448
- expect(parsed.icon).toBe(original.icon);
449
- });
461
+ expect(parsed.url).toBe(original.url)
462
+ expect(parsed.icon).toBe(original.icon)
463
+ })
450
464
 
451
465
  it('parsePairingUri extracts url and icon from a raw URI string', () => {
452
- const uri =
453
- `walletpair:?ch=${TEST_CH}&pubkey=${TEST_PUBKEY}&relay=wss%3A%2F%2Frelay.example.com%2Fv1&name=Test&url=https%3A%2F%2Fexample.com&icon=https%3A%2F%2Fexample.com%2Ficon.png`;
454
- const parsed = parsePairingUri(uri);
466
+ const uri = `walletpair:?ch=${TEST_CH}&pubkey=${TEST_PUBKEY}&relay=wss%3A%2F%2Frelay.example.com%2Fv1&name=Test&url=https%3A%2F%2Fexample.com&icon=https%3A%2F%2Fexample.com%2Ficon.png`
467
+ const parsed = parsePairingUri(uri)
455
468
 
456
- expect(parsed.ch).toBe(TEST_CH);
457
- expect(parsed.pubkey).toBe(TEST_PUBKEY);
458
- expect(parsed.url).toBe('https://example.com');
459
- expect(parsed.icon).toBe('https://example.com/icon.png');
460
- });
461
- });
469
+ expect(parsed.ch).toBe(TEST_CH)
470
+ expect(parsed.pubkey).toBe(TEST_PUBKEY)
471
+ expect(parsed.url).toBe('https://example.com')
472
+ expect(parsed.icon).toBe('https://example.com/icon.png')
473
+ })
474
+ })
462
475
 
463
476
  // ---------------------------------------------------------------------------
464
477
  // computeSessionFingerprint — additional edge cases
@@ -468,101 +481,101 @@ describe('computeSessionFingerprint edge cases', () => {
468
481
  it('zero-padding: output is always exactly 4 characters regardless of numeric value', () => {
469
482
  // Run many iterations; every result must be exactly 4 digits
470
483
  for (let i = 0; i < 50; i++) {
471
- const ch = generateChannelId();
472
- const kp = generateX25519KeyPair();
473
- const code = computeSessionFingerprint(ch, kp.publicKeyB64);
474
- expect(code).toMatch(/^\d{4}$/);
475
- expect(code).toHaveLength(4);
484
+ const ch = generateChannelId()
485
+ const kp = generateX25519KeyPair()
486
+ const code = computeSessionFingerprint(ch, kp.publicKeyB64)
487
+ expect(code).toMatch(/^\d{4}$/)
488
+ expect(code).toHaveLength(4)
476
489
  }
477
- });
490
+ })
478
491
 
479
492
  it('different channel IDs with same pubkey produce different fingerprints', () => {
480
- const kp = generateX25519KeyPair();
481
- const results = new Set<string>();
493
+ const kp = generateX25519KeyPair()
494
+ const results = new Set<string>()
482
495
  for (let i = 0; i < 15; i++) {
483
- results.add(computeSessionFingerprint(generateChannelId(), kp.publicKeyB64));
496
+ results.add(computeSessionFingerprint(generateChannelId(), kp.publicKeyB64))
484
497
  }
485
498
  // With 15 random channel IDs, collisions in a 10000-space are possible but very unlikely for all
486
- expect(results.size).toBeGreaterThan(1);
487
- });
499
+ expect(results.size).toBeGreaterThan(1)
500
+ })
488
501
 
489
502
  it('different dApp pubkeys with same channel ID produce different fingerprints', () => {
490
- const ch = generateChannelId();
491
- const results = new Set<string>();
503
+ const ch = generateChannelId()
504
+ const results = new Set<string>()
492
505
  for (let i = 0; i < 15; i++) {
493
- const kp = generateX25519KeyPair();
494
- results.add(computeSessionFingerprint(ch, kp.publicKeyB64));
506
+ const kp = generateX25519KeyPair()
507
+ results.add(computeSessionFingerprint(ch, kp.publicKeyB64))
495
508
  }
496
- expect(results.size).toBeGreaterThan(1);
497
- });
509
+ expect(results.size).toBeGreaterThan(1)
510
+ })
498
511
 
499
512
  it('same inputs always produce the same output (deterministic)', () => {
500
- const ch = generateChannelId();
501
- const kp = generateX25519KeyPair();
502
- const first = computeSessionFingerprint(ch, kp.publicKeyB64);
513
+ const ch = generateChannelId()
514
+ const kp = generateX25519KeyPair()
515
+ const first = computeSessionFingerprint(ch, kp.publicKeyB64)
503
516
  for (let i = 0; i < 10; i++) {
504
- expect(computeSessionFingerprint(ch, kp.publicKeyB64)).toBe(first);
517
+ expect(computeSessionFingerprint(ch, kp.publicKeyB64)).toBe(first)
505
518
  }
506
- });
507
- });
519
+ })
520
+ })
508
521
 
509
522
  // ---------------------------------------------------------------------------
510
523
  // Snapshot HMAC integrity
511
524
  // ---------------------------------------------------------------------------
512
525
 
513
526
  describe('signSnapshot / verifySnapshot', () => {
514
- const key = new Uint8Array(32).fill(42);
515
- const json = JSON.stringify({ channelId: 'abc', sendSeq: 5, recvSeq: 3 });
527
+ const key = new Uint8Array(32).fill(42)
528
+ const json = JSON.stringify({ channelId: 'abc', sendSeq: 5, recvSeq: 3 })
516
529
 
517
530
  it('sign produces <64hex>.<json> format', () => {
518
- const signed = signSnapshot(key, json);
519
- expect(signed[64]).toBe('.');
520
- expect(signed.slice(65)).toBe(json);
521
- expect(signed.slice(0, 64)).toMatch(/^[0-9a-f]{64}$/);
522
- });
531
+ const signed = signSnapshot(key, json)
532
+ expect(signed[64]).toBe('.')
533
+ expect(signed.slice(65)).toBe(json)
534
+ expect(signed.slice(0, 64)).toMatch(/^[0-9a-f]{64}$/)
535
+ })
523
536
 
524
537
  it('verify round-trips successfully', () => {
525
- const signed = signSnapshot(key, json);
526
- const result = verifySnapshot(key, signed);
527
- expect(result).toBe(json);
528
- });
538
+ const signed = signSnapshot(key, json)
539
+ const result = verifySnapshot(key, signed)
540
+ expect(result).toBe(json)
541
+ })
529
542
 
530
543
  it('verify rejects tampered JSON', () => {
531
- const signed = signSnapshot(key, json);
532
- const tampered = signed.slice(0, 65) + '{"channelId":"EVIL","sendSeq":0,"recvSeq":-1}';
533
- expect(verifySnapshot(key, tampered)).toBeNull();
534
- });
544
+ const signed = signSnapshot(key, json)
545
+ const tampered = `${signed.slice(0, 65)}{"channelId":"EVIL","sendSeq":0,"recvSeq":-1}`
546
+ expect(verifySnapshot(key, tampered)).toBeNull()
547
+ })
535
548
 
536
549
  it('verify rejects tampered HMAC', () => {
537
- const signed = signSnapshot(key, json);
538
- const tampered = '00'.repeat(32) + signed.slice(64);
539
- expect(verifySnapshot(key, tampered)).toBeNull();
540
- });
550
+ const signed = signSnapshot(key, json)
551
+ const tampered = '00'.repeat(32) + signed.slice(64)
552
+ expect(verifySnapshot(key, tampered)).toBeNull()
553
+ })
541
554
 
542
555
  it('verify rejects wrong key', () => {
543
- const signed = signSnapshot(key, json);
544
- const wrongKey = new Uint8Array(32).fill(99);
545
- expect(verifySnapshot(wrongKey, signed)).toBeNull();
546
- });
556
+ const signed = signSnapshot(key, json)
557
+ const wrongKey = new Uint8Array(32).fill(99)
558
+ expect(verifySnapshot(wrongKey, signed)).toBeNull()
559
+ })
547
560
 
548
561
  it('verify rejects malformed input (no dot)', () => {
549
- expect(verifySnapshot(key, json)).toBeNull();
550
- expect(verifySnapshot(key, '')).toBeNull();
551
- expect(verifySnapshot(key, 'short')).toBeNull();
552
- });
562
+ expect(verifySnapshot(key, json)).toBeNull()
563
+ expect(verifySnapshot(key, '')).toBeNull()
564
+ expect(verifySnapshot(key, 'short')).toBeNull()
565
+ })
553
566
 
554
567
  it('different keys produce different MACs', () => {
555
- const s1 = signSnapshot(key, json);
556
- const s2 = signSnapshot(new Uint8Array(32).fill(99), json);
557
- expect(s1.slice(0, 64)).not.toBe(s2.slice(0, 64));
558
- });
568
+ const s1 = signSnapshot(key, json)
569
+ const s2 = signSnapshot(new Uint8Array(32).fill(99), json)
570
+ expect(s1.slice(0, 64)).not.toBe(s2.slice(0, 64))
571
+ })
559
572
 
560
573
  it('deterministic — same key+json always same MAC', () => {
561
- const s1 = signSnapshot(key, json);
562
- const s2 = signSnapshot(key, json);
563
- expect(s1).toBe(s2);
564
- });
565
- });
574
+ const s1 = signSnapshot(key, json)
575
+ const s2 = signSnapshot(key, json)
576
+ expect(s1).toBe(s2)
577
+ })
578
+ })
566
579
 
567
580
  // ---------------------------------------------------------------------------
568
581
  // hex helpers
@@ -570,14 +583,14 @@ describe('signSnapshot / verifySnapshot', () => {
570
583
 
571
584
  describe('hex helpers', () => {
572
585
  it('bytesToHex / hexToBytes round-trip', () => {
573
- const bytes = new Uint8Array([0, 1, 15, 16, 255]);
574
- expect(hexToBytes(bytesToHex(bytes))).toEqual(bytes);
575
- });
586
+ const bytes = new Uint8Array([0, 1, 15, 16, 255])
587
+ expect(hexToBytes(bytesToHex(bytes))).toEqual(bytes)
588
+ })
576
589
 
577
590
  it('bytesToHex produces lowercase', () => {
578
- expect(bytesToHex(new Uint8Array([0xff, 0x0a]))).toBe('ff0a');
579
- });
580
- });
591
+ expect(bytesToHex(new Uint8Array([0xff, 0x0a]))).toBe('ff0a')
592
+ })
593
+ })
581
594
 
582
595
  // ---------------------------------------------------------------------------
583
596
  // parsePairingUri — required param validation (protocol compliance)
@@ -585,20 +598,20 @@ describe('hex helpers', () => {
585
598
 
586
599
  describe('parsePairingUri required params', () => {
587
600
  it('throws on missing required name param', () => {
588
- const uri = `walletpair:?ch=${TEST_CH}&pubkey=${TEST_PUBKEY}&url=https%3A%2F%2Fexample.com&icon=https%3A%2F%2Fexample.com%2Ficon.png`;
589
- expect(() => parsePairingUri(uri)).toThrow('missing required param "name"');
590
- });
601
+ const uri = `walletpair:?ch=${TEST_CH}&pubkey=${TEST_PUBKEY}&url=https%3A%2F%2Fexample.com&icon=https%3A%2F%2Fexample.com%2Ficon.png`
602
+ expect(() => parsePairingUri(uri)).toThrow('missing required param "name"')
603
+ })
591
604
 
592
605
  it('throws on missing required url param', () => {
593
- const uri = `walletpair:?ch=${TEST_CH}&pubkey=${TEST_PUBKEY}&name=Test&icon=https%3A%2F%2Fexample.com%2Ficon.png`;
594
- expect(() => parsePairingUri(uri)).toThrow('missing required param "url"');
595
- });
606
+ const uri = `walletpair:?ch=${TEST_CH}&pubkey=${TEST_PUBKEY}&name=Test&icon=https%3A%2F%2Fexample.com%2Ficon.png`
607
+ expect(() => parsePairingUri(uri)).toThrow('missing required param "url"')
608
+ })
596
609
 
597
610
  it('throws on missing required icon param', () => {
598
- const uri = `walletpair:?ch=${TEST_CH}&pubkey=${TEST_PUBKEY}&name=Test&url=https%3A%2F%2Fexample.com`;
599
- expect(() => parsePairingUri(uri)).toThrow('missing required param "icon"');
600
- });
601
- });
611
+ const uri = `walletpair:?ch=${TEST_CH}&pubkey=${TEST_PUBKEY}&name=Test&url=https%3A%2F%2Fexample.com`
612
+ expect(() => parsePairingUri(uri)).toThrow('missing required param "icon"')
613
+ })
614
+ })
602
615
 
603
616
  // ---------------------------------------------------------------------------
604
617
  // constantTimeEqual
@@ -606,21 +619,21 @@ describe('parsePairingUri required params', () => {
606
619
 
607
620
  describe('constantTimeEqual', () => {
608
621
  it('returns true for identical strings', () => {
609
- expect(constantTimeEqual('abc', 'abc')).toBe(true);
610
- });
622
+ expect(constantTimeEqual('abc', 'abc')).toBe(true)
623
+ })
611
624
 
612
625
  it('returns false for different strings', () => {
613
- expect(constantTimeEqual('abc', 'abd')).toBe(false);
614
- });
626
+ expect(constantTimeEqual('abc', 'abd')).toBe(false)
627
+ })
615
628
 
616
629
  it('returns false for different lengths', () => {
617
- expect(constantTimeEqual('abc', 'abcd')).toBe(false);
618
- });
630
+ expect(constantTimeEqual('abc', 'abcd')).toBe(false)
631
+ })
619
632
 
620
633
  it('returns true for empty strings', () => {
621
- expect(constantTimeEqual('', '')).toBe(true);
622
- });
623
- });
634
+ expect(constantTimeEqual('', '')).toBe(true)
635
+ })
636
+ })
624
637
 
625
638
  // ---------------------------------------------------------------------------
626
639
  // canonicalJson — spec test vector
@@ -628,8 +641,13 @@ describe('constantTimeEqual', () => {
628
641
 
629
642
  describe('canonicalJson', () => {
630
643
  it('matches spec test vector', () => {
631
- const input = {"methods":["wallet_signTransaction","wallet_signMessage"],"events":["accountsChanged","chainChanged"],"chains":["eip155:1","eip155:137"]};
632
- const expected = '{"chains":["eip155:1","eip155:137"],"events":["accountsChanged","chainChanged"],"methods":["wallet_signTransaction","wallet_signMessage"]}';
633
- expect(canonicalJson(input)).toBe(expected);
634
- });
635
- });
644
+ const input = {
645
+ methods: ['wallet_signTransaction', 'wallet_signMessage'],
646
+ events: ['accountsChanged', 'chainChanged'],
647
+ chains: ['eip155:1', 'eip155:137'],
648
+ }
649
+ const expected =
650
+ '{"chains":["eip155:1","eip155:137"],"events":["accountsChanged","chainChanged"],"methods":["wallet_signTransaction","wallet_signMessage"]}'
651
+ expect(canonicalJson(input)).toBe(expected)
652
+ })
653
+ })