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
@@ -5,15 +5,8 @@
5
5
  * classes, using pure functions that any implementation can replicate.
6
6
  */
7
7
 
8
- import { describe, expect, it } from 'vitest';
9
- import {
10
- b64urlDecode,
11
- bytesToHex,
12
- hexToBytes,
13
- sealPayload,
14
- unsealPayload,
15
- generateChannelId,
16
- } from '../../crypto.js';
8
+ import { describe, expect, it } from 'vitest'
9
+ import { b64urlDecode, bytesToHex, sealPayload, unsealPayload } from '../../crypto.js'
17
10
 
18
11
  // ---------------------------------------------------------------------------
19
12
  // Sequence number validator (pure model per Section 6.6.1)
@@ -26,21 +19,21 @@ import {
26
19
  */
27
20
  class SequenceValidator {
28
21
  /** Highest accepted sequence number. -1 means none accepted yet. */
29
- private lastAccepted = -1;
22
+ private lastAccepted = -1
30
23
 
31
24
  /**
32
25
  * Attempt to accept a sequence number.
33
26
  * Returns true if accepted, false if rejected (replay/non-increasing).
34
27
  */
35
28
  accept(seq: number): boolean {
36
- if (seq <= this.lastAccepted) return false;
37
- this.lastAccepted = seq;
38
- return true;
29
+ if (seq <= this.lastAccepted) return false
30
+ this.lastAccepted = seq
31
+ return true
39
32
  }
40
33
 
41
34
  /** Current high watermark. */
42
35
  get highWatermark(): number {
43
- return this.lastAccepted;
36
+ return this.lastAccepted
44
37
  }
45
38
  }
46
39
 
@@ -50,22 +43,22 @@ class SequenceValidator {
50
43
  * Limit at 2^31.
51
44
  */
52
45
  class SendSequence {
53
- private seq = 0;
46
+ private seq = 0
54
47
 
55
48
  /** Get the next sequence number for sending, or null if limit reached. */
56
49
  next(): number | null {
57
- if (this.seq >= 2 ** 31) return null;
58
- return this.seq++;
50
+ if (this.seq >= 2 ** 31) return null
51
+ return this.seq++
59
52
  }
60
53
 
61
54
  /** Current value (next seq that will be used). */
62
55
  get current(): number {
63
- return this.seq;
56
+ return this.seq
64
57
  }
65
58
 
66
59
  /** Set the counter to a specific value (for testing). */
67
60
  setTo(n: number): void {
68
- this.seq = n;
61
+ this.seq = n
69
62
  }
70
63
  }
71
64
 
@@ -75,34 +68,34 @@ class SendSequence {
75
68
 
76
69
  describe('Section 6.6.1 — Sequence number starts at 0', () => {
77
70
  it('first send sequence is 0', () => {
78
- const send = new SendSequence();
79
- expect(send.next()).toBe(0);
80
- });
71
+ const send = new SendSequence()
72
+ expect(send.next()).toBe(0)
73
+ })
81
74
 
82
75
  it('first accepted receive sequence can be 0', () => {
83
- const recv = new SequenceValidator();
84
- expect(recv.accept(0)).toBe(true);
85
- expect(recv.highWatermark).toBe(0);
86
- });
87
- });
76
+ const recv = new SequenceValidator()
77
+ expect(recv.accept(0)).toBe(true)
78
+ expect(recv.highWatermark).toBe(0)
79
+ })
80
+ })
88
81
 
89
82
  describe('Section 6.6.1 — Increments by 1', () => {
90
83
  it('send counter increments by 1 per message', () => {
91
- const send = new SendSequence();
92
- expect(send.next()).toBe(0);
93
- expect(send.next()).toBe(1);
94
- expect(send.next()).toBe(2);
95
- expect(send.next()).toBe(3);
96
- });
84
+ const send = new SendSequence()
85
+ expect(send.next()).toBe(0)
86
+ expect(send.next()).toBe(1)
87
+ expect(send.next()).toBe(2)
88
+ expect(send.next()).toBe(3)
89
+ })
97
90
 
98
91
  it('receive validator accepts strictly increasing sequence', () => {
99
- const recv = new SequenceValidator();
100
- expect(recv.accept(0)).toBe(true);
101
- expect(recv.accept(1)).toBe(true);
102
- expect(recv.accept(2)).toBe(true);
103
- expect(recv.accept(3)).toBe(true);
104
- });
105
- });
92
+ const recv = new SequenceValidator()
93
+ expect(recv.accept(0)).toBe(true)
94
+ expect(recv.accept(1)).toBe(true)
95
+ expect(recv.accept(2)).toBe(true)
96
+ expect(recv.accept(3)).toBe(true)
97
+ })
98
+ })
106
99
 
107
100
  // ---------------------------------------------------------------------------
108
101
  // Replay rejection (non-increasing)
@@ -110,34 +103,34 @@ describe('Section 6.6.1 — Increments by 1', () => {
110
103
 
111
104
  describe('Section 6.6.1 — Reject non-increasing (replay)', () => {
112
105
  it('rejects the same sequence number twice', () => {
113
- const recv = new SequenceValidator();
114
- expect(recv.accept(0)).toBe(true);
115
- expect(recv.accept(0)).toBe(false); // replay
116
- });
106
+ const recv = new SequenceValidator()
107
+ expect(recv.accept(0)).toBe(true)
108
+ expect(recv.accept(0)).toBe(false) // replay
109
+ })
117
110
 
118
111
  it('rejects a lower sequence number after a higher one', () => {
119
- const recv = new SequenceValidator();
120
- expect(recv.accept(5)).toBe(true);
121
- expect(recv.accept(3)).toBe(false); // lower than watermark
122
- expect(recv.accept(4)).toBe(false); // still lower
123
- expect(recv.accept(5)).toBe(false); // equal to watermark
124
- });
112
+ const recv = new SequenceValidator()
113
+ expect(recv.accept(5)).toBe(true)
114
+ expect(recv.accept(3)).toBe(false) // lower than watermark
115
+ expect(recv.accept(4)).toBe(false) // still lower
116
+ expect(recv.accept(5)).toBe(false) // equal to watermark
117
+ })
125
118
 
126
119
  it('rejects seq=0 replay after initial acceptance', () => {
127
- const recv = new SequenceValidator();
128
- recv.accept(0);
129
- recv.accept(1);
130
- expect(recv.accept(0)).toBe(false);
131
- });
120
+ const recv = new SequenceValidator()
121
+ recv.accept(0)
122
+ recv.accept(1)
123
+ expect(recv.accept(0)).toBe(false)
124
+ })
132
125
 
133
126
  it('multiple replays are all rejected', () => {
134
- const recv = new SequenceValidator();
135
- recv.accept(0);
127
+ const recv = new SequenceValidator()
128
+ recv.accept(0)
136
129
  for (let i = 0; i < 10; i++) {
137
- expect(recv.accept(0)).toBe(false);
130
+ expect(recv.accept(0)).toBe(false)
138
131
  }
139
- });
140
- });
132
+ })
133
+ })
141
134
 
142
135
  // ---------------------------------------------------------------------------
143
136
  // Gaps are valid (after reconnect)
@@ -145,79 +138,79 @@ describe('Section 6.6.1 — Reject non-increasing (replay)', () => {
145
138
 
146
139
  describe('Section 6.6.1 — Gaps valid (expected after reconnect)', () => {
147
140
  it('accepts seq=0 then seq=5 (gap of 4)', () => {
148
- const recv = new SequenceValidator();
149
- expect(recv.accept(0)).toBe(true);
150
- expect(recv.accept(5)).toBe(true);
151
- expect(recv.highWatermark).toBe(5);
152
- });
141
+ const recv = new SequenceValidator()
142
+ expect(recv.accept(0)).toBe(true)
143
+ expect(recv.accept(5)).toBe(true)
144
+ expect(recv.highWatermark).toBe(5)
145
+ })
153
146
 
154
147
  it('accepts large gaps', () => {
155
- const recv = new SequenceValidator();
156
- expect(recv.accept(0)).toBe(true);
157
- expect(recv.accept(1000)).toBe(true);
158
- expect(recv.accept(500000)).toBe(true);
159
- });
148
+ const recv = new SequenceValidator()
149
+ expect(recv.accept(0)).toBe(true)
150
+ expect(recv.accept(1000)).toBe(true)
151
+ expect(recv.accept(500000)).toBe(true)
152
+ })
160
153
 
161
154
  it('rejects values within a gap after the gap is established', () => {
162
- const recv = new SequenceValidator();
163
- recv.accept(0);
164
- recv.accept(10);
155
+ const recv = new SequenceValidator()
156
+ recv.accept(0)
157
+ recv.accept(10)
165
158
  // All values 0-10 are now below the watermark
166
159
  for (let i = 0; i <= 10; i++) {
167
- expect(recv.accept(i)).toBe(false);
160
+ expect(recv.accept(i)).toBe(false)
168
161
  }
169
162
  // 11 and above are accepted
170
- expect(recv.accept(11)).toBe(true);
171
- });
163
+ expect(recv.accept(11)).toBe(true)
164
+ })
172
165
 
173
166
  it('gap from initial state (first message is not 0)', () => {
174
- const recv = new SequenceValidator();
167
+ const recv = new SequenceValidator()
175
168
  // After reconnect, the first message might not be seq=0
176
- expect(recv.accept(42)).toBe(true);
177
- expect(recv.highWatermark).toBe(42);
178
- });
179
- });
169
+ expect(recv.accept(42)).toBe(true)
170
+ expect(recv.highWatermark).toBe(42)
171
+ })
172
+ })
180
173
 
181
174
  // ---------------------------------------------------------------------------
182
175
  // Limit at 2^31
183
176
  // ---------------------------------------------------------------------------
184
177
 
185
178
  describe('Section 6.6.1 — Limit at 2^31', () => {
186
- const LIMIT = 2 ** 31; // 2,147,483,648
179
+ const LIMIT = 2 ** 31 // 2,147,483,648
187
180
 
188
181
  it('send counter allows 2^31 - 1 as the last valid sequence', () => {
189
- const send = new SendSequence();
190
- send.setTo(LIMIT - 1);
191
- expect(send.next()).toBe(LIMIT - 1);
192
- });
182
+ const send = new SendSequence()
183
+ send.setTo(LIMIT - 1)
184
+ expect(send.next()).toBe(LIMIT - 1)
185
+ })
193
186
 
194
187
  it('send counter returns null (overflow) at 2^31', () => {
195
- const send = new SendSequence();
196
- send.setTo(LIMIT);
197
- expect(send.next()).toBeNull();
198
- });
188
+ const send = new SendSequence()
189
+ send.setTo(LIMIT)
190
+ expect(send.next()).toBeNull()
191
+ })
199
192
 
200
193
  it('receive validator accepts up to 2^31 - 1', () => {
201
- const recv = new SequenceValidator();
202
- expect(recv.accept(LIMIT - 1)).toBe(true);
203
- });
194
+ const recv = new SequenceValidator()
195
+ expect(recv.accept(LIMIT - 1)).toBe(true)
196
+ })
204
197
 
205
198
  it('send counter reaches limit after 2^31 messages', () => {
206
199
  // Verify the math: starting at 0, after LIMIT sends, next() returns null
207
- const send = new SendSequence();
208
- send.setTo(LIMIT - 1);
209
- const lastValid = send.next();
210
- expect(lastValid).toBe(LIMIT - 1);
211
- expect(send.next()).toBeNull(); // overflow
212
- });
200
+ const send = new SendSequence()
201
+ send.setTo(LIMIT - 1)
202
+ const lastValid = send.next()
203
+ expect(lastValid).toBe(LIMIT - 1)
204
+ expect(send.next()).toBeNull() // overflow
205
+ })
213
206
 
214
207
  it('the limit is 2^31 not 2^32-1 (signed integer safety)', () => {
215
208
  // Section 6.6.1: "The limit is 2^31 rather than 2^32 - 1 to avoid
216
209
  // signed integer overflow in languages where 32-bit integers are signed."
217
- expect(LIMIT).toBe(2147483648);
218
- expect(LIMIT).toBeLessThan(2 ** 32 - 1);
219
- });
220
- });
210
+ expect(LIMIT).toBe(2147483648)
211
+ expect(LIMIT).toBeLessThan(2 ** 32 - 1)
212
+ })
213
+ })
221
214
 
222
215
  // ---------------------------------------------------------------------------
223
216
  // Sequence persistence across reconnects
@@ -225,76 +218,76 @@ describe('Section 6.6.1 — Limit at 2^31', () => {
225
218
 
226
219
  describe('Section 6.6.1 — Counters persist across reconnects', () => {
227
220
  it('simulated reconnect preserves send counter', () => {
228
- const send = new SendSequence();
229
- send.next(); // 0
230
- send.next(); // 1
231
- send.next(); // 2
232
- const savedSeq = send.current; // 3
221
+ const send = new SendSequence()
222
+ send.next() // 0
223
+ send.next() // 1
224
+ send.next() // 2
225
+ const savedSeq = send.current // 3
233
226
 
234
227
  // Simulate reconnect: new SendSequence initialized from persisted state
235
- const restored = new SendSequence();
236
- restored.setTo(savedSeq);
237
- expect(restored.next()).toBe(3); // continues from where we left off
238
- expect(restored.next()).toBe(4);
239
- });
228
+ const restored = new SendSequence()
229
+ restored.setTo(savedSeq)
230
+ expect(restored.next()).toBe(3) // continues from where we left off
231
+ expect(restored.next()).toBe(4)
232
+ })
240
233
 
241
234
  it('simulated reconnect preserves receive watermark', () => {
242
- const recv = new SequenceValidator();
243
- recv.accept(0);
244
- recv.accept(1);
245
- recv.accept(2);
246
- const savedWatermark = recv.highWatermark; // 2
235
+ const recv = new SequenceValidator()
236
+ recv.accept(0)
237
+ recv.accept(1)
238
+ recv.accept(2)
239
+ const savedWatermark = recv.highWatermark // 2
247
240
 
248
241
  // Simulate reconnect: new SequenceValidator initialized from persisted state
249
- const restored = new SequenceValidator();
242
+ const restored = new SequenceValidator()
250
243
  // Set watermark by accepting the saved value
251
- restored.accept(savedWatermark);
244
+ restored.accept(savedWatermark)
252
245
  // Old sequences are rejected
253
- expect(restored.accept(0)).toBe(false);
254
- expect(restored.accept(1)).toBe(false);
255
- expect(restored.accept(2)).toBe(false);
246
+ expect(restored.accept(0)).toBe(false)
247
+ expect(restored.accept(1)).toBe(false)
248
+ expect(restored.accept(2)).toBe(false)
256
249
  // New sequences are accepted
257
- expect(restored.accept(3)).toBe(true);
258
- });
259
- });
250
+ expect(restored.accept(3)).toBe(true)
251
+ })
252
+ })
260
253
 
261
254
  // ---------------------------------------------------------------------------
262
255
  // seq_bytes encoding in sealed messages
263
256
  // ---------------------------------------------------------------------------
264
257
 
265
258
  describe('Section 6.6 — seq_bytes is 4-byte big-endian in sealed messages', () => {
266
- const key = new Uint8Array(32).fill(0xaa);
267
- const ch = 'bb'.repeat(32);
259
+ const key = new Uint8Array(32).fill(0xaa)
260
+ const ch = 'bb'.repeat(32)
268
261
 
269
262
  it('seq=0 encodes as 00000000', () => {
270
- const sealed = sealPayload(key, ch, 0, { test: true });
271
- const bytes = b64urlDecode(sealed);
272
- expect(bytesToHex(bytes.slice(0, 4))).toBe('00000000');
273
- });
263
+ const sealed = sealPayload(key, ch, 0, { test: true })
264
+ const bytes = b64urlDecode(sealed)
265
+ expect(bytesToHex(bytes.slice(0, 4))).toBe('00000000')
266
+ })
274
267
 
275
268
  it('seq=1 encodes as 00000001', () => {
276
- const sealed = sealPayload(key, ch, 1, { test: true });
277
- const bytes = b64urlDecode(sealed);
278
- expect(bytesToHex(bytes.slice(0, 4))).toBe('00000001');
279
- });
269
+ const sealed = sealPayload(key, ch, 1, { test: true })
270
+ const bytes = b64urlDecode(sealed)
271
+ expect(bytesToHex(bytes.slice(0, 4))).toBe('00000001')
272
+ })
280
273
 
281
274
  it('seq=256 encodes as 00000100', () => {
282
- const sealed = sealPayload(key, ch, 256, { test: true });
283
- const bytes = b64urlDecode(sealed);
284
- expect(bytesToHex(bytes.slice(0, 4))).toBe('00000100');
285
- });
275
+ const sealed = sealPayload(key, ch, 256, { test: true })
276
+ const bytes = b64urlDecode(sealed)
277
+ expect(bytesToHex(bytes.slice(0, 4))).toBe('00000100')
278
+ })
286
279
 
287
280
  it('seq=2^31-1 encodes as 7fffffff', () => {
288
- const sealed = sealPayload(key, ch, 2 ** 31 - 1, { test: true });
289
- const bytes = b64urlDecode(sealed);
290
- expect(bytesToHex(bytes.slice(0, 4))).toBe('7fffffff');
291
- });
281
+ const sealed = sealPayload(key, ch, 2 ** 31 - 1, { test: true })
282
+ const bytes = b64urlDecode(sealed)
283
+ expect(bytesToHex(bytes.slice(0, 4))).toBe('7fffffff')
284
+ })
292
285
 
293
286
  it('round-trip: seq is correctly extracted from sealed payload', () => {
294
287
  for (const seq of [0, 1, 42, 1000, 65535, 2 ** 31 - 1]) {
295
- const sealed = sealPayload(key, ch, seq, { n: seq });
296
- const result = unsealPayload(key, ch, sealed);
297
- expect(result.seq).toBe(seq);
288
+ const sealed = sealPayload(key, ch, seq, { n: seq })
289
+ const result = unsealPayload(key, ch, sealed)
290
+ expect(result.seq).toBe(seq)
298
291
  }
299
- });
300
- });
292
+ })
293
+ })