ethagent 3.0.2 → 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 +15 -116
- 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/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/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/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
|
@@ -1,1484 +1,49 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
type ContinuitySnapshotToken = {
|
|
52
|
-
chainId: number
|
|
53
|
-
identityRegistryAddress: string
|
|
54
|
-
agentId: string
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
type TransferContinuitySnapshotSlot = {
|
|
58
|
-
address: string
|
|
59
|
-
challenge: string
|
|
60
|
-
salt: string
|
|
61
|
-
nonce: string
|
|
62
|
-
encryptedKey: string
|
|
63
|
-
tag: string
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export type WalletContinuityRestoreAccessKey = {
|
|
67
|
-
address: string
|
|
68
|
-
challenge: string
|
|
69
|
-
salt: string
|
|
70
|
-
kemPublicKey: string
|
|
71
|
-
createdAt?: string
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
type WalletContinuitySnapshotSlot = {
|
|
75
|
-
address: string
|
|
76
|
-
challenge: string
|
|
77
|
-
salt: string
|
|
78
|
-
kemPublicKey: string
|
|
79
|
-
kemCiphertext: string
|
|
80
|
-
nonce: string
|
|
81
|
-
encryptedKey: string
|
|
82
|
-
tag: string
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
type SignatureContinuitySnapshotEnvelope = {
|
|
86
|
-
version: 1
|
|
87
|
-
envelopeVersion: typeof CONTINUITY_SNAPSHOT_ENVELOPE_VERSION
|
|
88
|
-
ownerAddress: string
|
|
89
|
-
createdAt: string
|
|
90
|
-
challenge: string
|
|
91
|
-
crypto: {
|
|
92
|
-
kem: 'ML-KEM-1024'
|
|
93
|
-
aead: 'AES-256-GCM'
|
|
94
|
-
kdf: 'HKDF-SHA256'
|
|
95
|
-
signature: 'EIP-191'
|
|
96
|
-
decryptsWith?: 'owner-signature'
|
|
97
|
-
}
|
|
98
|
-
salt: string
|
|
99
|
-
kemPublicKey: string
|
|
100
|
-
kemCiphertext: string
|
|
101
|
-
nonce: string
|
|
102
|
-
ciphertext: string
|
|
103
|
-
tag: string
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
type WalletContinuitySnapshotEnvelope = {
|
|
107
|
-
version: 1
|
|
108
|
-
envelopeVersion: typeof CONTINUITY_SNAPSHOT_ENVELOPE_VERSION
|
|
109
|
-
ownerAddress: string
|
|
110
|
-
createdAt: string
|
|
111
|
-
token: ContinuitySnapshotToken
|
|
112
|
-
accessEpoch: number
|
|
113
|
-
accessManifestHash: string
|
|
114
|
-
crypto: {
|
|
115
|
-
kem: 'ML-KEM-1024'
|
|
116
|
-
aead: 'AES-256-GCM'
|
|
117
|
-
kdf: 'HKDF-SHA256'
|
|
118
|
-
signature: 'EIP-191'
|
|
119
|
-
decryptsWith: 'wallet-signature-slots'
|
|
120
|
-
}
|
|
121
|
-
payloadNonce: string
|
|
122
|
-
payloadCiphertext: string
|
|
123
|
-
payloadTag: string
|
|
124
|
-
payloadHash: string
|
|
125
|
-
slots: WalletContinuitySnapshotSlot[]
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export type TransferContinuitySnapshotEnvelope = {
|
|
129
|
-
version: 1
|
|
130
|
-
envelopeVersion: typeof CONTINUITY_SNAPSHOT_ENVELOPE_VERSION
|
|
131
|
-
ownerAddress: string
|
|
132
|
-
createdAt: string
|
|
133
|
-
challenge: string
|
|
134
|
-
token: ContinuitySnapshotToken
|
|
135
|
-
targetAddress: string
|
|
136
|
-
targetHandle?: string
|
|
137
|
-
crypto: {
|
|
138
|
-
aead: 'AES-256-GCM'
|
|
139
|
-
kdf: 'HKDF-SHA256'
|
|
140
|
-
signature: 'EIP-191'
|
|
141
|
-
decryptsWith: 'transfer-signature-slot'
|
|
142
|
-
}
|
|
143
|
-
payloadNonce: string
|
|
144
|
-
payloadCiphertext: string
|
|
145
|
-
payloadTag: string
|
|
146
|
-
payloadHash: string
|
|
147
|
-
slots: {
|
|
148
|
-
owner: TransferContinuitySnapshotSlot
|
|
149
|
-
target: TransferContinuitySnapshotSlot
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
export type ContinuitySnapshotEnvelope =
|
|
154
|
-
| SignatureContinuitySnapshotEnvelope
|
|
155
|
-
| WalletContinuitySnapshotEnvelope
|
|
156
|
-
| TransferContinuitySnapshotEnvelope
|
|
157
|
-
|
|
158
|
-
type CreateContinuitySnapshotEnvelopeArgs = {
|
|
159
|
-
ownerAddress: string
|
|
160
|
-
walletSignature: string
|
|
161
|
-
payload: Omit<ContinuitySnapshotPayload, 'version' | 'ownerAddress' | 'createdAt'> & {
|
|
162
|
-
createdAt?: string
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
type CreateTransferContinuitySnapshotEnvelopeArgs = {
|
|
167
|
-
ownerAddress: string
|
|
168
|
-
ownerWalletSignature: string
|
|
169
|
-
targetAddress: string
|
|
170
|
-
targetWalletSignature: string
|
|
171
|
-
targetHandle?: string
|
|
172
|
-
token: ContinuitySnapshotToken
|
|
173
|
-
payload: Omit<ContinuitySnapshotPayload, 'version' | 'ownerAddress' | 'createdAt'> & {
|
|
174
|
-
createdAt?: string
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
type CreateWalletContinuitySnapshotEnvelopeArgs = {
|
|
179
|
-
ownerAddress: string
|
|
180
|
-
token: ContinuitySnapshotToken
|
|
181
|
-
signerAddress: string
|
|
182
|
-
signerWalletSignature: string
|
|
183
|
-
accessKeys: WalletContinuityRestoreAccessKey[]
|
|
184
|
-
accessEpoch?: number
|
|
185
|
-
payload: Omit<ContinuitySnapshotPayload, 'version' | 'ownerAddress' | 'createdAt'> & {
|
|
186
|
-
createdAt?: string
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
type RestoreContinuitySnapshotEnvelopeArgs = {
|
|
191
|
-
envelope: ContinuitySnapshotEnvelope
|
|
192
|
-
walletSignature: string
|
|
193
|
-
currentOwnerAddress?: string
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
export class ContinuitySnapshotOwnerMismatchError extends Error {
|
|
197
|
-
constructor(
|
|
198
|
-
readonly snapshotOwner: string,
|
|
199
|
-
readonly currentOwner: string,
|
|
200
|
-
) {
|
|
201
|
-
super('Continuity snapshot is encrypted for another wallet')
|
|
202
|
-
this.name = 'ContinuitySnapshotOwnerMismatchError'
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
export class ContinuityTransferSnapshotTargetMismatchError extends Error {
|
|
207
|
-
constructor(
|
|
208
|
-
readonly snapshotOwner: string,
|
|
209
|
-
readonly targetOwner: string,
|
|
210
|
-
readonly currentOwner: string,
|
|
211
|
-
) {
|
|
212
|
-
super('Transfer snapshot receiver does not match the current token owner')
|
|
213
|
-
this.name = 'ContinuityTransferSnapshotTargetMismatchError'
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
export class ContinuitySnapshotRestoreSlotMissingError extends Error {
|
|
218
|
-
constructor(readonly walletAddress: string) {
|
|
219
|
-
super('Restore slot missing')
|
|
220
|
-
this.name = 'ContinuitySnapshotRestoreSlotMissingError'
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const CONTINUITY_SNAPSHOT_CHALLENGE_MESSAGES = [
|
|
225
|
-
'Save or Restore Identity Files',
|
|
226
|
-
'Action: encrypt or decrypt local identity files',
|
|
227
|
-
'Private: SOUL.md, MEMORY.md, skills',
|
|
228
|
-
'Public: public skills and profile',
|
|
229
|
-
'Safety: no transaction, spending, or approvals',
|
|
230
|
-
'Version: 2',
|
|
231
|
-
] as const
|
|
232
|
-
|
|
233
|
-
export function createContinuitySnapshotChallenge(ownerAddress: string): string {
|
|
234
|
-
const checksum = toChecksumAddress(ownerAddress)
|
|
235
|
-
return [
|
|
236
|
-
CONTINUITY_SNAPSHOT_CHALLENGE_MESSAGES[0],
|
|
237
|
-
`Owner: ${checksum}`,
|
|
238
|
-
...CONTINUITY_SNAPSHOT_CHALLENGE_MESSAGES.slice(1),
|
|
239
|
-
].join('\n')
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
export const TRANSFER_SNAPSHOT_CHALLENGE_HEADER_LEGACY = 'Prepare Transfer Restore Snapshot'
|
|
243
|
-
export const TRANSFER_SNAPSHOT_CHALLENGE_HEADER_SENDER = 'Prepare Transfer Snapshot · Sender Restore Slot'
|
|
244
|
-
export const TRANSFER_SNAPSHOT_CHALLENGE_HEADER_RECEIVER = 'Prepare Transfer Snapshot · Receiver Restore Slot'
|
|
245
|
-
|
|
246
|
-
export function createTransferContinuitySnapshotChallenge(args: {
|
|
247
|
-
token: ContinuitySnapshotToken
|
|
248
|
-
ownerAddress: string
|
|
249
|
-
targetAddress: string
|
|
250
|
-
role?: 'sender' | 'receiver'
|
|
251
|
-
}): string {
|
|
252
|
-
const token = normalizeContinuitySnapshotToken(args.token)
|
|
253
|
-
const ownerAddress = toChecksumAddress(args.ownerAddress)
|
|
254
|
-
const targetAddress = toChecksumAddress(args.targetAddress)
|
|
255
|
-
const header = args.role === 'sender'
|
|
256
|
-
? TRANSFER_SNAPSHOT_CHALLENGE_HEADER_SENDER
|
|
257
|
-
: args.role === 'receiver'
|
|
258
|
-
? TRANSFER_SNAPSHOT_CHALLENGE_HEADER_RECEIVER
|
|
259
|
-
: TRANSFER_SNAPSHOT_CHALLENGE_HEADER_LEGACY
|
|
260
|
-
return [
|
|
261
|
-
header,
|
|
262
|
-
`ERC-8004 Chain ID: ${token.chainId}`,
|
|
263
|
-
`ERC-8004 Registry: ${token.identityRegistryAddress}`,
|
|
264
|
-
`ERC-8004 Token ID: ${token.agentId}`,
|
|
265
|
-
`Sender Owner: ${ownerAddress}`,
|
|
266
|
-
`Receiver Owner: ${targetAddress}`,
|
|
267
|
-
'Action: encrypt or decrypt local identity files for this token transfer',
|
|
268
|
-
'Private: SOUL.md, MEMORY.md, skills',
|
|
269
|
-
'Public: public skills and profile',
|
|
270
|
-
'Safety: no transaction, spending, or approvals',
|
|
271
|
-
'Version: 2',
|
|
272
|
-
].join('\n')
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
export type WalletChallengePurpose =
|
|
276
|
-
| 'create-agent'
|
|
277
|
-
| 'update-snapshot'
|
|
278
|
-
| 'update-ens-snapshot'
|
|
279
|
-
| 'clear-ens-snapshot'
|
|
280
|
-
| 'update-profile-snapshot'
|
|
281
|
-
| 'update-operators-snapshot'
|
|
282
|
-
| 'refetch-snapshot'
|
|
283
|
-
| 'operator-proof'
|
|
284
|
-
| 'restore-owner'
|
|
285
|
-
| 'restore-operator'
|
|
286
|
-
| 'transfer-prepare-sender'
|
|
287
|
-
| 'transfer-prepare-receiver'
|
|
288
|
-
|
|
289
|
-
const WALLET_CHALLENGE_V2_COPY: Record<WalletChallengePurpose, { title: string; action: string }> = {
|
|
290
|
-
'create-agent': { title: 'Create Agent Snapshot Key', action: 'Action: encrypt the new agent snapshot for owner restore' },
|
|
291
|
-
'update-snapshot': { title: 'Save Snapshot Encryption Key', action: 'Action: encrypt the updated agent snapshot' },
|
|
292
|
-
'update-ens-snapshot': { title: 'Update ENS in Agent Snapshot', action: 'Action: encrypt the snapshot with the new ENS name. No onchain ENS records change.' },
|
|
293
|
-
'clear-ens-snapshot': { title: 'Unlink ENS from Agent', action: 'Action: encrypt the snapshot with no ENS name. No onchain ENS records change.' },
|
|
294
|
-
'update-profile-snapshot': { title: 'Update Public Profile Snapshot Key', action: 'Action: encrypt the snapshot with the updated profile' },
|
|
295
|
-
'update-operators-snapshot': { title: 'Update Operator Wallets Snapshot Key', action: 'Action: encrypt the snapshot with the updated operator list' },
|
|
296
|
-
'refetch-snapshot': { title: 'Refetch Latest Snapshot', action: 'Action: decrypt the latest published snapshot' },
|
|
297
|
-
'operator-proof': { title: 'Authorize Operator Wallet Restore Access', action: 'Action: prove this operator wallet can decrypt future snapshots' },
|
|
298
|
-
'restore-owner': { title: 'Restore Agent with Owner Wallet', action: 'Action: decrypt the snapshot for the owner wallet' },
|
|
299
|
-
'restore-operator': { title: 'Restore Agent with Operator Wallet', action: 'Action: decrypt the snapshot for the authorized operator wallet' },
|
|
300
|
-
'transfer-prepare-sender': { title: 'Prepare Token Transfer (Sender)', action: 'Action: encrypt the transfer snapshot for the receiver' },
|
|
301
|
-
'transfer-prepare-receiver': { title: 'Receive Token Transfer (Receiver)', action: 'Action: prepare receiver decryption for the transfer snapshot' },
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
export function createWalletRestoreAccessChallenge(args: {
|
|
305
|
-
token: ContinuitySnapshotToken
|
|
306
|
-
ownerAddress: string
|
|
307
|
-
walletAddress: string
|
|
308
|
-
accessEpoch?: number
|
|
309
|
-
purpose?: WalletChallengePurpose
|
|
310
|
-
}): string {
|
|
311
|
-
const token = normalizeContinuitySnapshotToken(args.token)
|
|
312
|
-
const ownerAddress = toChecksumAddress(args.ownerAddress)
|
|
313
|
-
const walletAddress = toChecksumAddress(args.walletAddress)
|
|
314
|
-
if (args.purpose) {
|
|
315
|
-
const copy = WALLET_CHALLENGE_V2_COPY[args.purpose]
|
|
316
|
-
return [
|
|
317
|
-
copy.title,
|
|
318
|
-
`ERC-8004 Chain ID: ${token.chainId}`,
|
|
319
|
-
`ERC-8004 Registry: ${token.identityRegistryAddress}`,
|
|
320
|
-
`ERC-8004 Token ID: ${token.agentId}`,
|
|
321
|
-
`Owner: ${ownerAddress}`,
|
|
322
|
-
`Wallet: ${walletAddress}`,
|
|
323
|
-
`Access Epoch: ${args.accessEpoch ?? 1}`,
|
|
324
|
-
copy.action,
|
|
325
|
-
'Private: SOUL.md, MEMORY.md, skills',
|
|
326
|
-
'Safety: no transaction, spending, or approvals',
|
|
327
|
-
'Version: 3',
|
|
328
|
-
].join('\n')
|
|
329
|
-
}
|
|
330
|
-
return [
|
|
331
|
-
'Authorize Wallet Restore Access',
|
|
332
|
-
`ERC-8004 Chain ID: ${token.chainId}`,
|
|
333
|
-
`ERC-8004 Registry: ${token.identityRegistryAddress}`,
|
|
334
|
-
`ERC-8004 Token ID: ${token.agentId}`,
|
|
335
|
-
`Owner: ${ownerAddress}`,
|
|
336
|
-
`Wallet: ${walletAddress}`,
|
|
337
|
-
`Access Epoch: ${args.accessEpoch ?? 1}`,
|
|
338
|
-
'Action: create a restore key for encrypted identity snapshots',
|
|
339
|
-
'Private: SOUL.md, MEMORY.md, skills',
|
|
340
|
-
'Safety: no transaction, spending, or approvals',
|
|
341
|
-
'Version: 2',
|
|
342
|
-
].join('\n')
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
export function createWalletRestoreAccessKey(args: {
|
|
346
|
-
token: ContinuitySnapshotToken
|
|
347
|
-
ownerAddress: string
|
|
348
|
-
walletAddress: string
|
|
349
|
-
walletSignature: string
|
|
350
|
-
accessEpoch?: number
|
|
351
|
-
createdAt?: string
|
|
352
|
-
salt?: string
|
|
353
|
-
purpose?: WalletChallengePurpose
|
|
354
|
-
}): WalletContinuityRestoreAccessKey {
|
|
355
|
-
const walletAddress = toChecksumAddress(args.walletAddress)
|
|
356
|
-
const challenge = createWalletRestoreAccessChallenge({
|
|
357
|
-
token: args.token,
|
|
358
|
-
ownerAddress: args.ownerAddress,
|
|
359
|
-
walletAddress,
|
|
360
|
-
accessEpoch: args.accessEpoch,
|
|
361
|
-
purpose: args.purpose,
|
|
362
|
-
})
|
|
363
|
-
assertSignatureForAddress(challenge, args.walletSignature, walletAddress)
|
|
364
|
-
const salt = args.salt ? fromBase64(args.salt) : crypto.randomBytes(32)
|
|
365
|
-
const kemSeed = deriveWalletRestoreKemSeed(args.walletSignature, salt, walletAddress, challenge)
|
|
366
|
-
const kemKeys = ml_kem1024.keygen(kemSeed)
|
|
367
|
-
return {
|
|
368
|
-
address: walletAddress,
|
|
369
|
-
challenge,
|
|
370
|
-
salt: toBase64(salt),
|
|
371
|
-
kemPublicKey: toBase64(kemKeys.publicKey),
|
|
372
|
-
...(args.createdAt ? { createdAt: args.createdAt } : {}),
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
export function createContinuitySnapshotEnvelope(args: CreateContinuitySnapshotEnvelopeArgs): SignatureContinuitySnapshotEnvelope {
|
|
377
|
-
const ownerAddress = toChecksumAddress(args.ownerAddress)
|
|
378
|
-
const challenge = createContinuitySnapshotChallenge(ownerAddress)
|
|
379
|
-
assertSignatureForAddress(challenge, args.walletSignature, ownerAddress)
|
|
380
|
-
|
|
381
|
-
const createdAt = args.payload.createdAt ?? new Date().toISOString()
|
|
382
|
-
const payload = continuityPayloadFromArgs({
|
|
383
|
-
ownerAddress,
|
|
384
|
-
createdAt,
|
|
385
|
-
payload: args.payload,
|
|
386
|
-
})
|
|
387
|
-
|
|
388
|
-
const salt = crypto.randomBytes(32)
|
|
389
|
-
const kemSeed = deriveContinuityKemSeed(args.walletSignature, salt, ownerAddress)
|
|
390
|
-
const kemKeys = ml_kem1024.keygen(kemSeed)
|
|
391
|
-
const kem = ml_kem1024.encapsulate(kemKeys.publicKey)
|
|
392
|
-
const key = deriveContinuityAesKey(args.walletSignature, kem.sharedSecret, salt, ownerAddress)
|
|
393
|
-
const nonce = crypto.randomBytes(12)
|
|
394
|
-
const cipher = crypto.createCipheriv('aes-256-gcm', key, nonce)
|
|
395
|
-
cipher.setAAD(continuityAadFor(ownerAddress, createdAt))
|
|
396
|
-
const plaintext = Buffer.from(JSON.stringify(payload), 'utf8')
|
|
397
|
-
const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()])
|
|
398
|
-
const tag = cipher.getAuthTag()
|
|
399
|
-
|
|
400
|
-
return {
|
|
401
|
-
version: 1,
|
|
402
|
-
envelopeVersion: CONTINUITY_SNAPSHOT_ENVELOPE_VERSION,
|
|
403
|
-
ownerAddress,
|
|
404
|
-
createdAt,
|
|
405
|
-
challenge,
|
|
406
|
-
crypto: {
|
|
407
|
-
kem: 'ML-KEM-1024',
|
|
408
|
-
aead: 'AES-256-GCM',
|
|
409
|
-
kdf: 'HKDF-SHA256',
|
|
410
|
-
signature: 'EIP-191',
|
|
411
|
-
decryptsWith: 'owner-signature',
|
|
412
|
-
},
|
|
413
|
-
salt: toBase64(salt),
|
|
414
|
-
kemPublicKey: toBase64(kemKeys.publicKey),
|
|
415
|
-
kemCiphertext: toBase64(kem.cipherText),
|
|
416
|
-
nonce: toBase64(nonce),
|
|
417
|
-
ciphertext: toBase64(encrypted),
|
|
418
|
-
tag: toBase64(tag),
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
export function createWalletContinuitySnapshotEnvelope(
|
|
423
|
-
args: CreateWalletContinuitySnapshotEnvelopeArgs,
|
|
424
|
-
): WalletContinuitySnapshotEnvelope {
|
|
425
|
-
const ownerAddress = toChecksumAddress(args.ownerAddress)
|
|
426
|
-
const signerAddress = toChecksumAddress(args.signerAddress)
|
|
427
|
-
const token = normalizeContinuitySnapshotToken(args.token)
|
|
428
|
-
const accessEpoch = args.accessEpoch ?? 1
|
|
429
|
-
const accessKeys = normalizeWalletRestoreAccessKeys(args.accessKeys)
|
|
430
|
-
if (accessKeys.length === 0) throw new Error('At least one restore access key is required')
|
|
431
|
-
const signerKey = accessKeys.find(key => key.address.toLowerCase() === signerAddress.toLowerCase())
|
|
432
|
-
if (!signerKey) throw new Error('Snapshot signer is not an authorized restore wallet')
|
|
433
|
-
assertSignatureForAddress(signerKey.challenge, args.signerWalletSignature, signerAddress)
|
|
434
|
-
|
|
435
|
-
const createdAt = args.payload.createdAt ?? new Date().toISOString()
|
|
436
|
-
const payload = continuityPayloadFromArgs({
|
|
437
|
-
ownerAddress,
|
|
438
|
-
createdAt,
|
|
439
|
-
payload: {
|
|
440
|
-
...args.payload,
|
|
441
|
-
agent: normalizeAgentSnapshot({
|
|
442
|
-
...args.payload.agent,
|
|
443
|
-
chainId: args.payload.agent.chainId ?? token.chainId,
|
|
444
|
-
identityRegistryAddress: args.payload.agent.identityRegistryAddress ?? token.identityRegistryAddress,
|
|
445
|
-
agentId: args.payload.agent.agentId ?? token.agentId,
|
|
446
|
-
}),
|
|
447
|
-
},
|
|
448
|
-
})
|
|
449
|
-
const plaintext = Buffer.from(JSON.stringify(payload), 'utf8')
|
|
450
|
-
const contentKey = crypto.randomBytes(32)
|
|
451
|
-
const payloadNonce = crypto.randomBytes(12)
|
|
452
|
-
const accessManifestHash = walletAccessManifestHash({ ownerAddress, token, accessEpoch, accessKeys })
|
|
453
|
-
const payloadAad = walletPayloadAadFor({ ownerAddress, createdAt, token, accessEpoch, accessManifestHash })
|
|
454
|
-
const cipher = crypto.createCipheriv('aes-256-gcm', contentKey, payloadNonce)
|
|
455
|
-
cipher.setAAD(payloadAad)
|
|
456
|
-
const payloadCiphertext = Buffer.concat([cipher.update(plaintext), cipher.final()])
|
|
457
|
-
const payloadTag = cipher.getAuthTag()
|
|
458
|
-
|
|
459
|
-
return {
|
|
460
|
-
version: 1,
|
|
461
|
-
envelopeVersion: CONTINUITY_SNAPSHOT_ENVELOPE_VERSION,
|
|
462
|
-
ownerAddress,
|
|
463
|
-
createdAt,
|
|
464
|
-
token,
|
|
465
|
-
accessEpoch,
|
|
466
|
-
accessManifestHash,
|
|
467
|
-
crypto: {
|
|
468
|
-
kem: 'ML-KEM-1024',
|
|
469
|
-
aead: 'AES-256-GCM',
|
|
470
|
-
kdf: 'HKDF-SHA256',
|
|
471
|
-
signature: 'EIP-191',
|
|
472
|
-
decryptsWith: 'wallet-signature-slots',
|
|
473
|
-
},
|
|
474
|
-
payloadNonce: toBase64(payloadNonce),
|
|
475
|
-
payloadCiphertext: toBase64(payloadCiphertext),
|
|
476
|
-
payloadTag: toBase64(payloadTag),
|
|
477
|
-
payloadHash: sha256Hex(plaintext),
|
|
478
|
-
slots: accessKeys.map(key => createWalletSlot({ accessKey: key, contentKey, payloadAad, accessManifestHash })),
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
export function createTransferContinuitySnapshotEnvelope(
|
|
483
|
-
args: CreateTransferContinuitySnapshotEnvelopeArgs,
|
|
484
|
-
): TransferContinuitySnapshotEnvelope {
|
|
485
|
-
const ownerAddress = toChecksumAddress(args.ownerAddress)
|
|
486
|
-
const targetAddress = toChecksumAddress(args.targetAddress)
|
|
487
|
-
if (ownerAddress.toLowerCase() === targetAddress.toLowerCase()) {
|
|
488
|
-
throw new Error('Receiver wallet must be different from sender wallet')
|
|
489
|
-
}
|
|
490
|
-
const token = normalizeContinuitySnapshotToken(args.token)
|
|
491
|
-
const senderChallenge = createTransferContinuitySnapshotChallenge({ token, ownerAddress, targetAddress, role: 'sender' })
|
|
492
|
-
const receiverChallenge = createTransferContinuitySnapshotChallenge({ token, ownerAddress, targetAddress, role: 'receiver' })
|
|
493
|
-
const challenge = createTransferContinuitySnapshotChallenge({ token, ownerAddress, targetAddress })
|
|
494
|
-
assertSignatureForAddress(senderChallenge, args.ownerWalletSignature, ownerAddress)
|
|
495
|
-
assertSignatureForAddress(receiverChallenge, args.targetWalletSignature, targetAddress)
|
|
496
|
-
|
|
497
|
-
const createdAt = args.payload.createdAt ?? new Date().toISOString()
|
|
498
|
-
const payload = continuityPayloadFromArgs({
|
|
499
|
-
ownerAddress,
|
|
500
|
-
createdAt,
|
|
501
|
-
payload: {
|
|
502
|
-
...args.payload,
|
|
503
|
-
agent: normalizeAgentSnapshot({
|
|
504
|
-
...args.payload.agent,
|
|
505
|
-
chainId: args.payload.agent.chainId ?? token.chainId,
|
|
506
|
-
identityRegistryAddress: args.payload.agent.identityRegistryAddress ?? token.identityRegistryAddress,
|
|
507
|
-
agentId: args.payload.agent.agentId ?? token.agentId,
|
|
508
|
-
}),
|
|
509
|
-
},
|
|
510
|
-
})
|
|
511
|
-
const plaintext = Buffer.from(JSON.stringify(payload), 'utf8')
|
|
512
|
-
const contentKey = crypto.randomBytes(32)
|
|
513
|
-
const payloadNonce = crypto.randomBytes(12)
|
|
514
|
-
const payloadAad = transferPayloadAadFor({ ownerAddress, targetAddress, createdAt, token })
|
|
515
|
-
const cipher = crypto.createCipheriv('aes-256-gcm', contentKey, payloadNonce)
|
|
516
|
-
cipher.setAAD(payloadAad)
|
|
517
|
-
const payloadCiphertext = Buffer.concat([cipher.update(plaintext), cipher.final()])
|
|
518
|
-
const payloadTag = cipher.getAuthTag()
|
|
519
|
-
|
|
520
|
-
const ownerSlot = createTransferSlot({
|
|
521
|
-
address: ownerAddress,
|
|
522
|
-
challenge: senderChallenge,
|
|
523
|
-
walletSignature: args.ownerWalletSignature,
|
|
524
|
-
contentKey,
|
|
525
|
-
payloadAad,
|
|
526
|
-
})
|
|
527
|
-
const targetSlot = createTransferSlot({
|
|
528
|
-
address: targetAddress,
|
|
529
|
-
challenge: receiverChallenge,
|
|
530
|
-
walletSignature: args.targetWalletSignature,
|
|
531
|
-
contentKey,
|
|
532
|
-
payloadAad,
|
|
533
|
-
})
|
|
534
|
-
|
|
535
|
-
return {
|
|
536
|
-
version: 1,
|
|
537
|
-
envelopeVersion: CONTINUITY_SNAPSHOT_ENVELOPE_VERSION,
|
|
538
|
-
ownerAddress,
|
|
539
|
-
createdAt,
|
|
540
|
-
challenge,
|
|
541
|
-
token,
|
|
542
|
-
targetAddress,
|
|
543
|
-
...(args.targetHandle ? { targetHandle: args.targetHandle } : {}),
|
|
544
|
-
crypto: {
|
|
545
|
-
aead: 'AES-256-GCM',
|
|
546
|
-
kdf: 'HKDF-SHA256',
|
|
547
|
-
signature: 'EIP-191',
|
|
548
|
-
decryptsWith: 'transfer-signature-slot',
|
|
549
|
-
},
|
|
550
|
-
payloadNonce: toBase64(payloadNonce),
|
|
551
|
-
payloadCiphertext: toBase64(payloadCiphertext),
|
|
552
|
-
payloadTag: toBase64(payloadTag),
|
|
553
|
-
payloadHash: sha256Hex(plaintext),
|
|
554
|
-
slots: {
|
|
555
|
-
owner: ownerSlot,
|
|
556
|
-
target: targetSlot,
|
|
557
|
-
},
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
export function restoreContinuitySnapshotEnvelope(args: RestoreContinuitySnapshotEnvelopeArgs): ContinuitySnapshotPayload {
|
|
562
|
-
const envelope = normalizeContinuitySnapshotEnvelope(args.envelope)
|
|
563
|
-
if (isWalletContinuitySnapshotEnvelope(envelope)) {
|
|
564
|
-
return restoreWalletContinuitySnapshotEnvelope({
|
|
565
|
-
envelope,
|
|
566
|
-
walletSignature: args.walletSignature,
|
|
567
|
-
currentOwnerAddress: args.currentOwnerAddress,
|
|
568
|
-
})
|
|
569
|
-
}
|
|
570
|
-
if (isTransferContinuitySnapshotEnvelope(envelope)) {
|
|
571
|
-
return restoreTransferContinuitySnapshotEnvelope({
|
|
572
|
-
envelope,
|
|
573
|
-
walletSignature: args.walletSignature,
|
|
574
|
-
currentOwnerAddress: args.currentOwnerAddress,
|
|
575
|
-
})
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
assertSignatureForAddress(envelope.challenge, args.walletSignature, envelope.ownerAddress)
|
|
579
|
-
|
|
580
|
-
const salt = fromBase64(envelope.salt)
|
|
581
|
-
const kemSeed = deriveContinuityKemSeed(args.walletSignature, salt, envelope.ownerAddress)
|
|
582
|
-
const kemKeys = ml_kem1024.keygen(kemSeed)
|
|
583
|
-
const expectedPublicKey = toBase64(kemKeys.publicKey)
|
|
584
|
-
if (expectedPublicKey !== envelope.kemPublicKey) {
|
|
585
|
-
throw new Error('Wallet signature does not match this continuity snapshot')
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
const sharedSecret = ml_kem1024.decapsulate(fromBase64(envelope.kemCiphertext), kemKeys.secretKey)
|
|
589
|
-
const key = deriveContinuityAesKey(args.walletSignature, sharedSecret, salt, envelope.ownerAddress)
|
|
590
|
-
const decipher = crypto.createDecipheriv('aes-256-gcm', key, fromBase64(envelope.nonce))
|
|
591
|
-
decipher.setAAD(continuityAadFor(envelope.ownerAddress, envelope.createdAt))
|
|
592
|
-
decipher.setAuthTag(fromBase64(envelope.tag))
|
|
593
|
-
|
|
594
|
-
let decoded: unknown
|
|
595
|
-
try {
|
|
596
|
-
const plaintext = Buffer.concat([
|
|
597
|
-
decipher.update(fromBase64(envelope.ciphertext)),
|
|
598
|
-
decipher.final(),
|
|
599
|
-
]).toString('utf8')
|
|
600
|
-
decoded = JSON.parse(plaintext)
|
|
601
|
-
} catch {
|
|
602
|
-
throw new Error('Could not decrypt continuity snapshot with the supplied wallet signature')
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
const payload = normalizeContinuityPayload(decoded)
|
|
606
|
-
assertPayloadMatchesEnvelope(payload, envelope.ownerAddress, envelope.createdAt)
|
|
607
|
-
return payload
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
export function assertContinuitySnapshotOwner(envelope: ContinuitySnapshotEnvelope, currentOwner: string): void {
|
|
611
|
-
const normalized = normalizeContinuitySnapshotEnvelope(envelope)
|
|
612
|
-
const owner = toChecksumAddress(currentOwner)
|
|
613
|
-
if (isWalletContinuitySnapshotEnvelope(normalized)) {
|
|
614
|
-
const snapshotOwner = toChecksumAddress(normalized.ownerAddress)
|
|
615
|
-
if (snapshotOwner.toLowerCase() !== owner.toLowerCase()) {
|
|
616
|
-
throw new ContinuitySnapshotOwnerMismatchError(snapshotOwner, owner)
|
|
617
|
-
}
|
|
618
|
-
return
|
|
619
|
-
}
|
|
620
|
-
if (isTransferContinuitySnapshotEnvelope(normalized)) {
|
|
621
|
-
const snapshotOwner = toChecksumAddress(normalized.ownerAddress)
|
|
622
|
-
const targetOwner = toChecksumAddress(normalized.targetAddress)
|
|
623
|
-
if (
|
|
624
|
-
owner.toLowerCase() !== snapshotOwner.toLowerCase()
|
|
625
|
-
&& owner.toLowerCase() !== targetOwner.toLowerCase()
|
|
626
|
-
) {
|
|
627
|
-
throw new ContinuityTransferSnapshotTargetMismatchError(snapshotOwner, targetOwner, owner)
|
|
628
|
-
}
|
|
629
|
-
return
|
|
630
|
-
}
|
|
631
|
-
const snapshotOwner = toChecksumAddress(normalized.ownerAddress)
|
|
632
|
-
if (snapshotOwner.toLowerCase() !== owner.toLowerCase()) {
|
|
633
|
-
throw new ContinuitySnapshotOwnerMismatchError(snapshotOwner, owner)
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
export function serializeContinuitySnapshotEnvelope(envelope: ContinuitySnapshotEnvelope): string {
|
|
638
|
-
return JSON.stringify(normalizeContinuitySnapshotEnvelope(envelope), null, 2)
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
export function parseContinuitySnapshotEnvelope(raw: string | Uint8Array): ContinuitySnapshotEnvelope {
|
|
642
|
-
const text = typeof raw === 'string' ? raw : new TextDecoder().decode(raw)
|
|
643
|
-
const parsed = JSON.parse(text) as unknown
|
|
644
|
-
return normalizeContinuitySnapshotEnvelope(parsed)
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
export function transferSnapshotMetadataFromEnvelope(
|
|
648
|
-
envelope: ContinuitySnapshotEnvelope,
|
|
649
|
-
): TransferSnapshotMetadata | null {
|
|
650
|
-
const normalized = normalizeContinuitySnapshotEnvelope(envelope)
|
|
651
|
-
if (!isTransferContinuitySnapshotEnvelope(normalized)) return null
|
|
652
|
-
const slotCount = [normalized.slots.owner, normalized.slots.target]
|
|
653
|
-
.filter(slot => Boolean(slot?.encryptedKey && slot.address))
|
|
654
|
-
.length
|
|
655
|
-
if (slotCount < 2) return null
|
|
656
|
-
return {
|
|
657
|
-
kind: 'dual-wallet',
|
|
658
|
-
senderAddress: normalized.ownerAddress,
|
|
659
|
-
receiverAddress: normalized.targetAddress,
|
|
660
|
-
...(normalized.targetHandle ? { receiverHandle: normalized.targetHandle } : {}),
|
|
661
|
-
slotCount,
|
|
662
|
-
createdAt: normalized.createdAt,
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
export function walletContinuitySnapshotSlotForAddress(
|
|
667
|
-
envelope: ContinuitySnapshotEnvelope,
|
|
668
|
-
address: string,
|
|
669
|
-
): WalletContinuitySnapshotSlot | null {
|
|
670
|
-
const normalized = normalizeContinuitySnapshotEnvelope(envelope)
|
|
671
|
-
if (!isWalletContinuitySnapshotEnvelope(normalized)) return null
|
|
672
|
-
const checksum = toChecksumAddress(address)
|
|
673
|
-
return normalized.slots.find(slot => slot.address.toLowerCase() === checksum.toLowerCase()) ?? null
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
export function findRestorableAddressForSnapshot(
|
|
677
|
-
envelope: ContinuitySnapshotEnvelope,
|
|
678
|
-
candidates: ReadonlyArray<string>,
|
|
679
|
-
): string | null {
|
|
680
|
-
const normalized = normalizeContinuitySnapshotEnvelope(envelope)
|
|
681
|
-
const seen = new Set<string>()
|
|
682
|
-
const lowerCandidates: string[] = []
|
|
683
|
-
for (const candidate of candidates) {
|
|
684
|
-
if (!candidate) continue
|
|
685
|
-
const lower = candidate.toLowerCase()
|
|
686
|
-
if (seen.has(lower)) continue
|
|
687
|
-
seen.add(lower)
|
|
688
|
-
lowerCandidates.push(lower)
|
|
689
|
-
}
|
|
690
|
-
if (lowerCandidates.length === 0) return null
|
|
691
|
-
if (isWalletContinuitySnapshotEnvelope(normalized)) {
|
|
692
|
-
for (const slot of normalized.slots) {
|
|
693
|
-
if (lowerCandidates.includes(slot.address.toLowerCase())) return toChecksumAddress(slot.address)
|
|
694
|
-
}
|
|
695
|
-
return null
|
|
696
|
-
}
|
|
697
|
-
if (isTransferContinuitySnapshotEnvelope(normalized)) {
|
|
698
|
-
const ownerLower = normalized.ownerAddress.toLowerCase()
|
|
699
|
-
const targetLower = normalized.targetAddress.toLowerCase()
|
|
700
|
-
if (lowerCandidates.includes(ownerLower)) return toChecksumAddress(normalized.ownerAddress)
|
|
701
|
-
if (lowerCandidates.includes(targetLower)) return toChecksumAddress(normalized.targetAddress)
|
|
702
|
-
return null
|
|
703
|
-
}
|
|
704
|
-
const ownerLower = normalized.ownerAddress.toLowerCase()
|
|
705
|
-
return lowerCandidates.includes(ownerLower) ? toChecksumAddress(normalized.ownerAddress) : null
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
export function isWalletContinuitySnapshotEnvelope(input: unknown): input is WalletContinuitySnapshotEnvelope {
|
|
709
|
-
if (!input || typeof input !== 'object' || Array.isArray(input)) return false
|
|
710
|
-
const obj = input as Partial<WalletContinuitySnapshotEnvelope> & { crypto?: Partial<WalletContinuitySnapshotEnvelope['crypto']> }
|
|
711
|
-
return obj.version === 1
|
|
712
|
-
&& obj.envelopeVersion === CONTINUITY_SNAPSHOT_ENVELOPE_VERSION
|
|
713
|
-
&& typeof obj.ownerAddress === 'string'
|
|
714
|
-
&& typeof obj.createdAt === 'string'
|
|
715
|
-
&& !!obj.token
|
|
716
|
-
&& typeof obj.accessEpoch === 'number'
|
|
717
|
-
&& typeof obj.accessManifestHash === 'string'
|
|
718
|
-
&& obj.crypto?.decryptsWith === 'wallet-signature-slots'
|
|
719
|
-
&& typeof obj.payloadNonce === 'string'
|
|
720
|
-
&& typeof obj.payloadCiphertext === 'string'
|
|
721
|
-
&& typeof obj.payloadTag === 'string'
|
|
722
|
-
&& typeof obj.payloadHash === 'string'
|
|
723
|
-
&& Array.isArray(obj.slots)
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
function isTransferContinuitySnapshotEnvelope(input: unknown): input is TransferContinuitySnapshotEnvelope {
|
|
727
|
-
if (!input || typeof input !== 'object' || Array.isArray(input)) return false
|
|
728
|
-
const obj = input as Partial<TransferContinuitySnapshotEnvelope> & { crypto?: Partial<TransferContinuitySnapshotEnvelope['crypto']> }
|
|
729
|
-
return obj.version === 1
|
|
730
|
-
&& obj.envelopeVersion === CONTINUITY_SNAPSHOT_ENVELOPE_VERSION
|
|
731
|
-
&& typeof obj.ownerAddress === 'string'
|
|
732
|
-
&& typeof obj.createdAt === 'string'
|
|
733
|
-
&& typeof obj.challenge === 'string'
|
|
734
|
-
&& !!obj.token
|
|
735
|
-
&& typeof obj.targetAddress === 'string'
|
|
736
|
-
&& obj.crypto?.decryptsWith === 'transfer-signature-slot'
|
|
737
|
-
&& typeof obj.payloadNonce === 'string'
|
|
738
|
-
&& typeof obj.payloadCiphertext === 'string'
|
|
739
|
-
&& typeof obj.payloadTag === 'string'
|
|
740
|
-
&& typeof obj.payloadHash === 'string'
|
|
741
|
-
&& !!obj.slots
|
|
742
|
-
&& !!obj.slots.owner
|
|
743
|
-
&& !!obj.slots.target
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
function restoreTransferContinuitySnapshotEnvelope(args: {
|
|
747
|
-
envelope: TransferContinuitySnapshotEnvelope
|
|
748
|
-
walletSignature: string
|
|
749
|
-
currentOwnerAddress?: string
|
|
750
|
-
}): ContinuitySnapshotPayload {
|
|
751
|
-
const currentAddress = args.currentOwnerAddress
|
|
752
|
-
? toChecksumAddress(args.currentOwnerAddress)
|
|
753
|
-
: toChecksumAddress(recoverAddressFromSignature(args.envelope.challenge, args.walletSignature))
|
|
754
|
-
const slot = transferSlotForCurrentOwner(args.envelope, currentAddress)
|
|
755
|
-
assertSignatureForAddress(slot.challenge, args.walletSignature, slot.address)
|
|
756
|
-
|
|
757
|
-
let contentKey: Buffer
|
|
758
|
-
try {
|
|
759
|
-
const payloadAad = transferPayloadAadFor(args.envelope)
|
|
760
|
-
const slotKey = deriveTransferSlotKey(args.walletSignature, fromBase64(slot.salt), slot.address, slot.challenge)
|
|
761
|
-
const keyDecipher = crypto.createDecipheriv('aes-256-gcm', slotKey, fromBase64(slot.nonce))
|
|
762
|
-
keyDecipher.setAAD(transferSlotAadFor(slot, payloadAad))
|
|
763
|
-
keyDecipher.setAuthTag(fromBase64(slot.tag))
|
|
764
|
-
contentKey = Buffer.concat([
|
|
765
|
-
keyDecipher.update(fromBase64(slot.encryptedKey)),
|
|
766
|
-
keyDecipher.final(),
|
|
767
|
-
])
|
|
768
|
-
} catch {
|
|
769
|
-
throw new Error('Could not decrypt transfer snapshot key with the supplied wallet signature')
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
let decoded: unknown
|
|
773
|
-
try {
|
|
774
|
-
const payloadAad = transferPayloadAadFor(args.envelope)
|
|
775
|
-
const decipher = crypto.createDecipheriv('aes-256-gcm', contentKey, fromBase64(args.envelope.payloadNonce))
|
|
776
|
-
decipher.setAAD(payloadAad)
|
|
777
|
-
decipher.setAuthTag(fromBase64(args.envelope.payloadTag))
|
|
778
|
-
const plaintext = Buffer.concat([
|
|
779
|
-
decipher.update(fromBase64(args.envelope.payloadCiphertext)),
|
|
780
|
-
decipher.final(),
|
|
781
|
-
])
|
|
782
|
-
if (sha256Hex(plaintext) !== args.envelope.payloadHash) {
|
|
783
|
-
throw new Error('Transfer snapshot payload hash mismatch')
|
|
784
|
-
}
|
|
785
|
-
decoded = JSON.parse(plaintext.toString('utf8')) as unknown
|
|
786
|
-
} catch {
|
|
787
|
-
throw new Error('Could not decrypt continuity transfer snapshot with the supplied wallet signature')
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
const payload = normalizeContinuityPayload(decoded)
|
|
791
|
-
assertPayloadMatchesEnvelope(payload, args.envelope.ownerAddress, args.envelope.createdAt)
|
|
792
|
-
return payload
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
function restoreWalletContinuitySnapshotEnvelope(args: {
|
|
796
|
-
envelope: WalletContinuitySnapshotEnvelope
|
|
797
|
-
walletSignature: string
|
|
798
|
-
currentOwnerAddress?: string
|
|
799
|
-
}): ContinuitySnapshotPayload {
|
|
800
|
-
const slot = walletSlotForRestore(args.envelope, args.walletSignature, args.currentOwnerAddress)
|
|
801
|
-
assertSignatureForAddress(slot.challenge, args.walletSignature, slot.address)
|
|
802
|
-
|
|
803
|
-
let contentKey: Buffer
|
|
804
|
-
try {
|
|
805
|
-
const salt = fromBase64(slot.salt)
|
|
806
|
-
const kemSeed = deriveWalletRestoreKemSeed(args.walletSignature, salt, slot.address, slot.challenge)
|
|
807
|
-
const kemKeys = ml_kem1024.keygen(kemSeed)
|
|
808
|
-
if (toBase64(kemKeys.publicKey) !== slot.kemPublicKey) {
|
|
809
|
-
throw new Error('Wallet restore key mismatch')
|
|
810
|
-
}
|
|
811
|
-
const sharedSecret = ml_kem1024.decapsulate(fromBase64(slot.kemCiphertext), kemKeys.secretKey)
|
|
812
|
-
const slotKey = deriveWalletSlotAesKey(sharedSecret, salt, slot.address, slot.challenge, args.envelope.accessManifestHash)
|
|
813
|
-
const keyDecipher = crypto.createDecipheriv('aes-256-gcm', slotKey, fromBase64(slot.nonce))
|
|
814
|
-
const payloadAad = walletPayloadAadFor(args.envelope)
|
|
815
|
-
keyDecipher.setAAD(walletSlotAadFor(slot, payloadAad))
|
|
816
|
-
keyDecipher.setAuthTag(fromBase64(slot.tag))
|
|
817
|
-
contentKey = Buffer.concat([
|
|
818
|
-
keyDecipher.update(fromBase64(slot.encryptedKey)),
|
|
819
|
-
keyDecipher.final(),
|
|
820
|
-
])
|
|
821
|
-
} catch {
|
|
822
|
-
throw new Error('Could not decrypt wallet restore snapshot key with the supplied wallet signature')
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
let decoded: unknown
|
|
826
|
-
try {
|
|
827
|
-
const payloadAad = walletPayloadAadFor(args.envelope)
|
|
828
|
-
const decipher = crypto.createDecipheriv('aes-256-gcm', contentKey, fromBase64(args.envelope.payloadNonce))
|
|
829
|
-
decipher.setAAD(payloadAad)
|
|
830
|
-
decipher.setAuthTag(fromBase64(args.envelope.payloadTag))
|
|
831
|
-
const plaintext = Buffer.concat([
|
|
832
|
-
decipher.update(fromBase64(args.envelope.payloadCiphertext)),
|
|
833
|
-
decipher.final(),
|
|
834
|
-
])
|
|
835
|
-
if (sha256Hex(plaintext) !== args.envelope.payloadHash) {
|
|
836
|
-
throw new Error('Wallet restore snapshot payload hash mismatch')
|
|
837
|
-
}
|
|
838
|
-
decoded = JSON.parse(plaintext.toString('utf8')) as unknown
|
|
839
|
-
} catch {
|
|
840
|
-
throw new Error('Could not decrypt wallet restore snapshot with the supplied wallet signature')
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
const payload = normalizeContinuityPayload(decoded)
|
|
844
|
-
assertPayloadMatchesEnvelope(payload, args.envelope.ownerAddress, args.envelope.createdAt)
|
|
845
|
-
return payload
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
function walletSlotForRestore(
|
|
849
|
-
envelope: WalletContinuitySnapshotEnvelope,
|
|
850
|
-
walletSignature: string,
|
|
851
|
-
currentOwnerAddress?: string,
|
|
852
|
-
): WalletContinuitySnapshotSlot {
|
|
853
|
-
if (currentOwnerAddress) {
|
|
854
|
-
const address = toChecksumAddress(currentOwnerAddress)
|
|
855
|
-
const slot = envelope.slots.find(item => item.address.toLowerCase() === address.toLowerCase())
|
|
856
|
-
if (!slot) throw new ContinuitySnapshotRestoreSlotMissingError(address)
|
|
857
|
-
return slot
|
|
858
|
-
}
|
|
859
|
-
for (const slot of envelope.slots) {
|
|
860
|
-
try {
|
|
861
|
-
const recovered = recoverAddressFromSignature(slot.challenge, walletSignature)
|
|
862
|
-
if (recovered.toLowerCase() === slot.address.toLowerCase()) return slot
|
|
863
|
-
} catch {
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
throw new ContinuitySnapshotRestoreSlotMissingError('unknown')
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
function transferSlotForCurrentOwner(
|
|
870
|
-
envelope: TransferContinuitySnapshotEnvelope,
|
|
871
|
-
currentOwner: string,
|
|
872
|
-
): TransferContinuitySnapshotSlot {
|
|
873
|
-
if (currentOwner.toLowerCase() === envelope.ownerAddress.toLowerCase()) return envelope.slots.owner
|
|
874
|
-
if (currentOwner.toLowerCase() === envelope.targetAddress.toLowerCase()) return envelope.slots.target
|
|
875
|
-
throw new ContinuityTransferSnapshotTargetMismatchError(envelope.ownerAddress, envelope.targetAddress, currentOwner)
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
function createTransferSlot(args: {
|
|
879
|
-
address: string
|
|
880
|
-
challenge: string
|
|
881
|
-
walletSignature: string
|
|
882
|
-
contentKey: Uint8Array
|
|
883
|
-
payloadAad: Buffer
|
|
884
|
-
}): TransferContinuitySnapshotSlot {
|
|
885
|
-
const address = toChecksumAddress(args.address)
|
|
886
|
-
const salt = crypto.randomBytes(32)
|
|
887
|
-
const nonce = crypto.randomBytes(12)
|
|
888
|
-
const slotKey = deriveTransferSlotKey(args.walletSignature, salt, address, args.challenge)
|
|
889
|
-
const cipher = crypto.createCipheriv('aes-256-gcm', slotKey, nonce)
|
|
890
|
-
const slot: TransferContinuitySnapshotSlot = {
|
|
891
|
-
address,
|
|
892
|
-
challenge: args.challenge,
|
|
893
|
-
salt: toBase64(salt),
|
|
894
|
-
nonce: toBase64(nonce),
|
|
895
|
-
encryptedKey: '',
|
|
896
|
-
tag: '',
|
|
897
|
-
}
|
|
898
|
-
cipher.setAAD(transferSlotAadFor(slot, args.payloadAad))
|
|
899
|
-
const encryptedKey = Buffer.concat([cipher.update(args.contentKey), cipher.final()])
|
|
900
|
-
return {
|
|
901
|
-
...slot,
|
|
902
|
-
encryptedKey: toBase64(encryptedKey),
|
|
903
|
-
tag: toBase64(cipher.getAuthTag()),
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
function createWalletSlot(args: {
|
|
908
|
-
accessKey: WalletContinuityRestoreAccessKey
|
|
909
|
-
contentKey: Uint8Array
|
|
910
|
-
payloadAad: Buffer
|
|
911
|
-
accessManifestHash: string
|
|
912
|
-
}): WalletContinuitySnapshotSlot {
|
|
913
|
-
const accessKey = normalizeWalletRestoreAccessKey(args.accessKey)
|
|
914
|
-
const publicKey = fromBase64(accessKey.kemPublicKey)
|
|
915
|
-
const kem = ml_kem1024.encapsulate(publicKey)
|
|
916
|
-
const salt = fromBase64(accessKey.salt)
|
|
917
|
-
const nonce = crypto.randomBytes(12)
|
|
918
|
-
const slotKey = deriveWalletSlotAesKey(kem.sharedSecret, salt, accessKey.address, accessKey.challenge, args.accessManifestHash)
|
|
919
|
-
const cipher = crypto.createCipheriv('aes-256-gcm', slotKey, nonce)
|
|
920
|
-
const slot: WalletContinuitySnapshotSlot = {
|
|
921
|
-
address: accessKey.address,
|
|
922
|
-
challenge: accessKey.challenge,
|
|
923
|
-
salt: accessKey.salt,
|
|
924
|
-
kemPublicKey: accessKey.kemPublicKey,
|
|
925
|
-
kemCiphertext: toBase64(kem.cipherText),
|
|
926
|
-
nonce: toBase64(nonce),
|
|
927
|
-
encryptedKey: '',
|
|
928
|
-
tag: '',
|
|
929
|
-
}
|
|
930
|
-
cipher.setAAD(walletSlotAadFor(slot, args.payloadAad))
|
|
931
|
-
const encryptedKey = Buffer.concat([cipher.update(args.contentKey), cipher.final()])
|
|
932
|
-
return {
|
|
933
|
-
...slot,
|
|
934
|
-
encryptedKey: toBase64(encryptedKey),
|
|
935
|
-
tag: toBase64(cipher.getAuthTag()),
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
function normalizeContinuitySnapshotEnvelope(input: unknown): ContinuitySnapshotEnvelope {
|
|
940
|
-
if (!isContinuitySnapshotEnvelope(input)) throw new Error('Invalid continuity snapshot envelope')
|
|
941
|
-
if (input.envelopeVersion !== CONTINUITY_SNAPSHOT_ENVELOPE_VERSION) {
|
|
942
|
-
throw new Error('Unsupported continuity snapshot envelope version')
|
|
943
|
-
}
|
|
944
|
-
if (isWalletContinuitySnapshotEnvelope(input)) {
|
|
945
|
-
if (input.crypto.kem !== 'ML-KEM-1024' || input.crypto.aead !== 'AES-256-GCM' || input.crypto.decryptsWith !== 'wallet-signature-slots') {
|
|
946
|
-
throw new Error('Unsupported continuity snapshot crypto suite')
|
|
947
|
-
}
|
|
948
|
-
const ownerAddress = toChecksumAddress(input.ownerAddress)
|
|
949
|
-
const token = normalizeContinuitySnapshotToken(input.token)
|
|
950
|
-
const slots = input.slots.map(normalizeWalletSlot)
|
|
951
|
-
if (slots.length === 0) throw new Error('Continuity wallet snapshot needs at least one slot')
|
|
952
|
-
return {
|
|
953
|
-
...input,
|
|
954
|
-
ownerAddress,
|
|
955
|
-
token,
|
|
956
|
-
slots,
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
if (isTransferContinuitySnapshotEnvelope(input)) {
|
|
960
|
-
if (input.crypto.aead !== 'AES-256-GCM' || input.crypto.decryptsWith !== 'transfer-signature-slot') {
|
|
961
|
-
throw new Error('Unsupported continuity snapshot crypto suite')
|
|
962
|
-
}
|
|
963
|
-
const ownerAddress = toChecksumAddress(input.ownerAddress)
|
|
964
|
-
const targetAddress = toChecksumAddress(input.targetAddress)
|
|
965
|
-
return {
|
|
966
|
-
...input,
|
|
967
|
-
ownerAddress,
|
|
968
|
-
targetAddress,
|
|
969
|
-
token: normalizeContinuitySnapshotToken(input.token),
|
|
970
|
-
slots: {
|
|
971
|
-
owner: normalizeTransferSlot(input.slots.owner, ownerAddress),
|
|
972
|
-
target: normalizeTransferSlot(input.slots.target, targetAddress),
|
|
973
|
-
},
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
if (input.crypto.kem !== 'ML-KEM-1024' || input.crypto.aead !== 'AES-256-GCM') {
|
|
977
|
-
throw new Error('Unsupported continuity snapshot crypto suite')
|
|
978
|
-
}
|
|
979
|
-
return {
|
|
980
|
-
...input,
|
|
981
|
-
ownerAddress: toChecksumAddress(input.ownerAddress),
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
function isContinuitySnapshotEnvelope(input: unknown): input is ContinuitySnapshotEnvelope {
|
|
986
|
-
if (!input || typeof input !== 'object') return false
|
|
987
|
-
const obj = input as Record<string, unknown> & { walletSignature?: unknown; crypto?: unknown }
|
|
988
|
-
const base = obj.version === 1
|
|
989
|
-
&& obj.envelopeVersion === CONTINUITY_SNAPSHOT_ENVELOPE_VERSION
|
|
990
|
-
&& typeof obj.ownerAddress === 'string'
|
|
991
|
-
&& typeof obj.createdAt === 'string'
|
|
992
|
-
&& obj.walletSignature === undefined
|
|
993
|
-
&& !!obj.crypto
|
|
994
|
-
if (!base) return false
|
|
995
|
-
if (isWalletContinuitySnapshotEnvelope(input)) return true
|
|
996
|
-
if (isTransferContinuitySnapshotEnvelope(input)) return true
|
|
997
|
-
return typeof obj.challenge === 'string'
|
|
998
|
-
&& typeof obj.salt === 'string'
|
|
999
|
-
&& typeof obj.kemPublicKey === 'string'
|
|
1000
|
-
&& typeof obj.kemCiphertext === 'string'
|
|
1001
|
-
&& typeof obj.nonce === 'string'
|
|
1002
|
-
&& typeof obj.ciphertext === 'string'
|
|
1003
|
-
&& typeof obj.tag === 'string'
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
function continuityPayloadFromArgs(args: {
|
|
1007
|
-
ownerAddress: string
|
|
1008
|
-
createdAt: string
|
|
1009
|
-
payload: Omit<ContinuitySnapshotPayload, 'version' | 'ownerAddress' | 'createdAt'> & { createdAt?: string }
|
|
1010
|
-
}): ContinuitySnapshotPayload {
|
|
1011
|
-
const skills = normalizeContinuitySkills(args.payload.skills)
|
|
1012
|
-
return {
|
|
1013
|
-
version: 1,
|
|
1014
|
-
ownerAddress: args.ownerAddress,
|
|
1015
|
-
createdAt: args.createdAt,
|
|
1016
|
-
...(args.payload.sequence !== undefined ? { sequence: args.payload.sequence } : {}),
|
|
1017
|
-
agent: normalizeAgentSnapshot(args.payload.agent),
|
|
1018
|
-
files: normalizeContinuityFiles(args.payload.files),
|
|
1019
|
-
...(skills ? { skills } : {}),
|
|
1020
|
-
transcript: normalizeTranscript(args.payload.transcript),
|
|
1021
|
-
state: normalizeState(args.payload.state),
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
function normalizeContinuityPayload(input: unknown): ContinuitySnapshotPayload {
|
|
1026
|
-
if (!input || typeof input !== 'object') throw new Error('Continuity snapshot payload is invalid')
|
|
1027
|
-
const obj = input as Partial<ContinuitySnapshotPayload>
|
|
1028
|
-
if (obj.version !== 1) throw new Error('Continuity snapshot payload version is invalid')
|
|
1029
|
-
if (typeof obj.ownerAddress !== 'string') throw new Error('Continuity snapshot owner is invalid')
|
|
1030
|
-
if (typeof obj.createdAt !== 'string') throw new Error('Continuity snapshot timestamp is invalid')
|
|
1031
|
-
const skills = normalizeContinuitySkills(obj.skills)
|
|
1032
|
-
return {
|
|
1033
|
-
version: 1,
|
|
1034
|
-
ownerAddress: toChecksumAddress(obj.ownerAddress),
|
|
1035
|
-
createdAt: obj.createdAt,
|
|
1036
|
-
...(typeof obj.sequence === 'number' && Number.isSafeInteger(obj.sequence) ? { sequence: obj.sequence } : {}),
|
|
1037
|
-
agent: normalizeAgentSnapshot(obj.agent),
|
|
1038
|
-
files: normalizeContinuityFiles(obj.files),
|
|
1039
|
-
...(skills ? { skills } : {}),
|
|
1040
|
-
transcript: normalizeTranscript(obj.transcript),
|
|
1041
|
-
state: normalizeState(obj.state),
|
|
1042
|
-
}
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
export function normalizeContinuitySkills(input: unknown): ContinuitySkillsTree | undefined {
|
|
1046
|
-
if (input === undefined || input === null) return undefined
|
|
1047
|
-
if (typeof input !== 'object' || Array.isArray(input)) return undefined
|
|
1048
|
-
const obj = input as Record<string, unknown>
|
|
1049
|
-
const out: ContinuitySkillsTree = {}
|
|
1050
|
-
let count = 0
|
|
1051
|
-
const tryInsert = (key: string, rawValue: unknown): void => {
|
|
1052
|
-
if (count >= MAX_PRIVATE_SKILL_ENTRIES) return
|
|
1053
|
-
if (typeof rawValue !== 'string') return
|
|
1054
|
-
if (key.length === 0 || key.length > MAX_PRIVATE_SKILL_PATH_LEN) return
|
|
1055
|
-
if (key.includes('\0')) return
|
|
1056
|
-
if (key.includes('..')) return
|
|
1057
|
-
if (key.startsWith('/')) return
|
|
1058
|
-
if (/^[A-Za-z]:/.test(key)) return
|
|
1059
|
-
if (!isAcceptableSkillKey(key)) return
|
|
1060
|
-
if (Buffer.byteLength(rawValue, 'utf8') > MAX_PRIVATE_SKILL_BODY_BYTES) return
|
|
1061
|
-
if (out[key] !== undefined) return
|
|
1062
|
-
out[key] = rawValue
|
|
1063
|
-
count++
|
|
1064
|
-
}
|
|
1065
|
-
const legacyRoots = new Set<string>()
|
|
1066
|
-
const realSkillFolders = new Set<string>()
|
|
1067
|
-
for (const rawKey of Object.keys(obj)) {
|
|
1068
|
-
const key = rawKey.replace(/\\/g, '/')
|
|
1069
|
-
const segments = key.split('/')
|
|
1070
|
-
if (segments.length === 3 && segments[2] === 'SKILL.md' && segments[0] && segments[1]) {
|
|
1071
|
-
legacyRoots.add(`${segments[0]}/${segments[1]}`)
|
|
1072
|
-
}
|
|
1073
|
-
if (segments.length === 2 && segments[1] === 'SKILL.md' && segments[0]) {
|
|
1074
|
-
realSkillFolders.add(segments[0])
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1077
|
-
for (const [rawKey, rawValue] of Object.entries(obj)) {
|
|
1078
|
-
const key = rawKey.replace(/\\/g, '/')
|
|
1079
|
-
if (!isCanonicalFlatKey(key)) continue
|
|
1080
|
-
if (isUnderLegacyRoot(key, legacyRoots)) continue
|
|
1081
|
-
if (!keyHasRealSkillFolder(key, realSkillFolders)) continue
|
|
1082
|
-
tryInsert(key, rawValue)
|
|
1083
|
-
}
|
|
1084
|
-
for (const [rawKey, rawValue] of Object.entries(obj)) {
|
|
1085
|
-
const key = rawKey.replace(/\\/g, '/')
|
|
1086
|
-
if (
|
|
1087
|
-
isCanonicalFlatKey(key)
|
|
1088
|
-
&& !isUnderLegacyRoot(key, legacyRoots)
|
|
1089
|
-
&& keyHasRealSkillFolder(key, realSkillFolders)
|
|
1090
|
-
) continue
|
|
1091
|
-
const upgraded = upgradeLegacySkillKey(key, legacyRoots)
|
|
1092
|
-
if (!upgraded) continue
|
|
1093
|
-
tryInsert(upgraded, rawValue)
|
|
1094
|
-
}
|
|
1095
|
-
return count > 0 ? out : undefined
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
function isUnderLegacyRoot(key: string, legacyRoots: Set<string>): boolean {
|
|
1099
|
-
for (const root of legacyRoots) {
|
|
1100
|
-
if (key === `${root}/SKILL.md`) return true
|
|
1101
|
-
if (key.startsWith(`${root}/`)) return true
|
|
1102
|
-
}
|
|
1103
|
-
return false
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
function keyHasRealSkillFolder(key: string, realSkillFolders: Set<string>): boolean {
|
|
1107
|
-
const first = key.split('/')[0]
|
|
1108
|
-
if (!first) return false
|
|
1109
|
-
return realSkillFolders.has(first)
|
|
1110
|
-
}
|
|
1111
|
-
|
|
1112
|
-
function isCanonicalFlatKey(key: string): boolean {
|
|
1113
|
-
if (!PRIVATE_SKILL_FILE_RE.test(key)) return false
|
|
1114
|
-
const segments = key.split('/')
|
|
1115
|
-
if (segments.length < 2) return false
|
|
1116
|
-
const last = segments[segments.length - 1]!
|
|
1117
|
-
if (last === 'SKILL.md') return segments.length === 2
|
|
1118
|
-
return PRIVATE_SKILL_LAST_SEG_FILE_RE.test(last)
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
function isAcceptableSkillKey(key: string): boolean {
|
|
1122
|
-
return isCanonicalFlatKey(key)
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
function upgradeLegacySkillKey(key: string, legacyRoots: Set<string>): string | null {
|
|
1126
|
-
for (const root of legacyRoots) {
|
|
1127
|
-
if (key === `${root}/SKILL.md` || key.startsWith(`${root}/`)) {
|
|
1128
|
-
const [first, second] = root.split('/')
|
|
1129
|
-
if (!first || !second) continue
|
|
1130
|
-
const rest = key.slice(root.length + 1)
|
|
1131
|
-
const flattened = `${first}-${second}/${rest}`
|
|
1132
|
-
return isCanonicalFlatKey(flattened) ? flattened : null
|
|
1133
|
-
}
|
|
1134
|
-
}
|
|
1135
|
-
if (LEGACY_FLAT_NAME_MD_RE.test(key)) {
|
|
1136
|
-
const [category, file] = key.split('/')
|
|
1137
|
-
if (!category || !file) return null
|
|
1138
|
-
const slug = file.replace(/\.md$/i, '')
|
|
1139
|
-
if (!slug) return null
|
|
1140
|
-
const flattened = `${category}-${slug}/SKILL.md`
|
|
1141
|
-
return isCanonicalFlatKey(flattened) ? flattened : null
|
|
1142
|
-
}
|
|
1143
|
-
if (LEGACY_NESTED_SKILL_RE.test(key)) {
|
|
1144
|
-
const segments = key.split('/')
|
|
1145
|
-
if (segments.length < 3) return null
|
|
1146
|
-
const [first, second, ...rest] = segments
|
|
1147
|
-
if (!first || !second || rest.length === 0) return null
|
|
1148
|
-
const flattened = `${first}-${second}/${rest.join('/')}`
|
|
1149
|
-
return isCanonicalFlatKey(flattened) ? flattened : null
|
|
1150
|
-
}
|
|
1151
|
-
if (isCanonicalFlatKey(key)) return key
|
|
1152
|
-
return null
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
function normalizeAgentSnapshot(input: unknown): ContinuityAgentSnapshot {
|
|
1156
|
-
if (!input || typeof input !== 'object' || Array.isArray(input)) return {}
|
|
1157
|
-
const obj = input as Record<string, unknown>
|
|
1158
|
-
return {
|
|
1159
|
-
...(typeof obj.chainId === 'number' && Number.isSafeInteger(obj.chainId) && obj.chainId > 0 ? { chainId: obj.chainId } : {}),
|
|
1160
|
-
...(typeof obj.identityRegistryAddress === 'string' ? { identityRegistryAddress: obj.identityRegistryAddress } : {}),
|
|
1161
|
-
...(typeof obj.agentId === 'string' ? { agentId: obj.agentId } : {}),
|
|
1162
|
-
...(typeof obj.agentUri === 'string' ? { agentUri: obj.agentUri } : {}),
|
|
1163
|
-
...(typeof obj.metadataCid === 'string' ? { metadataCid: obj.metadataCid } : {}),
|
|
1164
|
-
...(typeof obj.name === 'string' ? { name: obj.name } : {}),
|
|
1165
|
-
...(typeof obj.description === 'string' ? { description: obj.description } : {}),
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
function normalizeContinuityFiles(input: unknown): ContinuityFiles {
|
|
1170
|
-
if (!input || typeof input !== 'object' || Array.isArray(input)) {
|
|
1171
|
-
throw new Error('Continuity snapshot files are invalid')
|
|
1172
|
-
}
|
|
1173
|
-
const obj = input as Partial<ContinuityFiles>
|
|
1174
|
-
if (typeof obj['SOUL.md'] !== 'string') throw new Error('SOUL.md is missing from continuity snapshot')
|
|
1175
|
-
if (typeof obj['MEMORY.md'] !== 'string') throw new Error('MEMORY.md is missing from continuity snapshot')
|
|
1176
|
-
return {
|
|
1177
|
-
'SOUL.md': obj['SOUL.md'],
|
|
1178
|
-
'MEMORY.md': obj['MEMORY.md'],
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
function normalizeTranscript(input: unknown): ContinuityTranscriptSummary[] {
|
|
1183
|
-
if (!Array.isArray(input)) return []
|
|
1184
|
-
return input.flatMap(item => {
|
|
1185
|
-
if (!item || typeof item !== 'object' || Array.isArray(item)) return []
|
|
1186
|
-
const obj = item as Partial<ContinuityTranscriptSummary>
|
|
1187
|
-
if (typeof obj.summary !== 'string' || !obj.summary.trim()) return []
|
|
1188
|
-
return [{
|
|
1189
|
-
...(typeof obj.sessionId === 'string' ? { sessionId: obj.sessionId } : {}),
|
|
1190
|
-
...(typeof obj.createdAt === 'string' ? { createdAt: obj.createdAt } : {}),
|
|
1191
|
-
summary: obj.summary,
|
|
1192
|
-
}]
|
|
1193
|
-
})
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
function normalizeState(input: unknown): Record<string, unknown> {
|
|
1197
|
-
if (!input || typeof input !== 'object' || Array.isArray(input)) return {}
|
|
1198
|
-
return input as Record<string, unknown>
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
function normalizeContinuitySnapshotToken(input: unknown): ContinuitySnapshotToken {
|
|
1202
|
-
if (!input || typeof input !== 'object' || Array.isArray(input)) {
|
|
1203
|
-
throw new Error('Continuity snapshot token is invalid')
|
|
1204
|
-
}
|
|
1205
|
-
const obj = input as Partial<ContinuitySnapshotToken>
|
|
1206
|
-
if (typeof obj.chainId !== 'number' || !Number.isSafeInteger(obj.chainId) || obj.chainId <= 0) {
|
|
1207
|
-
throw new Error('Continuity snapshot token chain is invalid')
|
|
1208
|
-
}
|
|
1209
|
-
if (typeof obj.identityRegistryAddress !== 'string') {
|
|
1210
|
-
throw new Error('Continuity snapshot token registry is invalid')
|
|
1211
|
-
}
|
|
1212
|
-
if (typeof obj.agentId !== 'string' || !/^\d+$/.test(obj.agentId)) {
|
|
1213
|
-
throw new Error('Continuity snapshot token id is invalid')
|
|
1214
|
-
}
|
|
1215
|
-
return {
|
|
1216
|
-
chainId: obj.chainId,
|
|
1217
|
-
identityRegistryAddress: toChecksumAddress(obj.identityRegistryAddress),
|
|
1218
|
-
agentId: obj.agentId,
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
function normalizeTransferSlot(input: unknown, expectedAddress: string): TransferContinuitySnapshotSlot {
|
|
1223
|
-
if (!input || typeof input !== 'object' || Array.isArray(input)) {
|
|
1224
|
-
throw new Error('Continuity transfer slot is invalid')
|
|
1225
|
-
}
|
|
1226
|
-
const obj = input as Partial<TransferContinuitySnapshotSlot>
|
|
1227
|
-
if (typeof obj.address !== 'string') throw new Error('Continuity transfer slot address is invalid')
|
|
1228
|
-
const address = toChecksumAddress(obj.address)
|
|
1229
|
-
if (address.toLowerCase() !== expectedAddress.toLowerCase()) {
|
|
1230
|
-
throw new Error('Continuity transfer slot address mismatch')
|
|
1231
|
-
}
|
|
1232
|
-
if (typeof obj.challenge !== 'string') throw new Error('Continuity transfer slot challenge is invalid')
|
|
1233
|
-
if (typeof obj.salt !== 'string') throw new Error('Continuity transfer slot salt is invalid')
|
|
1234
|
-
if (typeof obj.nonce !== 'string') throw new Error('Continuity transfer slot nonce is invalid')
|
|
1235
|
-
if (typeof obj.encryptedKey !== 'string') throw new Error('Continuity transfer slot key is invalid')
|
|
1236
|
-
if (typeof obj.tag !== 'string') throw new Error('Continuity transfer slot tag is invalid')
|
|
1237
|
-
return {
|
|
1238
|
-
address,
|
|
1239
|
-
challenge: obj.challenge,
|
|
1240
|
-
salt: obj.salt,
|
|
1241
|
-
nonce: obj.nonce,
|
|
1242
|
-
encryptedKey: obj.encryptedKey,
|
|
1243
|
-
tag: obj.tag,
|
|
1244
|
-
}
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
|
-
function normalizeWalletRestoreAccessKeys(input: unknown): WalletContinuityRestoreAccessKey[] {
|
|
1248
|
-
if (!Array.isArray(input)) return []
|
|
1249
|
-
const out: WalletContinuityRestoreAccessKey[] = []
|
|
1250
|
-
const seen = new Set<string>()
|
|
1251
|
-
for (const item of input) {
|
|
1252
|
-
const key = normalizeWalletRestoreAccessKey(item)
|
|
1253
|
-
const dedupe = key.address.toLowerCase()
|
|
1254
|
-
if (seen.has(dedupe)) continue
|
|
1255
|
-
seen.add(dedupe)
|
|
1256
|
-
out.push(key)
|
|
1257
|
-
}
|
|
1258
|
-
return out.sort((a, b) => a.address.toLowerCase().localeCompare(b.address.toLowerCase()))
|
|
1259
|
-
}
|
|
1260
|
-
|
|
1261
|
-
function normalizeWalletRestoreAccessKey(input: unknown): WalletContinuityRestoreAccessKey {
|
|
1262
|
-
if (!input || typeof input !== 'object' || Array.isArray(input)) {
|
|
1263
|
-
throw new Error('Wallet restore access key is invalid')
|
|
1264
|
-
}
|
|
1265
|
-
const obj = input as Partial<WalletContinuityRestoreAccessKey>
|
|
1266
|
-
if (typeof obj.address !== 'string') throw new Error('Wallet restore access address is invalid')
|
|
1267
|
-
if (typeof obj.challenge !== 'string') throw new Error('Wallet restore access challenge is invalid')
|
|
1268
|
-
if (typeof obj.salt !== 'string') throw new Error('Wallet restore access salt is invalid')
|
|
1269
|
-
if (typeof obj.kemPublicKey !== 'string') throw new Error('Wallet restore access public key is invalid')
|
|
1270
|
-
return {
|
|
1271
|
-
address: toChecksumAddress(obj.address),
|
|
1272
|
-
challenge: obj.challenge,
|
|
1273
|
-
salt: obj.salt,
|
|
1274
|
-
kemPublicKey: obj.kemPublicKey,
|
|
1275
|
-
...(typeof obj.createdAt === 'string' ? { createdAt: obj.createdAt } : {}),
|
|
1276
|
-
}
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
function normalizeWalletSlot(input: unknown): WalletContinuitySnapshotSlot {
|
|
1280
|
-
if (!input || typeof input !== 'object' || Array.isArray(input)) {
|
|
1281
|
-
throw new Error('Continuity wallet slot is invalid')
|
|
1282
|
-
}
|
|
1283
|
-
const obj = input as Partial<WalletContinuitySnapshotSlot>
|
|
1284
|
-
if (typeof obj.address !== 'string') throw new Error('Continuity wallet slot address is invalid')
|
|
1285
|
-
if (typeof obj.challenge !== 'string') throw new Error('Continuity wallet slot challenge is invalid')
|
|
1286
|
-
if (typeof obj.salt !== 'string') throw new Error('Continuity wallet slot salt is invalid')
|
|
1287
|
-
if (typeof obj.kemPublicKey !== 'string') throw new Error('Continuity wallet slot public key is invalid')
|
|
1288
|
-
if (typeof obj.kemCiphertext !== 'string') throw new Error('Continuity wallet slot ciphertext is invalid')
|
|
1289
|
-
if (typeof obj.nonce !== 'string') throw new Error('Continuity wallet slot nonce is invalid')
|
|
1290
|
-
if (typeof obj.encryptedKey !== 'string') throw new Error('Continuity wallet slot key is invalid')
|
|
1291
|
-
if (typeof obj.tag !== 'string') throw new Error('Continuity wallet slot tag is invalid')
|
|
1292
|
-
return {
|
|
1293
|
-
address: toChecksumAddress(obj.address),
|
|
1294
|
-
challenge: obj.challenge,
|
|
1295
|
-
salt: obj.salt,
|
|
1296
|
-
kemPublicKey: obj.kemPublicKey,
|
|
1297
|
-
kemCiphertext: obj.kemCiphertext,
|
|
1298
|
-
nonce: obj.nonce,
|
|
1299
|
-
encryptedKey: obj.encryptedKey,
|
|
1300
|
-
tag: obj.tag,
|
|
1301
|
-
}
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
function assertPayloadMatchesEnvelope(payload: ContinuitySnapshotPayload, ownerAddress: string, createdAt: string): void {
|
|
1305
|
-
if (payload.ownerAddress.toLowerCase() !== ownerAddress.toLowerCase()) {
|
|
1306
|
-
throw new Error('Continuity snapshot owner mismatch')
|
|
1307
|
-
}
|
|
1308
|
-
if (payload.createdAt !== createdAt) {
|
|
1309
|
-
throw new Error('Continuity snapshot timestamp mismatch')
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
|
|
1313
|
-
function assertSignatureForAddress(challenge: string, signature: string, address: string): void {
|
|
1314
|
-
const recovered = recoverAddressFromSignature(challenge, signature)
|
|
1315
|
-
if (recovered.toLowerCase() !== address.toLowerCase()) {
|
|
1316
|
-
throw new Error('Wallet signature does not match continuity snapshot owner')
|
|
1317
|
-
}
|
|
1318
|
-
}
|
|
1319
|
-
|
|
1320
|
-
function deriveContinuityKemSeed(walletSignature: string, salt: Uint8Array, ownerAddress: string): Uint8Array {
|
|
1321
|
-
return hkdf(
|
|
1322
|
-
Buffer.from(walletSignature, 'utf8'),
|
|
1323
|
-
salt,
|
|
1324
|
-
`ethagent:${CONTINUITY_SNAPSHOT_ENVELOPE_VERSION}:ml-kem1024:${ownerAddress.toLowerCase()}`,
|
|
1325
|
-
64,
|
|
1326
|
-
)
|
|
1327
|
-
}
|
|
1328
|
-
|
|
1329
|
-
function deriveContinuityAesKey(
|
|
1330
|
-
walletSignature: string,
|
|
1331
|
-
sharedSecret: Uint8Array,
|
|
1332
|
-
salt: Uint8Array,
|
|
1333
|
-
ownerAddress: string,
|
|
1334
|
-
): Buffer {
|
|
1335
|
-
return Buffer.from(hkdf(
|
|
1336
|
-
Buffer.concat([
|
|
1337
|
-
Buffer.from(walletSignature, 'utf8'),
|
|
1338
|
-
Buffer.from('\n', 'utf8'),
|
|
1339
|
-
Buffer.from(sharedSecret),
|
|
1340
|
-
]),
|
|
1341
|
-
salt,
|
|
1342
|
-
`ethagent:${CONTINUITY_SNAPSHOT_ENVELOPE_VERSION}:aes-256-gcm:${ownerAddress.toLowerCase()}`,
|
|
1343
|
-
32,
|
|
1344
|
-
))
|
|
1345
|
-
}
|
|
1346
|
-
|
|
1347
|
-
function deriveTransferSlotKey(walletSignature: string, salt: Uint8Array, address: string, challenge: string): Buffer {
|
|
1348
|
-
return Buffer.from(hkdf(
|
|
1349
|
-
Buffer.from(walletSignature, 'utf8'),
|
|
1350
|
-
salt,
|
|
1351
|
-
`ethagent:${CONTINUITY_SNAPSHOT_ENVELOPE_VERSION}:transfer-slot:${address.toLowerCase()}:${sha256Hex(challenge)}`,
|
|
1352
|
-
32,
|
|
1353
|
-
))
|
|
1354
|
-
}
|
|
1355
|
-
|
|
1356
|
-
function deriveWalletRestoreKemSeed(walletSignature: string, salt: Uint8Array, address: string, challenge: string): Uint8Array {
|
|
1357
|
-
return hkdf(
|
|
1358
|
-
Buffer.from(walletSignature, 'utf8'),
|
|
1359
|
-
salt,
|
|
1360
|
-
`ethagent:${CONTINUITY_SNAPSHOT_ENVELOPE_VERSION}:wallet-restore-kem:${address.toLowerCase()}:${sha256Hex(challenge)}`,
|
|
1361
|
-
64,
|
|
1362
|
-
)
|
|
1363
|
-
}
|
|
1364
|
-
|
|
1365
|
-
function deriveWalletSlotAesKey(
|
|
1366
|
-
sharedSecret: Uint8Array,
|
|
1367
|
-
salt: Uint8Array,
|
|
1368
|
-
address: string,
|
|
1369
|
-
challenge: string,
|
|
1370
|
-
accessManifestHash: string,
|
|
1371
|
-
): Buffer {
|
|
1372
|
-
return Buffer.from(hkdf(
|
|
1373
|
-
sharedSecret,
|
|
1374
|
-
salt,
|
|
1375
|
-
`ethagent:${CONTINUITY_SNAPSHOT_ENVELOPE_VERSION}:wallet-slot:${address.toLowerCase()}:${sha256Hex(challenge)}:${accessManifestHash}`,
|
|
1376
|
-
32,
|
|
1377
|
-
))
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
|
-
function continuityAadFor(ownerAddress: string, createdAt: string): Buffer {
|
|
1381
|
-
return Buffer.from(`${CONTINUITY_SNAPSHOT_ENVELOPE_VERSION}\n${ownerAddress.toLowerCase()}\n${createdAt}`, 'utf8')
|
|
1382
|
-
}
|
|
1383
|
-
|
|
1384
|
-
function walletAccessManifestHash(args: {
|
|
1385
|
-
ownerAddress: string
|
|
1386
|
-
token: ContinuitySnapshotToken
|
|
1387
|
-
accessEpoch: number
|
|
1388
|
-
accessKeys: WalletContinuityRestoreAccessKey[]
|
|
1389
|
-
}): string {
|
|
1390
|
-
const token = normalizeContinuitySnapshotToken(args.token)
|
|
1391
|
-
const accessKeys = normalizeWalletRestoreAccessKeys(args.accessKeys)
|
|
1392
|
-
return sha256Hex(JSON.stringify({
|
|
1393
|
-
ownerAddress: toChecksumAddress(args.ownerAddress).toLowerCase(),
|
|
1394
|
-
token: {
|
|
1395
|
-
chainId: token.chainId,
|
|
1396
|
-
identityRegistryAddress: token.identityRegistryAddress.toLowerCase(),
|
|
1397
|
-
agentId: token.agentId,
|
|
1398
|
-
},
|
|
1399
|
-
accessEpoch: args.accessEpoch,
|
|
1400
|
-
wallets: accessKeys.map(key => ({
|
|
1401
|
-
address: key.address.toLowerCase(),
|
|
1402
|
-
challengeHash: sha256Hex(key.challenge),
|
|
1403
|
-
salt: key.salt,
|
|
1404
|
-
kemPublicKey: key.kemPublicKey,
|
|
1405
|
-
})),
|
|
1406
|
-
}))
|
|
1407
|
-
}
|
|
1408
|
-
|
|
1409
|
-
function walletPayloadAadFor(args: {
|
|
1410
|
-
ownerAddress: string
|
|
1411
|
-
createdAt: string
|
|
1412
|
-
token: ContinuitySnapshotToken
|
|
1413
|
-
accessEpoch: number
|
|
1414
|
-
accessManifestHash: string
|
|
1415
|
-
}): Buffer {
|
|
1416
|
-
const token = normalizeContinuitySnapshotToken(args.token)
|
|
1417
|
-
return Buffer.from([
|
|
1418
|
-
CONTINUITY_SNAPSHOT_ENVELOPE_VERSION,
|
|
1419
|
-
'wallet-signature-slots',
|
|
1420
|
-
args.ownerAddress.toLowerCase(),
|
|
1421
|
-
args.createdAt,
|
|
1422
|
-
String(token.chainId),
|
|
1423
|
-
token.identityRegistryAddress.toLowerCase(),
|
|
1424
|
-
token.agentId,
|
|
1425
|
-
String(args.accessEpoch),
|
|
1426
|
-
args.accessManifestHash,
|
|
1427
|
-
].join('\n'), 'utf8')
|
|
1428
|
-
}
|
|
1429
|
-
|
|
1430
|
-
function transferPayloadAadFor(args: {
|
|
1431
|
-
ownerAddress: string
|
|
1432
|
-
targetAddress: string
|
|
1433
|
-
createdAt: string
|
|
1434
|
-
token: ContinuitySnapshotToken
|
|
1435
|
-
}): Buffer {
|
|
1436
|
-
const token = normalizeContinuitySnapshotToken(args.token)
|
|
1437
|
-
return Buffer.from([
|
|
1438
|
-
CONTINUITY_SNAPSHOT_ENVELOPE_VERSION,
|
|
1439
|
-
'transfer',
|
|
1440
|
-
args.ownerAddress.toLowerCase(),
|
|
1441
|
-
args.targetAddress.toLowerCase(),
|
|
1442
|
-
args.createdAt,
|
|
1443
|
-
String(token.chainId),
|
|
1444
|
-
token.identityRegistryAddress.toLowerCase(),
|
|
1445
|
-
token.agentId,
|
|
1446
|
-
].join('\n'), 'utf8')
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
|
-
function walletSlotAadFor(slot: Pick<WalletContinuitySnapshotSlot, 'address' | 'challenge' | 'kemPublicKey' | 'kemCiphertext'>, payloadAad: Buffer): Buffer {
|
|
1450
|
-
return Buffer.concat([
|
|
1451
|
-
payloadAad,
|
|
1452
|
-
Buffer.from([
|
|
1453
|
-
'',
|
|
1454
|
-
'wallet-slot',
|
|
1455
|
-
slot.address.toLowerCase(),
|
|
1456
|
-
sha256Hex(slot.challenge),
|
|
1457
|
-
slot.kemPublicKey,
|
|
1458
|
-
slot.kemCiphertext,
|
|
1459
|
-
].join('\n'), 'utf8'),
|
|
1460
|
-
])
|
|
1461
|
-
}
|
|
1462
|
-
|
|
1463
|
-
function transferSlotAadFor(slot: Pick<TransferContinuitySnapshotSlot, 'address' | 'challenge'>, payloadAad: Buffer): Buffer {
|
|
1464
|
-
return Buffer.concat([
|
|
1465
|
-
payloadAad,
|
|
1466
|
-
Buffer.from(`\nslot\n${slot.address.toLowerCase()}\n${sha256Hex(slot.challenge)}`, 'utf8'),
|
|
1467
|
-
])
|
|
1468
|
-
}
|
|
1469
|
-
|
|
1470
|
-
function hkdf(ikm: Uint8Array, salt: Uint8Array, info: string, length: number): Uint8Array {
|
|
1471
|
-
return new Uint8Array(crypto.hkdfSync('sha256', ikm, salt, Buffer.from(info, 'utf8'), length))
|
|
1472
|
-
}
|
|
1473
|
-
|
|
1474
|
-
function sha256Hex(value: string | Uint8Array): string {
|
|
1475
|
-
return crypto.createHash('sha256').update(value).digest('hex')
|
|
1476
|
-
}
|
|
1477
|
-
|
|
1478
|
-
function toBase64(bytes: Uint8Array): string {
|
|
1479
|
-
return Buffer.from(bytes).toString('base64')
|
|
1480
|
-
}
|
|
1481
|
-
|
|
1482
|
-
function fromBase64(value: string): Uint8Array {
|
|
1483
|
-
return new Uint8Array(Buffer.from(value, 'base64'))
|
|
1484
|
-
}
|
|
1
|
+
export {
|
|
2
|
+
createContinuitySnapshotChallenge,
|
|
3
|
+
createTransferContinuitySnapshotChallenge,
|
|
4
|
+
createWalletRestoreAccessChallenge,
|
|
5
|
+
TRANSFER_SNAPSHOT_CHALLENGE_HEADER_LEGACY,
|
|
6
|
+
TRANSFER_SNAPSHOT_CHALLENGE_HEADER_RECEIVER,
|
|
7
|
+
TRANSFER_SNAPSHOT_CHALLENGE_HEADER_SENDER,
|
|
8
|
+
} from './challenges.js'
|
|
9
|
+
export type { WalletChallengePurpose } from './challenges.js'
|
|
10
|
+
|
|
11
|
+
export { CONTINUITY_SNAPSHOT_ENVELOPE_VERSION } from './envelopeVersion.js'
|
|
12
|
+
|
|
13
|
+
export { normalizeContinuitySkills } from './skillsNormalization.js'
|
|
14
|
+
|
|
15
|
+
export type {
|
|
16
|
+
ContinuityFiles,
|
|
17
|
+
ContinuitySkillsTree,
|
|
18
|
+
ContinuityAgentSnapshot,
|
|
19
|
+
ContinuitySnapshotPayload,
|
|
20
|
+
TransferContinuitySnapshotSlot,
|
|
21
|
+
WalletContinuityRestoreAccessKey,
|
|
22
|
+
WalletContinuitySnapshotSlot,
|
|
23
|
+
TransferContinuitySnapshotEnvelope,
|
|
24
|
+
ContinuitySnapshotEnvelope,
|
|
25
|
+
} from './envelopeTypes.js'
|
|
26
|
+
|
|
27
|
+
export {
|
|
28
|
+
ContinuitySnapshotOwnerMismatchError,
|
|
29
|
+
ContinuityTransferSnapshotTargetMismatchError,
|
|
30
|
+
ContinuitySnapshotRestoreSlotMissingError,
|
|
31
|
+
} from './envelopeTypes.js'
|
|
32
|
+
|
|
33
|
+
export {
|
|
34
|
+
createWalletRestoreAccessKey,
|
|
35
|
+
createContinuitySnapshotEnvelope,
|
|
36
|
+
createWalletContinuitySnapshotEnvelope,
|
|
37
|
+
createTransferContinuitySnapshotEnvelope,
|
|
38
|
+
} from './envelopeCreate.js'
|
|
39
|
+
|
|
40
|
+
export {
|
|
41
|
+
restoreContinuitySnapshotEnvelope,
|
|
42
|
+
assertContinuitySnapshotOwner,
|
|
43
|
+
serializeContinuitySnapshotEnvelope,
|
|
44
|
+
parseContinuitySnapshotEnvelope,
|
|
45
|
+
transferSnapshotMetadataFromEnvelope,
|
|
46
|
+
walletContinuitySnapshotSlotForAddress,
|
|
47
|
+
findRestorableAddressForSnapshot,
|
|
48
|
+
isWalletContinuitySnapshotEnvelope,
|
|
49
|
+
} from './envelopeParse.js'
|