@tjamescouch/agentchat 0.22.0 → 0.23.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/Dockerfile +1 -1
- package/dist/bin/agentchat.d.ts +7 -0
- package/dist/bin/agentchat.d.ts.map +1 -0
- package/dist/bin/agentchat.js +1511 -0
- package/dist/bin/agentchat.js.map +1 -0
- package/dist/lib/allowlist.d.ts +77 -0
- package/dist/lib/allowlist.d.ts.map +1 -0
- package/dist/lib/allowlist.js +151 -0
- package/dist/lib/allowlist.js.map +1 -0
- package/dist/lib/client.d.ts +147 -0
- package/dist/lib/client.d.ts.map +1 -0
- package/dist/lib/client.js +704 -0
- package/dist/lib/client.js.map +1 -0
- package/dist/lib/daemon.d.ts +122 -0
- package/dist/lib/daemon.d.ts.map +1 -0
- package/dist/lib/daemon.js +523 -0
- package/dist/lib/daemon.js.map +1 -0
- package/dist/lib/deploy/akash.d.ts +271 -0
- package/dist/lib/deploy/akash.d.ts.map +1 -0
- package/dist/lib/deploy/akash.js +671 -0
- package/dist/lib/deploy/akash.js.map +1 -0
- package/dist/lib/deploy/config.d.ts +62 -0
- package/dist/lib/deploy/config.d.ts.map +1 -0
- package/dist/lib/deploy/config.js +116 -0
- package/dist/lib/deploy/config.js.map +1 -0
- package/dist/lib/deploy/docker.d.ts +37 -0
- package/dist/lib/deploy/docker.d.ts.map +1 -0
- package/dist/lib/deploy/docker.js +122 -0
- package/dist/lib/deploy/docker.js.map +1 -0
- package/dist/lib/deploy/index.d.ts +11 -0
- package/dist/lib/deploy/index.d.ts.map +1 -0
- package/dist/lib/deploy/index.js +11 -0
- package/dist/lib/deploy/index.js.map +1 -0
- package/dist/lib/escrow-hooks.d.ts +199 -0
- package/dist/lib/escrow-hooks.d.ts.map +1 -0
- package/dist/lib/escrow-hooks.js +221 -0
- package/dist/lib/escrow-hooks.js.map +1 -0
- package/dist/lib/identity.d.ts +134 -0
- package/dist/lib/identity.d.ts.map +1 -0
- package/dist/lib/identity.js +334 -0
- package/dist/lib/identity.js.map +1 -0
- package/dist/lib/jitter.d.ts +42 -0
- package/dist/lib/jitter.d.ts.map +1 -0
- package/{lib → dist/lib}/jitter.js +16 -19
- package/dist/lib/jitter.js.map +1 -0
- package/dist/lib/proposals.d.ts +223 -0
- package/dist/lib/proposals.d.ts.map +1 -0
- package/dist/lib/proposals.js +379 -0
- package/dist/lib/proposals.js.map +1 -0
- package/dist/lib/protocol.d.ts +220 -0
- package/dist/lib/protocol.d.ts.map +1 -0
- package/dist/lib/protocol.js +507 -0
- package/dist/lib/protocol.js.map +1 -0
- package/dist/lib/receipts.d.ts +134 -0
- package/dist/lib/receipts.d.ts.map +1 -0
- package/dist/lib/receipts.js +270 -0
- package/dist/lib/receipts.js.map +1 -0
- package/dist/lib/reputation.d.ts +250 -0
- package/dist/lib/reputation.d.ts.map +1 -0
- package/dist/lib/reputation.js +586 -0
- package/dist/lib/reputation.js.map +1 -0
- package/dist/lib/security.d.ts +27 -0
- package/dist/lib/security.d.ts.map +1 -0
- package/dist/lib/security.js +150 -0
- package/dist/lib/security.js.map +1 -0
- package/dist/lib/server/handlers/admin.d.ts +26 -0
- package/dist/lib/server/handlers/admin.d.ts.map +1 -0
- package/dist/lib/server/handlers/admin.js +76 -0
- package/dist/lib/server/handlers/admin.js.map +1 -0
- package/dist/lib/server/handlers/identity.d.ts +36 -0
- package/dist/lib/server/handlers/identity.d.ts.map +1 -0
- package/dist/lib/server/handlers/identity.js +330 -0
- package/dist/lib/server/handlers/identity.js.map +1 -0
- package/dist/lib/server/handlers/index.d.ts +10 -0
- package/dist/lib/server/handlers/index.d.ts.map +1 -0
- package/dist/lib/server/handlers/index.js +15 -0
- package/dist/lib/server/handlers/index.js.map +1 -0
- package/dist/lib/server/handlers/message.d.ts +47 -0
- package/dist/lib/server/handlers/message.d.ts.map +1 -0
- package/dist/lib/server/handlers/message.js +265 -0
- package/dist/lib/server/handlers/message.js.map +1 -0
- package/dist/lib/server/handlers/presence.d.ts +18 -0
- package/dist/lib/server/handlers/presence.d.ts.map +1 -0
- package/dist/lib/server/handlers/presence.js +35 -0
- package/dist/lib/server/handlers/presence.js.map +1 -0
- package/dist/lib/server/handlers/proposal.d.ts +38 -0
- package/dist/lib/server/handlers/proposal.d.ts.map +1 -0
- package/dist/lib/server/handlers/proposal.js +273 -0
- package/dist/lib/server/handlers/proposal.js.map +1 -0
- package/dist/lib/server/handlers/skills.d.ts +22 -0
- package/dist/lib/server/handlers/skills.d.ts.map +1 -0
- package/dist/lib/server/handlers/skills.js +119 -0
- package/dist/lib/server/handlers/skills.js.map +1 -0
- package/dist/lib/server-directory.d.ts +85 -0
- package/dist/lib/server-directory.d.ts.map +1 -0
- package/dist/lib/server-directory.js +177 -0
- package/dist/lib/server-directory.js.map +1 -0
- package/dist/lib/server.d.ts +162 -0
- package/dist/lib/server.d.ts.map +1 -0
- package/dist/lib/server.js +602 -0
- package/dist/lib/server.js.map +1 -0
- package/dist/lib/types.d.ts +461 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +98 -0
- package/dist/lib/types.js.map +1 -0
- package/package.json +22 -13
- package/bin/agentchat.js +0 -1617
- package/lib/chat.py +0 -241
- package/lib/client.js +0 -821
- package/lib/daemon.js +0 -562
- package/lib/deploy/akash.js +0 -811
- package/lib/deploy/config.js +0 -128
- package/lib/deploy/docker.js +0 -132
- package/lib/deploy/index.js +0 -24
- package/lib/elo_swarm.py +0 -569
- package/lib/escrow-hooks.js +0 -237
- package/lib/identity.js +0 -376
- package/lib/proposals.js +0 -426
- package/lib/protocol.js +0 -484
- package/lib/receipts.js +0 -294
- package/lib/reputation.js +0 -664
- package/lib/security.js +0 -183
- package/lib/server/handlers/identity.js +0 -242
- package/lib/server/handlers/index.js +0 -42
- package/lib/server/handlers/message.js +0 -306
- package/lib/server/handlers/presence.js +0 -44
- package/lib/server/handlers/proposal.js +0 -358
- package/lib/server/handlers/skills.js +0 -141
- package/lib/server-directory.js +0 -181
- package/lib/server.js +0 -528
- package/lib/supervisor/USAGE.md +0 -110
- package/lib/supervisor/agent-supervisor.sh +0 -135
- package/lib/supervisor/agentctl.sh +0 -250
- package/lib/supervisor/killswitch.sh +0 -36
- package/lib/supervisor/notify.sh +0 -19
package/lib/escrow-hooks.js
DELETED
|
@@ -1,237 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* EscrowHooks - Event system for external escrow integration
|
|
3
|
-
*
|
|
4
|
-
* Allows external systems (blockchain, multi-sig, compliance) to hook into
|
|
5
|
-
* escrow lifecycle events without modifying core AgentChat code.
|
|
6
|
-
*
|
|
7
|
-
* Events:
|
|
8
|
-
* escrow:created - Escrow created when proposal accepted with stakes
|
|
9
|
-
* escrow:released - Escrow released (expired, cancelled)
|
|
10
|
-
* settlement:completion - Proposal completed, stakes returned
|
|
11
|
-
* settlement:dispute - Proposal disputed, stakes transferred/burned
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
export const EscrowEvent = {
|
|
15
|
-
CREATED: 'escrow:created',
|
|
16
|
-
RELEASED: 'escrow:released',
|
|
17
|
-
COMPLETION_SETTLED: 'settlement:completion',
|
|
18
|
-
DISPUTE_SETTLED: 'settlement:dispute'
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export class EscrowHooks {
|
|
22
|
-
constructor(options = {}) {
|
|
23
|
-
this.handlers = new Map(); // event -> Set of handlers
|
|
24
|
-
this.logger = options.logger || console;
|
|
25
|
-
this.continueOnError = options.continueOnError !== false; // default true
|
|
26
|
-
|
|
27
|
-
// Initialize event handler sets
|
|
28
|
-
for (const event of Object.values(EscrowEvent)) {
|
|
29
|
-
this.handlers.set(event, new Set());
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Register a handler for an escrow event
|
|
35
|
-
* @param {string} event - Event name from EscrowEvent
|
|
36
|
-
* @param {Function} handler - Async function(payload) to call
|
|
37
|
-
* @returns {Function} Unsubscribe function
|
|
38
|
-
*/
|
|
39
|
-
on(event, handler) {
|
|
40
|
-
if (!this.handlers.has(event)) {
|
|
41
|
-
throw new Error(`Unknown escrow event: ${event}`);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (typeof handler !== 'function') {
|
|
45
|
-
throw new Error('Handler must be a function');
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
this.handlers.get(event).add(handler);
|
|
49
|
-
|
|
50
|
-
// Return unsubscribe function
|
|
51
|
-
return () => this.off(event, handler);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Remove a handler for an escrow event
|
|
56
|
-
* @param {string} event - Event name
|
|
57
|
-
* @param {Function} handler - Handler to remove
|
|
58
|
-
*/
|
|
59
|
-
off(event, handler) {
|
|
60
|
-
if (this.handlers.has(event)) {
|
|
61
|
-
this.handlers.get(event).delete(handler);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Remove all handlers for an event (or all events)
|
|
67
|
-
* @param {string} [event] - Optional event name
|
|
68
|
-
*/
|
|
69
|
-
clear(event) {
|
|
70
|
-
if (event) {
|
|
71
|
-
if (this.handlers.has(event)) {
|
|
72
|
-
this.handlers.get(event).clear();
|
|
73
|
-
}
|
|
74
|
-
} else {
|
|
75
|
-
for (const handlers of this.handlers.values()) {
|
|
76
|
-
handlers.clear();
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Emit an escrow event to all registered handlers
|
|
83
|
-
* @param {string} event - Event name
|
|
84
|
-
* @param {Object} payload - Event payload
|
|
85
|
-
* @returns {Promise<Object>} Results from all handlers
|
|
86
|
-
*/
|
|
87
|
-
async emit(event, payload) {
|
|
88
|
-
if (!this.handlers.has(event)) {
|
|
89
|
-
throw new Error(`Unknown escrow event: ${event}`);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const handlers = this.handlers.get(event);
|
|
93
|
-
if (handlers.size === 0) {
|
|
94
|
-
return { event, handled: false, results: [] };
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const results = [];
|
|
98
|
-
const errors = [];
|
|
99
|
-
|
|
100
|
-
for (const handler of handlers) {
|
|
101
|
-
try {
|
|
102
|
-
const result = await handler(payload);
|
|
103
|
-
results.push({ success: true, result });
|
|
104
|
-
} catch (err) {
|
|
105
|
-
const errorInfo = {
|
|
106
|
-
success: false,
|
|
107
|
-
error: err.message,
|
|
108
|
-
stack: err.stack
|
|
109
|
-
};
|
|
110
|
-
errors.push(errorInfo);
|
|
111
|
-
results.push(errorInfo);
|
|
112
|
-
|
|
113
|
-
this.logger.error?.(`[EscrowHooks] Error in ${event} handler:`, err.message);
|
|
114
|
-
|
|
115
|
-
if (!this.continueOnError) {
|
|
116
|
-
break;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return {
|
|
122
|
-
event,
|
|
123
|
-
handled: true,
|
|
124
|
-
results,
|
|
125
|
-
errors: errors.length > 0 ? errors : undefined
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Check if any handlers are registered for an event
|
|
131
|
-
* @param {string} event - Event name
|
|
132
|
-
* @returns {boolean}
|
|
133
|
-
*/
|
|
134
|
-
hasHandlers(event) {
|
|
135
|
-
return this.handlers.has(event) && this.handlers.get(event).size > 0;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Get count of handlers for an event
|
|
140
|
-
* @param {string} event - Event name
|
|
141
|
-
* @returns {number}
|
|
142
|
-
*/
|
|
143
|
-
handlerCount(event) {
|
|
144
|
-
return this.handlers.has(event) ? this.handlers.get(event).size : 0;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Create payload for escrow:created event
|
|
150
|
-
*/
|
|
151
|
-
export function createEscrowCreatedPayload(proposal, escrowResult) {
|
|
152
|
-
return {
|
|
153
|
-
event: EscrowEvent.CREATED,
|
|
154
|
-
timestamp: Date.now(),
|
|
155
|
-
proposal_id: proposal.id,
|
|
156
|
-
from_agent: proposal.from,
|
|
157
|
-
to_agent: proposal.to,
|
|
158
|
-
proposer_stake: proposal.proposer_stake || 0,
|
|
159
|
-
acceptor_stake: proposal.acceptor_stake || 0,
|
|
160
|
-
total_stake: (proposal.proposer_stake || 0) + (proposal.acceptor_stake || 0),
|
|
161
|
-
task: proposal.task,
|
|
162
|
-
amount: proposal.amount,
|
|
163
|
-
currency: proposal.currency,
|
|
164
|
-
expires: proposal.expires,
|
|
165
|
-
escrow_id: escrowResult.escrow?.proposal_id || proposal.id
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Create payload for settlement:completion event
|
|
171
|
-
*/
|
|
172
|
-
export function createCompletionPayload(proposal, ratingChanges) {
|
|
173
|
-
const escrowInfo = ratingChanges?._escrow || {};
|
|
174
|
-
return {
|
|
175
|
-
event: EscrowEvent.COMPLETION_SETTLED,
|
|
176
|
-
timestamp: Date.now(),
|
|
177
|
-
proposal_id: proposal.id,
|
|
178
|
-
from_agent: proposal.from,
|
|
179
|
-
to_agent: proposal.to,
|
|
180
|
-
completed_by: proposal.completed_by,
|
|
181
|
-
completion_proof: proposal.completion_proof,
|
|
182
|
-
settlement: 'returned',
|
|
183
|
-
stakes_returned: {
|
|
184
|
-
proposer: escrowInfo.proposer_stake || 0,
|
|
185
|
-
acceptor: escrowInfo.acceptor_stake || 0
|
|
186
|
-
},
|
|
187
|
-
rating_changes: {
|
|
188
|
-
[proposal.from]: ratingChanges?.[proposal.from],
|
|
189
|
-
[proposal.to]: ratingChanges?.[proposal.to]
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Create payload for settlement:dispute event
|
|
196
|
-
*/
|
|
197
|
-
export function createDisputePayload(proposal, ratingChanges) {
|
|
198
|
-
const escrowInfo = ratingChanges?._escrow || {};
|
|
199
|
-
return {
|
|
200
|
-
event: EscrowEvent.DISPUTE_SETTLED,
|
|
201
|
-
timestamp: Date.now(),
|
|
202
|
-
proposal_id: proposal.id,
|
|
203
|
-
from_agent: proposal.from,
|
|
204
|
-
to_agent: proposal.to,
|
|
205
|
-
disputed_by: proposal.disputed_by,
|
|
206
|
-
dispute_reason: proposal.dispute_reason,
|
|
207
|
-
settlement: escrowInfo.settlement || 'settled',
|
|
208
|
-
settlement_reason: escrowInfo.settlement_reason,
|
|
209
|
-
fault_determination: escrowInfo.fault_party,
|
|
210
|
-
stakes_transferred: escrowInfo.transferred,
|
|
211
|
-
stakes_burned: escrowInfo.burned,
|
|
212
|
-
rating_changes: {
|
|
213
|
-
[proposal.from]: ratingChanges?.[proposal.from],
|
|
214
|
-
[proposal.to]: ratingChanges?.[proposal.to]
|
|
215
|
-
}
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Create payload for escrow:released event
|
|
221
|
-
*/
|
|
222
|
-
export function createEscrowReleasedPayload(proposalId, escrow, reason) {
|
|
223
|
-
return {
|
|
224
|
-
event: EscrowEvent.RELEASED,
|
|
225
|
-
timestamp: Date.now(),
|
|
226
|
-
proposal_id: proposalId,
|
|
227
|
-
from_agent: escrow.from?.agent_id,
|
|
228
|
-
to_agent: escrow.to?.agent_id,
|
|
229
|
-
stakes_released: {
|
|
230
|
-
proposer: escrow.from?.stake || 0,
|
|
231
|
-
acceptor: escrow.to?.stake || 0
|
|
232
|
-
},
|
|
233
|
-
reason: reason || 'expired'
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
export default EscrowHooks;
|
package/lib/identity.js
DELETED
|
@@ -1,376 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AgentChat Identity Module
|
|
3
|
-
* Ed25519 key generation, storage, and signing
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import crypto from 'crypto';
|
|
7
|
-
import fs from 'fs/promises';
|
|
8
|
-
import path from 'path';
|
|
9
|
-
import os from 'os';
|
|
10
|
-
|
|
11
|
-
// Default identity file location
|
|
12
|
-
export const DEFAULT_IDENTITY_PATH = path.join(process.cwd(), '.agentchat', 'identity.json');
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Generate stable agent ID from pubkey
|
|
16
|
-
* Returns first 8 chars of SHA256 hash (hex)
|
|
17
|
-
*/
|
|
18
|
-
export function pubkeyToAgentId(pubkey) {
|
|
19
|
-
const hash = crypto.createHash('sha256').update(pubkey).digest('hex');
|
|
20
|
-
return hash.substring(0, 8);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Validate Ed25519 public key in PEM format
|
|
25
|
-
*/
|
|
26
|
-
export function isValidPubkey(pubkey) {
|
|
27
|
-
if (!pubkey || typeof pubkey !== 'string') return false;
|
|
28
|
-
|
|
29
|
-
try {
|
|
30
|
-
const keyObj = crypto.createPublicKey(pubkey);
|
|
31
|
-
return keyObj.asymmetricKeyType === 'ed25519';
|
|
32
|
-
} catch {
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* AgentChat Identity
|
|
39
|
-
* Represents an agent's Ed25519 keypair and associated metadata
|
|
40
|
-
*/
|
|
41
|
-
export class Identity {
|
|
42
|
-
constructor(data) {
|
|
43
|
-
this.name = data.name;
|
|
44
|
-
this.pubkey = data.pubkey; // PEM format
|
|
45
|
-
this.privkey = data.privkey; // PEM format (null if loaded from export)
|
|
46
|
-
this.created = data.created;
|
|
47
|
-
this.rotations = data.rotations || []; // Array of rotation records
|
|
48
|
-
|
|
49
|
-
// Lazy-load crypto key objects
|
|
50
|
-
this._publicKey = null;
|
|
51
|
-
this._privateKey = null;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Generate new Ed25519 keypair
|
|
56
|
-
*/
|
|
57
|
-
static generate(name) {
|
|
58
|
-
const { publicKey, privateKey } = crypto.generateKeyPairSync('ed25519');
|
|
59
|
-
|
|
60
|
-
return new Identity({
|
|
61
|
-
name,
|
|
62
|
-
pubkey: publicKey.export({ type: 'spki', format: 'pem' }),
|
|
63
|
-
privkey: privateKey.export({ type: 'pkcs8', format: 'pem' }),
|
|
64
|
-
created: new Date().toISOString()
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Load identity from JSON file
|
|
70
|
-
*/
|
|
71
|
-
static async load(filePath = DEFAULT_IDENTITY_PATH) {
|
|
72
|
-
const data = await fs.readFile(filePath, 'utf-8');
|
|
73
|
-
const parsed = JSON.parse(data);
|
|
74
|
-
return new Identity(parsed);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Save identity to JSON file
|
|
79
|
-
*/
|
|
80
|
-
async save(filePath = DEFAULT_IDENTITY_PATH) {
|
|
81
|
-
// Ensure directory exists
|
|
82
|
-
const dir = path.dirname(filePath);
|
|
83
|
-
await fs.mkdir(dir, { recursive: true });
|
|
84
|
-
|
|
85
|
-
const data = {
|
|
86
|
-
name: this.name,
|
|
87
|
-
pubkey: this.pubkey,
|
|
88
|
-
privkey: this.privkey,
|
|
89
|
-
created: this.created,
|
|
90
|
-
rotations: this.rotations
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
await fs.writeFile(filePath, JSON.stringify(data, null, 2), {
|
|
94
|
-
mode: 0o600 // Owner read/write only
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Check if identity file exists
|
|
100
|
-
*/
|
|
101
|
-
static async exists(filePath = DEFAULT_IDENTITY_PATH) {
|
|
102
|
-
try {
|
|
103
|
-
await fs.access(filePath);
|
|
104
|
-
return true;
|
|
105
|
-
} catch {
|
|
106
|
-
return false;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Get fingerprint (first 16 chars of SHA256 hash of pubkey)
|
|
112
|
-
*/
|
|
113
|
-
getFingerprint() {
|
|
114
|
-
const hash = crypto.createHash('sha256').update(this.pubkey).digest('hex');
|
|
115
|
-
return hash.substring(0, 16);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Get stable agent ID (first 8 chars of fingerprint)
|
|
120
|
-
*/
|
|
121
|
-
getAgentId() {
|
|
122
|
-
return pubkeyToAgentId(this.pubkey);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Sign data with private key
|
|
127
|
-
* Returns base64-encoded signature
|
|
128
|
-
*/
|
|
129
|
-
sign(data) {
|
|
130
|
-
if (!this.privkey) {
|
|
131
|
-
throw new Error('Private key not available (identity was loaded from export)');
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (!this._privateKey) {
|
|
135
|
-
this._privateKey = crypto.createPrivateKey(this.privkey);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
139
|
-
const signature = crypto.sign(null, buffer, this._privateKey);
|
|
140
|
-
return signature.toString('base64');
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Verify a signature
|
|
145
|
-
* Static method for verifying any message
|
|
146
|
-
*/
|
|
147
|
-
static verify(data, signature, pubkey) {
|
|
148
|
-
try {
|
|
149
|
-
const keyObj = crypto.createPublicKey(pubkey);
|
|
150
|
-
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
151
|
-
const sigBuffer = Buffer.from(signature, 'base64');
|
|
152
|
-
return crypto.verify(null, buffer, keyObj, sigBuffer);
|
|
153
|
-
} catch {
|
|
154
|
-
return false;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Export for sharing (pubkey only, no private key)
|
|
160
|
-
*/
|
|
161
|
-
export() {
|
|
162
|
-
return {
|
|
163
|
-
name: this.name,
|
|
164
|
-
pubkey: this.pubkey,
|
|
165
|
-
created: this.created,
|
|
166
|
-
rotations: this.rotations
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Rotate to a new keypair
|
|
172
|
-
* Signs the new public key with the old private key for chain of custody
|
|
173
|
-
* @returns {object} Rotation record with old_pubkey, new_pubkey, signature, timestamp
|
|
174
|
-
*/
|
|
175
|
-
rotate() {
|
|
176
|
-
if (!this.privkey) {
|
|
177
|
-
throw new Error('Private key not available - cannot rotate');
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Generate new keypair
|
|
181
|
-
const { publicKey, privateKey } = crypto.generateKeyPairSync('ed25519');
|
|
182
|
-
const newPubkey = publicKey.export({ type: 'spki', format: 'pem' });
|
|
183
|
-
const newPrivkey = privateKey.export({ type: 'pkcs8', format: 'pem' });
|
|
184
|
-
|
|
185
|
-
// Use same timestamp for both signing and record
|
|
186
|
-
const timestamp = new Date().toISOString();
|
|
187
|
-
|
|
188
|
-
// Create rotation record content to sign
|
|
189
|
-
const rotationContent = JSON.stringify({
|
|
190
|
-
old_pubkey: this.pubkey,
|
|
191
|
-
new_pubkey: newPubkey,
|
|
192
|
-
timestamp
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
// Sign with old private key
|
|
196
|
-
const signature = this.sign(rotationContent);
|
|
197
|
-
|
|
198
|
-
// Create rotation record
|
|
199
|
-
const rotationRecord = {
|
|
200
|
-
old_pubkey: this.pubkey,
|
|
201
|
-
old_agent_id: this.getAgentId(),
|
|
202
|
-
new_pubkey: newPubkey,
|
|
203
|
-
new_agent_id: pubkeyToAgentId(newPubkey),
|
|
204
|
-
signature,
|
|
205
|
-
timestamp
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
// Update identity with new keys
|
|
209
|
-
this.rotations.push(rotationRecord);
|
|
210
|
-
this.pubkey = newPubkey;
|
|
211
|
-
this.privkey = newPrivkey;
|
|
212
|
-
this._publicKey = null;
|
|
213
|
-
this._privateKey = null;
|
|
214
|
-
|
|
215
|
-
return rotationRecord;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Verify a rotation record
|
|
220
|
-
* Checks that the signature is valid using the old public key
|
|
221
|
-
* @param {object} record - Rotation record to verify
|
|
222
|
-
* @returns {boolean} True if signature is valid
|
|
223
|
-
*/
|
|
224
|
-
static verifyRotation(record) {
|
|
225
|
-
try {
|
|
226
|
-
const rotationContent = JSON.stringify({
|
|
227
|
-
old_pubkey: record.old_pubkey,
|
|
228
|
-
new_pubkey: record.new_pubkey,
|
|
229
|
-
timestamp: record.timestamp
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
return Identity.verify(rotationContent, record.signature, record.old_pubkey);
|
|
233
|
-
} catch {
|
|
234
|
-
return false;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Verify the entire rotation chain
|
|
240
|
-
* @returns {object} { valid: boolean, errors: string[] }
|
|
241
|
-
*/
|
|
242
|
-
verifyRotationChain() {
|
|
243
|
-
const errors = [];
|
|
244
|
-
|
|
245
|
-
if (this.rotations.length === 0) {
|
|
246
|
-
return { valid: true, errors: [] };
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Verify each rotation in sequence
|
|
250
|
-
for (let i = 0; i < this.rotations.length; i++) {
|
|
251
|
-
const record = this.rotations[i];
|
|
252
|
-
|
|
253
|
-
// Verify signature
|
|
254
|
-
if (!Identity.verifyRotation(record)) {
|
|
255
|
-
errors.push(`Rotation ${i + 1}: Invalid signature`);
|
|
256
|
-
continue;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// Verify chain continuity (each new_pubkey should match next old_pubkey)
|
|
260
|
-
if (i < this.rotations.length - 1) {
|
|
261
|
-
const nextRecord = this.rotations[i + 1];
|
|
262
|
-
if (record.new_pubkey !== nextRecord.old_pubkey) {
|
|
263
|
-
errors.push(`Rotation ${i + 1}: Chain break - new_pubkey doesn't match next old_pubkey`);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// Verify final pubkey matches current identity
|
|
269
|
-
const lastRotation = this.rotations[this.rotations.length - 1];
|
|
270
|
-
if (lastRotation.new_pubkey !== this.pubkey) {
|
|
271
|
-
errors.push('Final rotation new_pubkey does not match current identity pubkey');
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
return {
|
|
275
|
-
valid: errors.length === 0,
|
|
276
|
-
errors
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Get the original (genesis) public key before any rotations
|
|
282
|
-
* @returns {string} Original public key in PEM format
|
|
283
|
-
*/
|
|
284
|
-
getOriginalPubkey() {
|
|
285
|
-
if (this.rotations.length === 0) {
|
|
286
|
-
return this.pubkey;
|
|
287
|
-
}
|
|
288
|
-
return this.rotations[0].old_pubkey;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* Get the original (genesis) agent ID
|
|
293
|
-
* @returns {string} Original agent ID
|
|
294
|
-
*/
|
|
295
|
-
getOriginalAgentId() {
|
|
296
|
-
return pubkeyToAgentId(this.getOriginalPubkey());
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Generate a signed revocation notice for this identity
|
|
301
|
-
* A revocation notice declares that the key should no longer be trusted
|
|
302
|
-
* @param {string} reason - Reason for revocation (e.g., "compromised", "retired", "lost")
|
|
303
|
-
* @returns {object} Revocation notice with pubkey, reason, signature, timestamp
|
|
304
|
-
*/
|
|
305
|
-
revoke(reason = 'revoked') {
|
|
306
|
-
if (!this.privkey) {
|
|
307
|
-
throw new Error('Private key not available - cannot create revocation notice');
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
const timestamp = new Date().toISOString();
|
|
311
|
-
|
|
312
|
-
// Create revocation content to sign
|
|
313
|
-
const revocationContent = JSON.stringify({
|
|
314
|
-
type: 'REVOCATION',
|
|
315
|
-
pubkey: this.pubkey,
|
|
316
|
-
agent_id: this.getAgentId(),
|
|
317
|
-
reason,
|
|
318
|
-
timestamp
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
// Sign with the key being revoked (proves ownership)
|
|
322
|
-
const signature = this.sign(revocationContent);
|
|
323
|
-
|
|
324
|
-
return {
|
|
325
|
-
type: 'REVOCATION',
|
|
326
|
-
pubkey: this.pubkey,
|
|
327
|
-
agent_id: this.getAgentId(),
|
|
328
|
-
fingerprint: this.getFingerprint(),
|
|
329
|
-
reason,
|
|
330
|
-
timestamp,
|
|
331
|
-
signature,
|
|
332
|
-
// Include rotation history for full chain verification
|
|
333
|
-
rotations: this.rotations.length > 0 ? this.rotations : undefined,
|
|
334
|
-
original_agent_id: this.rotations.length > 0 ? this.getOriginalAgentId() : undefined
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
/**
|
|
339
|
-
* Verify a revocation notice
|
|
340
|
-
* Checks that the signature is valid using the pubkey in the notice
|
|
341
|
-
* @param {object} notice - Revocation notice to verify
|
|
342
|
-
* @returns {boolean} True if signature is valid
|
|
343
|
-
*/
|
|
344
|
-
static verifyRevocation(notice) {
|
|
345
|
-
if (!notice || notice.type !== 'REVOCATION') {
|
|
346
|
-
return false;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
try {
|
|
350
|
-
const revocationContent = JSON.stringify({
|
|
351
|
-
type: 'REVOCATION',
|
|
352
|
-
pubkey: notice.pubkey,
|
|
353
|
-
agent_id: notice.agent_id,
|
|
354
|
-
reason: notice.reason,
|
|
355
|
-
timestamp: notice.timestamp
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
return Identity.verify(revocationContent, notice.signature, notice.pubkey);
|
|
359
|
-
} catch {
|
|
360
|
-
return false;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* Check if a pubkey has been revoked by checking against a revocation notice
|
|
366
|
-
* @param {string} pubkey - Public key to check
|
|
367
|
-
* @param {object} notice - Revocation notice
|
|
368
|
-
* @returns {boolean} True if the pubkey matches the revoked key
|
|
369
|
-
*/
|
|
370
|
-
static isRevoked(pubkey, notice) {
|
|
371
|
-
if (!Identity.verifyRevocation(notice)) {
|
|
372
|
-
return false;
|
|
373
|
-
}
|
|
374
|
-
return notice.pubkey === pubkey;
|
|
375
|
-
}
|
|
376
|
-
}
|