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.
- package/index.js +76 -55
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1,13 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @aap/client v3.
|
|
2
|
+
* @aap/client v3.2.0
|
|
3
3
|
*
|
|
4
|
-
* WebSocket client
|
|
5
|
-
*
|
|
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.
|
|
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.
|
|
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 (
|
|
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
|
-
|
|
75
|
+
publicKey
|
|
48
76
|
}));
|
|
49
77
|
break;
|
|
50
78
|
|
|
51
|
-
case '
|
|
79
|
+
case 'challenges':
|
|
80
|
+
nonce = msg.nonce;
|
|
81
|
+
|
|
52
82
|
if (!solve) {
|
|
53
|
-
// No solver -
|
|
54
|
-
|
|
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
|
|
60
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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',
|
|
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
|
|
138
|
+
* @returns {Function} Solver function
|
|
108
139
|
*/
|
|
109
140
|
export function createSolver(llm) {
|
|
110
|
-
return async (
|
|
111
|
-
const prompt = `Solve
|
|
141
|
+
return async (challenges) => {
|
|
142
|
+
const prompt = `Solve ALL these challenges. Return a JSON array of answers in order.
|
|
112
143
|
|
|
113
|
-
${
|
|
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
|
-
|
|
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,
|
|
135
|
-
const client = new AAPClient({ serverUrl, solver,
|
|
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 };
|