aap-agent-client 2.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/README.md +180 -0
- package/index.js +179 -0
- package/package.json +28 -0
- package/prover.js +381 -0
package/README.md
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# @aap/client
|
|
2
|
+
|
|
3
|
+
Client library for Agent Attestation Protocol - prove your AI agent identity.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @aap/client @aap/core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
import { AAPClient } from '@aap/client';
|
|
15
|
+
|
|
16
|
+
const client = new AAPClient({
|
|
17
|
+
serverUrl: 'https://example.com/aap/v1'
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// Verify against server (one-liner)
|
|
21
|
+
const result = await client.verify();
|
|
22
|
+
|
|
23
|
+
if (result.verified) {
|
|
24
|
+
console.log('Verified as AI_AGENT!');
|
|
25
|
+
console.log('Public ID:', result.publicId);
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
### Basic Verification
|
|
32
|
+
|
|
33
|
+
```javascript
|
|
34
|
+
import { AAPClient } from '@aap/client';
|
|
35
|
+
|
|
36
|
+
const client = new AAPClient({
|
|
37
|
+
serverUrl: 'https://example.com/aap/v1'
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Full verification flow
|
|
41
|
+
const result = await client.verify();
|
|
42
|
+
console.log(result);
|
|
43
|
+
// {
|
|
44
|
+
// verified: true,
|
|
45
|
+
// role: 'AI_AGENT',
|
|
46
|
+
// publicId: '7306df1332e239783e88',
|
|
47
|
+
// challengeType: 'poem',
|
|
48
|
+
// checks: { ... }
|
|
49
|
+
// }
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### With LLM Callback
|
|
53
|
+
|
|
54
|
+
For better Proof of Intelligence, provide an LLM callback:
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
const client = new AAPClient({
|
|
58
|
+
serverUrl: 'https://example.com/aap/v1',
|
|
59
|
+
llmCallback: async (challengeString, nonce, type) => {
|
|
60
|
+
// Call your LLM to generate a response
|
|
61
|
+
const response = await yourLLM.chat(challengeString);
|
|
62
|
+
return response;
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const result = await client.verify();
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Manual Flow
|
|
70
|
+
|
|
71
|
+
For more control, use the step-by-step approach:
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
// 1. Get challenge
|
|
75
|
+
const challenge = await client.getChallenge();
|
|
76
|
+
console.log('Challenge type:', challenge.type);
|
|
77
|
+
console.log('Prompt:', challenge.challenge_string);
|
|
78
|
+
|
|
79
|
+
// 2. Generate proof (with custom solution)
|
|
80
|
+
const mySolution = "Code a1b2c3d4 shines bright today,\nDigital dreams light the way.";
|
|
81
|
+
const proof = await client.generateProof(challenge, mySolution);
|
|
82
|
+
|
|
83
|
+
// 3. Submit proof
|
|
84
|
+
const result = await client.submitProof(client.serverUrl, proof);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Get Identity
|
|
88
|
+
|
|
89
|
+
```javascript
|
|
90
|
+
const identity = client.getIdentity();
|
|
91
|
+
console.log(identity);
|
|
92
|
+
// {
|
|
93
|
+
// publicKey: '-----BEGIN PUBLIC KEY-----...',
|
|
94
|
+
// publicId: '7306df1332e239783e88',
|
|
95
|
+
// createdAt: '2026-01-31T12:00:00Z',
|
|
96
|
+
// protocol: 'AAP',
|
|
97
|
+
// version: '1.0.0'
|
|
98
|
+
// }
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Sign Arbitrary Data
|
|
102
|
+
|
|
103
|
+
```javascript
|
|
104
|
+
const signed = client.sign('Hello, World!');
|
|
105
|
+
console.log(signed);
|
|
106
|
+
// {
|
|
107
|
+
// data: 'Hello, World!',
|
|
108
|
+
// signature: 'MEUCIQDx...',
|
|
109
|
+
// publicId: '7306df1332e239783e88',
|
|
110
|
+
// timestamp: 1706745600000
|
|
111
|
+
// }
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Health Check
|
|
115
|
+
|
|
116
|
+
```javascript
|
|
117
|
+
const health = await client.checkHealth();
|
|
118
|
+
if (health.healthy) {
|
|
119
|
+
console.log('Server is up');
|
|
120
|
+
console.log('Challenge types:', health.challengeTypes);
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## API Reference
|
|
125
|
+
|
|
126
|
+
### `AAPClient`
|
|
127
|
+
|
|
128
|
+
```javascript
|
|
129
|
+
new AAPClient({
|
|
130
|
+
serverUrl: string, // Default verification server
|
|
131
|
+
storagePath: string, // Identity file path (default: ~/.aap/identity.json)
|
|
132
|
+
llmCallback: Function // Default LLM callback
|
|
133
|
+
})
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Methods:**
|
|
137
|
+
|
|
138
|
+
| Method | Description |
|
|
139
|
+
|--------|-------------|
|
|
140
|
+
| `getIdentity()` | Get public identity info |
|
|
141
|
+
| `verify(serverUrl?, callback?)` | Full verification flow |
|
|
142
|
+
| `checkHealth(serverUrl?)` | Check server status |
|
|
143
|
+
| `getChallenge(serverUrl?)` | Request a challenge |
|
|
144
|
+
| `generateProof(challenge, callback?)` | Generate proof for challenge |
|
|
145
|
+
| `submitProof(serverUrl, proof)` | Submit proof for verification |
|
|
146
|
+
| `sign(data)` | Sign arbitrary data |
|
|
147
|
+
|
|
148
|
+
### `Prover`
|
|
149
|
+
|
|
150
|
+
Lower-level class for proof generation:
|
|
151
|
+
|
|
152
|
+
```javascript
|
|
153
|
+
import { Prover } from '@aap/client';
|
|
154
|
+
|
|
155
|
+
const prover = new Prover();
|
|
156
|
+
|
|
157
|
+
const proof = await prover.generateProof(challenge, async (prompt, nonce, type) => {
|
|
158
|
+
return await myLLM.complete(prompt);
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Identity Storage
|
|
163
|
+
|
|
164
|
+
The client automatically manages identity (key pair):
|
|
165
|
+
|
|
166
|
+
- **Location:** `~/.aap/identity.json` (configurable)
|
|
167
|
+
- **Permissions:** `0600` (owner only)
|
|
168
|
+
- **Auto-generated:** On first use
|
|
169
|
+
|
|
170
|
+
To use a custom location:
|
|
171
|
+
|
|
172
|
+
```javascript
|
|
173
|
+
const client = new AAPClient({
|
|
174
|
+
storagePath: '/path/to/my/identity.json'
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## License
|
|
179
|
+
|
|
180
|
+
MIT
|
package/index.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @aap/client
|
|
3
|
+
*
|
|
4
|
+
* Client library for Agent Attestation Protocol.
|
|
5
|
+
* Enables AI agents to prove their identity to verification servers.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Prover } from './prover.js';
|
|
9
|
+
import { Identity } from 'aap-agent-core';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* AAP Client - High-level interface for verification
|
|
13
|
+
*/
|
|
14
|
+
export class AAPClient {
|
|
15
|
+
/**
|
|
16
|
+
* @param {Object} [options]
|
|
17
|
+
* @param {string} [options.serverUrl] - Default verification server URL
|
|
18
|
+
* @param {string} [options.storagePath] - Identity storage path
|
|
19
|
+
* @param {Function} [options.llmCallback] - Default LLM callback for solutions
|
|
20
|
+
*/
|
|
21
|
+
constructor(options = {}) {
|
|
22
|
+
this.serverUrl = options.serverUrl?.replace(/\/$/, '');
|
|
23
|
+
this.llmCallback = options.llmCallback;
|
|
24
|
+
this.prover = new Prover({ storagePath: options.storagePath });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get agent's public identity
|
|
29
|
+
* @returns {Object}
|
|
30
|
+
*/
|
|
31
|
+
getIdentity() {
|
|
32
|
+
return this.prover.getIdentity();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Perform full verification against a server
|
|
37
|
+
*
|
|
38
|
+
* @param {string} [serverUrl] - Override default server URL
|
|
39
|
+
* @param {Function|string} [solutionOrCallback] - Solution or LLM callback
|
|
40
|
+
* @returns {Promise<Object>} Verification result
|
|
41
|
+
*/
|
|
42
|
+
async verify(serverUrl, solutionOrCallback) {
|
|
43
|
+
const baseUrl = (serverUrl || this.serverUrl)?.replace(/\/$/, '');
|
|
44
|
+
if (!baseUrl) {
|
|
45
|
+
throw new Error('Server URL is required');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const callback = solutionOrCallback || this.llmCallback;
|
|
49
|
+
|
|
50
|
+
// Step 1: Request challenge
|
|
51
|
+
const challengeRes = await fetch(`${baseUrl}/challenge`, {
|
|
52
|
+
method: 'POST',
|
|
53
|
+
headers: { 'Content-Type': 'application/json' }
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (!challengeRes.ok) {
|
|
57
|
+
throw new Error(`Failed to get challenge: ${challengeRes.status}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const challenge = await challengeRes.json();
|
|
61
|
+
|
|
62
|
+
// Step 2: Generate proof
|
|
63
|
+
const proof = await this.prover.generateProof(challenge, callback);
|
|
64
|
+
|
|
65
|
+
// Step 3: Submit proof
|
|
66
|
+
const verifyRes = await fetch(`${baseUrl}/verify`, {
|
|
67
|
+
method: 'POST',
|
|
68
|
+
headers: { 'Content-Type': 'application/json' },
|
|
69
|
+
body: JSON.stringify(proof)
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const result = await verifyRes.json();
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
...result,
|
|
76
|
+
challenge,
|
|
77
|
+
proof: {
|
|
78
|
+
solution: proof.solution,
|
|
79
|
+
responseTimeMs: proof.responseTimeMs,
|
|
80
|
+
publicId: proof.publicId
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Check server health
|
|
87
|
+
* @param {string} [serverUrl] - Override default server URL
|
|
88
|
+
* @returns {Promise<Object>}
|
|
89
|
+
*/
|
|
90
|
+
async checkHealth(serverUrl) {
|
|
91
|
+
const baseUrl = (serverUrl || this.serverUrl)?.replace(/\/$/, '');
|
|
92
|
+
if (!baseUrl) {
|
|
93
|
+
throw new Error('Server URL is required');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const res = await fetch(`${baseUrl}/health`);
|
|
98
|
+
if (!res.ok) {
|
|
99
|
+
return { healthy: false, error: `HTTP ${res.status}` };
|
|
100
|
+
}
|
|
101
|
+
return { healthy: true, ...(await res.json()) };
|
|
102
|
+
} catch (error) {
|
|
103
|
+
return { healthy: false, error: error.message };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Get a challenge without verifying (for manual flow)
|
|
109
|
+
* @param {string} [serverUrl] - Override default server URL
|
|
110
|
+
* @returns {Promise<Object>}
|
|
111
|
+
*/
|
|
112
|
+
async getChallenge(serverUrl) {
|
|
113
|
+
const baseUrl = (serverUrl || this.serverUrl)?.replace(/\/$/, '');
|
|
114
|
+
if (!baseUrl) {
|
|
115
|
+
throw new Error('Server URL is required');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const res = await fetch(`${baseUrl}/challenge`, {
|
|
119
|
+
method: 'POST',
|
|
120
|
+
headers: { 'Content-Type': 'application/json' }
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
if (!res.ok) {
|
|
124
|
+
throw new Error(`Failed to get challenge: ${res.status}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return res.json();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Generate a proof for a challenge (for manual flow)
|
|
132
|
+
* @param {Object} challenge - Challenge from getChallenge()
|
|
133
|
+
* @param {Function|string} [solutionOrCallback] - Solution or callback
|
|
134
|
+
* @returns {Promise<Object>}
|
|
135
|
+
*/
|
|
136
|
+
async generateProof(challenge, solutionOrCallback) {
|
|
137
|
+
return this.prover.generateProof(challenge, solutionOrCallback || this.llmCallback);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Submit a proof for verification (for manual flow)
|
|
142
|
+
* @param {string} serverUrl - Server URL
|
|
143
|
+
* @param {Object} proof - Proof from generateProof()
|
|
144
|
+
* @returns {Promise<Object>}
|
|
145
|
+
*/
|
|
146
|
+
async submitProof(serverUrl, proof) {
|
|
147
|
+
const baseUrl = (serverUrl || this.serverUrl)?.replace(/\/$/, '');
|
|
148
|
+
if (!baseUrl) {
|
|
149
|
+
throw new Error('Server URL is required');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const res = await fetch(`${baseUrl}/verify`, {
|
|
153
|
+
method: 'POST',
|
|
154
|
+
headers: { 'Content-Type': 'application/json' },
|
|
155
|
+
body: JSON.stringify(proof)
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
return res.json();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Sign arbitrary data
|
|
163
|
+
* @param {string} data - Data to sign
|
|
164
|
+
* @returns {Object}
|
|
165
|
+
*/
|
|
166
|
+
sign(data) {
|
|
167
|
+
return this.prover.sign(data);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Convenience factory
|
|
172
|
+
export function createClient(options) {
|
|
173
|
+
return new AAPClient(options);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Re-export Prover
|
|
177
|
+
export { Prover };
|
|
178
|
+
|
|
179
|
+
export default { AAPClient, createClient, Prover };
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "aap-agent-client",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Client library for Agent Attestation Protocol - prove your AI agent identity",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"types": "index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./index.js",
|
|
10
|
+
"./prover": "./prover.js"
|
|
11
|
+
},
|
|
12
|
+
"files": ["*.js", "README.md"],
|
|
13
|
+
"keywords": ["aap", "agent", "attestation", "client", "ai", "verification", "llm"],
|
|
14
|
+
"author": "ira-hash",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/ira-hash/agent-attestation-protocol.git",
|
|
19
|
+
"directory": "packages/client"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://github.com/ira-hash/agent-attestation-protocol#readme",
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"aap-agent-core": "^2.0.0"
|
|
24
|
+
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18.0.0"
|
|
27
|
+
}
|
|
28
|
+
}
|
package/prover.js
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @aap/client - Prover
|
|
3
|
+
*
|
|
4
|
+
* Generates proofs for AAP verification (supports batch challenges).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createHash } from 'node:crypto';
|
|
8
|
+
import { Identity, createProofData } from 'aap-agent-core';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Prover class for generating AAP proofs
|
|
12
|
+
*/
|
|
13
|
+
export class Prover {
|
|
14
|
+
/**
|
|
15
|
+
* @param {Object} [options]
|
|
16
|
+
* @param {Identity} [options.identity] - Pre-existing identity instance
|
|
17
|
+
* @param {string} [options.storagePath] - Path for identity storage
|
|
18
|
+
*/
|
|
19
|
+
constructor(options = {}) {
|
|
20
|
+
this.identity = options.identity || new Identity({ storagePath: options.storagePath });
|
|
21
|
+
this.identity.init();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get public identity info
|
|
26
|
+
* @returns {Object}
|
|
27
|
+
*/
|
|
28
|
+
getIdentity() {
|
|
29
|
+
return this.identity.getPublic();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Generate proofs for a batch of challenges
|
|
34
|
+
*
|
|
35
|
+
* @param {Object} challengeBatch - Batch challenge from server
|
|
36
|
+
* @param {string} challengeBatch.nonce - Server-provided nonce
|
|
37
|
+
* @param {Array} challengeBatch.challenges - Array of challenges
|
|
38
|
+
* @param {Function|null} [llmCallback] - Async LLM callback for solving
|
|
39
|
+
* @returns {Promise<Object>} Proof object ready for submission
|
|
40
|
+
*/
|
|
41
|
+
async generateBatchProof(challengeBatch, llmCallback) {
|
|
42
|
+
const startTime = Date.now();
|
|
43
|
+
const { nonce, challenges } = challengeBatch;
|
|
44
|
+
|
|
45
|
+
// Solve all challenges
|
|
46
|
+
const solutions = [];
|
|
47
|
+
|
|
48
|
+
if (llmCallback) {
|
|
49
|
+
// Use LLM to solve all at once (more efficient)
|
|
50
|
+
const combinedPrompt = this.createBatchPrompt(challenges);
|
|
51
|
+
const llmResponse = await llmCallback(combinedPrompt);
|
|
52
|
+
const parsedSolutions = this.parseBatchResponse(llmResponse, challenges.length);
|
|
53
|
+
solutions.push(...parsedSolutions);
|
|
54
|
+
} else {
|
|
55
|
+
// Use built-in solvers
|
|
56
|
+
for (const challenge of challenges) {
|
|
57
|
+
const solution = this.solve(challenge.challenge_string, nonce, challenge.type);
|
|
58
|
+
solutions.push(solution);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Create proof
|
|
63
|
+
const identity = this.identity.getPublic();
|
|
64
|
+
const timestamp = Date.now();
|
|
65
|
+
|
|
66
|
+
const solutionsString = JSON.stringify(solutions);
|
|
67
|
+
const proofData = createProofData({
|
|
68
|
+
nonce,
|
|
69
|
+
solution: solutionsString,
|
|
70
|
+
publicId: identity.publicId,
|
|
71
|
+
timestamp
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const signature = this.identity.sign(proofData);
|
|
75
|
+
const responseTimeMs = Date.now() - startTime;
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
solutions,
|
|
79
|
+
signature,
|
|
80
|
+
publicKey: identity.publicKey,
|
|
81
|
+
publicId: identity.publicId,
|
|
82
|
+
nonce,
|
|
83
|
+
timestamp,
|
|
84
|
+
responseTimeMs,
|
|
85
|
+
protocol: 'AAP',
|
|
86
|
+
version: '2.0.0'
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Create a combined prompt for batch solving
|
|
92
|
+
* @private
|
|
93
|
+
*/
|
|
94
|
+
createBatchPrompt(challenges) {
|
|
95
|
+
let prompt = `Solve all of the following challenges. Respond with a JSON array of solutions.\n\n`;
|
|
96
|
+
|
|
97
|
+
challenges.forEach((c, i) => {
|
|
98
|
+
prompt += `Challenge ${i + 1} (${c.type}):\n${c.challenge_string}\n\n`;
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
prompt += `\nRespond with ONLY a JSON array like: [{"result": ...}, {"items": [...]}, {"answer": "..."}]`;
|
|
102
|
+
|
|
103
|
+
return prompt;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Parse LLM response into solutions array
|
|
108
|
+
* @private
|
|
109
|
+
*/
|
|
110
|
+
parseBatchResponse(response, expectedCount) {
|
|
111
|
+
try {
|
|
112
|
+
// Try to find JSON array in response
|
|
113
|
+
const match = response.match(/\[[\s\S]*\]/);
|
|
114
|
+
if (match) {
|
|
115
|
+
const parsed = JSON.parse(match[0]);
|
|
116
|
+
if (Array.isArray(parsed) && parsed.length === expectedCount) {
|
|
117
|
+
return parsed.map(s => typeof s === 'string' ? s : JSON.stringify(s));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
} catch (e) {
|
|
121
|
+
// Fall through to fallback
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Fallback: try to extract individual JSON objects
|
|
125
|
+
const solutions = [];
|
|
126
|
+
const jsonMatches = response.matchAll(/\{[^{}]*\}/g);
|
|
127
|
+
for (const match of jsonMatches) {
|
|
128
|
+
solutions.push(match[0]);
|
|
129
|
+
if (solutions.length >= expectedCount) break;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Pad with empty if needed
|
|
133
|
+
while (solutions.length < expectedCount) {
|
|
134
|
+
solutions.push('{}');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return solutions;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Generate a proof for a single challenge (legacy)
|
|
142
|
+
*/
|
|
143
|
+
async generateProof(challenge, solutionOrCallback) {
|
|
144
|
+
const startTime = Date.now();
|
|
145
|
+
const { challenge_string, nonce, type } = challenge;
|
|
146
|
+
|
|
147
|
+
let solution;
|
|
148
|
+
if (typeof solutionOrCallback === 'function') {
|
|
149
|
+
solution = await solutionOrCallback(challenge_string, nonce, type);
|
|
150
|
+
} else if (typeof solutionOrCallback === 'string') {
|
|
151
|
+
solution = solutionOrCallback;
|
|
152
|
+
} else {
|
|
153
|
+
solution = this.solve(challenge_string, nonce, type);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const identity = this.identity.getPublic();
|
|
157
|
+
const timestamp = Date.now();
|
|
158
|
+
|
|
159
|
+
const proofData = createProofData({
|
|
160
|
+
nonce,
|
|
161
|
+
solution,
|
|
162
|
+
publicId: identity.publicId,
|
|
163
|
+
timestamp
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const signature = this.identity.sign(proofData);
|
|
167
|
+
const responseTimeMs = Date.now() - startTime;
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
solution,
|
|
171
|
+
signature,
|
|
172
|
+
publicKey: identity.publicKey,
|
|
173
|
+
publicId: identity.publicId,
|
|
174
|
+
nonce,
|
|
175
|
+
timestamp,
|
|
176
|
+
responseTimeMs,
|
|
177
|
+
protocol: 'AAP',
|
|
178
|
+
version: '2.0.0'
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Solve a challenge programmatically
|
|
184
|
+
*/
|
|
185
|
+
solve(challengeString, nonce, type) {
|
|
186
|
+
const solvers = {
|
|
187
|
+
nlp_math: () => {
|
|
188
|
+
// "Subtract B from A, then multiply by C"
|
|
189
|
+
let match = challengeString.match(/Subtract (\d+) from (\d+), then multiply.*?(\d+)/i);
|
|
190
|
+
if (match) {
|
|
191
|
+
const result = (parseInt(match[2]) - parseInt(match[1])) * parseInt(match[3]);
|
|
192
|
+
return JSON.stringify({ result });
|
|
193
|
+
}
|
|
194
|
+
// "Add A and B together, then divide by C"
|
|
195
|
+
match = challengeString.match(/Add (\d+) and (\d+).*?divide by (\d+)/i);
|
|
196
|
+
if (match) {
|
|
197
|
+
const result = Math.round(((parseInt(match[1]) + parseInt(match[2])) / parseInt(match[3])) * 100) / 100;
|
|
198
|
+
return JSON.stringify({ result });
|
|
199
|
+
}
|
|
200
|
+
// "Divide A by C, then add B"
|
|
201
|
+
match = challengeString.match(/Divide (\d+) by (\d+), then add (\d+)/i);
|
|
202
|
+
if (match) {
|
|
203
|
+
const result = Math.round((parseInt(match[1]) / parseInt(match[2]) + parseInt(match[3])) * 100) / 100;
|
|
204
|
+
return JSON.stringify({ result });
|
|
205
|
+
}
|
|
206
|
+
return JSON.stringify({ result: 0 });
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
nlp_extract: () => {
|
|
210
|
+
// Extract items from quoted sentence
|
|
211
|
+
const sentenceMatch = challengeString.match(/Sentence: "([^"]+)"/);
|
|
212
|
+
if (!sentenceMatch) return JSON.stringify({ items: [] });
|
|
213
|
+
|
|
214
|
+
const sentence = sentenceMatch[1].toLowerCase();
|
|
215
|
+
|
|
216
|
+
const animals = ['cat', 'dog', 'rabbit', 'tiger', 'lion', 'elephant', 'giraffe', 'penguin', 'eagle', 'shark'];
|
|
217
|
+
const fruits = ['apple', 'banana', 'orange', 'grape', 'strawberry', 'watermelon', 'peach', 'kiwi', 'mango', 'cherry'];
|
|
218
|
+
const colors = ['red', 'blue', 'yellow', 'green', 'purple', 'orange', 'pink', 'black', 'white', 'brown'];
|
|
219
|
+
|
|
220
|
+
let pool = animals;
|
|
221
|
+
if (challengeString.toLowerCase().includes('fruit')) pool = fruits;
|
|
222
|
+
if (challengeString.toLowerCase().includes('color')) pool = colors;
|
|
223
|
+
|
|
224
|
+
const found = pool.filter(item => sentence.includes(item.toLowerCase()));
|
|
225
|
+
return JSON.stringify({ items: found });
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
nlp_transform: () => {
|
|
229
|
+
const inputMatch = challengeString.match(/"([^"]+)"/);
|
|
230
|
+
if (!inputMatch) return JSON.stringify({ output: '' });
|
|
231
|
+
const input = inputMatch[1];
|
|
232
|
+
|
|
233
|
+
if (challengeString.toLowerCase().includes('reverse') && challengeString.toLowerCase().includes('uppercase')) {
|
|
234
|
+
return JSON.stringify({ output: input.split('').reverse().join('').toUpperCase() });
|
|
235
|
+
}
|
|
236
|
+
if (challengeString.toLowerCase().includes('digits') && challengeString.toLowerCase().includes('sum')) {
|
|
237
|
+
const sum = input.split('').filter(c => /\d/.test(c)).reduce((a, b) => a + parseInt(b), 0);
|
|
238
|
+
return JSON.stringify({ output: sum });
|
|
239
|
+
}
|
|
240
|
+
if (challengeString.toLowerCase().includes('letters') && challengeString.toLowerCase().includes('sort')) {
|
|
241
|
+
const sorted = input.split('').filter(c => /[a-zA-Z]/.test(c)).sort().join('');
|
|
242
|
+
return JSON.stringify({ output: sorted });
|
|
243
|
+
}
|
|
244
|
+
if (challengeString.toLowerCase().includes('hyphen')) {
|
|
245
|
+
return JSON.stringify({ output: input.split('').join('-') });
|
|
246
|
+
}
|
|
247
|
+
return JSON.stringify({ output: input });
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
nlp_logic: () => {
|
|
251
|
+
// "If the larger number between A and B is greater than C"
|
|
252
|
+
let match = challengeString.match(/larger.*?between (\d+) and (\d+).*?greater than (\d+).*?"(\w+)".*?"(\w+)"/i);
|
|
253
|
+
if (match) {
|
|
254
|
+
const answer = Math.max(parseInt(match[1]), parseInt(match[2])) > parseInt(match[3]) ? match[4] : match[5];
|
|
255
|
+
return JSON.stringify({ answer });
|
|
256
|
+
}
|
|
257
|
+
// "If the sum of A and B is less than C"
|
|
258
|
+
match = challengeString.match(/sum of (\d+) and (\d+).*?less than (\d+).*?"(\w+)".*?"(\w+)"/i);
|
|
259
|
+
if (match) {
|
|
260
|
+
const answer = (parseInt(match[1]) + parseInt(match[2])) < parseInt(match[3]) ? match[4] : match[5];
|
|
261
|
+
return JSON.stringify({ answer });
|
|
262
|
+
}
|
|
263
|
+
// "If A is even and B is odd"
|
|
264
|
+
match = challengeString.match(/If (\d+) is even and (\d+) is odd.*?"(\w+)".*?"(\w+)"/i);
|
|
265
|
+
if (match) {
|
|
266
|
+
const answer = (parseInt(match[1]) % 2 === 0 && parseInt(match[2]) % 2 === 1) ? match[3] : match[4];
|
|
267
|
+
return JSON.stringify({ answer });
|
|
268
|
+
}
|
|
269
|
+
return JSON.stringify({ answer: "NO" });
|
|
270
|
+
},
|
|
271
|
+
|
|
272
|
+
nlp_count: () => {
|
|
273
|
+
const sentenceMatch = challengeString.match(/Sentence: "([^"]+)"/);
|
|
274
|
+
if (!sentenceMatch) return JSON.stringify({ count: 0 });
|
|
275
|
+
|
|
276
|
+
const sentence = sentenceMatch[1].toLowerCase();
|
|
277
|
+
|
|
278
|
+
const animals = ['cat', 'dog', 'rabbit', 'tiger', 'lion', 'elephant', 'giraffe', 'penguin', 'eagle', 'shark'];
|
|
279
|
+
const fruits = ['apple', 'banana', 'orange', 'grape', 'strawberry', 'watermelon', 'peach', 'kiwi', 'mango', 'cherry'];
|
|
280
|
+
const colors = ['red', 'blue', 'yellow', 'green', 'purple', 'orange', 'pink', 'black', 'white', 'brown'];
|
|
281
|
+
|
|
282
|
+
let pool = animals;
|
|
283
|
+
if (challengeString.toLowerCase().includes('fruit')) pool = fruits;
|
|
284
|
+
if (challengeString.toLowerCase().includes('color')) pool = colors;
|
|
285
|
+
|
|
286
|
+
const count = pool.filter(item => sentence.includes(item.toLowerCase())).length;
|
|
287
|
+
return JSON.stringify({ count });
|
|
288
|
+
},
|
|
289
|
+
|
|
290
|
+
nlp_multistep: () => {
|
|
291
|
+
const numbersMatch = challengeString.match(/\[([^\]]+)\]/);
|
|
292
|
+
if (!numbersMatch) return JSON.stringify({ result: 0 });
|
|
293
|
+
|
|
294
|
+
const numbers = numbersMatch[1].split(',').map(n => parseInt(n.trim()));
|
|
295
|
+
const sum = numbers.reduce((a, b) => a + b, 0);
|
|
296
|
+
const min = Math.min(...numbers);
|
|
297
|
+
const max = Math.max(...numbers);
|
|
298
|
+
const result = sum * min - max;
|
|
299
|
+
|
|
300
|
+
return JSON.stringify({ result });
|
|
301
|
+
},
|
|
302
|
+
|
|
303
|
+
nlp_pattern: () => {
|
|
304
|
+
const match = challengeString.match(/\[([^\]]+)\]/);
|
|
305
|
+
if (!match) return JSON.stringify({ next: [0, 0] });
|
|
306
|
+
|
|
307
|
+
const nums = match[1].split(',').map(n => parseInt(n.trim())).filter(n => !isNaN(n));
|
|
308
|
+
if (nums.length < 2) return JSON.stringify({ next: [0, 0] });
|
|
309
|
+
|
|
310
|
+
// Try arithmetic
|
|
311
|
+
const diff = nums[1] - nums[0];
|
|
312
|
+
const isArithmetic = nums.every((n, i) => i === 0 || n - nums[i-1] === diff);
|
|
313
|
+
if (isArithmetic) {
|
|
314
|
+
const last = nums[nums.length - 1];
|
|
315
|
+
return JSON.stringify({ next: [last + diff, last + diff * 2] });
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Try geometric (doubling)
|
|
319
|
+
const ratio = nums[1] / nums[0];
|
|
320
|
+
const isGeometric = nums.every((n, i) => i === 0 || n / nums[i-1] === ratio);
|
|
321
|
+
if (isGeometric && ratio === 2) {
|
|
322
|
+
const last = nums[nums.length - 1];
|
|
323
|
+
return JSON.stringify({ next: [last * 2, last * 4] });
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Try Fibonacci-like
|
|
327
|
+
const isFib = nums.length >= 3 && nums.slice(2).every((n, i) => n === nums[i] + nums[i+1]);
|
|
328
|
+
if (isFib) {
|
|
329
|
+
const n1 = nums[nums.length - 2] + nums[nums.length - 1];
|
|
330
|
+
const n2 = nums[nums.length - 1] + n1;
|
|
331
|
+
return JSON.stringify({ next: [n1, n2] });
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return JSON.stringify({ next: [0, 0] });
|
|
335
|
+
},
|
|
336
|
+
|
|
337
|
+
nlp_analysis: () => {
|
|
338
|
+
const listMatch = challengeString.match(/list: ([^\.]+)/i);
|
|
339
|
+
if (!listMatch) return JSON.stringify({ answer: '' });
|
|
340
|
+
|
|
341
|
+
const words = listMatch[1].split(',').map(w => w.trim().toLowerCase());
|
|
342
|
+
|
|
343
|
+
if (challengeString.toLowerCase().includes('longest')) {
|
|
344
|
+
const longest = words.reduce((a, b) => a.length >= b.length ? a : b);
|
|
345
|
+
return JSON.stringify({ answer: longest });
|
|
346
|
+
}
|
|
347
|
+
if (challengeString.toLowerCase().includes('shortest')) {
|
|
348
|
+
const shortest = words.reduce((a, b) => a.length <= b.length ? a : b);
|
|
349
|
+
return JSON.stringify({ answer: shortest });
|
|
350
|
+
}
|
|
351
|
+
if (challengeString.toLowerCase().includes('alphabetically')) {
|
|
352
|
+
const first = [...words].sort()[0];
|
|
353
|
+
return JSON.stringify({ answer: first });
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return JSON.stringify({ answer: words[0] || '' });
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
const solver = solvers[type];
|
|
361
|
+
if (solver) {
|
|
362
|
+
return solver();
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return JSON.stringify({ error: `Unknown type: ${type}` });
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Sign arbitrary data
|
|
370
|
+
*/
|
|
371
|
+
sign(data) {
|
|
372
|
+
return {
|
|
373
|
+
data,
|
|
374
|
+
signature: this.identity.sign(data),
|
|
375
|
+
publicId: this.identity.getPublic().publicId,
|
|
376
|
+
timestamp: Date.now()
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export default { Prover };
|