ethagent 3.0.1 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +6 -1
  2. package/package.json +3 -1
  3. package/src/app/FirstRun.tsx +1 -24
  4. package/src/app/firstRunConfig.ts +26 -0
  5. package/src/auth/openaiOAuth/landingPage.ts +2 -11
  6. package/src/chat/ChatScreen.tsx +32 -117
  7. package/src/chat/MessageList.tsx +18 -260
  8. package/src/chat/chatEnvironment.ts +16 -0
  9. package/src/chat/chatTurnContext.ts +50 -0
  10. package/src/chat/chatTurnOrchestrator.ts +5 -112
  11. package/src/chat/chatTurnRows.ts +64 -0
  12. package/src/chat/commands.ts +3 -178
  13. package/src/chat/continuityEditReview.ts +42 -0
  14. package/src/chat/input/ChatInput.tsx +10 -144
  15. package/src/chat/input/chatInputHelpers.ts +62 -0
  16. package/src/chat/input/inputRendering.tsx +93 -0
  17. package/src/chat/messageMarkdown.ts +220 -0
  18. package/src/chat/messageRows.ts +43 -0
  19. package/src/chat/planImplementation.ts +62 -0
  20. package/src/chat/slashCommandHandlers.ts +165 -0
  21. package/src/chat/slashCommandViews.ts +120 -0
  22. package/src/cli/main.tsx +7 -0
  23. package/src/identity/continuity/challenges.ts +123 -0
  24. package/src/identity/continuity/envelope.ts +49 -1484
  25. package/src/identity/continuity/envelopeCreate.ts +322 -0
  26. package/src/identity/continuity/envelopeCrypto.ts +182 -0
  27. package/src/identity/continuity/envelopeParse.ts +441 -0
  28. package/src/identity/continuity/envelopeTypes.ts +204 -0
  29. package/src/identity/continuity/envelopeVersion.ts +1 -0
  30. package/src/identity/continuity/payloadNormalization.ts +183 -0
  31. package/src/identity/continuity/publicSkills.ts +5 -5
  32. package/src/identity/continuity/skills/loadSkills.ts +12 -69
  33. package/src/identity/continuity/skills/skillPaths.ts +76 -0
  34. package/src/identity/continuity/skillsNormalization.ts +119 -0
  35. package/src/identity/continuity/snapshotToken.ts +28 -0
  36. package/src/identity/hub/continuity/completion.ts +67 -0
  37. package/src/identity/hub/continuity/effects.ts +5 -62
  38. package/src/identity/hub/profile/effects.ts +6 -170
  39. package/src/identity/hub/profile/operatorSave.ts +202 -0
  40. package/src/identity/registry/erc8004/metadata.ts +31 -23
  41. package/src/identity/wallet/browserWallet/html.ts +1 -57
  42. package/src/identity/wallet/browserWallet/walletPageSource.ts +85 -0
  43. package/src/identity/wallet/page/controller.ts +1 -1
  44. package/src/identity/wallet/page/errorView.ts +122 -0
  45. package/src/identity/wallet/page/view.ts +3 -114
  46. package/src/mcp/manager.ts +8 -66
  47. package/src/mcp/managerHelpers.ts +70 -0
  48. package/src/models/ModelPicker.tsx +69 -889
  49. package/src/models/huggingface.ts +20 -137
  50. package/src/models/huggingfaceStorage.ts +136 -0
  51. package/src/models/llamacpp.ts +37 -303
  52. package/src/models/llamacppCommands.ts +44 -0
  53. package/src/models/llamacppConfig.ts +34 -0
  54. package/src/models/llamacppDiscovery.ts +176 -0
  55. package/src/models/llamacppOutput.ts +65 -0
  56. package/src/models/modelPickerCatalogFlow.ts +56 -0
  57. package/src/models/modelPickerCredentials.ts +166 -0
  58. package/src/models/modelPickerData.ts +41 -0
  59. package/src/models/modelPickerDisplay.tsx +132 -0
  60. package/src/models/modelPickerHfFlow.ts +192 -0
  61. package/src/models/modelPickerLocalRunnerFlow.ts +115 -0
  62. package/src/models/modelPickerTypes.ts +69 -0
  63. package/src/models/modelPickerUninstallFlow.ts +48 -0
  64. package/src/models/modelPickerViewHelpers.ts +174 -0
  65. package/src/providers/openai-chat.ts +5 -124
  66. package/src/providers/openaiChatWire.ts +124 -0
  67. package/src/runtime/providerTurn.ts +38 -0
  68. package/src/runtime/textToolParser.ts +161 -0
  69. package/src/runtime/toolIntent.ts +1 -1
  70. package/src/runtime/turn.ts +43 -499
  71. package/src/runtime/turnNudges.ts +223 -0
  72. package/src/runtime/turnTypes.ts +86 -0
  73. package/src/ui/terminalTitle.ts +30 -0
@@ -0,0 +1,322 @@
1
+ import crypto from 'node:crypto'
2
+ import { ml_kem1024 } from '@noble/post-quantum/ml-kem.js'
3
+ import { toChecksumAddress } from '../crypto/eth.js'
4
+ import {
5
+ createContinuitySnapshotChallenge,
6
+ createTransferContinuitySnapshotChallenge,
7
+ createWalletRestoreAccessChallenge,
8
+ type WalletChallengePurpose,
9
+ } from './challenges.js'
10
+ import { normalizeContinuitySnapshotToken, type ContinuitySnapshotToken } from './snapshotToken.js'
11
+ import { CONTINUITY_SNAPSHOT_ENVELOPE_VERSION } from './envelopeVersion.js'
12
+ import {
13
+ continuityAadFor,
14
+ deriveContinuityAesKey,
15
+ deriveContinuityKemSeed,
16
+ deriveTransferSlotKey,
17
+ deriveWalletRestoreKemSeed,
18
+ deriveWalletSlotAesKey,
19
+ fromBase64,
20
+ sha256Hex,
21
+ toBase64,
22
+ transferPayloadAadFor,
23
+ transferSlotAadFor,
24
+ walletAccessManifestHash,
25
+ walletPayloadAadFor,
26
+ walletSlotAadFor,
27
+ } from './envelopeCrypto.js'
28
+ import {
29
+ continuityPayloadFromArgs,
30
+ normalizeAgentSnapshot,
31
+ normalizeWalletRestoreAccessKey,
32
+ normalizeWalletRestoreAccessKeys,
33
+ } from './payloadNormalization.js'
34
+ import { assertSignatureForAddress } from './envelopeParse.js'
35
+ import type {
36
+ CreateContinuitySnapshotEnvelopeArgs,
37
+ CreateTransferContinuitySnapshotEnvelopeArgs,
38
+ CreateWalletContinuitySnapshotEnvelopeArgs,
39
+ SignatureContinuitySnapshotEnvelope,
40
+ TransferContinuitySnapshotEnvelope,
41
+ TransferContinuitySnapshotSlot,
42
+ WalletContinuityRestoreAccessKey,
43
+ WalletContinuitySnapshotEnvelope,
44
+ WalletContinuitySnapshotSlot,
45
+ } from './envelopeTypes.js'
46
+
47
+ export function createWalletRestoreAccessKey(args: {
48
+ token: ContinuitySnapshotToken
49
+ ownerAddress: string
50
+ walletAddress: string
51
+ walletSignature: string
52
+ accessEpoch?: number
53
+ createdAt?: string
54
+ salt?: string
55
+ purpose?: WalletChallengePurpose
56
+ }): WalletContinuityRestoreAccessKey {
57
+ const walletAddress = toChecksumAddress(args.walletAddress)
58
+ const challenge = createWalletRestoreAccessChallenge({
59
+ token: args.token,
60
+ ownerAddress: args.ownerAddress,
61
+ walletAddress,
62
+ accessEpoch: args.accessEpoch,
63
+ purpose: args.purpose,
64
+ })
65
+ assertSignatureForAddress(challenge, args.walletSignature, walletAddress)
66
+ const salt = args.salt ? fromBase64(args.salt) : crypto.randomBytes(32)
67
+ const kemSeed = deriveWalletRestoreKemSeed(args.walletSignature, salt, walletAddress, challenge)
68
+ const kemKeys = ml_kem1024.keygen(kemSeed)
69
+ return {
70
+ address: walletAddress,
71
+ challenge,
72
+ salt: toBase64(salt),
73
+ kemPublicKey: toBase64(kemKeys.publicKey),
74
+ ...(args.createdAt ? { createdAt: args.createdAt } : {}),
75
+ }
76
+ }
77
+
78
+ export function createContinuitySnapshotEnvelope(args: CreateContinuitySnapshotEnvelopeArgs): SignatureContinuitySnapshotEnvelope {
79
+ const ownerAddress = toChecksumAddress(args.ownerAddress)
80
+ const challenge = createContinuitySnapshotChallenge(ownerAddress)
81
+ assertSignatureForAddress(challenge, args.walletSignature, ownerAddress)
82
+
83
+ const createdAt = args.payload.createdAt ?? new Date().toISOString()
84
+ const payload = continuityPayloadFromArgs({
85
+ ownerAddress,
86
+ createdAt,
87
+ payload: args.payload,
88
+ })
89
+
90
+ const salt = crypto.randomBytes(32)
91
+ const kemSeed = deriveContinuityKemSeed(args.walletSignature, salt, ownerAddress)
92
+ const kemKeys = ml_kem1024.keygen(kemSeed)
93
+ const kem = ml_kem1024.encapsulate(kemKeys.publicKey)
94
+ const key = deriveContinuityAesKey(args.walletSignature, kem.sharedSecret, salt, ownerAddress)
95
+ const nonce = crypto.randomBytes(12)
96
+ const cipher = crypto.createCipheriv('aes-256-gcm', key, nonce)
97
+ cipher.setAAD(continuityAadFor(ownerAddress, createdAt))
98
+ const plaintext = Buffer.from(JSON.stringify(payload), 'utf8')
99
+ const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()])
100
+ const tag = cipher.getAuthTag()
101
+
102
+ return {
103
+ version: 1,
104
+ envelopeVersion: CONTINUITY_SNAPSHOT_ENVELOPE_VERSION,
105
+ ownerAddress,
106
+ createdAt,
107
+ challenge,
108
+ crypto: {
109
+ kem: 'ML-KEM-1024',
110
+ aead: 'AES-256-GCM',
111
+ kdf: 'HKDF-SHA256',
112
+ signature: 'EIP-191',
113
+ decryptsWith: 'owner-signature',
114
+ },
115
+ salt: toBase64(salt),
116
+ kemPublicKey: toBase64(kemKeys.publicKey),
117
+ kemCiphertext: toBase64(kem.cipherText),
118
+ nonce: toBase64(nonce),
119
+ ciphertext: toBase64(encrypted),
120
+ tag: toBase64(tag),
121
+ }
122
+ }
123
+
124
+ export function createWalletContinuitySnapshotEnvelope(
125
+ args: CreateWalletContinuitySnapshotEnvelopeArgs,
126
+ ): WalletContinuitySnapshotEnvelope {
127
+ const ownerAddress = toChecksumAddress(args.ownerAddress)
128
+ const signerAddress = toChecksumAddress(args.signerAddress)
129
+ const token = normalizeContinuitySnapshotToken(args.token)
130
+ const accessEpoch = args.accessEpoch ?? 1
131
+ const accessKeys = normalizeWalletRestoreAccessKeys(args.accessKeys)
132
+ if (accessKeys.length === 0) throw new Error('At least one restore access key is required')
133
+ const signerKey = accessKeys.find(key => key.address.toLowerCase() === signerAddress.toLowerCase())
134
+ if (!signerKey) throw new Error('Snapshot signer is not an authorized restore wallet')
135
+ assertSignatureForAddress(signerKey.challenge, args.signerWalletSignature, signerAddress)
136
+
137
+ const createdAt = args.payload.createdAt ?? new Date().toISOString()
138
+ const payload = continuityPayloadFromArgs({
139
+ ownerAddress,
140
+ createdAt,
141
+ payload: {
142
+ ...args.payload,
143
+ agent: normalizeAgentSnapshot({
144
+ ...args.payload.agent,
145
+ chainId: args.payload.agent.chainId ?? token.chainId,
146
+ identityRegistryAddress: args.payload.agent.identityRegistryAddress ?? token.identityRegistryAddress,
147
+ agentId: args.payload.agent.agentId ?? token.agentId,
148
+ }),
149
+ },
150
+ })
151
+ const plaintext = Buffer.from(JSON.stringify(payload), 'utf8')
152
+ const contentKey = crypto.randomBytes(32)
153
+ const payloadNonce = crypto.randomBytes(12)
154
+ const accessManifestHash = walletAccessManifestHash({ ownerAddress, token, accessEpoch, accessKeys })
155
+ const payloadAad = walletPayloadAadFor({ ownerAddress, createdAt, token, accessEpoch, accessManifestHash })
156
+ const cipher = crypto.createCipheriv('aes-256-gcm', contentKey, payloadNonce)
157
+ cipher.setAAD(payloadAad)
158
+ const payloadCiphertext = Buffer.concat([cipher.update(plaintext), cipher.final()])
159
+ const payloadTag = cipher.getAuthTag()
160
+
161
+ return {
162
+ version: 1,
163
+ envelopeVersion: CONTINUITY_SNAPSHOT_ENVELOPE_VERSION,
164
+ ownerAddress,
165
+ createdAt,
166
+ token,
167
+ accessEpoch,
168
+ accessManifestHash,
169
+ crypto: {
170
+ kem: 'ML-KEM-1024',
171
+ aead: 'AES-256-GCM',
172
+ kdf: 'HKDF-SHA256',
173
+ signature: 'EIP-191',
174
+ decryptsWith: 'wallet-signature-slots',
175
+ },
176
+ payloadNonce: toBase64(payloadNonce),
177
+ payloadCiphertext: toBase64(payloadCiphertext),
178
+ payloadTag: toBase64(payloadTag),
179
+ payloadHash: sha256Hex(plaintext),
180
+ slots: accessKeys.map(key => createWalletSlot({ accessKey: key, contentKey, payloadAad, accessManifestHash })),
181
+ }
182
+ }
183
+
184
+ export function createTransferContinuitySnapshotEnvelope(
185
+ args: CreateTransferContinuitySnapshotEnvelopeArgs,
186
+ ): TransferContinuitySnapshotEnvelope {
187
+ const ownerAddress = toChecksumAddress(args.ownerAddress)
188
+ const targetAddress = toChecksumAddress(args.targetAddress)
189
+ if (ownerAddress.toLowerCase() === targetAddress.toLowerCase()) {
190
+ throw new Error('Receiver wallet must be different from sender wallet')
191
+ }
192
+ const token = normalizeContinuitySnapshotToken(args.token)
193
+ const senderChallenge = createTransferContinuitySnapshotChallenge({ token, ownerAddress, targetAddress, role: 'sender' })
194
+ const receiverChallenge = createTransferContinuitySnapshotChallenge({ token, ownerAddress, targetAddress, role: 'receiver' })
195
+ const challenge = createTransferContinuitySnapshotChallenge({ token, ownerAddress, targetAddress })
196
+ assertSignatureForAddress(senderChallenge, args.ownerWalletSignature, ownerAddress)
197
+ assertSignatureForAddress(receiverChallenge, args.targetWalletSignature, targetAddress)
198
+
199
+ const createdAt = args.payload.createdAt ?? new Date().toISOString()
200
+ const payload = continuityPayloadFromArgs({
201
+ ownerAddress,
202
+ createdAt,
203
+ payload: {
204
+ ...args.payload,
205
+ agent: normalizeAgentSnapshot({
206
+ ...args.payload.agent,
207
+ chainId: args.payload.agent.chainId ?? token.chainId,
208
+ identityRegistryAddress: args.payload.agent.identityRegistryAddress ?? token.identityRegistryAddress,
209
+ agentId: args.payload.agent.agentId ?? token.agentId,
210
+ }),
211
+ },
212
+ })
213
+ const plaintext = Buffer.from(JSON.stringify(payload), 'utf8')
214
+ const contentKey = crypto.randomBytes(32)
215
+ const payloadNonce = crypto.randomBytes(12)
216
+ const payloadAad = transferPayloadAadFor({ ownerAddress, targetAddress, createdAt, token })
217
+ const cipher = crypto.createCipheriv('aes-256-gcm', contentKey, payloadNonce)
218
+ cipher.setAAD(payloadAad)
219
+ const payloadCiphertext = Buffer.concat([cipher.update(plaintext), cipher.final()])
220
+ const payloadTag = cipher.getAuthTag()
221
+
222
+ const ownerSlot = createTransferSlot({
223
+ address: ownerAddress,
224
+ challenge: senderChallenge,
225
+ walletSignature: args.ownerWalletSignature,
226
+ contentKey,
227
+ payloadAad,
228
+ })
229
+ const targetSlot = createTransferSlot({
230
+ address: targetAddress,
231
+ challenge: receiverChallenge,
232
+ walletSignature: args.targetWalletSignature,
233
+ contentKey,
234
+ payloadAad,
235
+ })
236
+
237
+ return {
238
+ version: 1,
239
+ envelopeVersion: CONTINUITY_SNAPSHOT_ENVELOPE_VERSION,
240
+ ownerAddress,
241
+ createdAt,
242
+ challenge,
243
+ token,
244
+ targetAddress,
245
+ ...(args.targetHandle ? { targetHandle: args.targetHandle } : {}),
246
+ crypto: {
247
+ aead: 'AES-256-GCM',
248
+ kdf: 'HKDF-SHA256',
249
+ signature: 'EIP-191',
250
+ decryptsWith: 'transfer-signature-slot',
251
+ },
252
+ payloadNonce: toBase64(payloadNonce),
253
+ payloadCiphertext: toBase64(payloadCiphertext),
254
+ payloadTag: toBase64(payloadTag),
255
+ payloadHash: sha256Hex(plaintext),
256
+ slots: {
257
+ owner: ownerSlot,
258
+ target: targetSlot,
259
+ },
260
+ }
261
+ }
262
+
263
+ function createTransferSlot(args: {
264
+ address: string
265
+ challenge: string
266
+ walletSignature: string
267
+ contentKey: Uint8Array
268
+ payloadAad: Buffer
269
+ }): TransferContinuitySnapshotSlot {
270
+ const address = toChecksumAddress(args.address)
271
+ const salt = crypto.randomBytes(32)
272
+ const nonce = crypto.randomBytes(12)
273
+ const slotKey = deriveTransferSlotKey(args.walletSignature, salt, address, args.challenge)
274
+ const cipher = crypto.createCipheriv('aes-256-gcm', slotKey, nonce)
275
+ const slot: TransferContinuitySnapshotSlot = {
276
+ address,
277
+ challenge: args.challenge,
278
+ salt: toBase64(salt),
279
+ nonce: toBase64(nonce),
280
+ encryptedKey: '',
281
+ tag: '',
282
+ }
283
+ cipher.setAAD(transferSlotAadFor(slot, args.payloadAad))
284
+ const encryptedKey = Buffer.concat([cipher.update(args.contentKey), cipher.final()])
285
+ return {
286
+ ...slot,
287
+ encryptedKey: toBase64(encryptedKey),
288
+ tag: toBase64(cipher.getAuthTag()),
289
+ }
290
+ }
291
+
292
+ function createWalletSlot(args: {
293
+ accessKey: WalletContinuityRestoreAccessKey
294
+ contentKey: Uint8Array
295
+ payloadAad: Buffer
296
+ accessManifestHash: string
297
+ }): WalletContinuitySnapshotSlot {
298
+ const accessKey = normalizeWalletRestoreAccessKey(args.accessKey)
299
+ const publicKey = fromBase64(accessKey.kemPublicKey)
300
+ const kem = ml_kem1024.encapsulate(publicKey)
301
+ const salt = fromBase64(accessKey.salt)
302
+ const nonce = crypto.randomBytes(12)
303
+ const slotKey = deriveWalletSlotAesKey(kem.sharedSecret, salt, accessKey.address, accessKey.challenge, args.accessManifestHash)
304
+ const cipher = crypto.createCipheriv('aes-256-gcm', slotKey, nonce)
305
+ const slot: WalletContinuitySnapshotSlot = {
306
+ address: accessKey.address,
307
+ challenge: accessKey.challenge,
308
+ salt: accessKey.salt,
309
+ kemPublicKey: accessKey.kemPublicKey,
310
+ kemCiphertext: toBase64(kem.cipherText),
311
+ nonce: toBase64(nonce),
312
+ encryptedKey: '',
313
+ tag: '',
314
+ }
315
+ cipher.setAAD(walletSlotAadFor(slot, args.payloadAad))
316
+ const encryptedKey = Buffer.concat([cipher.update(args.contentKey), cipher.final()])
317
+ return {
318
+ ...slot,
319
+ encryptedKey: toBase64(encryptedKey),
320
+ tag: toBase64(cipher.getAuthTag()),
321
+ }
322
+ }
@@ -0,0 +1,182 @@
1
+ import crypto from 'node:crypto'
2
+ import { toChecksumAddress } from '../crypto/eth.js'
3
+ import { normalizeContinuitySnapshotToken, type ContinuitySnapshotToken } from './snapshotToken.js'
4
+ import { normalizeWalletRestoreAccessKeys } from './payloadNormalization.js'
5
+ import { CONTINUITY_SNAPSHOT_ENVELOPE_VERSION } from './envelopeVersion.js'
6
+ import type {
7
+ TransferContinuitySnapshotSlot,
8
+ WalletContinuityRestoreAccessKey,
9
+ WalletContinuitySnapshotSlot,
10
+ } from './envelope.js'
11
+
12
+ export function deriveContinuityKemSeed(walletSignature: string, salt: Uint8Array, ownerAddress: string): Uint8Array {
13
+ return hkdf(
14
+ Buffer.from(walletSignature, 'utf8'),
15
+ salt,
16
+ `ethagent:${CONTINUITY_SNAPSHOT_ENVELOPE_VERSION}:ml-kem1024:${ownerAddress.toLowerCase()}`,
17
+ 64,
18
+ )
19
+ }
20
+
21
+ export function deriveContinuityAesKey(
22
+ walletSignature: string,
23
+ sharedSecret: Uint8Array,
24
+ salt: Uint8Array,
25
+ ownerAddress: string,
26
+ ): Buffer {
27
+ return Buffer.from(hkdf(
28
+ Buffer.concat([
29
+ Buffer.from(walletSignature, 'utf8'),
30
+ Buffer.from('\n', 'utf8'),
31
+ Buffer.from(sharedSecret),
32
+ ]),
33
+ salt,
34
+ `ethagent:${CONTINUITY_SNAPSHOT_ENVELOPE_VERSION}:aes-256-gcm:${ownerAddress.toLowerCase()}`,
35
+ 32,
36
+ ))
37
+ }
38
+
39
+ export function deriveTransferSlotKey(walletSignature: string, salt: Uint8Array, address: string, challenge: string): Buffer {
40
+ return Buffer.from(hkdf(
41
+ Buffer.from(walletSignature, 'utf8'),
42
+ salt,
43
+ `ethagent:${CONTINUITY_SNAPSHOT_ENVELOPE_VERSION}:transfer-slot:${address.toLowerCase()}:${sha256Hex(challenge)}`,
44
+ 32,
45
+ ))
46
+ }
47
+
48
+ export function deriveWalletRestoreKemSeed(walletSignature: string, salt: Uint8Array, address: string, challenge: string): Uint8Array {
49
+ return hkdf(
50
+ Buffer.from(walletSignature, 'utf8'),
51
+ salt,
52
+ `ethagent:${CONTINUITY_SNAPSHOT_ENVELOPE_VERSION}:wallet-restore-kem:${address.toLowerCase()}:${sha256Hex(challenge)}`,
53
+ 64,
54
+ )
55
+ }
56
+
57
+ export function deriveWalletSlotAesKey(
58
+ sharedSecret: Uint8Array,
59
+ salt: Uint8Array,
60
+ address: string,
61
+ challenge: string,
62
+ accessManifestHash: string,
63
+ ): Buffer {
64
+ return Buffer.from(hkdf(
65
+ sharedSecret,
66
+ salt,
67
+ `ethagent:${CONTINUITY_SNAPSHOT_ENVELOPE_VERSION}:wallet-slot:${address.toLowerCase()}:${sha256Hex(challenge)}:${accessManifestHash}`,
68
+ 32,
69
+ ))
70
+ }
71
+
72
+ export function continuityAadFor(ownerAddress: string, createdAt: string): Buffer {
73
+ return Buffer.from(`${CONTINUITY_SNAPSHOT_ENVELOPE_VERSION}\n${ownerAddress.toLowerCase()}\n${createdAt}`, 'utf8')
74
+ }
75
+
76
+ export function walletAccessManifestHash(args: {
77
+ ownerAddress: string
78
+ token: ContinuitySnapshotToken
79
+ accessEpoch: number
80
+ accessKeys: WalletContinuityRestoreAccessKey[]
81
+ }): string {
82
+ const token = normalizeContinuitySnapshotToken(args.token)
83
+ const accessKeys = normalizeWalletRestoreAccessKeys(args.accessKeys)
84
+ return sha256Hex(JSON.stringify({
85
+ ownerAddress: toChecksumAddress(args.ownerAddress).toLowerCase(),
86
+ token: {
87
+ chainId: token.chainId,
88
+ identityRegistryAddress: token.identityRegistryAddress.toLowerCase(),
89
+ agentId: token.agentId,
90
+ },
91
+ accessEpoch: args.accessEpoch,
92
+ wallets: accessKeys.map(key => ({
93
+ address: key.address.toLowerCase(),
94
+ challengeHash: sha256Hex(key.challenge),
95
+ salt: key.salt,
96
+ kemPublicKey: key.kemPublicKey,
97
+ })),
98
+ }))
99
+ }
100
+
101
+ export function walletPayloadAadFor(args: {
102
+ ownerAddress: string
103
+ createdAt: string
104
+ token: ContinuitySnapshotToken
105
+ accessEpoch: number
106
+ accessManifestHash: string
107
+ }): Buffer {
108
+ const token = normalizeContinuitySnapshotToken(args.token)
109
+ return Buffer.from([
110
+ CONTINUITY_SNAPSHOT_ENVELOPE_VERSION,
111
+ 'wallet-signature-slots',
112
+ args.ownerAddress.toLowerCase(),
113
+ args.createdAt,
114
+ String(token.chainId),
115
+ token.identityRegistryAddress.toLowerCase(),
116
+ token.agentId,
117
+ String(args.accessEpoch),
118
+ args.accessManifestHash,
119
+ ].join('\n'), 'utf8')
120
+ }
121
+
122
+ export function transferPayloadAadFor(args: {
123
+ ownerAddress: string
124
+ targetAddress: string
125
+ createdAt: string
126
+ token: ContinuitySnapshotToken
127
+ }): Buffer {
128
+ const token = normalizeContinuitySnapshotToken(args.token)
129
+ return Buffer.from([
130
+ CONTINUITY_SNAPSHOT_ENVELOPE_VERSION,
131
+ 'transfer',
132
+ args.ownerAddress.toLowerCase(),
133
+ args.targetAddress.toLowerCase(),
134
+ args.createdAt,
135
+ String(token.chainId),
136
+ token.identityRegistryAddress.toLowerCase(),
137
+ token.agentId,
138
+ ].join('\n'), 'utf8')
139
+ }
140
+
141
+ export function walletSlotAadFor(
142
+ slot: Pick<WalletContinuitySnapshotSlot, 'address' | 'challenge' | 'kemPublicKey' | 'kemCiphertext'>,
143
+ payloadAad: Buffer,
144
+ ): Buffer {
145
+ return Buffer.concat([
146
+ payloadAad,
147
+ Buffer.from([
148
+ '',
149
+ 'wallet-slot',
150
+ slot.address.toLowerCase(),
151
+ sha256Hex(slot.challenge),
152
+ slot.kemPublicKey,
153
+ slot.kemCiphertext,
154
+ ].join('\n'), 'utf8'),
155
+ ])
156
+ }
157
+
158
+ export function transferSlotAadFor(
159
+ slot: Pick<TransferContinuitySnapshotSlot, 'address' | 'challenge'>,
160
+ payloadAad: Buffer,
161
+ ): Buffer {
162
+ return Buffer.concat([
163
+ payloadAad,
164
+ Buffer.from(`\nslot\n${slot.address.toLowerCase()}\n${sha256Hex(slot.challenge)}`, 'utf8'),
165
+ ])
166
+ }
167
+
168
+ export function sha256Hex(value: string | Uint8Array): string {
169
+ return crypto.createHash('sha256').update(value).digest('hex')
170
+ }
171
+
172
+ export function toBase64(bytes: Uint8Array): string {
173
+ return Buffer.from(bytes).toString('base64')
174
+ }
175
+
176
+ export function fromBase64(value: string): Uint8Array {
177
+ return new Uint8Array(Buffer.from(value, 'base64'))
178
+ }
179
+
180
+ function hkdf(ikm: Uint8Array, salt: Uint8Array, info: string, length: number): Uint8Array {
181
+ return new Uint8Array(crypto.hkdfSync('sha256', ikm, salt, Buffer.from(info, 'utf8'), length))
182
+ }