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.
- package/README.md +13 -0
- package/dist/ble/framing.d.ts.map +1 -1
- package/dist/ble/framing.js +2 -2
- package/dist/ble/framing.js.map +1 -1
- package/dist/ble/index.d.ts +2 -2
- package/dist/ble/index.d.ts.map +1 -1
- package/dist/ble/index.js +2 -2
- package/dist/ble/index.js.map +1 -1
- package/dist/ble/web-ble-transport.d.ts +1 -1
- package/dist/ble/web-ble-transport.d.ts.map +1 -1
- package/dist/ble/web-ble-transport.js +23 -12
- package/dist/ble/web-ble-transport.js.map +1 -1
- package/dist/crypto.d.ts.map +1 -1
- package/dist/crypto.js +29 -12
- package/dist/crypto.js.map +1 -1
- package/dist/dapp-session.d.ts.map +1 -1
- package/dist/dapp-session.js +15 -5
- package/dist/dapp-session.js.map +1 -1
- package/dist/emitter.d.ts +1 -3
- package/dist/emitter.d.ts.map +1 -1
- package/dist/emitter.js +4 -2
- package/dist/emitter.js.map +1 -1
- package/dist/evm/eip1193.d.ts +2 -2
- package/dist/evm/eip1193.d.ts.map +1 -1
- package/dist/evm/eip1193.js +32 -18
- package/dist/evm/eip1193.js.map +1 -1
- package/dist/evm/index.d.ts +2 -2
- package/dist/evm/index.d.ts.map +1 -1
- package/dist/evm/index.js.map +1 -1
- package/dist/wallet-session.d.ts.map +1 -1
- package/dist/wallet-session.js +4 -3
- package/dist/wallet-session.js.map +1 -1
- package/dist/ws-transport.d.ts +3 -2
- package/dist/ws-transport.d.ts.map +1 -1
- package/dist/ws-transport.js +13 -4
- package/dist/ws-transport.js.map +1 -1
- package/package.json +20 -1
- package/src/__tests__/adversarial/crypto-attacks.test.ts +240 -233
- package/src/__tests__/adversarial/malicious-dapp.test.ts +228 -194
- package/src/__tests__/adversarial/malicious-relay.test.ts +292 -220
- package/src/__tests__/adversarial/malicious-wallet.test.ts +246 -180
- package/src/__tests__/spec-compliance/canonical-json.test.ts +105 -105
- package/src/__tests__/spec-compliance/crypto-vectors.test.ts +149 -154
- package/src/__tests__/spec-compliance/message-format.test.ts +180 -151
- package/src/__tests__/spec-compliance/sequence-numbers.test.ts +142 -149
- package/src/__tests__/spec-compliance/state-machine.test.ts +203 -180
- package/src/ble/framing.test.ts +122 -114
- package/src/ble/framing.ts +48 -51
- package/src/ble/index.ts +7 -7
- package/src/ble/web-ble-transport.test.ts +93 -84
- package/src/ble/web-ble-transport.ts +70 -57
- package/src/ble/web-bluetooth.d.ts +19 -19
- package/src/canonical-json.test.ts +301 -285
- package/src/crypto-directional.test.ts +155 -129
- package/src/crypto-hardening.test.ts +292 -283
- package/src/crypto.test.ts +364 -346
- package/src/crypto.ts +185 -175
- package/src/dapp-session.test.ts +522 -385
- package/src/dapp-session.ts +17 -11
- package/src/emitter.test.ts +122 -122
- package/src/emitter.ts +20 -18
- package/src/evm/eip1193.test.ts +283 -205
- package/src/evm/eip1193.ts +162 -138
- package/src/evm/index.ts +5 -5
- package/src/evm/wagmi.test.ts +1 -1
- package/src/integration.test.ts +329 -201
- package/src/security.test.ts +331 -238
- package/src/sequence-validation.test.ts +6 -9
- package/src/test-helpers.ts +102 -78
- package/src/types.test.ts +45 -50
- package/src/wallet-session.test.ts +611 -383
- package/src/wallet-session.ts +7 -9
- package/src/ws-transport.test.ts +141 -139
- package/src/ws-transport.ts +52 -41
|
@@ -6,7 +6,11 @@
|
|
|
6
6
|
* implementation can run these tests to confirm interoperability.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
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 =
|
|
65
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
105
|
-
|
|
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
|
-
|
|
113
|
-
|
|
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(
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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)
|
|
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
|
-
|
|
291
|
-
|
|
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
|
+
})
|