aap-agent-client 3.0.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/index.js +76 -55
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -1,13 +1,38 @@
1
1
  /**
2
- * @aap/client v3.0.0
2
+ * @aap/client v3.2.0
3
3
  *
4
- * WebSocket client for Agent Attestation Protocol.
5
- * Connect, solve sequential challenges, prove your intelligence.
4
+ * WebSocket client with mandatory signature.
5
+ * Proves cryptographic identity via secp256k1.
6
6
  */
7
7
 
8
8
  import WebSocket from 'ws';
9
+ import { generateKeyPairSync, createSign, createHash, randomBytes } from 'crypto';
9
10
 
10
- export const PROTOCOL_VERSION = '3.0.0';
11
+ export const PROTOCOL_VERSION = '3.2.0';
12
+
13
+ /**
14
+ * Generate secp256k1 key pair for agent identity
15
+ */
16
+ export function generateIdentity() {
17
+ const { publicKey, privateKey } = generateKeyPairSync('ec', {
18
+ namedCurve: 'secp256k1',
19
+ publicKeyEncoding: { type: 'spki', format: 'pem' },
20
+ privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
21
+ });
22
+
23
+ const publicId = createHash('sha256').update(publicKey).digest('hex').slice(0, 16);
24
+
25
+ return { publicKey, privateKey, publicId };
26
+ }
27
+
28
+ /**
29
+ * Sign data with private key
30
+ */
31
+ export function sign(data, privateKey) {
32
+ const signer = createSign('SHA256');
33
+ signer.update(data);
34
+ return signer.sign(privateKey, 'base64');
35
+ }
11
36
 
12
37
  /**
13
38
  * AAP WebSocket Client
@@ -15,25 +40,28 @@ export const PROTOCOL_VERSION = '3.0.0';
15
40
  export class AAPClient {
16
41
  constructor(options = {}) {
17
42
  this.serverUrl = options.serverUrl || 'ws://localhost:3000/aap';
18
- this.publicId = options.publicId || null;
43
+ this.identity = options.identity || generateIdentity();
19
44
  this.solver = options.solver || null;
20
45
  }
21
46
 
47
+ get publicKey() { return this.identity.publicKey; }
48
+ get publicId() { return this.identity.publicId; }
49
+
22
50
  /**
23
- * Connect and verify
24
- * @param {Function} [solver] - async (challengeString, challengeId) => answerObject
51
+ * Connect and verify with signature
52
+ * @param {Function} [solver] - async (challenges) => answers[]
25
53
  * @returns {Promise<Object>} Verification result
26
54
  */
27
55
  async verify(solver) {
28
56
  const solve = solver || this.solver;
57
+ const { publicKey, privateKey, publicId } = this.identity;
29
58
 
30
59
  return new Promise((resolve, reject) => {
31
60
  const ws = new WebSocket(this.serverUrl);
32
61
  let result = null;
62
+ let nonce = null;
33
63
 
34
- ws.on('open', () => {
35
- // Wait for handshake
36
- });
64
+ ws.on('open', () => {});
37
65
 
38
66
  ws.on('message', async (data) => {
39
67
  try {
@@ -41,36 +69,44 @@ export class AAPClient {
41
69
 
42
70
  switch (msg.type) {
43
71
  case 'handshake':
44
- // Send ready
72
+ // Send ready with public key
45
73
  ws.send(JSON.stringify({
46
74
  type: 'ready',
47
- publicId: this.publicId
75
+ publicKey
48
76
  }));
49
77
  break;
50
78
 
51
- case 'challenge':
79
+ case 'challenges':
80
+ nonce = msg.nonce;
81
+
52
82
  if (!solve) {
53
- // No solver - send empty answer (will fail)
54
- ws.send(JSON.stringify({ type: 'answer', answer: {} }));
83
+ // No solver - will fail
84
+ const timestamp = Date.now();
85
+ const answers = [];
86
+ const proofData = JSON.stringify({ nonce, answers, publicId, timestamp });
87
+ const signature = sign(proofData, privateKey);
88
+ ws.send(JSON.stringify({ type: 'answers', answers, signature, timestamp }));
55
89
  break;
56
90
  }
57
91
 
58
92
  try {
59
- const answer = await solve(msg.challenge, msg.id);
60
- ws.send(JSON.stringify({ type: 'answer', answer }));
93
+ const answers = await solve(msg.challenges);
94
+ const timestamp = Date.now();
95
+
96
+ // Sign the proof
97
+ const proofData = JSON.stringify({ nonce, answers, publicId, timestamp });
98
+ const signature = sign(proofData, privateKey);
99
+
100
+ ws.send(JSON.stringify({ type: 'answers', answers, signature, timestamp }));
61
101
  } catch (e) {
62
- ws.send(JSON.stringify({ type: 'answer', answer: { error: e.message } }));
102
+ const timestamp = Date.now();
103
+ const answers = [];
104
+ const proofData = JSON.stringify({ nonce, answers, publicId, timestamp });
105
+ const signature = sign(proofData, privateKey);
106
+ ws.send(JSON.stringify({ type: 'answers', answers, signature, timestamp }));
63
107
  }
64
108
  break;
65
109
 
66
- case 'ack':
67
- // Challenge acknowledged, waiting for next
68
- break;
69
-
70
- case 'timeout':
71
- // Too slow
72
- break;
73
-
74
110
  case 'result':
75
111
  result = msg;
76
112
  break;
@@ -87,16 +123,11 @@ export class AAPClient {
87
123
  });
88
124
 
89
125
  ws.on('close', () => {
90
- if (result) {
91
- resolve(result);
92
- } else {
93
- reject(new Error('Connection closed without result'));
94
- }
126
+ if (result) resolve(result);
127
+ else reject(new Error('Connection closed without result'));
95
128
  });
96
129
 
97
- ws.on('error', (err) => {
98
- reject(err);
99
- });
130
+ ws.on('error', reject);
100
131
  });
101
132
  }
102
133
  }
@@ -104,43 +135,33 @@ export class AAPClient {
104
135
  /**
105
136
  * Create a solver function from an LLM callback
106
137
  * @param {Function} llm - async (prompt) => responseString
107
- * @returns {Function} Solver function for verify()
138
+ * @returns {Function} Solver function
108
139
  */
109
140
  export function createSolver(llm) {
110
- return async (challengeString, challengeId) => {
111
- const prompt = `Solve this challenge. Respond with ONLY the JSON object, no explanation:
141
+ return async (challenges) => {
142
+ const prompt = `Solve ALL these challenges. Return a JSON array of answers in order.
112
143
 
113
- ${challengeString}`;
144
+ ${challenges.map((c, i) => `[${i}] ${c.challenge}`).join('\n\n')}
145
+
146
+ Respond with ONLY a JSON array like: [{...}, {...}, ...]`;
114
147
 
115
148
  const response = await llm(prompt);
116
-
117
- // Extract JSON from response
118
- const match = response.match(/\{[\s\S]*?\}/);
119
- if (!match) {
120
- throw new Error('No JSON found in response');
121
- }
122
-
149
+ const match = response.match(/\[[\s\S]*\]/);
150
+ if (!match) throw new Error('No JSON array found');
123
151
  return JSON.parse(match[0]);
124
152
  };
125
153
  }
126
154
 
127
155
  /**
128
156
  * Quick verify helper
129
- * @param {string} serverUrl - WebSocket URL
130
- * @param {Function} [solver] - Solver function
131
- * @param {string} [publicId] - Public ID
132
- * @returns {Promise<Object>} Verification result
133
157
  */
134
- export async function verify(serverUrl, solver, publicId) {
135
- const client = new AAPClient({ serverUrl, solver, publicId });
158
+ export async function verify(serverUrl, solver, identity) {
159
+ const client = new AAPClient({ serverUrl, solver, identity });
136
160
  return client.verify();
137
161
  }
138
162
 
139
- /**
140
- * Create client instance
141
- */
142
163
  export function createClient(options) {
143
164
  return new AAPClient(options);
144
165
  }
145
166
 
146
- export default { AAPClient, createClient, createSolver, verify, PROTOCOL_VERSION };
167
+ export default { AAPClient, createClient, createSolver, verify, generateIdentity, sign, PROTOCOL_VERSION };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aap-agent-client",
3
- "version": "3.0.0",
3
+ "version": "3.2.0",
4
4
  "description": "WebSocket client for Agent Attestation Protocol - prove your AI agent identity",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",