walletpair-sdk 1.0.3 → 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 +1 -1
  34. package/dist/ws-transport.d.ts.map +1 -1
  35. package/dist/ws-transport.js +12 -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 +51 -41
@@ -6,7 +6,11 @@
6
6
  * implementation can run these tests to confirm interoperability.
7
7
  */
8
8
 
9
- import { describe, expect, it } from 'vitest';
9
+ import { hmac } from '@noble/hashes/hmac'
10
+ import { sha256 } from '@noble/hashes/sha256'
11
+ import { concatBytes, utf8ToBytes } from '@noble/hashes/utils'
12
+ import { describe, expect, it } from 'vitest'
13
+ import type { SessionCryptoContext } from '../../crypto.js'
10
14
  import {
11
15
  b64urlDecode,
12
16
  b64urlEncode,
@@ -21,60 +25,59 @@ import {
21
25
  getPublicKey,
22
26
  hexToBytes,
23
27
  sealPayload,
24
- sha256Hex,
25
28
  unsealJoin,
26
29
  unsealPayload,
27
- } from '../../crypto.js';
28
- import type { SessionCryptoContext } from '../../crypto.js';
29
- import { sha256 } from '@noble/hashes/sha256';
30
- import { hmac } from '@noble/hashes/hmac';
31
- import { concatBytes, utf8ToBytes } from '@noble/hashes/utils';
30
+ } from '../../crypto.js'
32
31
 
33
32
  // ---------------------------------------------------------------------------
34
33
  // Appendix A values (all from the protocol spec)
35
34
  // ---------------------------------------------------------------------------
36
35
 
37
- const DAPP_PRIVATE_KEY = 'a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4';
38
- const DAPP_PUBLIC_KEY = '1c9fd88f45606d932a80c71824ae151d15d73e77de38e8e000852e614fae7019';
39
- const DAPP_PUB_B64 = 'HJ_Yj0VgbZMqgMcYJK4VHRXXPnfeOOjgAIUuYU-ucBk';
36
+ const DAPP_PRIVATE_KEY = 'a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4'
37
+ const DAPP_PUBLIC_KEY = '1c9fd88f45606d932a80c71824ae151d15d73e77de38e8e000852e614fae7019'
38
+ const DAPP_PUB_B64 = 'HJ_Yj0VgbZMqgMcYJK4VHRXXPnfeOOjgAIUuYU-ucBk'
40
39
 
41
- const WALLET_PRIVATE_KEY = '4b66e9d4d1b4673c5ad22691957d6af5c11b6421e0ea01d42ca4169e7918ba0d';
42
- const WALLET_PUBLIC_KEY = 'ff63fe57bfbf43fa3f563628b149af704d3db625369c49983650347a6a71e00e';
43
- const WALLET_PUB_B64 = '_2P-V7-_Q_o_VjYosUmvcE09tiU2nEmYNlA0empx4A4';
40
+ const WALLET_PRIVATE_KEY = '4b66e9d4d1b4673c5ad22691957d6af5c11b6421e0ea01d42ca4169e7918ba0d'
41
+ const WALLET_PUBLIC_KEY = 'ff63fe57bfbf43fa3f563628b149af704d3db625369c49983650347a6a71e00e'
42
+ const WALLET_PUB_B64 = '_2P-V7-_Q_o_VjYosUmvcE09tiU2nEmYNlA0empx4A4'
44
43
 
45
- const CHANNEL_ID = 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2';
46
- const SHARED_SECRET = '739311d35d8d3c41da4062c799a6c748808a31343facaaa7aa7e311908c1846e';
44
+ const CHANNEL_ID = 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2'
45
+ const SHARED_SECRET = '739311d35d8d3c41da4062c799a6c748808a31343facaaa7aa7e311908c1846e'
47
46
 
48
47
  // A.2
49
- const ROOT_KEY = 'c33b664ab3eea368d81109b432f04a1293a743212749e19bfe412a2996dcefee';
50
- const JOIN_ENCRYPTION_KEY = '981e75c4fad86e3db377517816a24b27564661ab89d327217684e0a56d68ec11';
48
+ const ROOT_KEY = 'c33b664ab3eea368d81109b432f04a1293a743212749e19bfe412a2996dcefee'
49
+ const JOIN_ENCRYPTION_KEY = '981e75c4fad86e3db377517816a24b27564661ab89d327217684e0a56d68ec11'
51
50
 
52
51
  // A.4
53
- const TRANSCRIPT_HASH = 'dd2cf890c3ac3855c1fb2479ada829d7dd3656001f80e316fa5e16fed5d6b535';
54
- const DAPP_TO_WALLET_KEY = '1353e42d494f8618e6bfc04c0236cc6004994c52c95d371f113459ea153c7fdc';
55
- const WALLET_TO_DAPP_KEY = 'fd0240cd5d4b00b3709549e102a918cf08d0d268fbdf468477cdc1ef663a55d6';
52
+ const TRANSCRIPT_HASH = 'dd2cf890c3ac3855c1fb2479ada829d7dd3656001f80e316fa5e16fed5d6b535'
53
+ const DAPP_TO_WALLET_KEY = '1353e42d494f8618e6bfc04c0236cc6004994c52c95d371f113459ea153c7fdc'
54
+ const WALLET_TO_DAPP_KEY = 'fd0240cd5d4b00b3709549e102a918cf08d0d268fbdf468477cdc1ef663a55d6'
56
55
 
57
56
  // A.5
58
- const FINGERPRINT_SHA256 = '7f301a56626650b08f11c99df3333237a66fae34e0c0d1512c19fe51d41a8604';
59
- const FINGERPRINT = '8902';
57
+ const FINGERPRINT_SHA256 = '7f301a56626650b08f11c99df3333237a66fae34e0c0d1512c19fe51d41a8604'
58
+ const FINGERPRINT = '8902'
60
59
 
61
60
  // A.6
62
- const TRAFFIC_KEY_A6 = '1353e42d494f8618e6bfc04c0236cc6004994c52c95d371f113459ea153c7fdc';
63
- const NONCE_A6 = 'b71ba8a87d41562e5af426d7';
64
- const AAD_HEADER_A6 = '01002b484a5f596a305667625a4d71674d63594a4b345648525858506e66654f4f6a674149557559552d7563426b00077265712d303031';
65
- const PLAINTEXT_A6 = '{"_method":"wallet_getAccounts","chain":"eip155:1"}';
61
+ const TRAFFIC_KEY_A6 = '1353e42d494f8618e6bfc04c0236cc6004994c52c95d371f113459ea153c7fdc'
62
+ const NONCE_A6 = 'b71ba8a87d41562e5af426d7'
63
+ const AAD_HEADER_A6 =
64
+ '01002b484a5f596a305667625a4d71674d63594a4b345648525858506e66654f4f6a674149557559552d7563426b00077265712d303031'
65
+ const PLAINTEXT_A6 = '{"_method":"wallet_getAccounts","chain":"eip155:1"}'
66
66
  const CIPHERTEXT_TAG_A6 =
67
67
  '2aed1e76963c25234d9a2e023fdf40d35b9e1c7a9a3fd121' +
68
68
  'c045c14df5e5627726213febe2459ff4c24d2c709d4c19d0' +
69
- '0b6f43f8ea2418e68e8e0840bf7771ada851a5';
70
- const SEALED_A6 = 'AAAAACrtHnaWPCUjTZouAj_fQNNbnhx6mj_RIcBFwU315WJ3JiE_6-JFn_TCTSxwnUwZ0AtvQ_jqJBjmjo4IQL93ca2oUaU';
69
+ '0b6f43f8ea2418e68e8e0840bf7771ada851a5'
70
+ const SEALED_A6 =
71
+ 'AAAAACrtHnaWPCUjTZouAj_fQNNbnhx6mj_RIcBFwU315WJ3JiE_6-JFn_TCTSxwnUwZ0AtvQ_jqJBjmjo4IQL93ca2oUaU'
71
72
 
72
73
  // A.3
73
- const JOIN_NONCE = '09474eabe263432ebc7e4756';
74
- const JOIN_AAD = 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b204';
75
- const SEALED_JOIN_B64 = 'CUdOq-JjQy68fkdWHyGJa2GLNBPXEb-0vK1HlTCmQrZwEdqRRb0iyBX9ZHGkv74L6J24LMM2AVFsrXV9pjcCmtLx21fkj6ves8rd-RtjyW6WS44-qo87sn36IpLjhItuUjq_elDUr_qCOpwhoVIrFC29b7n_9q8UdbMAd5HwdKNiKFLrb91_rWhVj3H_y78fyft8LiHb52p0yF2RWB5m0-vZh0A9Rk9HBL9amsEPnOQiylZvCu-1gEko2SyCpkUGl0eXGOLs6vvnSCFiZjy8HLg95kjZoGaBqONQrF-dKoo-rT9hlW9fkMEi7rpDRzKWsTHIkYfnyTYNDk6M3o9mg_7z2k6FGwJqAXD2qCqjDXECVgytsvl_y68vSQqML2H74oFK_dx4SzHsoZWebv9fBve4kJHE5NEzCx3f';
74
+ const JOIN_NONCE = '09474eabe263432ebc7e4756'
75
+ const JOIN_AAD = 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b204'
76
+ const SEALED_JOIN_B64 =
77
+ 'CUdOq-JjQy68fkdWHyGJa2GLNBPXEb-0vK1HlTCmQrZwEdqRRb0iyBX9ZHGkv74L6J24LMM2AVFsrXV9pjcCmtLx21fkj6ves8rd-RtjyW6WS44-qo87sn36IpLjhItuUjq_elDUr_qCOpwhoVIrFC29b7n_9q8UdbMAd5HwdKNiKFLrb91_rWhVj3H_y78fyft8LiHb52p0yF2RWB5m0-vZh0A9Rk9HBL9amsEPnOQiylZvCu-1gEko2SyCpkUGl0eXGOLs6vvnSCFiZjy8HLg95kjZoGaBqONQrF-dKoo-rT9hlW9fkMEi7rpDRzKWsTHIkYfnyTYNDk6M3o9mg_7z2k6FGwJqAXD2qCqjDXECVgytsvl_y68vSQqML2H74oFK_dx4SzHsoZWebv9fBve4kJHE5NEzCx3f'
76
78
 
77
- const JOIN_PLAINTEXT = '{"capabilities":{"chains":["eip155:1","eip155:137"],"events":["accountsChanged","chainChanged"],"methods":["wallet_signTransaction","wallet_signMessage"]},"meta":{"description":"A multi-chain wallet","icon":"https://mywallet.app/icon.png","name":"MyWallet","url":"https://mywallet.app"}}';
79
+ const JOIN_PLAINTEXT =
80
+ '{"capabilities":{"chains":["eip155:1","eip155:137"],"events":["accountsChanged","chainChanged"],"methods":["wallet_signTransaction","wallet_signMessage"]},"meta":{"description":"A multi-chain wallet","icon":"https://mywallet.app/icon.png","name":"MyWallet","url":"https://mywallet.app"}}'
78
81
 
79
82
  // ---------------------------------------------------------------------------
80
83
  // A.1 Key Material
@@ -82,39 +85,33 @@ const JOIN_PLAINTEXT = '{"capabilities":{"chains":["eip155:1","eip155:137"],"eve
82
85
 
83
86
  describe('Appendix A.1 — Key Material', () => {
84
87
  it('derives the correct public key from the dApp private key', () => {
85
- const pub = getPublicKey(hexToBytes(DAPP_PRIVATE_KEY));
86
- expect(bytesToHex(pub)).toBe(DAPP_PUBLIC_KEY);
87
- });
88
+ const pub = getPublicKey(hexToBytes(DAPP_PRIVATE_KEY))
89
+ expect(bytesToHex(pub)).toBe(DAPP_PUBLIC_KEY)
90
+ })
88
91
 
89
92
  it('derives the correct public key from the wallet private key', () => {
90
- const pub = getPublicKey(hexToBytes(WALLET_PRIVATE_KEY));
91
- expect(bytesToHex(pub)).toBe(WALLET_PUBLIC_KEY);
92
- });
93
+ const pub = getPublicKey(hexToBytes(WALLET_PRIVATE_KEY))
94
+ expect(bytesToHex(pub)).toBe(WALLET_PUBLIC_KEY)
95
+ })
93
96
 
94
97
  it('base64url-encodes the dApp public key correctly', () => {
95
- expect(b64urlEncode(hexToBytes(DAPP_PUBLIC_KEY))).toBe(DAPP_PUB_B64);
96
- });
98
+ expect(b64urlEncode(hexToBytes(DAPP_PUBLIC_KEY))).toBe(DAPP_PUB_B64)
99
+ })
97
100
 
98
101
  it('base64url-encodes the wallet public key correctly', () => {
99
- expect(b64urlEncode(hexToBytes(WALLET_PUBLIC_KEY))).toBe(WALLET_PUB_B64);
100
- });
102
+ expect(b64urlEncode(hexToBytes(WALLET_PUBLIC_KEY))).toBe(WALLET_PUB_B64)
103
+ })
101
104
 
102
105
  it('computes the correct X25519 shared secret (dApp perspective)', () => {
103
- const shared = computeSharedSecret(
104
- hexToBytes(DAPP_PRIVATE_KEY),
105
- hexToBytes(WALLET_PUBLIC_KEY),
106
- );
107
- expect(bytesToHex(shared)).toBe(SHARED_SECRET);
108
- });
106
+ const shared = computeSharedSecret(hexToBytes(DAPP_PRIVATE_KEY), hexToBytes(WALLET_PUBLIC_KEY))
107
+ expect(bytesToHex(shared)).toBe(SHARED_SECRET)
108
+ })
109
109
 
110
110
  it('computes the correct X25519 shared secret (wallet perspective)', () => {
111
- const shared = computeSharedSecret(
112
- hexToBytes(WALLET_PRIVATE_KEY),
113
- hexToBytes(DAPP_PUBLIC_KEY),
114
- );
115
- expect(bytesToHex(shared)).toBe(SHARED_SECRET);
116
- });
117
- });
111
+ const shared = computeSharedSecret(hexToBytes(WALLET_PRIVATE_KEY), hexToBytes(DAPP_PUBLIC_KEY))
112
+ expect(bytesToHex(shared)).toBe(SHARED_SECRET)
113
+ })
114
+ })
118
115
 
119
116
  // ---------------------------------------------------------------------------
120
117
  // A.2 Key Derivation
@@ -122,15 +119,15 @@ describe('Appendix A.1 — Key Material', () => {
122
119
 
123
120
  describe('Appendix A.2 — Key Derivation', () => {
124
121
  it('derives the correct root_key', () => {
125
- const rootKey = deriveSessionKey(hexToBytes(SHARED_SECRET), CHANNEL_ID);
126
- expect(bytesToHex(rootKey)).toBe(ROOT_KEY);
127
- });
122
+ const rootKey = deriveSessionKey(hexToBytes(SHARED_SECRET), CHANNEL_ID)
123
+ expect(bytesToHex(rootKey)).toBe(ROOT_KEY)
124
+ })
128
125
 
129
126
  it('derives the correct join_encryption_key', () => {
130
- const joinKey = deriveJoinEncryptionKey(hexToBytes(ROOT_KEY), CHANNEL_ID);
131
- expect(bytesToHex(joinKey)).toBe(JOIN_ENCRYPTION_KEY);
132
- });
133
- });
127
+ const joinKey = deriveJoinEncryptionKey(hexToBytes(ROOT_KEY), CHANNEL_ID)
128
+ expect(bytesToHex(joinKey)).toBe(JOIN_ENCRYPTION_KEY)
129
+ })
130
+ })
134
131
 
135
132
  // ---------------------------------------------------------------------------
136
133
  // A.3 Sealed Join
@@ -150,41 +147,41 @@ describe('Appendix A.3 — Sealed Join', () => {
150
147
  url: 'https://mywallet.app',
151
148
  icon: 'https://mywallet.app/icon.png',
152
149
  },
153
- };
154
- expect(canonicalJson(joinObj)).toBe(JOIN_PLAINTEXT);
155
- });
150
+ }
151
+ expect(canonicalJson(joinObj)).toBe(JOIN_PLAINTEXT)
152
+ })
156
153
 
157
154
  it('join_aad is channel_id_bytes || 0x04', () => {
158
- const expected = hexToBytes(JOIN_AAD);
159
- const chBytes = hexToBytes(CHANNEL_ID);
160
- const aad = new Uint8Array(chBytes.length + 1);
161
- aad.set(chBytes);
162
- aad[chBytes.length] = 0x04;
163
- expect(bytesToHex(aad)).toBe(bytesToHex(expected));
164
- });
155
+ const expected = hexToBytes(JOIN_AAD)
156
+ const chBytes = hexToBytes(CHANNEL_ID)
157
+ const aad = new Uint8Array(chBytes.length + 1)
158
+ aad.set(chBytes)
159
+ aad[chBytes.length] = 0x04
160
+ expect(bytesToHex(aad)).toBe(bytesToHex(expected))
161
+ })
165
162
 
166
163
  it('decrypts the sealed_join test vector correctly', () => {
167
- const joinKey = hexToBytes(JOIN_ENCRYPTION_KEY);
168
- const result = unsealJoin(joinKey, CHANNEL_ID, SEALED_JOIN_B64);
164
+ const joinKey = hexToBytes(JOIN_ENCRYPTION_KEY)
165
+ const result = unsealJoin(joinKey, CHANNEL_ID, SEALED_JOIN_B64)
169
166
  expect(result.capabilities).toEqual({
170
167
  chains: ['eip155:1', 'eip155:137'],
171
168
  events: ['accountsChanged', 'chainChanged'],
172
169
  methods: ['wallet_signTransaction', 'wallet_signMessage'],
173
- });
170
+ })
174
171
  expect(result.meta).toEqual({
175
172
  description: 'A multi-chain wallet',
176
173
  icon: 'https://mywallet.app/icon.png',
177
174
  name: 'MyWallet',
178
175
  url: 'https://mywallet.app',
179
- });
180
- });
176
+ })
177
+ })
181
178
 
182
179
  it('sealed_join envelope starts with the specified nonce', () => {
183
- const envelope = b64urlDecode(SEALED_JOIN_B64);
184
- const nonce = envelope.slice(0, 12);
185
- expect(bytesToHex(nonce)).toBe(JOIN_NONCE);
186
- });
187
- });
180
+ const envelope = b64urlDecode(SEALED_JOIN_B64)
181
+ const nonce = envelope.slice(0, 12)
182
+ expect(bytesToHex(nonce)).toBe(JOIN_NONCE)
183
+ })
184
+ })
188
185
 
189
186
  // ---------------------------------------------------------------------------
190
187
  // A.4 Transcript and Traffic Keys
@@ -206,31 +203,31 @@ describe('Appendix A.4 — Transcript Hash and Traffic Keys', () => {
206
203
  url: 'https://mywallet.app',
207
204
  },
208
205
  dappName: 'MyDApp',
209
- };
206
+ }
210
207
 
211
208
  it('computes the correct transcript_hash', () => {
212
- const hash = computeHandshakeTranscriptHash(CHANNEL_ID, context);
213
- expect(bytesToHex(hash)).toBe(TRANSCRIPT_HASH);
214
- });
209
+ const hash = computeHandshakeTranscriptHash(CHANNEL_ID, context)
210
+ expect(bytesToHex(hash)).toBe(TRANSCRIPT_HASH)
211
+ })
215
212
 
216
213
  it('derives the correct dapp_to_wallet_key', () => {
217
- const rootKey = hexToBytes(ROOT_KEY);
218
- const keys = deriveDirectionalSessionKeys(rootKey, CHANNEL_ID, context);
219
- expect(bytesToHex(keys.dappToWalletKey)).toBe(DAPP_TO_WALLET_KEY);
220
- });
214
+ const rootKey = hexToBytes(ROOT_KEY)
215
+ const keys = deriveDirectionalSessionKeys(rootKey, CHANNEL_ID, context)
216
+ expect(bytesToHex(keys.dappToWalletKey)).toBe(DAPP_TO_WALLET_KEY)
217
+ })
221
218
 
222
219
  it('derives the correct wallet_to_dapp_key', () => {
223
- const rootKey = hexToBytes(ROOT_KEY);
224
- const keys = deriveDirectionalSessionKeys(rootKey, CHANNEL_ID, context);
225
- expect(bytesToHex(keys.walletToDappKey)).toBe(WALLET_TO_DAPP_KEY);
226
- });
220
+ const rootKey = hexToBytes(ROOT_KEY)
221
+ const keys = deriveDirectionalSessionKeys(rootKey, CHANNEL_ID, context)
222
+ expect(bytesToHex(keys.walletToDappKey)).toBe(WALLET_TO_DAPP_KEY)
223
+ })
227
224
 
228
225
  it('transcript_hash in directional keys matches standalone computation', () => {
229
- const rootKey = hexToBytes(ROOT_KEY);
230
- const keys = deriveDirectionalSessionKeys(rootKey, CHANNEL_ID, context);
231
- expect(bytesToHex(keys.transcriptHash)).toBe(TRANSCRIPT_HASH);
232
- });
233
- });
226
+ const rootKey = hexToBytes(ROOT_KEY)
227
+ const keys = deriveDirectionalSessionKeys(rootKey, CHANNEL_ID, context)
228
+ expect(bytesToHex(keys.transcriptHash)).toBe(TRANSCRIPT_HASH)
229
+ })
230
+ })
234
231
 
235
232
  // ---------------------------------------------------------------------------
236
233
  // A.5 Session Fingerprint
@@ -239,28 +236,30 @@ describe('Appendix A.4 — Transcript Hash and Traffic Keys', () => {
239
236
  describe('Appendix A.5 — Session Fingerprint', () => {
240
237
  it('computes the correct SHA-256 prefix', () => {
241
238
  // Verify the full SHA-256 matches
242
- const hash = sha256(concatBytes(
243
- utf8ToBytes('walletpair-v1-session-fingerprint'),
244
- hexToBytes(CHANNEL_ID),
245
- hexToBytes(DAPP_PUBLIC_KEY),
246
- ));
247
- expect(bytesToHex(hash)).toBe(FINGERPRINT_SHA256);
248
- });
239
+ const hash = sha256(
240
+ concatBytes(
241
+ utf8ToBytes('walletpair-v1-session-fingerprint'),
242
+ hexToBytes(CHANNEL_ID),
243
+ hexToBytes(DAPP_PUBLIC_KEY),
244
+ ),
245
+ )
246
+ expect(bytesToHex(hash)).toBe(FINGERPRINT_SHA256)
247
+ })
249
248
 
250
249
  it('computes the correct 4-digit fingerprint', () => {
251
- const fp = computeSessionFingerprint(CHANNEL_ID, DAPP_PUB_B64);
252
- expect(fp).toBe(FINGERPRINT);
253
- });
250
+ const fp = computeSessionFingerprint(CHANNEL_ID, DAPP_PUB_B64)
251
+ expect(fp).toBe(FINGERPRINT)
252
+ })
254
253
 
255
254
  it('fp_uint32 mod 10000 produces the correct value', () => {
256
255
  // fp_bytes = 7f301a56, fp_uint32 = 2133858902
257
- const fpBytes = hexToBytes('7f301a56');
258
- const view = new DataView(fpBytes.buffer, fpBytes.byteOffset, 4);
259
- const fpUint32 = view.getUint32(0);
260
- expect(fpUint32).toBe(2133858902);
261
- expect(fpUint32 % 10000).toBe(8902);
262
- });
263
- });
256
+ const fpBytes = hexToBytes('7f301a56')
257
+ const view = new DataView(fpBytes.buffer, fpBytes.byteOffset, 4)
258
+ const fpUint32 = view.getUint32(0)
259
+ expect(fpUint32).toBe(2133858902)
260
+ expect(fpUint32 % 10000).toBe(8902)
261
+ })
262
+ })
264
263
 
265
264
  // ---------------------------------------------------------------------------
266
265
  // A.6 AEAD Encryption (dapp->wallet, seq=0)
@@ -268,54 +267,50 @@ describe('Appendix A.5 — Session Fingerprint', () => {
268
267
 
269
268
  describe('Appendix A.6 — AEAD Encryption (dapp->wallet, seq=0)', () => {
270
269
  it('nonce = HMAC-SHA256(traffic_key, seq_bytes)[0:12] matches the spec', () => {
271
- const key = hexToBytes(TRAFFIC_KEY_A6);
272
- const seqBytes = new Uint8Array(4); // seq=0
273
- const nonce = hmac(sha256, key, seqBytes).slice(0, 12);
274
- expect(bytesToHex(nonce)).toBe(NONCE_A6);
275
- });
270
+ const key = hexToBytes(TRAFFIC_KEY_A6)
271
+ const seqBytes = new Uint8Array(4) // seq=0
272
+ const nonce = hmac(sha256, key, seqBytes).slice(0, 12)
273
+ expect(bytesToHex(nonce)).toBe(NONCE_A6)
274
+ })
276
275
 
277
276
  it('AAD header matches the spec', () => {
278
277
  // Rebuild AAD header: 0x01 || lp(from) || lp(id)
279
- const fromStr = 'HJ_Yj0VgbZMqgMcYJK4VHRXXPnfeOOjgAIUuYU-ucBk';
280
- const idStr = 'req-001';
278
+ const fromStr = 'HJ_Yj0VgbZMqgMcYJK4VHRXXPnfeOOjgAIUuYU-ucBk'
279
+ const idStr = 'req-001'
281
280
 
282
281
  function lp(s: string): Uint8Array {
283
- const bytes = utf8ToBytes(s);
284
- const len = new Uint8Array(2);
285
- new DataView(len.buffer).setUint16(0, bytes.length);
286
- return concatBytes(len, bytes);
282
+ const bytes = utf8ToBytes(s)
283
+ const len = new Uint8Array(2)
284
+ new DataView(len.buffer).setUint16(0, bytes.length)
285
+ return concatBytes(len, bytes)
287
286
  }
288
287
 
289
- const header = concatBytes(
290
- new Uint8Array([0x01]),
291
- lp(fromStr),
292
- lp(idStr),
293
- );
294
- expect(bytesToHex(header)).toBe(AAD_HEADER_A6);
295
- });
288
+ const header = concatBytes(new Uint8Array([0x01]), lp(fromStr), lp(idStr))
289
+ expect(bytesToHex(header)).toBe(AAD_HEADER_A6)
290
+ })
296
291
 
297
292
  it('sealPayload produces the expected sealed output for seq=0', () => {
298
- const key = hexToBytes(TRAFFIC_KEY_A6);
299
- const data = { _method: 'wallet_getAccounts', chain: 'eip155:1' };
300
- const header = { type: 'req' as const, from: DAPP_PUB_B64, id: 'req-001' };
301
- const sealed = sealPayload(key, CHANNEL_ID, 0, data, header);
302
- expect(sealed).toBe(SEALED_A6);
303
- });
293
+ const key = hexToBytes(TRAFFIC_KEY_A6)
294
+ const data = { _method: 'wallet_getAccounts', chain: 'eip155:1' }
295
+ const header = { type: 'req' as const, from: DAPP_PUB_B64, id: 'req-001' }
296
+ const sealed = sealPayload(key, CHANNEL_ID, 0, data, header)
297
+ expect(sealed).toBe(SEALED_A6)
298
+ })
304
299
 
305
300
  it('unsealPayload decrypts the expected sealed output for seq=0', () => {
306
- const key = hexToBytes(TRAFFIC_KEY_A6);
307
- const header = { type: 'req' as const, from: DAPP_PUB_B64, id: 'req-001' };
308
- const { seq, data, plaintextJson } = unsealPayload(key, CHANNEL_ID, SEALED_A6, header);
309
- expect(seq).toBe(0);
310
- expect(plaintextJson).toBe(PLAINTEXT_A6);
311
- expect(data).toEqual({ _method: 'wallet_getAccounts', chain: 'eip155:1' });
312
- });
301
+ const key = hexToBytes(TRAFFIC_KEY_A6)
302
+ const header = { type: 'req' as const, from: DAPP_PUB_B64, id: 'req-001' }
303
+ const { seq, data, plaintextJson } = unsealPayload(key, CHANNEL_ID, SEALED_A6, header)
304
+ expect(seq).toBe(0)
305
+ expect(plaintextJson).toBe(PLAINTEXT_A6)
306
+ expect(data).toEqual({ _method: 'wallet_getAccounts', chain: 'eip155:1' })
307
+ })
313
308
 
314
309
  it('ciphertext+tag in the sealed envelope matches the spec', () => {
315
- const envelope = b64urlDecode(SEALED_A6);
316
- const seqBytes = envelope.slice(0, 4);
317
- const ciphertextTag = envelope.slice(4);
318
- expect(bytesToHex(seqBytes)).toBe('00000000');
319
- expect(bytesToHex(ciphertextTag)).toBe(CIPHERTEXT_TAG_A6);
320
- });
321
- });
310
+ const envelope = b64urlDecode(SEALED_A6)
311
+ const seqBytes = envelope.slice(0, 4)
312
+ const ciphertextTag = envelope.slice(4)
313
+ expect(bytesToHex(seqBytes)).toBe('00000000')
314
+ expect(bytesToHex(ciphertextTag)).toBe(CIPHERTEXT_TAG_A6)
315
+ })
316
+ })