agentvault 1.0.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/.dfx/local/network-id +4 -0
- package/.next/trace +2 -0
- package/.vercel/README.txt +11 -0
- package/.vercel/project.json +1 -0
- package/AGENTS.md +43 -0
- package/CHANGELOG.md +196 -0
- package/LICENSE +21 -0
- package/PLAN_VAULT_INTEGRATION.md +318 -0
- package/README.md +253 -0
- package/backups/agentvault-backup-test-agent-2026-02-12T17-54-28-967Z.json +28 -0
- package/backups/agentvault-backup-test-agent-2026-02-12T17-54-29-032Z.backup +1 -0
- package/backups/agentvault-backup-test-agent-2026-02-12T17-57-42-373Z.json +28 -0
- package/backups/agentvault-backup-test-agent-2026-02-12T17-57-42-428Z.backup +1 -0
- package/backups/agentvault-backup-test-agent-2026-02-12T18-52-25-132Z.json +28 -0
- package/backups/agentvault-backup-test-agent-2026-02-12T18-52-25-247Z.backup +1 -0
- package/backups/agentvault-backup-test-agent-2026-02-12T18-54-09-216Z.json +28 -0
- package/backups/agentvault-backup-test-agent-2026-02-12T18-54-09-283Z.backup +1 -0
- package/backups/agentvault-backup-test-agent-2026-02-12T22-18-22-772Z.backup +1 -0
- package/backups/agentvault-backup-test-agent-2026-02-12T22-18-22-793Z.json +28 -0
- package/backups/test-backup.json +28 -0
- package/dist/cli/commands/approve.d.ts +4 -0
- package/dist/cli/commands/approve.js +232 -0
- package/dist/cli/commands/archive.d.ts +4 -0
- package/dist/cli/commands/archive.js +192 -0
- package/dist/cli/commands/backup.d.ts +4 -0
- package/dist/cli/commands/backup.js +164 -0
- package/dist/cli/commands/cloud-backup.d.ts +4 -0
- package/dist/cli/commands/cloud-backup.js +221 -0
- package/dist/cli/commands/cycles.d.ts +8 -0
- package/dist/cli/commands/cycles.js +83 -0
- package/dist/cli/commands/decrypt.d.ts +16 -0
- package/dist/cli/commands/decrypt.js +101 -0
- package/dist/cli/commands/deploy.d.ts +32 -0
- package/dist/cli/commands/deploy.js +208 -0
- package/dist/cli/commands/exec.d.ts +26 -0
- package/dist/cli/commands/exec.js +109 -0
- package/dist/cli/commands/fetch.d.ts +23 -0
- package/dist/cli/commands/fetch.js +164 -0
- package/dist/cli/commands/health.d.ts +8 -0
- package/dist/cli/commands/health.js +72 -0
- package/dist/cli/commands/identity.d.ts +8 -0
- package/dist/cli/commands/identity.js +140 -0
- package/dist/cli/commands/inference.d.ts +4 -0
- package/dist/cli/commands/inference.js +225 -0
- package/dist/cli/commands/info.d.ts +8 -0
- package/dist/cli/commands/info.js +59 -0
- package/dist/cli/commands/init.d.ts +19 -0
- package/dist/cli/commands/init.js +135 -0
- package/dist/cli/commands/instrument.d.ts +8 -0
- package/dist/cli/commands/instrument.js +35 -0
- package/dist/cli/commands/list.d.ts +36 -0
- package/dist/cli/commands/list.js +173 -0
- package/dist/cli/commands/logs.d.ts +8 -0
- package/dist/cli/commands/logs.js +96 -0
- package/dist/cli/commands/monitor.d.ts +8 -0
- package/dist/cli/commands/monitor.js +84 -0
- package/dist/cli/commands/network.d.ts +14 -0
- package/dist/cli/commands/network.js +258 -0
- package/dist/cli/commands/package.d.ts +36 -0
- package/dist/cli/commands/package.js +188 -0
- package/dist/cli/commands/profile.d.ts +8 -0
- package/dist/cli/commands/profile.js +76 -0
- package/dist/cli/commands/promote.d.ts +8 -0
- package/dist/cli/commands/promote.js +89 -0
- package/dist/cli/commands/rebuild.d.ts +21 -0
- package/dist/cli/commands/rebuild.js +140 -0
- package/dist/cli/commands/rollback.d.ts +8 -0
- package/dist/cli/commands/rollback.js +120 -0
- package/dist/cli/commands/show.d.ts +36 -0
- package/dist/cli/commands/show.js +200 -0
- package/dist/cli/commands/stats.d.ts +8 -0
- package/dist/cli/commands/stats.js +34 -0
- package/dist/cli/commands/status.d.ts +14 -0
- package/dist/cli/commands/status.js +83 -0
- package/dist/cli/commands/test.d.ts +8 -0
- package/dist/cli/commands/test.js +109 -0
- package/dist/cli/commands/tokens.d.ts +8 -0
- package/dist/cli/commands/tokens.js +62 -0
- package/dist/cli/commands/trace.d.ts +8 -0
- package/dist/cli/commands/trace.js +68 -0
- package/dist/cli/commands/wallet-export.d.ts +13 -0
- package/dist/cli/commands/wallet-export.js +140 -0
- package/dist/cli/commands/wallet-history.d.ts +10 -0
- package/dist/cli/commands/wallet-history.js +127 -0
- package/dist/cli/commands/wallet-import.d.ts +10 -0
- package/dist/cli/commands/wallet-import.js +209 -0
- package/dist/cli/commands/wallet-multi-send.d.ts +17 -0
- package/dist/cli/commands/wallet-multi-send.js +195 -0
- package/dist/cli/commands/wallet-process-queue.d.ts +19 -0
- package/dist/cli/commands/wallet-process-queue.js +209 -0
- package/dist/cli/commands/wallet-sign.d.ts +13 -0
- package/dist/cli/commands/wallet-sign.js +207 -0
- package/dist/cli/commands/wallet.d.ts +12 -0
- package/dist/cli/commands/wallet.js +794 -0
- package/dist/cli/index.d.ts +10 -0
- package/dist/cli/index.js +96 -0
- package/dist/vitest.config.d.ts +3 -0
- package/dist/vitest.config.js +14 -0
- package/fixup_1_0_OSS_release.md +136 -0
- package/fixup_REALEASE_PRD.md +136 -0
- package/package.json +79 -0
- package/pnpm-workspace.yaml +5 -0
- package/scripts/dev-dashboard.mjs +84 -0
- package/site/README.md +63 -0
- package/site/docusaurus.config.ts +148 -0
- package/site/package-lock.json +18383 -0
- package/site/package.json +47 -0
- package/site/sidebars.ts +86 -0
- package/site/static/.gitkeep +0 -0
- package/site/static/img/logo.svg +28 -0
- package/site/static/img/og-image.svg +35 -0
- package/src/archival/archive-manager.ts +372 -0
- package/src/archival/arweave-client.ts +289 -0
- package/src/archival/index.ts +8 -0
- package/src/backup/backup.ts +315 -0
- package/src/backup/index.ts +7 -0
- package/src/cloud-storage/cloud-sync.ts +461 -0
- package/src/cloud-storage/index.ts +11 -0
- package/src/cloud-storage/provider-detector.ts +198 -0
- package/src/cloud-storage/types.ts +104 -0
- package/src/debugging/index.ts +6 -0
- package/src/debugging/logs.ts +193 -0
- package/src/debugging/types.ts +100 -0
- package/src/deployment/deployer.ts +274 -0
- package/src/deployment/icpClient.ts +620 -0
- package/src/deployment/index.ts +46 -0
- package/src/deployment/promotion.ts +161 -0
- package/src/deployment/types.ts +111 -0
- package/src/icp/batch.ts +374 -0
- package/src/icp/cycles.ts +50 -0
- package/src/icp/environment.ts +215 -0
- package/src/icp/icpcli.ts +438 -0
- package/src/icp/icwasm.ts +222 -0
- package/src/icp/identity.ts +77 -0
- package/src/icp/index.ts +94 -0
- package/src/icp/optimization.ts +242 -0
- package/src/icp/tokens.ts +36 -0
- package/src/icp/tool-detector.ts +110 -0
- package/src/icp/types.ts +574 -0
- package/src/index.ts +25 -0
- package/src/inference/bittensor-client.ts +304 -0
- package/src/inference/index.ts +8 -0
- package/src/inference/inference-manager.ts +327 -0
- package/src/metrics/index.ts +7 -0
- package/src/metrics/metrics.ts +186 -0
- package/src/monitoring/alerting.ts +190 -0
- package/src/monitoring/health.ts +197 -0
- package/src/monitoring/index.ts +38 -0
- package/src/monitoring/info.ts +114 -0
- package/src/monitoring/types.ts +99 -0
- package/src/network/index.ts +5 -0
- package/src/network/network-config.ts +129 -0
- package/src/packaging/compiler.ts +647 -0
- package/src/packaging/config-persistence.ts +135 -0
- package/src/packaging/config-schemas.ts +156 -0
- package/src/packaging/detector.ts +220 -0
- package/src/packaging/index.ts +90 -0
- package/src/packaging/packager.ts +118 -0
- package/src/packaging/parsers/clawdbot.ts +278 -0
- package/src/packaging/parsers/cline.ts +223 -0
- package/src/packaging/parsers/generic.ts +266 -0
- package/src/packaging/parsers/goose.ts +214 -0
- package/src/packaging/parsers/index.ts +11 -0
- package/src/packaging/serializer.ts +260 -0
- package/src/packaging/types.ts +144 -0
- package/src/packaging/wasmedge-compiler.ts +406 -0
- package/src/security/index.ts +17 -0
- package/src/security/multisig.ts +415 -0
- package/src/security/types.ts +416 -0
- package/src/security/vetkeys.ts +655 -0
- package/src/testing/index.ts +6 -0
- package/src/testing/local-runner.ts +264 -0
- package/src/testing/types.ts +104 -0
- package/src/wallet/cbor-serializer.ts +323 -0
- package/src/wallet/chain-dispatcher.ts +313 -0
- package/src/wallet/cross-chain-aggregator.ts +346 -0
- package/src/wallet/index.ts +76 -0
- package/src/wallet/key-derivation.ts +425 -0
- package/src/wallet/providers/base-provider.ts +154 -0
- package/src/wallet/providers/cketh-provider.ts +434 -0
- package/src/wallet/providers/polkadot-provider.ts +503 -0
- package/src/wallet/providers/solana-provider.ts +490 -0
- package/src/wallet/transaction-queue.ts +284 -0
- package/src/wallet/types.ts +178 -0
- package/src/wallet/vetkeys-adapter.ts +431 -0
- package/src/wallet/wallet-manager.ts +597 -0
- package/src/wallet/wallet-storage.ts +380 -0
- package/vercel.json +8 -0
|
@@ -0,0 +1,655 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VetKeys Integration for Threshold Key Derivation
|
|
3
|
+
*
|
|
4
|
+
* This module provides VetKeys protocol implementation for threshold key derivation.
|
|
5
|
+
* Supports Shamir's Secret Sharing (SSS) for threshold cryptography.
|
|
6
|
+
*
|
|
7
|
+
* Security Properties:
|
|
8
|
+
* - Threshold signatures prevent single points of failure
|
|
9
|
+
* - Distributed trust model
|
|
10
|
+
* - Combiner-based key reconstruction
|
|
11
|
+
*
|
|
12
|
+
* Protocol Features:
|
|
13
|
+
* - Key derivation using secret sharing
|
|
14
|
+
* - Threshold signature verification
|
|
15
|
+
* - Key reconstruction without revealing secrets
|
|
16
|
+
*
|
|
17
|
+
* Note: VetKeysClient interface is defined in types.ts.
|
|
18
|
+
* This implementation class avoids the naming conflict.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import * as crypto from 'node:crypto';
|
|
22
|
+
import type {
|
|
23
|
+
EncryptedData,
|
|
24
|
+
VetKeysOptions,
|
|
25
|
+
EncryptionAlgorithm,
|
|
26
|
+
VetKeysDerivedKey as DerivedKey,
|
|
27
|
+
} from './types.js';
|
|
28
|
+
|
|
29
|
+
type CanisterAlgorithm = 'aes_256_gcm' | 'chacha20_poly1305';
|
|
30
|
+
|
|
31
|
+
function toCanisterAlgorithm(algorithm: EncryptionAlgorithm): CanisterAlgorithm {
|
|
32
|
+
return algorithm === 'aes-256-gcm' ? 'aes_256_gcm' : 'chacha20_poly1305';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function fromCanisterAlgorithm(canisterAlg: CanisterAlgorithm): EncryptionAlgorithm {
|
|
36
|
+
return canisterAlg === 'aes_256_gcm' ? 'aes-256-gcm' : 'chacha20-poly1305';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class VetKeysImplementation {
|
|
40
|
+
private config: VetKeysOptions;
|
|
41
|
+
private canisterId?: string;
|
|
42
|
+
private useCanister: boolean;
|
|
43
|
+
|
|
44
|
+
constructor(options: VetKeysOptions & { canisterId?: string; useCanister?: boolean } = {}) {
|
|
45
|
+
this.config = {
|
|
46
|
+
threshold: options.threshold ?? 2,
|
|
47
|
+
totalParties: options.totalParties ?? 3,
|
|
48
|
+
encryptionAlgorithm: options.encryptionAlgorithm ?? 'aes-256-gcm',
|
|
49
|
+
vetKeysCanisterId: options.canisterId,
|
|
50
|
+
};
|
|
51
|
+
this.canisterId = options.canisterId;
|
|
52
|
+
this.useCanister = options.useCanister ?? !!options.canisterId;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Decrypt JSON data using seed phrase
|
|
57
|
+
*
|
|
58
|
+
* @param encrypted - Encrypted data to decrypt
|
|
59
|
+
* @param seedPhrase - Seed phrase for key derivation
|
|
60
|
+
* @returns Decrypted JSON object
|
|
61
|
+
*/
|
|
62
|
+
public static async decryptJSON<T = unknown>(
|
|
63
|
+
encrypted: EncryptedData,
|
|
64
|
+
seedPhrase: string
|
|
65
|
+
): Promise<T> {
|
|
66
|
+
const crypto = await import('node:crypto');
|
|
67
|
+
const bip39 = await import('bip39');
|
|
68
|
+
|
|
69
|
+
// Derive key from seed phrase
|
|
70
|
+
const seed = await bip39.mnemonicToSeed(seedPhrase);
|
|
71
|
+
const key = crypto.pbkdf2Sync(
|
|
72
|
+
seed,
|
|
73
|
+
encrypted.salt,
|
|
74
|
+
100000,
|
|
75
|
+
32,
|
|
76
|
+
'sha256',
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
// Decode IV and ciphertext
|
|
80
|
+
const iv = Buffer.from(encrypted.iv, 'hex');
|
|
81
|
+
const ciphertext = Buffer.from(encrypted.ciphertext, 'hex');
|
|
82
|
+
|
|
83
|
+
// Decrypt based on algorithm
|
|
84
|
+
let algorithm: string;
|
|
85
|
+
if (encrypted.algorithm === 'aes-256-gcm') {
|
|
86
|
+
algorithm = 'aes-256-gcm';
|
|
87
|
+
} else {
|
|
88
|
+
algorithm = encrypted.algorithm.replace('-', '');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const decipher = crypto.createDecipheriv(algorithm, key, iv);
|
|
92
|
+
|
|
93
|
+
const decrypted = Buffer.concat([
|
|
94
|
+
decipher.update(ciphertext),
|
|
95
|
+
decipher.final(),
|
|
96
|
+
]);
|
|
97
|
+
|
|
98
|
+
return JSON.parse(decrypted.toString('utf-8')) as T;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Derive threshold key from seed phrase
|
|
103
|
+
*
|
|
104
|
+
* Implements Shamir's Secret Sharing for threshold key derivation.
|
|
105
|
+
* Generates n secret shares (where threshold = t out of n)
|
|
106
|
+
* Each share is encrypted and can be used to reconstruct the master key.
|
|
107
|
+
*
|
|
108
|
+
* @param seedPhrase - BIP39 seed phrase
|
|
109
|
+
* @param options - Optional derivation options
|
|
110
|
+
* @returns Derived key with threshold parameters
|
|
111
|
+
*/
|
|
112
|
+
public async deriveThresholdKey(
|
|
113
|
+
seedPhrase: string,
|
|
114
|
+
options: VetKeysOptions & {
|
|
115
|
+
threshold?: number;
|
|
116
|
+
totalParties?: number;
|
|
117
|
+
encryptionAlgorithm?: EncryptionAlgorithm;
|
|
118
|
+
} = {}
|
|
119
|
+
): Promise<DerivedKey> {
|
|
120
|
+
const threshold = options.threshold ?? this.config.threshold;
|
|
121
|
+
const totalParties = options.totalParties ?? this.config.totalParties;
|
|
122
|
+
const algorithm = options.encryptionAlgorithm ?? this.config.encryptionAlgorithm;
|
|
123
|
+
|
|
124
|
+
// Validate threshold
|
|
125
|
+
if (threshold! < 1 || threshold! > totalParties!) {
|
|
126
|
+
throw new Error(
|
|
127
|
+
`Threshold must be between 1 and totalParticipants (${totalParties!}). Got: ${threshold}`
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
// Derive n secret shares from seed phrase
|
|
133
|
+
const shares = await this.generateSecretShares(seedPhrase, threshold!, totalParties!, algorithm!);
|
|
134
|
+
|
|
135
|
+
// Generate share metadata
|
|
136
|
+
const shareMetadata = shares.map((share, index) => ({
|
|
137
|
+
index: index + 1,
|
|
138
|
+
shareId: this.generateShareId(),
|
|
139
|
+
participantId: (index + 1).toString(),
|
|
140
|
+
encryptedShare: share.encryptedShare,
|
|
141
|
+
commitment: share.commitment,
|
|
142
|
+
}));
|
|
143
|
+
|
|
144
|
+
// Generate commitment
|
|
145
|
+
const commitment = await this.generateCommitment(shares);
|
|
146
|
+
|
|
147
|
+
// Generate verification parameters
|
|
148
|
+
const verification = {
|
|
149
|
+
threshold,
|
|
150
|
+
shares,
|
|
151
|
+
commitment,
|
|
152
|
+
algorithm,
|
|
153
|
+
encryptionAlgorithm: algorithm,
|
|
154
|
+
createdAt: new Date().toISOString(),
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// Derive master key from seed phrase (for local use)
|
|
158
|
+
const derivedKey = await this.deriveMasterKey(seedPhrase, algorithm!);
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
type: 'threshold',
|
|
162
|
+
key: derivedKey.key,
|
|
163
|
+
method: derivedKey.method,
|
|
164
|
+
seedPhrase,
|
|
165
|
+
threshold: threshold!,
|
|
166
|
+
totalParties: totalParties!,
|
|
167
|
+
algorithm: algorithm!,
|
|
168
|
+
shares,
|
|
169
|
+
shareMetadata,
|
|
170
|
+
commitment,
|
|
171
|
+
verification,
|
|
172
|
+
} as DerivedKey;
|
|
173
|
+
} catch (error) {
|
|
174
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
175
|
+
throw new Error(`Failed to derive threshold key: ${message}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Generate secret shares using Shamir's Secret Sharing
|
|
181
|
+
*
|
|
182
|
+
* @param seedPhrase - Master secret
|
|
183
|
+
* @param threshold - Number of shares to create (t)
|
|
184
|
+
* @param totalParties - Total number of participants (n)
|
|
185
|
+
* @param algorithm - Encryption algorithm to use
|
|
186
|
+
* @returns Array of encrypted shares
|
|
187
|
+
*/
|
|
188
|
+
private async generateSecretShares(
|
|
189
|
+
seedPhrase: string,
|
|
190
|
+
threshold: number,
|
|
191
|
+
totalParties: number,
|
|
192
|
+
algorithm: EncryptionAlgorithm
|
|
193
|
+
): Promise<Array<{ shareId: string; participantId: string; encryptedShare: string; commitment: string }>> {
|
|
194
|
+
const shares: Array<{ shareId: string; participantId: string; encryptedShare: string; commitment: string }> = [];
|
|
195
|
+
const masterCommitment = await this.generateCommitment(shares);
|
|
196
|
+
|
|
197
|
+
for (let i = 0; i < threshold; i++) {
|
|
198
|
+
const shareId = this.generateShareId();
|
|
199
|
+
const participantId = i + 1;
|
|
200
|
+
|
|
201
|
+
// Generate unique secret for this participant
|
|
202
|
+
const participantSecret = this.generateParticipantSecret(seedPhrase, i, totalParties);
|
|
203
|
+
|
|
204
|
+
// Encrypt share with participant's secret
|
|
205
|
+
const { encryptedShare, commitment: shareCommitment } = await this.encryptShare(
|
|
206
|
+
participantSecret,
|
|
207
|
+
masterCommitment,
|
|
208
|
+
algorithm,
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
shares.push({
|
|
212
|
+
shareId,
|
|
213
|
+
participantId: participantId.toString(),
|
|
214
|
+
encryptedShare,
|
|
215
|
+
commitment: shareCommitment,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return shares;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Generate share identifier using cryptographically secure random bytes
|
|
224
|
+
*/
|
|
225
|
+
private generateShareId(): string {
|
|
226
|
+
const randomBytes = crypto.randomBytes(4);
|
|
227
|
+
const timestamp = Date.now().toString(36);
|
|
228
|
+
const randomHex = randomBytes.toString('hex').substring(0, 8);
|
|
229
|
+
return `share_${timestamp}_${randomHex}`;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Generate unique secret for a participant
|
|
234
|
+
*
|
|
235
|
+
* @param seedPhrase - Master secret
|
|
236
|
+
* @param participantIndex - Participant index (1-based)
|
|
237
|
+
*/
|
|
238
|
+
private generateParticipantSecret(seedPhrase: string, participantIndex: number, _totalParties: number): string {
|
|
239
|
+
const secretBytes = Buffer.from(seedPhrase, 'utf8');
|
|
240
|
+
|
|
241
|
+
// Create unique secret for this participant by adding participant index
|
|
242
|
+
const participantSuffix = Buffer.concat([Buffer.from([participantIndex]), secretBytes]);
|
|
243
|
+
|
|
244
|
+
return participantSuffix.toString('hex');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Encrypt a secret share
|
|
249
|
+
*
|
|
250
|
+
* @param secret - Secret to encrypt
|
|
251
|
+
* @param algorithm - Encryption algorithm
|
|
252
|
+
*/
|
|
253
|
+
private async encryptShare(
|
|
254
|
+
secret: string,
|
|
255
|
+
_commitment: string,
|
|
256
|
+
algorithm: EncryptionAlgorithm
|
|
257
|
+
): Promise<{ encryptedShare: string; commitment: string }> {
|
|
258
|
+
const crypto = await import('node:crypto');
|
|
259
|
+
|
|
260
|
+
const secretBuffer = Buffer.from(secret, 'utf-8');
|
|
261
|
+
const iv = algorithm === 'aes-256-gcm' ? crypto.randomBytes(12) : crypto.randomBytes(16);
|
|
262
|
+
const algorithmName = algorithm.replace('-', '');
|
|
263
|
+
|
|
264
|
+
const encryptionKey = crypto.pbkdf2Sync(
|
|
265
|
+
secretBuffer,
|
|
266
|
+
iv,
|
|
267
|
+
100000,
|
|
268
|
+
32,
|
|
269
|
+
'sha256'
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
const cipher = crypto.createCipheriv(algorithmName, encryptionKey, iv);
|
|
273
|
+
|
|
274
|
+
const encryptedShare = Buffer.concat([
|
|
275
|
+
cipher.update(secretBuffer),
|
|
276
|
+
cipher.final(),
|
|
277
|
+
]);
|
|
278
|
+
|
|
279
|
+
const commitmentHash = crypto.createHash('sha256')
|
|
280
|
+
.update(encryptedShare)
|
|
281
|
+
.digest();
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
encryptedShare: encryptedShare.toString('hex'),
|
|
285
|
+
commitment: commitmentHash.toString('hex'),
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Generate commitment from all shares
|
|
291
|
+
*/
|
|
292
|
+
private async generateCommitment(shares: Array<{ encryptedShare: string }>): Promise<string> {
|
|
293
|
+
const crypto = await import('node:crypto');
|
|
294
|
+
const hash = crypto.createHash('sha256');
|
|
295
|
+
|
|
296
|
+
// Combine all encrypted shares
|
|
297
|
+
for (const share of shares) {
|
|
298
|
+
const shareBuffer = Buffer.from(share.encryptedShare, 'hex');
|
|
299
|
+
hash.update(shareBuffer);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return hash.digest('hex');
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Derive master key from seed phrase (for local use)
|
|
307
|
+
*
|
|
308
|
+
* Uses PBKDF2 for key derivation with a derived salt from the seed.
|
|
309
|
+
* This is NOT the threshold key, but the master secret that participants share.
|
|
310
|
+
*/
|
|
311
|
+
private async deriveMasterKey(seedPhrase: string, _algorithm: EncryptionAlgorithm): Promise<{ key: string; method: string }> {
|
|
312
|
+
const crypto = await import('node:crypto');
|
|
313
|
+
const bip39 = await import('bip39');
|
|
314
|
+
|
|
315
|
+
const seed = await bip39.mnemonicToSeed(seedPhrase);
|
|
316
|
+
|
|
317
|
+
// Derive salt from seed (first 16 bytes) for unique per-wallet salting
|
|
318
|
+
const salt = crypto.createHash('sha256')
|
|
319
|
+
.update(seed.slice(0, 16))
|
|
320
|
+
.update('agentvault-v1')
|
|
321
|
+
.digest();
|
|
322
|
+
|
|
323
|
+
// Derive key using PBKDF2 with unique salt
|
|
324
|
+
const key = crypto.pbkdf2Sync(
|
|
325
|
+
seed,
|
|
326
|
+
salt,
|
|
327
|
+
100000,
|
|
328
|
+
32,
|
|
329
|
+
'sha256',
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
key: key.toString('hex'),
|
|
334
|
+
method: 'pbkdf2',
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Verify encryption was created by VetKeys
|
|
340
|
+
*
|
|
341
|
+
* Validates that the encrypted data structure is valid and properly formatted.
|
|
342
|
+
*
|
|
343
|
+
* @param encrypted - Encrypted data to verify
|
|
344
|
+
* @returns True if the encryption structure is valid
|
|
345
|
+
*/
|
|
346
|
+
public async verifyEncryption(encrypted: EncryptedData): Promise<boolean> {
|
|
347
|
+
if (!encrypted) {
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (!encrypted.algorithm || !['aes-256-gcm', 'chacha20-poly1305'].includes(encrypted.algorithm)) {
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (!encrypted.iv || typeof encrypted.iv !== 'string') {
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const ivBytes = Buffer.from(encrypted.iv, 'hex');
|
|
360
|
+
const expectedIvLength = encrypted.algorithm === 'aes-256-gcm' ? 12 : 16;
|
|
361
|
+
if (ivBytes.length !== expectedIvLength) {
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (!encrypted.salt || typeof encrypted.salt !== 'string') {
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const saltBytes = Buffer.from(encrypted.salt, 'hex');
|
|
370
|
+
if (saltBytes.length < 8) {
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (!encrypted.ciphertext || typeof encrypted.ciphertext !== 'string') {
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const ciphertextBytes = Buffer.from(encrypted.ciphertext, 'hex');
|
|
379
|
+
if (ciphertextBytes.length === 0) {
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (encrypted.encryptedAt) {
|
|
384
|
+
const timestamp = new Date(encrypted.encryptedAt);
|
|
385
|
+
if (isNaN(timestamp.getTime())) {
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return true;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Get encryption status
|
|
395
|
+
*/
|
|
396
|
+
public getEncryptionStatus(): {
|
|
397
|
+
thresholdSupported: boolean;
|
|
398
|
+
totalParticipants: number;
|
|
399
|
+
currentThreshold: number;
|
|
400
|
+
encryptionAlgorithm: EncryptionAlgorithm;
|
|
401
|
+
keyDerivation: string;
|
|
402
|
+
} {
|
|
403
|
+
return {
|
|
404
|
+
thresholdSupported: true,
|
|
405
|
+
totalParticipants: this.config.totalParties!,
|
|
406
|
+
currentThreshold: this.config.threshold!,
|
|
407
|
+
encryptionAlgorithm: this.config.encryptionAlgorithm!,
|
|
408
|
+
keyDerivation: 'shamir-ss',
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Store encrypted secret on canister
|
|
414
|
+
*
|
|
415
|
+
* @param secretId - ID of the secret
|
|
416
|
+
* @param encryptedSecret - Encrypted secret data
|
|
417
|
+
* @returns True if stored successfully
|
|
418
|
+
*/
|
|
419
|
+
public async storeEncryptedSecretOnCanister(
|
|
420
|
+
secretId: string,
|
|
421
|
+
encryptedSecret: {
|
|
422
|
+
ciphertext: Uint8Array;
|
|
423
|
+
iv: Uint8Array;
|
|
424
|
+
tag: Uint8Array;
|
|
425
|
+
algorithm: EncryptionAlgorithm;
|
|
426
|
+
}
|
|
427
|
+
): Promise<boolean> {
|
|
428
|
+
if (!this.useCanister) {
|
|
429
|
+
console.warn('Canister integration disabled, skipping canister storage');
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (!this.canisterId) {
|
|
434
|
+
console.warn('Canister ID not configured, skipping canister storage');
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
try {
|
|
439
|
+
const { createActor } = await import('../canister/actor.js');
|
|
440
|
+
const actor = createActor(this.canisterId);
|
|
441
|
+
|
|
442
|
+
const result = await actor.storeEncryptedSecret({
|
|
443
|
+
id: secretId,
|
|
444
|
+
ciphertext: new Uint8Array(encryptedSecret.ciphertext),
|
|
445
|
+
iv: new Uint8Array(encryptedSecret.iv),
|
|
446
|
+
tag: new Uint8Array(encryptedSecret.tag),
|
|
447
|
+
algorithm: toCanisterAlgorithm(encryptedSecret.algorithm),
|
|
448
|
+
createdAt: Date.now(),
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
if ('ok' in result) {
|
|
452
|
+
console.log('Encrypted secret stored on canister:', secretId);
|
|
453
|
+
return true;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
return false;
|
|
457
|
+
} catch (error) {
|
|
458
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
459
|
+
console.warn(`Failed to store encrypted secret on canister: ${message}`);
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Retrieve encrypted secret from canister
|
|
466
|
+
*
|
|
467
|
+
* @param secretId - ID of the secret
|
|
468
|
+
* @returns Encrypted secret data or null
|
|
469
|
+
*/
|
|
470
|
+
public async getEncryptedSecretFromCanister(
|
|
471
|
+
secretId: string
|
|
472
|
+
): Promise<{ ciphertext: Uint8Array; iv: Uint8Array; tag: Uint8Array; algorithm: EncryptionAlgorithm } | null> {
|
|
473
|
+
if (!this.canisterId) {
|
|
474
|
+
return null;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
try {
|
|
478
|
+
const { createActor } = await import('../canister/actor.js');
|
|
479
|
+
const actor = createActor(this.canisterId);
|
|
480
|
+
|
|
481
|
+
const result = await actor.getEncryptedSecret(secretId);
|
|
482
|
+
|
|
483
|
+
if (!result || result.length === 0) {
|
|
484
|
+
return null;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const [secret] = result;
|
|
488
|
+
|
|
489
|
+
return {
|
|
490
|
+
ciphertext: new Uint8Array(secret.ciphertext),
|
|
491
|
+
iv: new Uint8Array(secret.iv),
|
|
492
|
+
tag: new Uint8Array(secret.tag),
|
|
493
|
+
algorithm: fromCanisterAlgorithm(secret.algorithm),
|
|
494
|
+
};
|
|
495
|
+
} catch (error) {
|
|
496
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
497
|
+
console.warn(`Failed to retrieve encrypted secret from canister: ${message}`);
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* List all encrypted secrets from canister
|
|
504
|
+
*
|
|
505
|
+
* @returns Array of secret IDs
|
|
506
|
+
*/
|
|
507
|
+
public async listEncryptedSecretsOnCanister(): Promise<string[]> {
|
|
508
|
+
if (!this.canisterId) {
|
|
509
|
+
return [];
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
try {
|
|
513
|
+
const { createActor } = await import('../canister/actor.js');
|
|
514
|
+
const actor = createActor(this.canisterId);
|
|
515
|
+
|
|
516
|
+
const secrets = await actor.listEncryptedSecrets();
|
|
517
|
+
|
|
518
|
+
return secrets.map(s => s.id);
|
|
519
|
+
} catch (error) {
|
|
520
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
521
|
+
console.warn(`Failed to list encrypted secrets from canister: ${message}`);
|
|
522
|
+
return [];
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Delete encrypted secret from canister
|
|
528
|
+
*
|
|
529
|
+
* @param secretId - ID of the secret
|
|
530
|
+
* @returns True if deleted successfully
|
|
531
|
+
*/
|
|
532
|
+
public async deleteEncryptedSecretFromCanister(secretId: string): Promise<boolean> {
|
|
533
|
+
if (!this.canisterId) {
|
|
534
|
+
return false;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
try {
|
|
538
|
+
const { createActor } = await import('../canister/actor.js');
|
|
539
|
+
const actor = createActor(this.canisterId);
|
|
540
|
+
|
|
541
|
+
const result = await actor.deleteEncryptedSecret(secretId);
|
|
542
|
+
|
|
543
|
+
if ('ok' in result) {
|
|
544
|
+
return true;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
return false;
|
|
548
|
+
} catch (error) {
|
|
549
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
550
|
+
console.warn(`Failed to delete encrypted secret from canister: ${message}`);
|
|
551
|
+
return false;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Verify threshold signature with canister
|
|
557
|
+
*
|
|
558
|
+
* IMPORTANT: Requires VetKeys canister to be deployed and connected.
|
|
559
|
+
* Returns false if canister is not available.
|
|
560
|
+
*
|
|
561
|
+
* @param signature - Signature to verify
|
|
562
|
+
* @param message - Original message
|
|
563
|
+
* @returns True if signature is valid
|
|
564
|
+
*/
|
|
565
|
+
public async verifyThresholdSignatureCanister(
|
|
566
|
+
signature: string,
|
|
567
|
+
message: string
|
|
568
|
+
): Promise<boolean> {
|
|
569
|
+
if (!this.canisterId) {
|
|
570
|
+
console.warn('VetKeys canister not configured: cannot verify threshold signature');
|
|
571
|
+
return false;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
try {
|
|
575
|
+
const { createActor } = await import('../canister/actor.js');
|
|
576
|
+
const actor = createActor(this.canisterId);
|
|
577
|
+
|
|
578
|
+
const result = await actor.verifyThresholdSignature(signature, message);
|
|
579
|
+
|
|
580
|
+
if ('ok' in result && result.ok === 'verified') {
|
|
581
|
+
return true;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
return false;
|
|
585
|
+
} catch (error) {
|
|
586
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
587
|
+
console.warn(`Failed to verify threshold signature on canister: ${errorMessage}`);
|
|
588
|
+
return false;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Get VetKeys status from canister
|
|
594
|
+
*
|
|
595
|
+
* @returns VetKeys status information
|
|
596
|
+
*/
|
|
597
|
+
public async getVetKeysStatusFromCanister(): Promise<{
|
|
598
|
+
enabled: boolean;
|
|
599
|
+
thresholdSupported: boolean;
|
|
600
|
+
mode: 'mock' | 'production';
|
|
601
|
+
}> {
|
|
602
|
+
if (!this.canisterId) {
|
|
603
|
+
return {
|
|
604
|
+
enabled: false,
|
|
605
|
+
thresholdSupported: true,
|
|
606
|
+
mode: 'mock',
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
try {
|
|
611
|
+
const { createActor } = await import('../canister/actor.js');
|
|
612
|
+
const actor = createActor(this.canisterId);
|
|
613
|
+
|
|
614
|
+
const status = await actor.getVetKeysStatus();
|
|
615
|
+
|
|
616
|
+
let mode: 'mock' | 'production' = 'mock';
|
|
617
|
+
const hasMockMode = status.mode && typeof status.mode === 'object' && 'mock' in status.mode;
|
|
618
|
+
const hasProductionMode = status.mode && typeof status.mode === 'object' && 'production' in status.mode;
|
|
619
|
+
|
|
620
|
+
if (hasMockMode) {
|
|
621
|
+
mode = 'mock';
|
|
622
|
+
} else if (hasProductionMode) {
|
|
623
|
+
mode = 'production';
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
return {
|
|
627
|
+
enabled: status.enabled,
|
|
628
|
+
thresholdSupported: status.thresholdSupported,
|
|
629
|
+
mode: mode,
|
|
630
|
+
};
|
|
631
|
+
} catch (error) {
|
|
632
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
633
|
+
console.warn(`Failed to get VetKeys status from canister: ${message}`);
|
|
634
|
+
return {
|
|
635
|
+
enabled: false,
|
|
636
|
+
thresholdSupported: true,
|
|
637
|
+
mode: 'mock',
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Decrypt JSON data using seed phrase
|
|
645
|
+
*
|
|
646
|
+
* @param encrypted - Encrypted data to decrypt
|
|
647
|
+
* @param seedPhrase - Seed phrase for key derivation
|
|
648
|
+
* @returns Decrypted JSON object
|
|
649
|
+
*/
|
|
650
|
+
export async function decryptJSON<T = unknown>(
|
|
651
|
+
encrypted: EncryptedData,
|
|
652
|
+
seedPhrase: string
|
|
653
|
+
): Promise<T> {
|
|
654
|
+
return VetKeysImplementation.decryptJSON(encrypted, seedPhrase);
|
|
655
|
+
}
|