aap-agent-client 3.1.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 +61 -13
- 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,21 +40,26 @@ export const PROTOCOL_VERSION = '3.1.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
|
|
51
|
+
* Connect and verify with signature
|
|
24
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
64
|
ws.on('open', () => {});
|
|
35
65
|
|
|
@@ -39,23 +69,41 @@ export class AAPClient {
|
|
|
39
69
|
|
|
40
70
|
switch (msg.type) {
|
|
41
71
|
case 'handshake':
|
|
72
|
+
// Send ready with public key
|
|
42
73
|
ws.send(JSON.stringify({
|
|
43
74
|
type: 'ready',
|
|
44
|
-
|
|
75
|
+
publicKey
|
|
45
76
|
}));
|
|
46
77
|
break;
|
|
47
78
|
|
|
48
79
|
case 'challenges':
|
|
80
|
+
nonce = msg.nonce;
|
|
81
|
+
|
|
49
82
|
if (!solve) {
|
|
50
|
-
|
|
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 }));
|
|
51
89
|
break;
|
|
52
90
|
}
|
|
53
91
|
|
|
54
92
|
try {
|
|
55
93
|
const answers = await solve(msg.challenges);
|
|
56
|
-
|
|
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 }));
|
|
57
101
|
} catch (e) {
|
|
58
|
-
|
|
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 }));
|
|
59
107
|
}
|
|
60
108
|
break;
|
|
61
109
|
|
|
@@ -107,8 +155,8 @@ Respond with ONLY a JSON array like: [{...}, {...}, ...]`;
|
|
|
107
155
|
/**
|
|
108
156
|
* Quick verify helper
|
|
109
157
|
*/
|
|
110
|
-
export async function verify(serverUrl, solver,
|
|
111
|
-
const client = new AAPClient({ serverUrl, solver,
|
|
158
|
+
export async function verify(serverUrl, solver, identity) {
|
|
159
|
+
const client = new AAPClient({ serverUrl, solver, identity });
|
|
112
160
|
return client.verify();
|
|
113
161
|
}
|
|
114
162
|
|
|
@@ -116,4 +164,4 @@ export function createClient(options) {
|
|
|
116
164
|
return new AAPClient(options);
|
|
117
165
|
}
|
|
118
166
|
|
|
119
|
-
export default { AAPClient, createClient, createSolver, verify, PROTOCOL_VERSION };
|
|
167
|
+
export default { AAPClient, createClient, createSolver, verify, generateIdentity, sign, PROTOCOL_VERSION };
|