@provnai/vex-sdk 1.5.0 → 1.6.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 CHANGED
@@ -1,4 +1,4 @@
1
- # VEX SDK for TypeScript (v1.5.0) 🛡️js
1
+ # VEX SDK for TypeScript (v1.6.0) 🛡️js
2
2
  ## Cognitive Routing & Silicon-Rooted Evidence
3
3
 
4
4
  Official TypeScript implementation of the VEX Protocol. Designed to wrap agent tool-calls in a cryptographically verifiable envelope.
package/dist/agent.d.ts CHANGED
@@ -13,26 +13,46 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
+ import { VexCapsule } from './builder';
16
17
  export interface VexConfig {
17
18
  identityKey: string;
18
19
  vanguardUrl: string;
20
+ aid?: string;
21
+ }
22
+ export interface VexResult {
23
+ status: string;
24
+ outcome: 'ALLOW' | 'HALT' | 'ESCALATE';
25
+ reason_code?: string;
26
+ capsule_root: string;
27
+ capability_token?: string;
28
+ [key: string]: unknown;
19
29
  }
20
30
  export declare class VexAgent {
21
31
  private config;
22
32
  private sdk;
33
+ private currentToken?;
23
34
  constructor(config: VexConfig);
24
35
  private ensureSDK;
36
+ /**
37
+ * Retrieves the Gate's public key for HPKE encryption.
38
+ */
39
+ fetchPublicKey(): Promise<string>;
25
40
  /**
26
41
  * Executes a tool via the VEX verifiable execution loop.
27
42
  */
28
- execute(toolName: string, params: Record<string, any>, intentContext?: string): Promise<any>;
43
+ /**
44
+ * Locally verifies a VEX Continuation Token (v3) against the Gate's public key.
45
+ * Ensures the token was signed by the authoritative Gate and binds to the current capsule.
46
+ */
47
+ verifyToken(tokenBase64: string, expectedCapsuleRoot?: string): Promise<boolean>;
48
+ execute(toolName: string, params: Record<string, unknown>, intentContext?: string): Promise<VexResult>;
29
49
  /**
30
50
  * Manually construct a signed Evidence Capsule without dispatching it.
31
51
  */
32
- buildCapsule(toolName: string, params: Record<string, any>, intentContext?: string): Promise<any>;
52
+ buildCapsule(toolName: string, params: Record<string, unknown>, intentContext?: string): Promise<VexCapsule>;
33
53
  /**
34
54
  * Serializes the Evidence Capsule into the v0x03 Binary Wire format.
35
55
  */
36
- toBinary(capsule: any): Buffer;
56
+ toBinary(capsule: VexCapsule): Buffer;
37
57
  private hashObject;
38
58
  }
package/dist/agent.js CHANGED
@@ -14,14 +14,52 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ var desc = Object.getOwnPropertyDescriptor(m, k);
20
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
21
+ desc = { enumerable: true, get: function() { return m[k]; } };
22
+ }
23
+ Object.defineProperty(o, k2, desc);
24
+ }) : (function(o, m, k, k2) {
25
+ if (k2 === undefined) k2 = k;
26
+ o[k2] = m[k];
27
+ }));
28
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
29
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
30
+ }) : function(o, v) {
31
+ o["default"] = v;
32
+ });
33
+ var __importStar = (this && this.__importStar) || (function () {
34
+ var ownKeys = function(o) {
35
+ ownKeys = Object.getOwnPropertyNames || function (o) {
36
+ var ar = [];
37
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
38
+ return ar;
39
+ };
40
+ return ownKeys(o);
41
+ };
42
+ return function (mod) {
43
+ if (mod && mod.__esModule) return mod;
44
+ var result = {};
45
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
46
+ __setModuleDefault(result, mod);
47
+ return result;
48
+ };
49
+ })();
17
50
  var __importDefault = (this && this.__importDefault) || function (mod) {
18
51
  return (mod && mod.__esModule) ? mod : { "default": mod };
19
52
  };
20
53
  Object.defineProperty(exports, "__esModule", { value: true });
21
54
  exports.VexAgent = void 0;
55
+ const crypto = __importStar(require("crypto"));
56
+ const { canonicalize } = require('json-canonicalize');
22
57
  const axios_1 = __importDefault(require("axios"));
23
58
  const builder_1 = require("./builder");
24
59
  const sdk_1 = require("@provncloud/sdk");
60
+ const { CipherSuite } = require('hpke-js');
61
+ const { DhkemX25519HkdfSha256, HkdfSha256 } = require('@hpke/dhkem-x25519');
62
+ const { Aes128Gcm } = require('@hpke/core');
25
63
  class VexAgent {
26
64
  constructor(config) {
27
65
  this.sdk = null;
@@ -33,9 +71,53 @@ class VexAgent {
33
71
  this.sdk = new sdk_1.ProvnSDK();
34
72
  }
35
73
  }
74
+ /**
75
+ * Retrieves the Gate's public key for HPKE encryption.
76
+ */
77
+ async fetchPublicKey() {
78
+ const response = await axios_1.default.get(`${this.config.vanguardUrl}/public_key`);
79
+ return response.data.public_key;
80
+ }
36
81
  /**
37
82
  * Executes a tool via the VEX verifiable execution loop.
38
83
  */
84
+ /**
85
+ * Locally verifies a VEX Continuation Token (v3) against the Gate's public key.
86
+ * Ensures the token was signed by the authoritative Gate and binds to the current capsule.
87
+ */
88
+ async verifyToken(tokenBase64, expectedCapsuleRoot) {
89
+ const { canonicalize } = require('json-canonicalize');
90
+ const crypto = require('crypto');
91
+ try {
92
+ const token = JSON.parse(Buffer.from(tokenBase64, 'base64').toString());
93
+ const gatePkBase64 = await this.fetchPublicKey();
94
+ const gatePublicKey = crypto.createPublicKey({
95
+ key: Buffer.from(gatePkBase64, 'base64'),
96
+ format: 'der',
97
+ type: 'spki', // Ed25519 in SPKI format
98
+ });
99
+ // 1. Re-hash the payload (JCS)
100
+ const payloadHash = crypto.createHash('sha256')
101
+ .update(canonicalize(token.payload))
102
+ .digest();
103
+ // 2. Verify signature
104
+ const isSignatureValid = crypto.verify(null, payloadHash, gatePublicKey, Buffer.from(token.signature, 'hex'));
105
+ if (!isSignatureValid) {
106
+ console.error('VEX Token Verification Failed: Invalid Signature');
107
+ return false;
108
+ }
109
+ // 3. Bind to capsule root
110
+ if (expectedCapsuleRoot && token.payload.source_capsule_root !== expectedCapsuleRoot) {
111
+ console.error('VEX Token Verification Failed: Root Mismatch');
112
+ return false;
113
+ }
114
+ return true;
115
+ }
116
+ catch (error) {
117
+ console.error('VEX Token Verification Error:', error);
118
+ return false;
119
+ }
120
+ }
39
121
  async execute(toolName, params, intentContext) {
40
122
  await this.ensureSDK();
41
123
  // 1. Build Capsule
@@ -71,7 +153,8 @@ class VexAgent {
71
153
  return result;
72
154
  }
73
155
  catch (error) {
74
- console.error('VEX Execution Failed:', error.response?.data || error.message);
156
+ const err = error;
157
+ console.error('VEX Execution Failed:', err.response?.data || err.message);
75
158
  throw error;
76
159
  }
77
160
  }
@@ -83,7 +166,10 @@ class VexAgent {
83
166
  // This is a simplified implementation for the SDK.
84
167
  // It demonstrates how the builder is leveraged.
85
168
  const intent = {
169
+ schema: 'vex/intent/v3',
170
+ aid: this.config.aid || '00'.repeat(32),
86
171
  request_sha256: this.hashObject(params),
172
+ commands: [toolName, params],
87
173
  confidence: 1.0,
88
174
  capabilities: ['sdk_execution']
89
175
  };
@@ -97,6 +183,7 @@ class VexAgent {
97
183
  trace_root: '00'.repeat(32),
98
184
  nonce: Date.now(),
99
185
  prev_hash: '00'.repeat(32), // Start of chain
186
+ binding_status: 'UNBOUND',
100
187
  supervision: {
101
188
  branch_completeness: 1.0,
102
189
  contradictions: 0,
@@ -104,7 +191,7 @@ class VexAgent {
104
191
  }
105
192
  };
106
193
  const identity = {
107
- aid: '00'.repeat(32), // Placeholder for hardware AID
194
+ aid: this.config.aid || '00'.repeat(32),
108
195
  identity_type: 'software_sim',
109
196
  pcrs: { '0': '00'.repeat(32) }
110
197
  };
@@ -113,7 +200,35 @@ class VexAgent {
113
200
  receipt_hash: '00'.repeat(32),
114
201
  timestamp: Math.floor(Date.now() / 1000)
115
202
  };
116
- const intent_hash = builder_1.VEPBuilder.hashSegment(intent);
203
+ // --- Phase 2: HPKE Encryption (Optional/v3) ---
204
+ let intent_hash;
205
+ const gatePkBase64 = await this.fetchPublicKey().catch(() => null);
206
+ if (gatePkBase64) {
207
+ const suite = new CipherSuite({
208
+ kem: new DhkemX25519HkdfSha256(),
209
+ kdf: new HkdfSha256(),
210
+ aead: new Aes128Gcm(),
211
+ });
212
+ const recipientPublicKeyRaw = new Uint8Array(Buffer.from(gatePkBase64, 'base64'));
213
+ const recipientKey = await suite.importKey('raw', recipientPublicKeyRaw, true);
214
+ const info = new Uint8Array(Buffer.from('vex/intent/v3'));
215
+ const { enc, ct } = await suite.seal({
216
+ recipientPublicKey: recipientKey,
217
+ info: info
218
+ }, new Uint8Array(Buffer.from(JSON.stringify(intent))));
219
+ // In v1.6.0, the "Intent Pillar" commitment is the hash of the ciphertext
220
+ intent_hash = require('crypto').createHash('sha256').update(Buffer.from(ct)).digest('hex');
221
+ // Add HPKE metadata to the segment so the Gate can decrypt
222
+ intent.hpke = {
223
+ enc: Buffer.from(enc).toString('base64'),
224
+ ciphertext: Buffer.from(ct).toString('base64'),
225
+ schema: 'vex/intent/v3/encrypted'
226
+ };
227
+ }
228
+ else {
229
+ // Fallback: Standard JCS hash for Transparent intents
230
+ intent_hash = builder_1.VEPBuilder.hashSegment(intent);
231
+ }
117
232
  const authority_hash = builder_1.VEPBuilder.hashSegment(authority);
118
233
  const identity_hash = builder_1.VEPBuilder.hashSegment(identity);
119
234
  const witness_hash = builder_1.VEPBuilder.hashSegment(witness, false); // Minimal scope
@@ -180,8 +295,6 @@ class VexAgent {
180
295
  return Buffer.concat([header, ...segments, header]);
181
296
  }
182
297
  hashObject(obj) {
183
- const { canonicalize } = require('json-canonicalize');
184
- const crypto = require('crypto');
185
298
  const canonicalJSON = canonicalize(obj);
186
299
  return crypto.createHash('sha256').update(canonicalJSON).digest('hex');
187
300
  }
package/dist/builder.d.ts CHANGED
@@ -14,10 +14,15 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  export interface IntentSegment {
17
+ schema: string;
18
+ aid: string;
17
19
  request_sha256: string;
20
+ commands: unknown[];
18
21
  confidence: number;
19
22
  capabilities: string[];
20
23
  magpie_source?: string;
24
+ intent_context?: string;
25
+ [key: string]: unknown;
21
26
  }
22
27
  export interface AuthoritySegment {
23
28
  capsule_id: string;
@@ -26,22 +31,27 @@ export interface AuthoritySegment {
26
31
  trace_root: string;
27
32
  nonce: number;
28
33
  prev_hash?: string;
34
+ binding_status: string;
35
+ continuation_token?: string;
29
36
  supervision?: {
30
37
  branch_completeness?: number;
31
38
  contradictions?: number;
32
39
  confidence?: number;
33
40
  };
34
- gate_sensors?: Record<string, any>;
41
+ gate_sensors?: Record<string, unknown>;
42
+ [key: string]: unknown;
35
43
  }
36
44
  export interface IdentitySegment {
37
45
  aid: string;
38
46
  identity_type: string;
39
47
  pcrs: Record<string, string>;
48
+ [key: string]: unknown;
40
49
  }
41
50
  export interface WitnessSegment {
42
51
  chora_node_id: string;
43
52
  receipt_hash: string;
44
53
  timestamp: number;
54
+ [key: string]: unknown;
45
55
  }
46
56
  export interface VexPillars {
47
57
  intent: IntentSegment;
@@ -49,11 +59,24 @@ export interface VexPillars {
49
59
  identity: IdentitySegment;
50
60
  witness: WitnessSegment;
51
61
  }
62
+ export interface VexCapsule extends VexPillars {
63
+ intent_hash: string;
64
+ authority_hash: string;
65
+ identity_hash: string;
66
+ witness_hash: string;
67
+ capsule_root: string;
68
+ crypto: {
69
+ algo: string;
70
+ signature_scope: string;
71
+ signature_b64: string;
72
+ signature_raw: Buffer;
73
+ };
74
+ }
52
75
  export declare class VEPBuilder {
53
76
  /**
54
77
  * Computes the SHA-256 hash of a JCS canonicalized object.
55
78
  */
56
- static hashSegment(segment: any, inclusive?: boolean): string;
79
+ static hashSegment(segment: Record<string, unknown>, inclusive?: boolean): string;
57
80
  /**
58
81
  * Calculates the capsule_root commitment using a 4-leaf binary Merkle tree
59
82
  * with domain separation (0x00 for leaves, 0x01 for internal nodes).
@@ -22,9 +22,9 @@ export declare const vexMiddleware: (config: {
22
22
  }) => {
23
23
  onToolCall({ toolName, args }: {
24
24
  toolName: string;
25
- args: any;
25
+ args: Record<string, unknown>;
26
26
  }): Promise<{
27
27
  status: string;
28
- vex_root: any;
28
+ vex_root: string;
29
29
  }>;
30
30
  };
package/package.json CHANGED
@@ -1,31 +1,34 @@
1
- {
2
- "name": "@provnai/vex-sdk",
3
- "version": "1.5.0",
4
- "description": "High-level routing and cognitive abstraction SDK for the VEX Protocol",
5
- "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
7
- "scripts": {
8
- "build": "tsc",
9
- "test": "jest",
10
- "lint": "eslint src/**/*.ts"
11
- },
12
- "dependencies": {
13
- "@provncloud/sdk": "^0.3.0",
14
- "axios": "^1.6.0",
15
- "json-canonicalize": "^1.0.4"
16
- },
17
- "devDependencies": {
18
- "@types/jest": "^29.5.0",
19
- "@types/node": "^20.0.0",
20
- "@typescript-eslint/eslint-plugin": "^8.57.1",
21
- "@typescript-eslint/parser": "^8.57.1",
22
- "@eslint/js": "^9.39.4",
23
- "eslint": "^9.11.0",
24
- "jest": "^29.5.0",
25
- "ts-jest": "^29.1.0",
26
- "typescript": "^5.0.0"
27
- },
28
- "publishConfig": {
29
- "access": "public"
30
- }
31
- }
1
+ {
2
+ "name": "@provnai/vex-sdk",
3
+ "version": "1.6.0",
4
+ "description": "High-level routing and cognitive abstraction SDK for the VEX Protocol",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "test": "jest",
10
+ "lint": "eslint src/**/*.ts"
11
+ },
12
+ "dependencies": {
13
+ "@provncloud/sdk": "^0.3.0",
14
+ "hpke-js": "^1.2.0",
15
+ "@hpke/core": "^1.2.7",
16
+ "@hpke/dhkem-x25519": "^1.2.7",
17
+ "axios": "^1.6.0",
18
+ "json-canonicalize": "^1.0.4"
19
+ },
20
+ "devDependencies": {
21
+ "@types/jest": "^29.5.0",
22
+ "@types/node": "^20.0.0",
23
+ "@typescript-eslint/eslint-plugin": "^8.57.1",
24
+ "@typescript-eslint/parser": "^8.57.1",
25
+ "@eslint/js": "^9.39.4",
26
+ "eslint": "^9.11.0",
27
+ "jest": "^29.5.0",
28
+ "ts-jest": "^29.1.0",
29
+ "typescript": "^5.0.0"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ }
34
+ }
package/src/agent.ts CHANGED
@@ -14,19 +14,36 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
+ import * as crypto from 'crypto';
18
+ const { canonicalize } = require('json-canonicalize');
17
19
  import axios from 'axios';
18
- import { VEPBuilder, VexPillars } from './builder';
20
+ import { VEPBuilder, VexCapsule, IntentSegment, AuthoritySegment } from './builder';
19
21
  import { ProvnSDK, init } from '@provncloud/sdk';
22
+ const { CipherSuite } = require('hpke-js');
23
+ const { DhkemX25519HkdfSha256, HkdfSha256 } = require('@hpke/dhkem-x25519');
24
+ const { Aes128Gcm } = require('@hpke/core');
20
25
 
21
26
  export interface VexConfig {
22
27
  identityKey: string; // Hex or Base64 Ed25519 private key
23
28
  vanguardUrl: string; // Target proxy endpoint (McpVanguard/VEX sidecar)
29
+ aid?: string; // Agent Identity ID (NEW in v3)
30
+ }
31
+
32
+ export interface VexResult {
33
+ status: string;
34
+ outcome: 'ALLOW' | 'HALT' | 'ESCALATE';
35
+ reason_code?: string;
36
+ capsule_root: string;
37
+ capability_token?: string;
38
+ [key: string]: unknown;
24
39
  }
25
40
 
26
41
  export class VexAgent {
27
42
  private config: VexConfig;
28
43
  private sdk: ProvnSDK | null = null;
29
44
 
45
+ private currentToken?: string;
46
+
30
47
  constructor(config: VexConfig) {
31
48
  this.config = config;
32
49
  }
@@ -38,10 +55,66 @@ export class VexAgent {
38
55
  }
39
56
  }
40
57
 
58
+ /**
59
+ * Retrieves the Gate's public key for HPKE encryption.
60
+ */
61
+ async fetchPublicKey(): Promise<string> {
62
+ const response = await axios.get(`${this.config.vanguardUrl}/public_key`);
63
+ return response.data.public_key;
64
+ }
65
+
41
66
  /**
42
67
  * Executes a tool via the VEX verifiable execution loop.
43
68
  */
44
- async execute(toolName: string, params: Record<string, any>, intentContext?: string): Promise<any> {
69
+ /**
70
+ * Locally verifies a VEX Continuation Token (v3) against the Gate's public key.
71
+ * Ensures the token was signed by the authoritative Gate and binds to the current capsule.
72
+ */
73
+ async verifyToken(tokenBase64: string, expectedCapsuleRoot?: string): Promise<boolean> {
74
+ const { canonicalize } = require('json-canonicalize');
75
+ const crypto = require('crypto');
76
+
77
+ try {
78
+ const token = JSON.parse(Buffer.from(tokenBase64, 'base64').toString());
79
+ const gatePkBase64 = await this.fetchPublicKey();
80
+ const gatePublicKey = crypto.createPublicKey({
81
+ key: Buffer.from(gatePkBase64, 'base64'),
82
+ format: 'der',
83
+ type: 'spki', // Ed25519 in SPKI format
84
+ });
85
+
86
+ // 1. Re-hash the payload (JCS)
87
+ const payloadHash = crypto.createHash('sha256')
88
+ .update(canonicalize(token.payload))
89
+ .digest();
90
+
91
+ // 2. Verify signature
92
+ const isSignatureValid = crypto.verify(
93
+ null,
94
+ payloadHash,
95
+ gatePublicKey,
96
+ Buffer.from(token.signature, 'hex')
97
+ );
98
+
99
+ if (!isSignatureValid) {
100
+ console.error('VEX Token Verification Failed: Invalid Signature');
101
+ return false;
102
+ }
103
+
104
+ // 3. Bind to capsule root
105
+ if (expectedCapsuleRoot && token.payload.source_capsule_root !== expectedCapsuleRoot) {
106
+ console.error('VEX Token Verification Failed: Root Mismatch');
107
+ return false;
108
+ }
109
+
110
+ return true;
111
+ } catch (error) {
112
+ console.error('VEX Token Verification Error:', error);
113
+ return false;
114
+ }
115
+ }
116
+
117
+ async execute(toolName: string, params: Record<string, unknown>, intentContext?: string): Promise<VexResult> {
45
118
  await this.ensureSDK();
46
119
 
47
120
  // 1. Build Capsule
@@ -79,12 +152,13 @@ export class VexAgent {
79
152
 
80
153
  // 4. Store Capability Token
81
154
  if (result.capability_token) {
82
- (this as any).currentToken = result.capability_token;
155
+ this.currentToken = result.capability_token;
83
156
  }
84
157
 
85
- return result;
86
- } catch (error: any) {
87
- console.error('VEX Execution Failed:', error.response?.data || error.message);
158
+ return result as VexResult;
159
+ } catch (error) {
160
+ const err = error as { response?: { data: Record<string, unknown> }, message: string };
161
+ console.error('VEX Execution Failed:', err.response?.data || err.message);
88
162
  throw error;
89
163
  }
90
164
  }
@@ -92,13 +166,16 @@ export class VexAgent {
92
166
  /**
93
167
  * Manually construct a signed Evidence Capsule without dispatching it.
94
168
  */
95
- async buildCapsule(toolName: string, params: Record<string, any>, intentContext?: string): Promise<any> {
169
+ async buildCapsule(toolName: string, params: Record<string, unknown>, intentContext?: string): Promise<VexCapsule> {
96
170
  await this.ensureSDK();
97
171
  // This is a simplified implementation for the SDK.
98
172
  // It demonstrates how the builder is leveraged.
99
173
 
100
- const intent: any = {
174
+ const intent: IntentSegment = {
175
+ schema: 'vex/intent/v3',
176
+ aid: this.config.aid || '00'.repeat(32),
101
177
  request_sha256: this.hashObject(params),
178
+ commands: [toolName, params],
102
179
  confidence: 1.0,
103
180
  capabilities: ['sdk_execution']
104
181
  };
@@ -106,13 +183,14 @@ export class VexAgent {
106
183
  intent.intent_context = intentContext;
107
184
  }
108
185
 
109
- const authority = {
186
+ const authority: AuthoritySegment = {
110
187
  capsule_id: require('crypto').randomUUID(),
111
188
  outcome: 'ALLOW',
112
189
  reason_code: 'SDK_GENERATED',
113
190
  trace_root: '00'.repeat(32),
114
191
  nonce: Date.now(),
115
192
  prev_hash: '00'.repeat(32), // Start of chain
193
+ binding_status: 'UNBOUND',
116
194
  supervision: {
117
195
  branch_completeness: 1.0,
118
196
  contradictions: 0,
@@ -121,7 +199,7 @@ export class VexAgent {
121
199
  };
122
200
 
123
201
  const identity = {
124
- aid: '00'.repeat(32), // Placeholder for hardware AID
202
+ aid: this.config.aid || '00'.repeat(32),
125
203
  identity_type: 'software_sim',
126
204
  pcrs: { '0': '00'.repeat(32) }
127
205
  };
@@ -132,7 +210,43 @@ export class VexAgent {
132
210
  timestamp: Math.floor(Date.now() / 1000)
133
211
  };
134
212
 
135
- const intent_hash = VEPBuilder.hashSegment(intent);
213
+ // --- Phase 2: HPKE Encryption (Optional/v3) ---
214
+ let intent_hash: string;
215
+ const gatePkBase64 = await this.fetchPublicKey().catch(() => null);
216
+
217
+ if (gatePkBase64) {
218
+ const suite = new CipherSuite({
219
+ kem: new DhkemX25519HkdfSha256(),
220
+ kdf: new HkdfSha256(),
221
+ aead: new Aes128Gcm(),
222
+ });
223
+
224
+ const recipientPublicKeyRaw = new Uint8Array(Buffer.from(gatePkBase64, 'base64'));
225
+ const recipientKey = await suite.importKey('raw', recipientPublicKeyRaw, true);
226
+ const info = new Uint8Array(Buffer.from('vex/intent/v3'));
227
+
228
+ const { enc, ct } = await suite.seal(
229
+ {
230
+ recipientPublicKey: recipientKey,
231
+ info: info
232
+ },
233
+ new Uint8Array(Buffer.from(JSON.stringify(intent)))
234
+ );
235
+
236
+ // In v1.6.0, the "Intent Pillar" commitment is the hash of the ciphertext
237
+ intent_hash = require('crypto').createHash('sha256').update(Buffer.from(ct)).digest('hex');
238
+
239
+ // Add HPKE metadata to the segment so the Gate can decrypt
240
+ intent.hpke = {
241
+ enc: Buffer.from(enc).toString('base64'),
242
+ ciphertext: Buffer.from(ct).toString('base64'),
243
+ schema: 'vex/intent/v3/encrypted'
244
+ };
245
+ } else {
246
+ // Fallback: Standard JCS hash for Transparent intents
247
+ intent_hash = VEPBuilder.hashSegment(intent);
248
+ }
249
+
136
250
  const authority_hash = VEPBuilder.hashSegment(authority);
137
251
  const identity_hash = VEPBuilder.hashSegment(identity);
138
252
  const witness_hash = VEPBuilder.hashSegment(witness, false); // Minimal scope
@@ -176,7 +290,7 @@ export class VexAgent {
176
290
  /**
177
291
  * Serializes the Evidence Capsule into the v0x03 Binary Wire format.
178
292
  */
179
- toBinary(capsule: any): Buffer {
293
+ toBinary(capsule: VexCapsule): Buffer {
180
294
  const { canonicalize } = require('json-canonicalize');
181
295
 
182
296
  // --- Header (76 Bytes) ---
@@ -207,9 +321,7 @@ export class VexAgent {
207
321
  return Buffer.concat([header, ...segments, header]);
208
322
  }
209
323
 
210
- private hashObject(obj: any): string {
211
- const { canonicalize } = require('json-canonicalize');
212
- const crypto = require('crypto');
324
+ private hashObject(obj: Record<string, unknown>): string {
213
325
  const canonicalJSON = canonicalize(obj);
214
326
  return crypto.createHash('sha256').update(canonicalJSON).digest('hex');
215
327
  }
package/src/builder.ts CHANGED
@@ -18,10 +18,15 @@ import * as crypto from 'crypto';
18
18
  const { canonicalize } = require('json-canonicalize');
19
19
 
20
20
  export interface IntentSegment {
21
+ schema: string; // NEW (v3)
22
+ aid: string; // NEW (v3)
21
23
  request_sha256: string;
24
+ commands: unknown[]; // NEW (v3)
22
25
  confidence: number;
23
26
  capabilities: string[];
24
27
  magpie_source?: string;
28
+ intent_context?: string; // Context for v3 enforcement
29
+ [key: string]: unknown; // For HPKE and other metadata
25
30
  }
26
31
 
27
32
  export interface AuthoritySegment {
@@ -31,24 +36,29 @@ export interface AuthoritySegment {
31
36
  trace_root: string;
32
37
  nonce: number;
33
38
  prev_hash?: string; // VEX Ledger Link
39
+ binding_status: string; // NEW (v3)
40
+ continuation_token?: string; // NEW (v3)
34
41
  supervision?: { // MCS Signals
35
42
  branch_completeness?: number;
36
43
  contradictions?: number;
37
44
  confidence?: number;
38
45
  };
39
- gate_sensors?: Record<string, any>;
46
+ gate_sensors?: Record<string, unknown>;
47
+ [key: string]: unknown;
40
48
  }
41
49
 
42
50
  export interface IdentitySegment {
43
51
  aid: string;
44
52
  identity_type: string;
45
53
  pcrs: Record<string, string>;
54
+ [key: string]: unknown;
46
55
  }
47
56
 
48
57
  export interface WitnessSegment {
49
58
  chora_node_id: string;
50
59
  receipt_hash: string;
51
60
  timestamp: number;
61
+ [key: string]: unknown;
52
62
  }
53
63
 
54
64
  export interface VexPillars {
@@ -58,11 +68,25 @@ export interface VexPillars {
58
68
  witness: WitnessSegment;
59
69
  }
60
70
 
71
+ export interface VexCapsule extends VexPillars {
72
+ intent_hash: string;
73
+ authority_hash: string;
74
+ identity_hash: string;
75
+ witness_hash: string;
76
+ capsule_root: string;
77
+ crypto: {
78
+ algo: string;
79
+ signature_scope: string;
80
+ signature_b64: string;
81
+ signature_raw: Buffer;
82
+ };
83
+ }
84
+
61
85
  export class VEPBuilder {
62
86
  /**
63
87
  * Computes the SHA-256 hash of a JCS canonicalized object.
64
88
  */
65
- static hashSegment(segment: any, inclusive: boolean = true): string {
89
+ static hashSegment(segment: Record<string, unknown>, inclusive: boolean = true): string {
66
90
  let dataToHash = segment;
67
91
 
68
92
  if (!inclusive) {
package/src/index.ts CHANGED
@@ -14,8 +14,6 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
- import { VexAgent } from './agent';
18
- import { VEPBuilder } from './builder';
19
17
 
20
18
  export * from './agent';
21
19
  export * from './builder';
package/src/middleware.ts CHANGED
@@ -24,7 +24,7 @@ export const vexMiddleware = (config: { identityKey: string, vanguardUrl: string
24
24
 
25
25
  return {
26
26
  // This is a pattern used by Vercel AI SDK to intercept tool calls
27
- async onToolCall({ toolName, args }: { toolName: string, args: any }) {
27
+ async onToolCall({ toolName, args }: { toolName: string, args: Record<string, unknown> }) {
28
28
  console.log(`[VEX] Securing tool call: ${toolName}`);
29
29
 
30
30
  try {
@@ -19,11 +19,15 @@ describe('VexAgent', () => {
19
19
  vanguardUrl: 'http://localhost:3000'
20
20
  };
21
21
 
22
+ beforeEach(() => {
23
+ // Valid 32-byte X25519 public key (non-zero)
24
+ const validPkBase64 = 'uSJQ6R87m8qPk96hQc64b5S67890123456789012344=';
25
+ mockedAxios.get.mockResolvedValue({ data: { public_key: validPkBase64 } });
26
+ mockedAxios.post.mockResolvedValue({ data: { status: 'verified', outcome: 'ALLOW' } });
27
+ });
28
+
22
29
  test('execute() constructs a capsule and sends it via POST', async () => {
23
30
  const agent = new VexAgent(config);
24
-
25
- mockedAxios.post.mockResolvedValue({ data: { status: 'verified' } });
26
-
27
31
  const result = await agent.execute('test_tool', { foo: 'bar' });
28
32
 
29
33
  expect(mockedAxios.post).toHaveBeenCalledWith(
@@ -1,5 +1,9 @@
1
1
  import { VexAgent } from '../src/agent';
2
2
 
3
+ jest.mock('axios');
4
+ import axios from 'axios';
5
+ const mockedAxios = axios as jest.Mocked<typeof axios>;
6
+
3
7
  jest.mock('@provncloud/sdk', () => ({
4
8
  ProvnSDK: jest.fn().mockImplementation(() => ({
5
9
  generateKeypair: jest.fn(),
@@ -25,6 +29,10 @@ describe('Binary Parity', () => {
25
29
  const crypto = require('crypto');
26
30
  jest.spyOn(crypto, 'randomUUID').mockReturnValue('0'.repeat(64));
27
31
 
32
+ // Mock fetchPublicKey to avoid network calls
33
+ const validPkBase64 = 'uSJQ6R87m8qPk96hQc64b5S67890123456789012344=';
34
+ mockedAxios.get.mockResolvedValue({ data: { public_key: validPkBase64 } });
35
+
28
36
  // Mock SDK signing
29
37
  const mockSDK = {
30
38
  createClaimNow: jest.fn().mockReturnValue({}),
@@ -1,17 +1,15 @@
1
1
  import { VEPBuilder } from '../src/builder';
2
2
 
3
3
  describe('VEX SDK Parity Verification', () => {
4
- test('calculateCapsuleRoot matches Rust test vector (v1.5.0 Merkle Shift)', () => {
5
- // Test hashes provided in COWORKER_HANDOFF.md.resolved
4
+ test('calculateCapsuleRoot matches Rust test vector (v1.6.0 Protocol Alignment)', () => {
6
5
  const parityHashes = {
7
- authority_hash: "6fac0de31355fc1dfe36eee1e0c226f7cc36dd58eaad0aca0c2d3873b4784d35",
6
+ authority_hash: "1f66eab08c7276b5bd65b6624193eb216159a675e43b85d827de85ec065495c6",
8
7
  identity_hash: "7869bae0249b33e09b881a0b44faba6ee3f4bab7edcc2aa5a5e9290e2563c828",
9
- intent_hash: "e02504ea88bd9f05a744cd8a462a114dc2045eb7210ea8c6f5ff2679663c92cb",
10
- witness_hash: "174dfb80917cca8a8d4760b82656e78df0778cb3aadd60b51cd018b3313d5733"
8
+ intent_hash: "ce4041d35af4dd0c00b60a04c80779516178097f7ab7e20fea6da2996dc06446",
9
+ witness_hash: "2aa5ae39acd791e6ae12341b4e82ec16cfcdd2ab4e46a8fb48389dff6217fd42"
11
10
  };
12
11
 
13
- // Recalculated for 4-leaf Merkle Tree with 0x00/0x01 domain separation (Definitive v1.5.0)
14
- const expectedRoot = "8acf6d45aedaf61c61142ea8f9f7a89bc90994532313f20fcc1493a95e36d405";
12
+ const expectedRoot = "35ef4684c3168f54f040e0e6a24d5bde35464731e6c32bb34bcc30fbb69c8255";
15
13
 
16
14
  const calculatedRoot = VEPBuilder.calculateCapsuleRoot(parityHashes);
17
15
  expect(calculatedRoot).toBe(expectedRoot);
@@ -31,16 +29,22 @@ describe('VEX SDK Parity Verification', () => {
31
29
  test('hashSegment matches expected output (Verification of JCS + SHA256)', () => {
32
30
  // Intent Pillar (Inclusive)
33
31
  const intent = {
34
- request_sha256: "e02504ea88bd9f05a744cd8a462a114dc2045eb7210ea8c6f5ff2679663c92cb",
35
- confidence: 0.95,
36
- capabilities: ["filesystem", "network"]
32
+ schema: "vex/intent/v3",
33
+ aid: "00".repeat(32),
34
+ request_sha256: "ce4041d35af4dd0c00b60a04c80779516178097f7ab7e20fea6da2996dc06446",
35
+ commands: ["test_tool", { foo: "bar" }],
36
+ confidence: 1.0,
37
+ capabilities: ["sdk_execution"]
37
38
  };
38
39
  // This is a sanity check that JCS stable-sorting works
39
40
  const hash1 = VEPBuilder.hashSegment(intent);
40
41
  const hash2 = VEPBuilder.hashSegment({
41
- capabilities: ["filesystem", "network"],
42
- confidence: 0.95,
43
- request_sha256: "e02504ea88bd9f05a744cd8a462a114dc2045eb7210ea8c6f5ff2679663c92cb"
42
+ capabilities: ["sdk_execution"],
43
+ confidence: 1.0,
44
+ commands: ["test_tool", { foo: "bar" }],
45
+ request_sha256: "ce4041d35af4dd0c00b60a04c80779516178097f7ab7e20fea6da2996dc06446",
46
+ aid: "00".repeat(32),
47
+ schema: "vex/intent/v3"
44
48
  });
45
49
 
46
50
  expect(hash1).toBe(hash2);