verdifax 0.2.10 → 0.3.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 +37 -1
- package/dist/index.d.mts +88 -1
- package/dist/index.d.ts +88 -1
- package/dist/index.js +115 -2
- package/dist/index.mjs +119 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -39,7 +39,7 @@ console.log(`Sealed run ${run.run_id}, manifest hash ${run.manifest.ManifestHash
|
|
|
39
39
|
// Verify the seal recomputes correctly.
|
|
40
40
|
const v = await client.verifyRun(run.run_id);
|
|
41
41
|
if (v.verified) {
|
|
42
|
-
console.log("✓ Manifest hash recomputes
|
|
42
|
+
console.log("✓ Manifest hash recomputes.");
|
|
43
43
|
}
|
|
44
44
|
```
|
|
45
45
|
|
|
@@ -52,6 +52,8 @@ if (v.verified) {
|
|
|
52
52
|
| `client.execute(req)` | `POST /execute` | required |
|
|
53
53
|
| `client.getRun(id)` | `GET /runs/{id}` | required |
|
|
54
54
|
| `client.verifyRun(id)` | `GET /runs/{id}/verify` | required |
|
|
55
|
+
| `client.registerRequestKey()` | `POST /keys/request-key` | required |
|
|
56
|
+
| `client.clearRequestKey()` | `DELETE /keys/request-key` | required |
|
|
55
57
|
|
|
56
58
|
The full HTTP surface (every endpoint, deletion-receipt list,
|
|
57
59
|
DCAE verify, admin endpoints) is documented in the
|
|
@@ -96,6 +98,40 @@ try {
|
|
|
96
98
|
}
|
|
97
99
|
```
|
|
98
100
|
|
|
101
|
+
## Request signing
|
|
102
|
+
|
|
103
|
+
Request signing is optional. A client with no signing key sends the same requests as before.
|
|
104
|
+
|
|
105
|
+
You can register an Ed25519 public key on your API key, so that each run must also be signed with a private key that only you hold. The key pair is created on your machine, and Verdifax receives only the public key.
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import { VerdifaxClient, generateRequestKeyPair } from "verdifax";
|
|
109
|
+
|
|
110
|
+
// Run once, then store privateKey as a secret.
|
|
111
|
+
const { privateKey } = generateRequestKeyPair();
|
|
112
|
+
|
|
113
|
+
const client = new VerdifaxClient({
|
|
114
|
+
apiKey: process.env.VERDIFAX_API_KEY!,
|
|
115
|
+
signingKey: privateKey, // the 32 raw bytes, as a Buffer or Uint8Array
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
await client.registerRequestKey(); // sends the public key with its proof
|
|
119
|
+
|
|
120
|
+
// execute, attest, attestClaudeResponse and attestOpenAIResponse now sign.
|
|
121
|
+
await client.execute({
|
|
122
|
+
program_id: "1111111111111111111111111111111111111111111111111111111111111111",
|
|
123
|
+
route_id: "demo-attestation",
|
|
124
|
+
registry_record_hash: "2222222222222222222222222222222222222222222222222222222222222222",
|
|
125
|
+
payload_text: "first signed attestation",
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
await client.clearRequestKey(); // stops requiring signatures on this API key
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Registering a key changes that API key for every client that uses it: the server then refuses a run without a valid signature, so every caller on the key needs the private key. Each request carries the time and a fresh random nonce. The server refuses a repeated nonce and a time more than five minutes from its own clock.
|
|
132
|
+
|
|
133
|
+
The signature shows the holder of the key requested the run. It does not show the key holder accepted the outcome.
|
|
134
|
+
|
|
99
135
|
## Trust model
|
|
100
136
|
|
|
101
137
|
This SDK only mediates HTTP I/O, it does NOT verify sealed
|
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { KeyObject } from 'node:crypto';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Type definitions for the Verdifax orchestrator REST API.
|
|
3
5
|
*
|
|
@@ -48,6 +50,37 @@ interface ExecuteRequest {
|
|
|
48
50
|
ai_output_text?: string;
|
|
49
51
|
/** Caller-attested actor + decision metadata; open schema. */
|
|
50
52
|
attested_context?: Record<string, unknown>;
|
|
53
|
+
/** Filled in by the client when the `signingKey` option is set. */
|
|
54
|
+
request_signature?: RequestSignature;
|
|
55
|
+
}
|
|
56
|
+
/** The signature an API key with a registered request key must carry.
|
|
57
|
+
* The public key comes from the registration, never from the request. */
|
|
58
|
+
interface RequestSignature {
|
|
59
|
+
/** Base64 Ed25519 signature. */
|
|
60
|
+
signature: string;
|
|
61
|
+
/** RFC 3339 UTC with whole seconds, for example 2026-11-05T02:18:00Z. */
|
|
62
|
+
signed_at: string;
|
|
63
|
+
/** 16 random bytes as 32 lowercase hex characters, fresh per request. */
|
|
64
|
+
nonce: string;
|
|
65
|
+
}
|
|
66
|
+
/** A key pair from {@link generateRequestKeyPair}, as raw bytes. */
|
|
67
|
+
interface RequestKeyPair {
|
|
68
|
+
/** The 32-byte Ed25519 public key. */
|
|
69
|
+
publicKey: Buffer;
|
|
70
|
+
/** The 32-byte Ed25519 private key. Keep it secret. */
|
|
71
|
+
privateKey: Buffer;
|
|
72
|
+
}
|
|
73
|
+
/** Body of POST /keys/request-key. */
|
|
74
|
+
interface RequestKeyRegistrationBody {
|
|
75
|
+
/** Base64 of the raw 32-byte public key. */
|
|
76
|
+
public_key: string;
|
|
77
|
+
/** Base64 signature proving the caller holds the private key. */
|
|
78
|
+
proof: string;
|
|
79
|
+
}
|
|
80
|
+
/** Returned by {@link VerdifaxClient.registerRequestKey}. */
|
|
81
|
+
interface RequestKeyRegistration {
|
|
82
|
+
ok: boolean;
|
|
83
|
+
public_key: string;
|
|
51
84
|
}
|
|
52
85
|
interface ExecuteResponse {
|
|
53
86
|
ok: boolean;
|
|
@@ -132,6 +165,10 @@ interface ClientConfig {
|
|
|
132
165
|
* it on each subsequent retry (exponential backoff with full
|
|
133
166
|
* jitter). Defaults to 200ms. */
|
|
134
167
|
retryBaseDelayMs?: number;
|
|
168
|
+
/** The 32 raw bytes of an Ed25519 private key. When set, every path to
|
|
169
|
+
* POST /execute signs its request. The key is never sent to the
|
|
170
|
+
* server. Leave unset for unsigned requests. */
|
|
171
|
+
signingKey?: Uint8Array;
|
|
135
172
|
}
|
|
136
173
|
/** Friendly shape for {@link VerdifaxClient.attest}. Mirrors the
|
|
137
174
|
* Python SDK's `attest()` signature so polyglot teams have parity. */
|
|
@@ -258,6 +295,11 @@ interface AdminAPIKey {
|
|
|
258
295
|
* | `adminRevokeKey(id)` | DELETE /admin/keys/{id} | required |
|
|
259
296
|
* | `attestClaudeResponse(...)` | POST /execute (helper) | required |
|
|
260
297
|
* | `attestOpenAIResponse(...)` | POST /execute (helper) | required |
|
|
298
|
+
* | `registerRequestKey()` | POST /keys/request-key | required |
|
|
299
|
+
* | `clearRequestKey()` | DELETE /keys/request-key | required |
|
|
300
|
+
*
|
|
301
|
+
* With the `signingKey` option set, every path to POST /execute carries
|
|
302
|
+
* a fresh request signature. Without it, requests are unsigned.
|
|
261
303
|
*
|
|
262
304
|
* Idempotent GETs that hit a transient failure (502/503/504/429)
|
|
263
305
|
* are retried with exponential backoff + full jitter. Non-
|
|
@@ -271,6 +313,7 @@ declare class VerdifaxClient {
|
|
|
271
313
|
private readonly userAgent;
|
|
272
314
|
private readonly maxRetries;
|
|
273
315
|
private readonly retryBaseDelayMs;
|
|
316
|
+
private readonly signingKey;
|
|
274
317
|
constructor(config?: ClientConfig);
|
|
275
318
|
health(): Promise<Health>;
|
|
276
319
|
version(): Promise<VerdifaxVersion>;
|
|
@@ -280,6 +323,13 @@ declare class VerdifaxClient {
|
|
|
280
323
|
* string (for plain text). Validates the hex fields and
|
|
281
324
|
* routes the call through to the canonical /execute. */
|
|
282
325
|
attest(req: AttestRequest): Promise<ExecuteResponse>;
|
|
326
|
+
/** Registers this client's public key on its API key, with the proof
|
|
327
|
+
* that the client holds the private key. From then on the server
|
|
328
|
+
* refuses a run on this API key without a request signature. */
|
|
329
|
+
registerRequestKey(): Promise<RequestKeyRegistration>;
|
|
330
|
+
/** Clears the registered public key, so the server stops requiring
|
|
331
|
+
* request signatures on this API key. */
|
|
332
|
+
clearRequestKey(): Promise<void>;
|
|
283
333
|
getRun(runId: number): Promise<Run>;
|
|
284
334
|
verifyRun(runId: number): Promise<VerifyResponse>;
|
|
285
335
|
listRuns(params?: ListRunsParams): Promise<{
|
|
@@ -339,6 +389,43 @@ declare class VerdifaxClient {
|
|
|
339
389
|
|
|
340
390
|
declare function fromEnv(): VerdifaxClient;
|
|
341
391
|
|
|
392
|
+
/**
|
|
393
|
+
* Optional customer request signing.
|
|
394
|
+
*
|
|
395
|
+
* A customer can register an Ed25519 public key on an API key. From then
|
|
396
|
+
* on the orchestrator accepts a run only with a signature by the matching
|
|
397
|
+
* private key. The private key stays with the customer: it is never sent
|
|
398
|
+
* to the server, logged, or placed in an error message.
|
|
399
|
+
*/
|
|
400
|
+
|
|
401
|
+
/** Creates a new Ed25519 key pair on this machine, as raw bytes: a
|
|
402
|
+
* 32-byte public key and a 32-byte private key. Nothing is sent to
|
|
403
|
+
* the server. Store the private key as a secret. */
|
|
404
|
+
declare function generateRequestKeyPair(): RequestKeyPair;
|
|
405
|
+
/** The bytes a request signature signs: the fields joined with single
|
|
406
|
+
* zero bytes, matching the orchestrator's RequestSignaturePreimage. */
|
|
407
|
+
declare function requestSignaturePreimage(fields: {
|
|
408
|
+
payload_hash: string;
|
|
409
|
+
program_id: string;
|
|
410
|
+
route_id: string;
|
|
411
|
+
registry_record_hash: string;
|
|
412
|
+
signed_at: string;
|
|
413
|
+
nonce: string;
|
|
414
|
+
}): Buffer;
|
|
415
|
+
/** Signs one execute request. The server hashes payload_text when it is
|
|
416
|
+
* non-empty, otherwise the payload string exactly as sent (it does not
|
|
417
|
+
* base64-decode it), so this does the same.
|
|
418
|
+
*
|
|
419
|
+
* `signedAt` and `nonce` default to the current time and fresh random
|
|
420
|
+
* bytes. A nonce the server has already seen is refused. */
|
|
421
|
+
declare function signRequest(signingKey: Uint8Array | KeyObject, req: ExecuteRequest, options?: {
|
|
422
|
+
signedAt?: string;
|
|
423
|
+
nonce?: string;
|
|
424
|
+
}): RequestSignature;
|
|
425
|
+
/** The body of POST /keys/request-key: the base64 public key and a
|
|
426
|
+
* signature proving the caller holds its private key. */
|
|
427
|
+
declare function requestKeyRegistrationBody(signingKey: Uint8Array | KeyObject): RequestKeyRegistrationBody;
|
|
428
|
+
|
|
342
429
|
/**
|
|
343
430
|
* Input-validation helpers, mirrors the Python SDK's
|
|
344
431
|
* `_validation.py` and the Go SDK's `validation.go` for
|
|
@@ -411,4 +498,4 @@ declare class APIKeyRequiredError extends VerdifaxError {
|
|
|
411
498
|
constructor();
|
|
412
499
|
}
|
|
413
500
|
|
|
414
|
-
export { APIError, APIKeyRequiredError, type AdminAPIKey, type AdminCreateKeyResponse, type AdminEraseRequest, type AttestRequest, AuthError, type ClientConfig, type DCAEProofBundle, type DCAEVerdict, type DeletionReceipt, type ExecuteRequest, type ExecuteResponse, type ExecutionManifest, type FormulaEra, type Health, type Hex64, type ListRunsParams, NotFoundError, type PDFFormat, type Run, type RunSummary, VerdifaxClient, VerdifaxError, type VerdifaxVersion, type VerifyResponse, fromEnv, validateHex64, validateRouteId };
|
|
501
|
+
export { APIError, APIKeyRequiredError, type AdminAPIKey, type AdminCreateKeyResponse, type AdminEraseRequest, type AttestRequest, AuthError, type ClientConfig, type DCAEProofBundle, type DCAEVerdict, type DeletionReceipt, type ExecuteRequest, type ExecuteResponse, type ExecutionManifest, type FormulaEra, type Health, type Hex64, type ListRunsParams, NotFoundError, type PDFFormat, type RequestKeyPair, type RequestKeyRegistration, type RequestKeyRegistrationBody, type RequestSignature, type Run, type RunSummary, VerdifaxClient, VerdifaxError, type VerdifaxVersion, type VerifyResponse, fromEnv, generateRequestKeyPair, requestKeyRegistrationBody, requestSignaturePreimage, signRequest, validateHex64, validateRouteId };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { KeyObject } from 'node:crypto';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Type definitions for the Verdifax orchestrator REST API.
|
|
3
5
|
*
|
|
@@ -48,6 +50,37 @@ interface ExecuteRequest {
|
|
|
48
50
|
ai_output_text?: string;
|
|
49
51
|
/** Caller-attested actor + decision metadata; open schema. */
|
|
50
52
|
attested_context?: Record<string, unknown>;
|
|
53
|
+
/** Filled in by the client when the `signingKey` option is set. */
|
|
54
|
+
request_signature?: RequestSignature;
|
|
55
|
+
}
|
|
56
|
+
/** The signature an API key with a registered request key must carry.
|
|
57
|
+
* The public key comes from the registration, never from the request. */
|
|
58
|
+
interface RequestSignature {
|
|
59
|
+
/** Base64 Ed25519 signature. */
|
|
60
|
+
signature: string;
|
|
61
|
+
/** RFC 3339 UTC with whole seconds, for example 2026-11-05T02:18:00Z. */
|
|
62
|
+
signed_at: string;
|
|
63
|
+
/** 16 random bytes as 32 lowercase hex characters, fresh per request. */
|
|
64
|
+
nonce: string;
|
|
65
|
+
}
|
|
66
|
+
/** A key pair from {@link generateRequestKeyPair}, as raw bytes. */
|
|
67
|
+
interface RequestKeyPair {
|
|
68
|
+
/** The 32-byte Ed25519 public key. */
|
|
69
|
+
publicKey: Buffer;
|
|
70
|
+
/** The 32-byte Ed25519 private key. Keep it secret. */
|
|
71
|
+
privateKey: Buffer;
|
|
72
|
+
}
|
|
73
|
+
/** Body of POST /keys/request-key. */
|
|
74
|
+
interface RequestKeyRegistrationBody {
|
|
75
|
+
/** Base64 of the raw 32-byte public key. */
|
|
76
|
+
public_key: string;
|
|
77
|
+
/** Base64 signature proving the caller holds the private key. */
|
|
78
|
+
proof: string;
|
|
79
|
+
}
|
|
80
|
+
/** Returned by {@link VerdifaxClient.registerRequestKey}. */
|
|
81
|
+
interface RequestKeyRegistration {
|
|
82
|
+
ok: boolean;
|
|
83
|
+
public_key: string;
|
|
51
84
|
}
|
|
52
85
|
interface ExecuteResponse {
|
|
53
86
|
ok: boolean;
|
|
@@ -132,6 +165,10 @@ interface ClientConfig {
|
|
|
132
165
|
* it on each subsequent retry (exponential backoff with full
|
|
133
166
|
* jitter). Defaults to 200ms. */
|
|
134
167
|
retryBaseDelayMs?: number;
|
|
168
|
+
/** The 32 raw bytes of an Ed25519 private key. When set, every path to
|
|
169
|
+
* POST /execute signs its request. The key is never sent to the
|
|
170
|
+
* server. Leave unset for unsigned requests. */
|
|
171
|
+
signingKey?: Uint8Array;
|
|
135
172
|
}
|
|
136
173
|
/** Friendly shape for {@link VerdifaxClient.attest}. Mirrors the
|
|
137
174
|
* Python SDK's `attest()` signature so polyglot teams have parity. */
|
|
@@ -258,6 +295,11 @@ interface AdminAPIKey {
|
|
|
258
295
|
* | `adminRevokeKey(id)` | DELETE /admin/keys/{id} | required |
|
|
259
296
|
* | `attestClaudeResponse(...)` | POST /execute (helper) | required |
|
|
260
297
|
* | `attestOpenAIResponse(...)` | POST /execute (helper) | required |
|
|
298
|
+
* | `registerRequestKey()` | POST /keys/request-key | required |
|
|
299
|
+
* | `clearRequestKey()` | DELETE /keys/request-key | required |
|
|
300
|
+
*
|
|
301
|
+
* With the `signingKey` option set, every path to POST /execute carries
|
|
302
|
+
* a fresh request signature. Without it, requests are unsigned.
|
|
261
303
|
*
|
|
262
304
|
* Idempotent GETs that hit a transient failure (502/503/504/429)
|
|
263
305
|
* are retried with exponential backoff + full jitter. Non-
|
|
@@ -271,6 +313,7 @@ declare class VerdifaxClient {
|
|
|
271
313
|
private readonly userAgent;
|
|
272
314
|
private readonly maxRetries;
|
|
273
315
|
private readonly retryBaseDelayMs;
|
|
316
|
+
private readonly signingKey;
|
|
274
317
|
constructor(config?: ClientConfig);
|
|
275
318
|
health(): Promise<Health>;
|
|
276
319
|
version(): Promise<VerdifaxVersion>;
|
|
@@ -280,6 +323,13 @@ declare class VerdifaxClient {
|
|
|
280
323
|
* string (for plain text). Validates the hex fields and
|
|
281
324
|
* routes the call through to the canonical /execute. */
|
|
282
325
|
attest(req: AttestRequest): Promise<ExecuteResponse>;
|
|
326
|
+
/** Registers this client's public key on its API key, with the proof
|
|
327
|
+
* that the client holds the private key. From then on the server
|
|
328
|
+
* refuses a run on this API key without a request signature. */
|
|
329
|
+
registerRequestKey(): Promise<RequestKeyRegistration>;
|
|
330
|
+
/** Clears the registered public key, so the server stops requiring
|
|
331
|
+
* request signatures on this API key. */
|
|
332
|
+
clearRequestKey(): Promise<void>;
|
|
283
333
|
getRun(runId: number): Promise<Run>;
|
|
284
334
|
verifyRun(runId: number): Promise<VerifyResponse>;
|
|
285
335
|
listRuns(params?: ListRunsParams): Promise<{
|
|
@@ -339,6 +389,43 @@ declare class VerdifaxClient {
|
|
|
339
389
|
|
|
340
390
|
declare function fromEnv(): VerdifaxClient;
|
|
341
391
|
|
|
392
|
+
/**
|
|
393
|
+
* Optional customer request signing.
|
|
394
|
+
*
|
|
395
|
+
* A customer can register an Ed25519 public key on an API key. From then
|
|
396
|
+
* on the orchestrator accepts a run only with a signature by the matching
|
|
397
|
+
* private key. The private key stays with the customer: it is never sent
|
|
398
|
+
* to the server, logged, or placed in an error message.
|
|
399
|
+
*/
|
|
400
|
+
|
|
401
|
+
/** Creates a new Ed25519 key pair on this machine, as raw bytes: a
|
|
402
|
+
* 32-byte public key and a 32-byte private key. Nothing is sent to
|
|
403
|
+
* the server. Store the private key as a secret. */
|
|
404
|
+
declare function generateRequestKeyPair(): RequestKeyPair;
|
|
405
|
+
/** The bytes a request signature signs: the fields joined with single
|
|
406
|
+
* zero bytes, matching the orchestrator's RequestSignaturePreimage. */
|
|
407
|
+
declare function requestSignaturePreimage(fields: {
|
|
408
|
+
payload_hash: string;
|
|
409
|
+
program_id: string;
|
|
410
|
+
route_id: string;
|
|
411
|
+
registry_record_hash: string;
|
|
412
|
+
signed_at: string;
|
|
413
|
+
nonce: string;
|
|
414
|
+
}): Buffer;
|
|
415
|
+
/** Signs one execute request. The server hashes payload_text when it is
|
|
416
|
+
* non-empty, otherwise the payload string exactly as sent (it does not
|
|
417
|
+
* base64-decode it), so this does the same.
|
|
418
|
+
*
|
|
419
|
+
* `signedAt` and `nonce` default to the current time and fresh random
|
|
420
|
+
* bytes. A nonce the server has already seen is refused. */
|
|
421
|
+
declare function signRequest(signingKey: Uint8Array | KeyObject, req: ExecuteRequest, options?: {
|
|
422
|
+
signedAt?: string;
|
|
423
|
+
nonce?: string;
|
|
424
|
+
}): RequestSignature;
|
|
425
|
+
/** The body of POST /keys/request-key: the base64 public key and a
|
|
426
|
+
* signature proving the caller holds its private key. */
|
|
427
|
+
declare function requestKeyRegistrationBody(signingKey: Uint8Array | KeyObject): RequestKeyRegistrationBody;
|
|
428
|
+
|
|
342
429
|
/**
|
|
343
430
|
* Input-validation helpers, mirrors the Python SDK's
|
|
344
431
|
* `_validation.py` and the Go SDK's `validation.go` for
|
|
@@ -411,4 +498,4 @@ declare class APIKeyRequiredError extends VerdifaxError {
|
|
|
411
498
|
constructor();
|
|
412
499
|
}
|
|
413
500
|
|
|
414
|
-
export { APIError, APIKeyRequiredError, type AdminAPIKey, type AdminCreateKeyResponse, type AdminEraseRequest, type AttestRequest, AuthError, type ClientConfig, type DCAEProofBundle, type DCAEVerdict, type DeletionReceipt, type ExecuteRequest, type ExecuteResponse, type ExecutionManifest, type FormulaEra, type Health, type Hex64, type ListRunsParams, NotFoundError, type PDFFormat, type Run, type RunSummary, VerdifaxClient, VerdifaxError, type VerdifaxVersion, type VerifyResponse, fromEnv, validateHex64, validateRouteId };
|
|
501
|
+
export { APIError, APIKeyRequiredError, type AdminAPIKey, type AdminCreateKeyResponse, type AdminEraseRequest, type AttestRequest, AuthError, type ClientConfig, type DCAEProofBundle, type DCAEVerdict, type DeletionReceipt, type ExecuteRequest, type ExecuteResponse, type ExecutionManifest, type FormulaEra, type Health, type Hex64, type ListRunsParams, NotFoundError, type PDFFormat, type RequestKeyPair, type RequestKeyRegistration, type RequestKeyRegistrationBody, type RequestSignature, type Run, type RunSummary, VerdifaxClient, VerdifaxError, type VerdifaxVersion, type VerifyResponse, fromEnv, generateRequestKeyPair, requestKeyRegistrationBody, requestSignaturePreimage, signRequest, validateHex64, validateRouteId };
|
package/dist/index.js
CHANGED
|
@@ -27,6 +27,10 @@ __export(index_exports, {
|
|
|
27
27
|
VerdifaxClient: () => VerdifaxClient,
|
|
28
28
|
VerdifaxError: () => VerdifaxError,
|
|
29
29
|
fromEnv: () => fromEnv,
|
|
30
|
+
generateRequestKeyPair: () => generateRequestKeyPair,
|
|
31
|
+
requestKeyRegistrationBody: () => requestKeyRegistrationBody,
|
|
32
|
+
requestSignaturePreimage: () => requestSignaturePreimage,
|
|
33
|
+
signRequest: () => signRequest,
|
|
30
34
|
validateHex64: () => validateHex64,
|
|
31
35
|
validateRouteId: () => validateRouteId
|
|
32
36
|
});
|
|
@@ -75,6 +79,84 @@ var APIKeyRequiredError = class extends VerdifaxError {
|
|
|
75
79
|
}
|
|
76
80
|
};
|
|
77
81
|
|
|
82
|
+
// src/signing.ts
|
|
83
|
+
var import_node_crypto = require("crypto");
|
|
84
|
+
var REQUEST_SIGNATURE_FORM = "verdifax.request.v1";
|
|
85
|
+
var REQUEST_KEY_PROOF_FORM = "verdifax.request-key.v1";
|
|
86
|
+
var SIGNING_KEY_BYTES = 32;
|
|
87
|
+
var PKCS8_ED25519_HEADER = Buffer.from(
|
|
88
|
+
"302e020100300506032b657004220420",
|
|
89
|
+
"hex"
|
|
90
|
+
);
|
|
91
|
+
function rawPublicKey(key) {
|
|
92
|
+
return Buffer.from(key.export({ format: "der", type: "spki" }).subarray(-32));
|
|
93
|
+
}
|
|
94
|
+
function generateRequestKeyPair() {
|
|
95
|
+
const { publicKey, privateKey } = (0, import_node_crypto.generateKeyPairSync)("ed25519");
|
|
96
|
+
return {
|
|
97
|
+
publicKey: rawPublicKey(publicKey),
|
|
98
|
+
privateKey: Buffer.from(
|
|
99
|
+
privateKey.export({ format: "der", type: "pkcs8" }).subarray(-32)
|
|
100
|
+
)
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function loadSigningKey(key) {
|
|
104
|
+
if (key instanceof import_node_crypto.KeyObject) return key;
|
|
105
|
+
if (!(key instanceof Uint8Array) || key.length !== SIGNING_KEY_BYTES) {
|
|
106
|
+
throw new VerdifaxError(
|
|
107
|
+
"verdifax: signingKey must be the 32 raw bytes of an Ed25519 private key"
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
return (0, import_node_crypto.createPrivateKey)({
|
|
111
|
+
key: Buffer.concat([PKCS8_ED25519_HEADER, key]),
|
|
112
|
+
format: "der",
|
|
113
|
+
type: "pkcs8"
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
function requestSignaturePreimage(fields) {
|
|
117
|
+
return Buffer.from(
|
|
118
|
+
[
|
|
119
|
+
REQUEST_SIGNATURE_FORM,
|
|
120
|
+
fields.payload_hash,
|
|
121
|
+
fields.program_id,
|
|
122
|
+
fields.route_id,
|
|
123
|
+
fields.registry_record_hash,
|
|
124
|
+
fields.signed_at,
|
|
125
|
+
fields.nonce
|
|
126
|
+
].join("\0"),
|
|
127
|
+
"utf8"
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
function signRequest(signingKey, req, options = {}) {
|
|
131
|
+
const key = loadSigningKey(signingKey);
|
|
132
|
+
const signed_at = options.signedAt ?? (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d+Z$/, "Z");
|
|
133
|
+
const nonce = options.nonce ?? (0, import_node_crypto.randomBytes)(16).toString("hex");
|
|
134
|
+
const payload_hash = (0, import_node_crypto.createHash)("sha256").update(Buffer.from(req.payload_text || req.payload || "", "utf8")).digest("hex");
|
|
135
|
+
const preimage = requestSignaturePreimage({
|
|
136
|
+
payload_hash,
|
|
137
|
+
program_id: req.program_id,
|
|
138
|
+
route_id: req.route_id,
|
|
139
|
+
registry_record_hash: req.registry_record_hash,
|
|
140
|
+
signed_at,
|
|
141
|
+
nonce
|
|
142
|
+
});
|
|
143
|
+
return {
|
|
144
|
+
signature: (0, import_node_crypto.sign)(null, preimage, key).toString("base64"),
|
|
145
|
+
signed_at,
|
|
146
|
+
nonce
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function requestKeyRegistrationBody(signingKey) {
|
|
150
|
+
const key = loadSigningKey(signingKey);
|
|
151
|
+
const public_key = rawPublicKey((0, import_node_crypto.createPublicKey)(key)).toString("base64");
|
|
152
|
+
const proof = (0, import_node_crypto.sign)(
|
|
153
|
+
null,
|
|
154
|
+
Buffer.from(`${REQUEST_KEY_PROOF_FORM}\0${public_key}`, "utf8"),
|
|
155
|
+
key
|
|
156
|
+
).toString("base64");
|
|
157
|
+
return { public_key, proof };
|
|
158
|
+
}
|
|
159
|
+
|
|
78
160
|
// src/validation.ts
|
|
79
161
|
function validateHex64(value, fieldName) {
|
|
80
162
|
if (typeof value !== "string" || value.length !== 64) {
|
|
@@ -105,7 +187,7 @@ function validateRouteId(value) {
|
|
|
105
187
|
}
|
|
106
188
|
|
|
107
189
|
// src/version.ts
|
|
108
|
-
var SDK_VERSION = "0.
|
|
190
|
+
var SDK_VERSION = "0.3.0";
|
|
109
191
|
|
|
110
192
|
// src/client.ts
|
|
111
193
|
var DEFAULT_BASE_URL = "https://api.verdifax.com";
|
|
@@ -114,6 +196,7 @@ var DEFAULT_MAX_RETRIES = 3;
|
|
|
114
196
|
var DEFAULT_RETRY_BASE_DELAY_MS = 200;
|
|
115
197
|
var SDK_USER_AGENT = `verdifax-sdk-node/${SDK_VERSION}`;
|
|
116
198
|
var HEADER_API_KEY = "X-Verdifax-Key";
|
|
199
|
+
var REQUEST_KEY_PATH = "/keys/request-key";
|
|
117
200
|
var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
|
|
118
201
|
var VerdifaxClient = class {
|
|
119
202
|
baseURL;
|
|
@@ -122,6 +205,7 @@ var VerdifaxClient = class {
|
|
|
122
205
|
userAgent;
|
|
123
206
|
maxRetries;
|
|
124
207
|
retryBaseDelayMs;
|
|
208
|
+
signingKey;
|
|
125
209
|
constructor(config = {}) {
|
|
126
210
|
this.baseURL = (config.baseURL ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
127
211
|
this.apiKey = config.apiKey ?? "";
|
|
@@ -129,6 +213,7 @@ var VerdifaxClient = class {
|
|
|
129
213
|
this.userAgent = config.userAgent ?? SDK_USER_AGENT;
|
|
130
214
|
this.maxRetries = config.maxRetries === void 0 ? DEFAULT_MAX_RETRIES : Math.max(0, config.maxRetries);
|
|
131
215
|
this.retryBaseDelayMs = config.retryBaseDelayMs ?? DEFAULT_RETRY_BASE_DELAY_MS;
|
|
216
|
+
this.signingKey = config.signingKey === void 0 ? void 0 : loadSigningKey(config.signingKey);
|
|
132
217
|
}
|
|
133
218
|
// ── Health + Version (anonymous) ──────────────────────────────
|
|
134
219
|
async health() {
|
|
@@ -150,7 +235,8 @@ var VerdifaxClient = class {
|
|
|
150
235
|
"verdifax: program_id, route_id, and registry_record_hash are required"
|
|
151
236
|
);
|
|
152
237
|
}
|
|
153
|
-
|
|
238
|
+
const body = this.signingKey ? { ...req, request_signature: signRequest(this.signingKey, req) } : req;
|
|
239
|
+
return this.request("POST", "/execute", body);
|
|
154
240
|
}
|
|
155
241
|
/** High-level convenience wrapper around `execute()`. Accepts
|
|
156
242
|
* either a Buffer (for binary payloads, auto-base64) or a
|
|
@@ -185,6 +271,29 @@ var VerdifaxClient = class {
|
|
|
185
271
|
}
|
|
186
272
|
return this.execute(body);
|
|
187
273
|
}
|
|
274
|
+
// ── Request signing (auth) ───────────────────────────────────
|
|
275
|
+
/** Registers this client's public key on its API key, with the proof
|
|
276
|
+
* that the client holds the private key. From then on the server
|
|
277
|
+
* refuses a run on this API key without a request signature. */
|
|
278
|
+
async registerRequestKey() {
|
|
279
|
+
this.requireAPIKey();
|
|
280
|
+
if (!this.signingKey) {
|
|
281
|
+
throw new VerdifaxError(
|
|
282
|
+
"verdifax: registerRequestKey needs the signingKey client option"
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
return this.request(
|
|
286
|
+
"POST",
|
|
287
|
+
REQUEST_KEY_PATH,
|
|
288
|
+
requestKeyRegistrationBody(this.signingKey)
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
/** Clears the registered public key, so the server stops requiring
|
|
292
|
+
* request signatures on this API key. */
|
|
293
|
+
async clearRequestKey() {
|
|
294
|
+
this.requireAPIKey();
|
|
295
|
+
await this.request("DELETE", REQUEST_KEY_PATH);
|
|
296
|
+
}
|
|
188
297
|
// ── Runs ──────────────────────────────────────────────────────
|
|
189
298
|
async getRun(runId) {
|
|
190
299
|
this.requireAPIKey();
|
|
@@ -459,6 +568,10 @@ function fromEnv() {
|
|
|
459
568
|
VerdifaxClient,
|
|
460
569
|
VerdifaxError,
|
|
461
570
|
fromEnv,
|
|
571
|
+
generateRequestKeyPair,
|
|
572
|
+
requestKeyRegistrationBody,
|
|
573
|
+
requestSignaturePreimage,
|
|
574
|
+
signRequest,
|
|
462
575
|
validateHex64,
|
|
463
576
|
validateRouteId
|
|
464
577
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -41,6 +41,92 @@ var APIKeyRequiredError = class extends VerdifaxError {
|
|
|
41
41
|
}
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
+
// src/signing.ts
|
|
45
|
+
import {
|
|
46
|
+
createHash,
|
|
47
|
+
createPrivateKey,
|
|
48
|
+
createPublicKey,
|
|
49
|
+
generateKeyPairSync,
|
|
50
|
+
KeyObject,
|
|
51
|
+
randomBytes,
|
|
52
|
+
sign
|
|
53
|
+
} from "crypto";
|
|
54
|
+
var REQUEST_SIGNATURE_FORM = "verdifax.request.v1";
|
|
55
|
+
var REQUEST_KEY_PROOF_FORM = "verdifax.request-key.v1";
|
|
56
|
+
var SIGNING_KEY_BYTES = 32;
|
|
57
|
+
var PKCS8_ED25519_HEADER = Buffer.from(
|
|
58
|
+
"302e020100300506032b657004220420",
|
|
59
|
+
"hex"
|
|
60
|
+
);
|
|
61
|
+
function rawPublicKey(key) {
|
|
62
|
+
return Buffer.from(key.export({ format: "der", type: "spki" }).subarray(-32));
|
|
63
|
+
}
|
|
64
|
+
function generateRequestKeyPair() {
|
|
65
|
+
const { publicKey, privateKey } = generateKeyPairSync("ed25519");
|
|
66
|
+
return {
|
|
67
|
+
publicKey: rawPublicKey(publicKey),
|
|
68
|
+
privateKey: Buffer.from(
|
|
69
|
+
privateKey.export({ format: "der", type: "pkcs8" }).subarray(-32)
|
|
70
|
+
)
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function loadSigningKey(key) {
|
|
74
|
+
if (key instanceof KeyObject) return key;
|
|
75
|
+
if (!(key instanceof Uint8Array) || key.length !== SIGNING_KEY_BYTES) {
|
|
76
|
+
throw new VerdifaxError(
|
|
77
|
+
"verdifax: signingKey must be the 32 raw bytes of an Ed25519 private key"
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
return createPrivateKey({
|
|
81
|
+
key: Buffer.concat([PKCS8_ED25519_HEADER, key]),
|
|
82
|
+
format: "der",
|
|
83
|
+
type: "pkcs8"
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function requestSignaturePreimage(fields) {
|
|
87
|
+
return Buffer.from(
|
|
88
|
+
[
|
|
89
|
+
REQUEST_SIGNATURE_FORM,
|
|
90
|
+
fields.payload_hash,
|
|
91
|
+
fields.program_id,
|
|
92
|
+
fields.route_id,
|
|
93
|
+
fields.registry_record_hash,
|
|
94
|
+
fields.signed_at,
|
|
95
|
+
fields.nonce
|
|
96
|
+
].join("\0"),
|
|
97
|
+
"utf8"
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
function signRequest(signingKey, req, options = {}) {
|
|
101
|
+
const key = loadSigningKey(signingKey);
|
|
102
|
+
const signed_at = options.signedAt ?? (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d+Z$/, "Z");
|
|
103
|
+
const nonce = options.nonce ?? randomBytes(16).toString("hex");
|
|
104
|
+
const payload_hash = createHash("sha256").update(Buffer.from(req.payload_text || req.payload || "", "utf8")).digest("hex");
|
|
105
|
+
const preimage = requestSignaturePreimage({
|
|
106
|
+
payload_hash,
|
|
107
|
+
program_id: req.program_id,
|
|
108
|
+
route_id: req.route_id,
|
|
109
|
+
registry_record_hash: req.registry_record_hash,
|
|
110
|
+
signed_at,
|
|
111
|
+
nonce
|
|
112
|
+
});
|
|
113
|
+
return {
|
|
114
|
+
signature: sign(null, preimage, key).toString("base64"),
|
|
115
|
+
signed_at,
|
|
116
|
+
nonce
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function requestKeyRegistrationBody(signingKey) {
|
|
120
|
+
const key = loadSigningKey(signingKey);
|
|
121
|
+
const public_key = rawPublicKey(createPublicKey(key)).toString("base64");
|
|
122
|
+
const proof = sign(
|
|
123
|
+
null,
|
|
124
|
+
Buffer.from(`${REQUEST_KEY_PROOF_FORM}\0${public_key}`, "utf8"),
|
|
125
|
+
key
|
|
126
|
+
).toString("base64");
|
|
127
|
+
return { public_key, proof };
|
|
128
|
+
}
|
|
129
|
+
|
|
44
130
|
// src/validation.ts
|
|
45
131
|
function validateHex64(value, fieldName) {
|
|
46
132
|
if (typeof value !== "string" || value.length !== 64) {
|
|
@@ -71,7 +157,7 @@ function validateRouteId(value) {
|
|
|
71
157
|
}
|
|
72
158
|
|
|
73
159
|
// src/version.ts
|
|
74
|
-
var SDK_VERSION = "0.
|
|
160
|
+
var SDK_VERSION = "0.3.0";
|
|
75
161
|
|
|
76
162
|
// src/client.ts
|
|
77
163
|
var DEFAULT_BASE_URL = "https://api.verdifax.com";
|
|
@@ -80,6 +166,7 @@ var DEFAULT_MAX_RETRIES = 3;
|
|
|
80
166
|
var DEFAULT_RETRY_BASE_DELAY_MS = 200;
|
|
81
167
|
var SDK_USER_AGENT = `verdifax-sdk-node/${SDK_VERSION}`;
|
|
82
168
|
var HEADER_API_KEY = "X-Verdifax-Key";
|
|
169
|
+
var REQUEST_KEY_PATH = "/keys/request-key";
|
|
83
170
|
var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
|
|
84
171
|
var VerdifaxClient = class {
|
|
85
172
|
baseURL;
|
|
@@ -88,6 +175,7 @@ var VerdifaxClient = class {
|
|
|
88
175
|
userAgent;
|
|
89
176
|
maxRetries;
|
|
90
177
|
retryBaseDelayMs;
|
|
178
|
+
signingKey;
|
|
91
179
|
constructor(config = {}) {
|
|
92
180
|
this.baseURL = (config.baseURL ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
93
181
|
this.apiKey = config.apiKey ?? "";
|
|
@@ -95,6 +183,7 @@ var VerdifaxClient = class {
|
|
|
95
183
|
this.userAgent = config.userAgent ?? SDK_USER_AGENT;
|
|
96
184
|
this.maxRetries = config.maxRetries === void 0 ? DEFAULT_MAX_RETRIES : Math.max(0, config.maxRetries);
|
|
97
185
|
this.retryBaseDelayMs = config.retryBaseDelayMs ?? DEFAULT_RETRY_BASE_DELAY_MS;
|
|
186
|
+
this.signingKey = config.signingKey === void 0 ? void 0 : loadSigningKey(config.signingKey);
|
|
98
187
|
}
|
|
99
188
|
// ── Health + Version (anonymous) ──────────────────────────────
|
|
100
189
|
async health() {
|
|
@@ -116,7 +205,8 @@ var VerdifaxClient = class {
|
|
|
116
205
|
"verdifax: program_id, route_id, and registry_record_hash are required"
|
|
117
206
|
);
|
|
118
207
|
}
|
|
119
|
-
|
|
208
|
+
const body = this.signingKey ? { ...req, request_signature: signRequest(this.signingKey, req) } : req;
|
|
209
|
+
return this.request("POST", "/execute", body);
|
|
120
210
|
}
|
|
121
211
|
/** High-level convenience wrapper around `execute()`. Accepts
|
|
122
212
|
* either a Buffer (for binary payloads, auto-base64) or a
|
|
@@ -151,6 +241,29 @@ var VerdifaxClient = class {
|
|
|
151
241
|
}
|
|
152
242
|
return this.execute(body);
|
|
153
243
|
}
|
|
244
|
+
// ── Request signing (auth) ───────────────────────────────────
|
|
245
|
+
/** Registers this client's public key on its API key, with the proof
|
|
246
|
+
* that the client holds the private key. From then on the server
|
|
247
|
+
* refuses a run on this API key without a request signature. */
|
|
248
|
+
async registerRequestKey() {
|
|
249
|
+
this.requireAPIKey();
|
|
250
|
+
if (!this.signingKey) {
|
|
251
|
+
throw new VerdifaxError(
|
|
252
|
+
"verdifax: registerRequestKey needs the signingKey client option"
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
return this.request(
|
|
256
|
+
"POST",
|
|
257
|
+
REQUEST_KEY_PATH,
|
|
258
|
+
requestKeyRegistrationBody(this.signingKey)
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
/** Clears the registered public key, so the server stops requiring
|
|
262
|
+
* request signatures on this API key. */
|
|
263
|
+
async clearRequestKey() {
|
|
264
|
+
this.requireAPIKey();
|
|
265
|
+
await this.request("DELETE", REQUEST_KEY_PATH);
|
|
266
|
+
}
|
|
154
267
|
// ── Runs ──────────────────────────────────────────────────────
|
|
155
268
|
async getRun(runId) {
|
|
156
269
|
this.requireAPIKey();
|
|
@@ -424,6 +537,10 @@ export {
|
|
|
424
537
|
VerdifaxClient,
|
|
425
538
|
VerdifaxError,
|
|
426
539
|
fromEnv,
|
|
540
|
+
generateRequestKeyPair,
|
|
541
|
+
requestKeyRegistrationBody,
|
|
542
|
+
requestSignaturePreimage,
|
|
543
|
+
signRequest,
|
|
427
544
|
validateHex64,
|
|
428
545
|
validateRouteId
|
|
429
546
|
};
|