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.
- package/README.md +6 -1
- package/package.json +3 -1
- package/src/app/FirstRun.tsx +1 -24
- package/src/app/firstRunConfig.ts +26 -0
- package/src/auth/openaiOAuth/landingPage.ts +2 -11
- package/src/chat/ChatScreen.tsx +32 -117
- package/src/chat/MessageList.tsx +18 -260
- package/src/chat/chatEnvironment.ts +16 -0
- package/src/chat/chatTurnContext.ts +50 -0
- package/src/chat/chatTurnOrchestrator.ts +5 -112
- package/src/chat/chatTurnRows.ts +64 -0
- package/src/chat/commands.ts +3 -178
- package/src/chat/continuityEditReview.ts +42 -0
- package/src/chat/input/ChatInput.tsx +10 -144
- package/src/chat/input/chatInputHelpers.ts +62 -0
- package/src/chat/input/inputRendering.tsx +93 -0
- package/src/chat/messageMarkdown.ts +220 -0
- package/src/chat/messageRows.ts +43 -0
- package/src/chat/planImplementation.ts +62 -0
- package/src/chat/slashCommandHandlers.ts +165 -0
- package/src/chat/slashCommandViews.ts +120 -0
- package/src/cli/main.tsx +7 -0
- package/src/identity/continuity/challenges.ts +123 -0
- package/src/identity/continuity/envelope.ts +49 -1484
- package/src/identity/continuity/envelopeCreate.ts +322 -0
- package/src/identity/continuity/envelopeCrypto.ts +182 -0
- package/src/identity/continuity/envelopeParse.ts +441 -0
- package/src/identity/continuity/envelopeTypes.ts +204 -0
- package/src/identity/continuity/envelopeVersion.ts +1 -0
- package/src/identity/continuity/payloadNormalization.ts +183 -0
- package/src/identity/continuity/publicSkills.ts +5 -5
- package/src/identity/continuity/skills/loadSkills.ts +12 -69
- package/src/identity/continuity/skills/skillPaths.ts +76 -0
- package/src/identity/continuity/skillsNormalization.ts +119 -0
- package/src/identity/continuity/snapshotToken.ts +28 -0
- package/src/identity/hub/continuity/completion.ts +67 -0
- package/src/identity/hub/continuity/effects.ts +5 -62
- package/src/identity/hub/profile/effects.ts +6 -170
- package/src/identity/hub/profile/operatorSave.ts +202 -0
- package/src/identity/registry/erc8004/metadata.ts +31 -23
- package/src/identity/wallet/browserWallet/html.ts +1 -57
- package/src/identity/wallet/browserWallet/walletPageSource.ts +85 -0
- package/src/identity/wallet/page/controller.ts +1 -1
- package/src/identity/wallet/page/errorView.ts +122 -0
- package/src/identity/wallet/page/view.ts +3 -114
- package/src/mcp/manager.ts +8 -66
- package/src/mcp/managerHelpers.ts +70 -0
- package/src/models/ModelPicker.tsx +69 -889
- package/src/models/huggingface.ts +20 -137
- package/src/models/huggingfaceStorage.ts +136 -0
- package/src/models/llamacpp.ts +37 -303
- package/src/models/llamacppCommands.ts +44 -0
- package/src/models/llamacppConfig.ts +34 -0
- package/src/models/llamacppDiscovery.ts +176 -0
- package/src/models/llamacppOutput.ts +65 -0
- package/src/models/modelPickerCatalogFlow.ts +56 -0
- package/src/models/modelPickerCredentials.ts +166 -0
- package/src/models/modelPickerData.ts +41 -0
- package/src/models/modelPickerDisplay.tsx +132 -0
- package/src/models/modelPickerHfFlow.ts +192 -0
- package/src/models/modelPickerLocalRunnerFlow.ts +115 -0
- package/src/models/modelPickerTypes.ts +69 -0
- package/src/models/modelPickerUninstallFlow.ts +48 -0
- package/src/models/modelPickerViewHelpers.ts +174 -0
- package/src/providers/openai-chat.ts +5 -124
- package/src/providers/openaiChatWire.ts +124 -0
- package/src/runtime/providerTurn.ts +38 -0
- package/src/runtime/textToolParser.ts +161 -0
- package/src/runtime/toolIntent.ts +1 -1
- package/src/runtime/turn.ts +43 -499
- package/src/runtime/turnNudges.ts +223 -0
- package/src/runtime/turnTypes.ts +86 -0
- 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
|
+
}
|