verdifax 1.3.0 → 1.4.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 +58 -1
- package/dist/index.d.mts +207 -1
- package/dist/index.d.ts +207 -1
- package/dist/index.js +296 -3
- package/dist/index.mjs +294 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -105,6 +105,13 @@ if (v.verified) {
|
|
|
105
105
|
| `client.testAccountPolicy({ inputs, document?, documentHash? })` | `POST /account/policies/test` | owner key |
|
|
106
106
|
| `client.getAccountPolicyHistory()` | `GET /account/policies/history` | owner key |
|
|
107
107
|
| `client.activateAccountPolicy(documentHash)` | `POST /account/policies/{document_hash}/activate` | owner key |
|
|
108
|
+
| `client.registerApprover(name, approver)` | `POST /account/approvers` | owner key |
|
|
109
|
+
| `client.listApprovers()` | `GET /account/approvers` | owner key |
|
|
110
|
+
| `client.revokeApprover(id)` | `DELETE /account/approvers/{id}` | owner key |
|
|
111
|
+
| `client.getSignOffHistory()` | `GET /account/signoffs/history` | owner key |
|
|
112
|
+
| `client.getNewestSignOff({ asOwner? })` | `GET /signoffs/newest`, or `GET /account/signoffs/newest` with `asOwner` | required, or owner key |
|
|
113
|
+
| `client.signOff(target, decision, approverKey, { note?, asOwner? })` | `POST /signoffs`, or `POST /account/signoffs` with `asOwner` | required, or owner key |
|
|
114
|
+
| `client.getSignOffs(runId, { asOwner? })` | `GET /runs/{id}/signoffs`, or `GET /account/runs/{id}/signoffs` with `asOwner` | required, or owner key |
|
|
108
115
|
| `client.adminCreateKey(name, { accountId? })` | `POST /admin/keys` | admin |
|
|
109
116
|
| `client.adminListKeys()` | `GET /admin/keys` | admin |
|
|
110
117
|
| `client.adminRevokeKey(id)` | `DELETE /admin/keys/{id}` | admin |
|
|
@@ -115,7 +122,7 @@ if (v.verified) {
|
|
|
115
122
|
|
|
116
123
|
`client.job(jobId)` returns a [job helper](#jobs) and makes no request until a step runs.
|
|
117
124
|
|
|
118
|
-
These functions make no request: `captureEnvironment`, `formatProviderPayload`, `signRequest`, `signRequestV2`, `requestKeyRegistrationBody`, `requestKeyChangeApproval`, `requestKeyChangePreimage`, `policyActivationApproval`, `policyActivationPreimage`, `requestSignaturePreimage`, `requestSignaturePreimageV2`, `generateRequestKeyPair`, `validateHex64`, `validateJobName`, `validateRouteId`, `dseDispatchHash`, `dcaeClosureHash` and `cresReceiptHash`. `RUN_STATUSES` lists every run status the server uses, and `EXECUTE_FIELDS` lists every top-level key a run request may hold.
|
|
125
|
+
These functions make no request: `captureEnvironment`, `formatProviderPayload`, `signRequest`, `signRequestV2`, `requestKeyRegistrationBody`, `requestKeyChangeApproval`, `requestKeyChangePreimage`, `policyActivationApproval`, `policyActivationPreimage`, `signOffPreimage`, `approverKeyProof`, `requestSignaturePreimage`, `requestSignaturePreimageV2`, `generateRequestKeyPair`, `validateHex64`, `validateJobName`, `validateRouteId`, `dseDispatchHash`, `dcaeClosureHash` and `cresReceiptHash`. `RUN_STATUSES` lists every run status the server uses, and `EXECUTE_FIELDS` lists every top-level key a run request may hold.
|
|
119
126
|
|
|
120
127
|
`getRunArtifacts`, `getPublicBundle` and `getPublicBundleByToken` return the exact JSON text the server sent, so you can save it and check it with `verdifax-verify`.
|
|
121
128
|
|
|
@@ -293,6 +300,56 @@ The account's view covers the runs its keys made through Verdifax. It does not c
|
|
|
293
300
|
|
|
294
301
|
`exportAccountRuns` and `exportAccountRunsCSV` take the same period and `job_id` as `exportRuns` and check the CSV counts the same way. The JSON form also carries `account_id`, and `account_policy_documents` and `account_policy_history` once the account has loaded a policy. `listAccountRuns` pages like `listRuns`. `revokeAccountKey` revokes a member key, and a key in another account, or an owner key, throws `NotFoundError`. `adminCreateKey(name, { accountId })` puts a new customer key in an existing account, by its number.
|
|
295
302
|
|
|
303
|
+
## Sign-off
|
|
304
|
+
|
|
305
|
+
A sign-off is an approver's decision, approve or reject, on one exact result: a completed run's record, a refused or halted run's receipt, or a whole job by its final step. The approver signs it on their own machine with an approver key, and Verdifax links it to the account's previous sign-off and logs it on the public log.
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
import { writeFileSync } from "node:fs";
|
|
309
|
+
import { VerdifaxClient, approverKeyProof, generateRequestKeyPair } from "verdifax";
|
|
310
|
+
|
|
311
|
+
// The approver creates a key on their own machine and keeps the private key.
|
|
312
|
+
const approverKey = generateRequestKeyPair().privateKey;
|
|
313
|
+
const proof = approverKeyProof(approverKey); // { public_key, proof }, safe to hand to the owner
|
|
314
|
+
|
|
315
|
+
// The account's owner registers the approver key.
|
|
316
|
+
const owner = new VerdifaxClient({ apiKey: process.env.VERDIFAX_OWNER_KEY! });
|
|
317
|
+
await owner.registerApprover("credit review", proof);
|
|
318
|
+
|
|
319
|
+
// Any key in the account submits the approver's signed decision on one exact result.
|
|
320
|
+
const member = new VerdifaxClient({ apiKey: process.env.VERDIFAX_API_KEY! });
|
|
321
|
+
const bundle = JSON.parse(await member.getRunArtifacts(1042));
|
|
322
|
+
const signOff = await member.signOff(
|
|
323
|
+
{ kind: "record", run_id: 1042, digest: bundle.bundle_anchor.anchored_digest },
|
|
324
|
+
"approve",
|
|
325
|
+
approverKey,
|
|
326
|
+
{ note: "Checked against the loan file." }, // only its SHA-256 is sent
|
|
327
|
+
);
|
|
328
|
+
console.log(signOff.sequence, signOff.signoff_hash);
|
|
329
|
+
|
|
330
|
+
// Save the run's sign-offs and check them with verdifax-verify --signoff signoffs.json.
|
|
331
|
+
writeFileSync("signoffs.json", JSON.stringify(await member.getSignOffs(1042)));
|
|
332
|
+
|
|
333
|
+
// The owner saves the account's whole chain for verdifax-verify --signoff-history.
|
|
334
|
+
writeFileSync("signoff-history.json", JSON.stringify(await owner.getSignOffHistory()));
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
An approver key is registered by the account's owner, and it is never a request key in the same account. The server refuses to register a key that is, so the key that requested a run cannot sign it off. `registerApprover` takes the approver's signing key, from which it makes the proof on this machine, or the `{ public_key, proof }` that `approverKeyProof` made on the approver's own machine, so the owner need not hold the approver's private key. It refuses a proof that does not verify before sending. `revokeApprover` revokes a key, and its earlier sign-offs stay on the record. `listApprovers` lists revoked keys too.
|
|
338
|
+
|
|
339
|
+
`signOff` reads `getNewestSignOff` first for the account's chain, `account-N`, and its newest sign-off, then signs over them, the target, the decision, the note's hash, the time and a fresh nonce. The approver's private key is never sent. The target's `digest` is what the result's public-log entry covers: for a record, the `bundle_anchor.anchored_digest` of the bundle `getRunArtifacts` returns; for a receipt, its hash; for a job, the final step's record digest, read the same way as a record's, with `job_id`. The note is hashed on your machine and only its SHA-256 is sent and signed: the note's text never reaches Verdifax, so keep it yourself. `signOff` refuses a bad target, decision, key or note before sending anything. It also checks that the record the server returns carries the signature, `approver_key`, `decision`, `previous_signoff_hash` and `target` that were sent, and throws `VerdifaxError` if not. An HTTP 409 means another sign-off came first or the target is not this account's result: it throws the usual `APIError` and is not retried.
|
|
340
|
+
|
|
341
|
+
With `asOwner: true`, `signOff`, `getNewestSignOff` and `getSignOffs` use the owner key's `/account` routes. `getSignOffs` returns a run's sign-offs oldest first, in the form `verdifax-verify --signoff FILE` reads: for a member key, a run it made; with `asOwner`, any run of the account. `getSignOffHistory` returns the account's whole chain, oldest first, in the form `verdifax-verify --signoff-history FILE` reads, and `exportAccountRuns` carries the same chain as `signoffs`. The SDK returns `signoffs` as the server sent it, without checking it: check it with `verdifax-verify --signoff-history`.
|
|
342
|
+
|
|
343
|
+
A sign-off shows that the holder of a registered approver key signed that decision on that exact result, and that Verdifax logged it on the public log. It does not show that the review was careful, that the result is right, or who the person is. A key is not an identity.
|
|
344
|
+
|
|
345
|
+
A sign-off gates nothing on its own.
|
|
346
|
+
|
|
347
|
+
A later sign-off on the same result supersedes the earlier one, and nothing is deleted.
|
|
348
|
+
|
|
349
|
+
Verdifax did not review the result.
|
|
350
|
+
|
|
351
|
+
Since v0.22.0, the verifier reads sign-offs with `--signoff` and `--signoff-history`.
|
|
352
|
+
|
|
296
353
|
## Evidence export
|
|
297
354
|
|
|
298
355
|
`exportRuns` lists the runs the server holds for your key in a period, failures included, with up to 10,000 rows per export. `exportRunsCSV` returns the same rows as raw CSV text.
|
package/dist/index.d.mts
CHANGED
|
@@ -569,6 +569,11 @@ interface RunsExport {
|
|
|
569
569
|
* key in the account, with the account's policy documents and changes. */
|
|
570
570
|
interface AccountRunsExport extends RunsExport {
|
|
571
571
|
account_id: string;
|
|
572
|
+
/** The account's whole chain of sign-offs, oldest first, as
|
|
573
|
+
* {@link VerdifaxClient.getSignOffHistory} returns it. Only the owner
|
|
574
|
+
* key's export carries it, and the SDK returns it as the server sent
|
|
575
|
+
* it, without checking it: check it with `verdifax-verify`. */
|
|
576
|
+
signoffs?: SignOffRecord[];
|
|
572
577
|
}
|
|
573
578
|
/** Returned by {@link VerdifaxClient.exportRunsCSV}. */
|
|
574
579
|
interface RunsExportCSV {
|
|
@@ -755,6 +760,127 @@ interface AdminAPIKey {
|
|
|
755
760
|
run_count: number;
|
|
756
761
|
revoked: boolean;
|
|
757
762
|
}
|
|
763
|
+
/** The kind of result a sign-off names: a completed run's record, a
|
|
764
|
+
* refused or halted run's receipt, or a whole job by its final step. */
|
|
765
|
+
type SignOffTargetKind = "record" | "receipt" | "job";
|
|
766
|
+
/** An approver's decision on one result. */
|
|
767
|
+
type SignOffDecision = "approve" | "reject";
|
|
768
|
+
/** The exact result a sign-off names, by what its public-log entry
|
|
769
|
+
* covers. */
|
|
770
|
+
interface SignOffTarget {
|
|
771
|
+
kind: SignOffTargetKind;
|
|
772
|
+
/** The run: for a job, the run of its final step. */
|
|
773
|
+
run_id: number;
|
|
774
|
+
/** What the result's public-log entry covers. For a record, its
|
|
775
|
+
* `bundle_anchor.anchored_digest` in the bundle
|
|
776
|
+
* {@link VerdifaxClient.getRunArtifacts} returns. For a receipt, its
|
|
777
|
+
* hash. For a job, the final step's record digest, read the same way
|
|
778
|
+
* as a record's. 64 lowercase hex characters. */
|
|
779
|
+
digest: Hex64;
|
|
780
|
+
/** The job, for a job target only. */
|
|
781
|
+
job_id?: string;
|
|
782
|
+
}
|
|
783
|
+
/** One sign-off, verdifax.signoff.v1, as the server records it and
|
|
784
|
+
* `verdifax-verify --signoff` and `--signoff-history` read it. */
|
|
785
|
+
interface SignOffRecord {
|
|
786
|
+
format: string;
|
|
787
|
+
/** The account's chain, `account-N`. */
|
|
788
|
+
chain_id: string;
|
|
789
|
+
sequence: number;
|
|
790
|
+
/** The sign-off this one follows, or 64 zeros for the account's first. */
|
|
791
|
+
previous_signoff_hash: Hex64;
|
|
792
|
+
target: SignOffTarget;
|
|
793
|
+
decision: SignOffDecision;
|
|
794
|
+
/** SHA-256 of the approver's note, when there was one. The note's text
|
|
795
|
+
* never reaches Verdifax. */
|
|
796
|
+
note_hash?: string;
|
|
797
|
+
/** The base64 Ed25519 public key of the approver who signed. */
|
|
798
|
+
approver_key: string;
|
|
799
|
+
/** Base64 Ed25519 signature by the approver key. */
|
|
800
|
+
signature: string;
|
|
801
|
+
signed_at: string;
|
|
802
|
+
nonce: string;
|
|
803
|
+
recorded_at: string;
|
|
804
|
+
signoff_hash: Hex64;
|
|
805
|
+
/** Where the sign-off was logged on the public log. */
|
|
806
|
+
anchor?: Record<string, unknown>;
|
|
807
|
+
}
|
|
808
|
+
/** What an approver signs against: the account's chain and its newest
|
|
809
|
+
* sign-off. Returned by {@link VerdifaxClient.getNewestSignOff}. */
|
|
810
|
+
interface NewestSignOff {
|
|
811
|
+
ok: boolean;
|
|
812
|
+
/** The account's chain, `account-N`. */
|
|
813
|
+
chain_id: string;
|
|
814
|
+
/** The newest sign-off's hash, or 64 zeros when there is none yet. */
|
|
815
|
+
newest_signoff_hash: Hex64;
|
|
816
|
+
/** The newest sign-off's place in the chain, 0 when there is none. */
|
|
817
|
+
sequence: number;
|
|
818
|
+
}
|
|
819
|
+
/** The approver key's part of a sign-off. */
|
|
820
|
+
interface SignOffSignature {
|
|
821
|
+
/** The base64 Ed25519 public key of the approver. */
|
|
822
|
+
approver_key: string;
|
|
823
|
+
/** Base64 Ed25519 signature by the approver key. */
|
|
824
|
+
signature: string;
|
|
825
|
+
/** RFC 3339 UTC with whole seconds. */
|
|
826
|
+
signed_at: string;
|
|
827
|
+
/** 16 random bytes as 32 lowercase hex characters, fresh per sign-off. */
|
|
828
|
+
nonce: string;
|
|
829
|
+
}
|
|
830
|
+
/** Options for {@link VerdifaxClient.signOff}. */
|
|
831
|
+
interface SignOffOptions {
|
|
832
|
+
/** The approver's note. It is hashed on this machine and only its
|
|
833
|
+
* SHA-256 is sent and signed: the text never reaches Verdifax. Keep the
|
|
834
|
+
* text yourself to show it matches the hash. */
|
|
835
|
+
note?: string;
|
|
836
|
+
/** Use the owner key's routes, /account/signoffs/newest and
|
|
837
|
+
* /account/signoffs, instead of a member key's. */
|
|
838
|
+
asOwner?: boolean;
|
|
839
|
+
}
|
|
840
|
+
/** For a method that serves both a member key's route and the owner
|
|
841
|
+
* key's /account route. */
|
|
842
|
+
interface AsOwnerOptions {
|
|
843
|
+
/** Use the owner key's /account route. */
|
|
844
|
+
asOwner?: boolean;
|
|
845
|
+
}
|
|
846
|
+
/** An approver key's public key and the proof that its holder has the
|
|
847
|
+
* private key, from {@link approverKeyProof}. */
|
|
848
|
+
interface ApproverKeyProof {
|
|
849
|
+
/** Base64 of the raw 32-byte public key. */
|
|
850
|
+
public_key: string;
|
|
851
|
+
/** Base64 Ed25519 signature over verdifax.approver-key.v1, a zero byte
|
|
852
|
+
* and the base64 public key. */
|
|
853
|
+
proof: string;
|
|
854
|
+
}
|
|
855
|
+
/** An approver key registered to an account. A key is not an identity:
|
|
856
|
+
* `name` is the label the owner gave it. */
|
|
857
|
+
interface Approver {
|
|
858
|
+
id: number;
|
|
859
|
+
public_key: string;
|
|
860
|
+
name: string;
|
|
861
|
+
/** The account, `account-N`. */
|
|
862
|
+
account_id: string;
|
|
863
|
+
registered_at: string;
|
|
864
|
+
revoked: boolean;
|
|
865
|
+
revoked_at?: string;
|
|
866
|
+
}
|
|
867
|
+
/** Returned by {@link VerdifaxClient.registerApprover}. */
|
|
868
|
+
interface ApproverRegistration extends Approver {
|
|
869
|
+
ok: boolean;
|
|
870
|
+
}
|
|
871
|
+
/** Returned by {@link VerdifaxClient.listApprovers}: revoked keys
|
|
872
|
+
* included. */
|
|
873
|
+
interface ApproverList {
|
|
874
|
+
ok: boolean;
|
|
875
|
+
account_id: string;
|
|
876
|
+
approvers: Approver[];
|
|
877
|
+
}
|
|
878
|
+
/** Returned by {@link VerdifaxClient.revokeApprover}. */
|
|
879
|
+
interface ApproverRevocation {
|
|
880
|
+
ok: boolean;
|
|
881
|
+
id: number;
|
|
882
|
+
revoked: boolean;
|
|
883
|
+
}
|
|
758
884
|
|
|
759
885
|
/**
|
|
760
886
|
* The job helper: runs the steps of one job and keeps the map of step ids
|
|
@@ -869,6 +995,13 @@ declare const MAX_BATCH_ROWS = 1000;
|
|
|
869
995
|
* | `testAccountPolicy(req)` | POST /account/policies/test | owner key |
|
|
870
996
|
* | `getAccountPolicyHistory()` | GET /account/policies/history | owner key |
|
|
871
997
|
* | `activateAccountPolicy(documentHash)` | POST /account/policies/{document_hash}/activate | owner key |
|
|
998
|
+
* | `registerApprover(name, approver)` | POST /account/approvers | owner key |
|
|
999
|
+
* | `listApprovers()` | GET /account/approvers | owner key |
|
|
1000
|
+
* | `revokeApprover(id)` | DELETE /account/approvers/{id} | owner key |
|
|
1001
|
+
* | `signOff(target, decision, approverKey, options)` | POST /signoffs, or POST /account/signoffs with asOwner | required |
|
|
1002
|
+
* | `getNewestSignOff(options)` | GET /signoffs/newest, or GET /account/signoffs/newest with asOwner | required |
|
|
1003
|
+
* | `getSignOffs(runId, options)` | GET /runs/{id}/signoffs, or GET /account/runs/{id}/signoffs with asOwner | required |
|
|
1004
|
+
* | `getSignOffHistory()` | GET /account/signoffs/history | owner key |
|
|
872
1005
|
* | `job(jobId)` | none, returns a {@link VerdifaxJob} | none |
|
|
873
1006
|
*
|
|
874
1007
|
* With the `signingKey` option set, every path to POST /execute carries
|
|
@@ -1037,6 +1170,56 @@ declare class VerdifaxClient {
|
|
|
1037
1170
|
* `account-N`. The server refuses an unsigned account change, so this
|
|
1038
1171
|
* needs the `signingKey` option and refuses to send without it. */
|
|
1039
1172
|
activateAccountPolicy(documentHash: Hex64): Promise<PolicyActivation>;
|
|
1173
|
+
/** Registers an approver key to the owner's account. `approver` is the
|
|
1174
|
+
* approver's signing key, from which the proof is made on this machine,
|
|
1175
|
+
* or the `{ public_key, proof }` that {@link approverKeyProof} made on
|
|
1176
|
+
* the approver's own machine, so the owner need not hold the private
|
|
1177
|
+
* key. A proof that does not verify is refused before sending. The
|
|
1178
|
+
* server refuses a key that is a request key in the same account.
|
|
1179
|
+
* `name` is a label, not an identity. */
|
|
1180
|
+
registerApprover(name: string, approver: Uint8Array | KeyObject | ApproverKeyProof): Promise<ApproverRegistration>;
|
|
1181
|
+
/** The account's approver keys, revoked ones included. */
|
|
1182
|
+
listApprovers(): Promise<ApproverList>;
|
|
1183
|
+
/** Revokes one of the account's approver keys. Its earlier sign-offs
|
|
1184
|
+
* stay on the record. A key that is not an active approver in the
|
|
1185
|
+
* account throws {@link NotFoundError}. */
|
|
1186
|
+
revokeApprover(approverId: number): Promise<ApproverRevocation>;
|
|
1187
|
+
/** What an approver signs against: the account's chain `account-N` and
|
|
1188
|
+
* its newest sign-off (64 zeros when there is none yet). With
|
|
1189
|
+
* `asOwner` it reads the owner key's route. */
|
|
1190
|
+
getNewestSignOff(options?: AsOwnerOptions): Promise<NewestSignOff>;
|
|
1191
|
+
/** Signs an approver's decision, approve or reject, on one exact result
|
|
1192
|
+
* and submits it. It first reads {@link getNewestSignOff} for the
|
|
1193
|
+
* account's chain and its newest sign-off, then signs over them with
|
|
1194
|
+
* `approverKey` on this machine. The approver's private key is never
|
|
1195
|
+
* sent.
|
|
1196
|
+
*
|
|
1197
|
+
* `target.digest` is what the result's public-log entry covers: for a
|
|
1198
|
+
* record, the `bundle_anchor.anchored_digest` of the bundle
|
|
1199
|
+
* {@link getRunArtifacts} returns; for a receipt, its hash; for a job,
|
|
1200
|
+
* the final step's record digest, read as a record's, with `job_id`.
|
|
1201
|
+
*
|
|
1202
|
+
* `note` is hashed here and only its SHA-256 is sent and signed: the
|
|
1203
|
+
* text never reaches Verdifax. With `asOwner` it uses the owner key's
|
|
1204
|
+
* routes. Bad input is refused before anything is sent.
|
|
1205
|
+
*
|
|
1206
|
+
* The record the server returns must carry the signature,
|
|
1207
|
+
* approver_key, decision, previous_signoff_hash and target that were
|
|
1208
|
+
* sent, or this throws {@link VerdifaxError}.
|
|
1209
|
+
*
|
|
1210
|
+
* A later sign-off on the same result supersedes this one, and neither
|
|
1211
|
+
* is deleted. An HTTP 409 means the chain moved or the target is not
|
|
1212
|
+
* this account's result: it throws the usual {@link APIError} and is
|
|
1213
|
+
* not retried. */
|
|
1214
|
+
signOff(target: SignOffTarget, decision: SignOffDecision, approverKey: Uint8Array | KeyObject, options?: SignOffOptions): Promise<SignOffRecord>;
|
|
1215
|
+
/** The sign-offs on one run, oldest first, in the form
|
|
1216
|
+
* `verdifax-verify --signoff FILE` reads: for a member key, a run it
|
|
1217
|
+
* made; with `asOwner`, any run of the account. The latest is the
|
|
1218
|
+
* decision in force. */
|
|
1219
|
+
getSignOffs(runId: number, options?: AsOwnerOptions): Promise<SignOffRecord[]>;
|
|
1220
|
+
/** The account's whole chain of sign-offs, oldest first, in the form
|
|
1221
|
+
* `verdifax-verify --signoff-history FILE` reads. Needs the owner key. */
|
|
1222
|
+
getSignOffHistory(): Promise<SignOffRecord[]>;
|
|
1040
1223
|
getRun(runId: number): Promise<Run>;
|
|
1041
1224
|
verifyRun(runId: number): Promise<VerifyResponse>;
|
|
1042
1225
|
listRuns(params?: ListRunsParams): Promise<{
|
|
@@ -1349,6 +1532,29 @@ declare function policyActivationApproval(signingKey: Uint8Array | KeyObject, fi
|
|
|
1349
1532
|
signedAt?: string;
|
|
1350
1533
|
nonce?: string;
|
|
1351
1534
|
}): PolicyActivationSignature;
|
|
1535
|
+
/** The bytes an approver key signs for one sign-off, matching the
|
|
1536
|
+
* orchestrator's SignOffPreimage: the fields joined with single zero
|
|
1537
|
+
* bytes. `chain_id` and `previous_signoff_hash` come from GET
|
|
1538
|
+
* /signoffs/newest (or /account/signoffs/newest for the owner key), so a
|
|
1539
|
+
* signature cannot be moved to another account, another result or
|
|
1540
|
+
* another point in the chain. `target.job_id` is empty unless the target
|
|
1541
|
+
* is a job, and `note_hash` is empty when there is no note. */
|
|
1542
|
+
declare function signOffPreimage(fields: {
|
|
1543
|
+
chain_id: string;
|
|
1544
|
+
previous_signoff_hash: string;
|
|
1545
|
+
target: SignOffTarget;
|
|
1546
|
+
decision: SignOffDecision;
|
|
1547
|
+
note_hash: string;
|
|
1548
|
+
signed_at: string;
|
|
1549
|
+
nonce: string;
|
|
1550
|
+
}): Buffer;
|
|
1551
|
+
/** The approver key's base64 public key and its proof: its signature over
|
|
1552
|
+
* verdifax.approver-key.v1, a zero byte and the base64 public key, which
|
|
1553
|
+
* shows its holder has the private key. The approver can run this on
|
|
1554
|
+
* their own machine and give the result to the account's owner, who
|
|
1555
|
+
* registers it with {@link VerdifaxClient.registerApprover}, so the owner
|
|
1556
|
+
* never holds the approver's private key. */
|
|
1557
|
+
declare function approverKeyProof(approverKey: Uint8Array | KeyObject): ApproverKeyProof;
|
|
1352
1558
|
|
|
1353
1559
|
/**
|
|
1354
1560
|
* Input-validation helpers, mirrors the Python SDK's
|
|
@@ -1434,4 +1640,4 @@ declare class APIKeyRequiredError extends VerdifaxError {
|
|
|
1434
1640
|
constructor();
|
|
1435
1641
|
}
|
|
1436
1642
|
|
|
1437
|
-
export { APIError, APIKeyRequiredError, type Account, type AccountInfo, type AccountKey, type AccountKeyList, type AccountKeyRevocation, type AccountRunsExport, type AdminAPIKey, type AdminCreateAccountOptions, type AdminCreateAccountResponse, type AdminCreateKeyOptions, type AdminCreateKeyResponse, type AdminEraseRequest, type AttestRequest, type AttestationList, AuthError, CRES_ENGINE_VERSION, CRES_PREIMAGE_VERSION, type ClientConfig, type CreateAccountKeyOptions, type CreatedAccountKey, type DCAEProofBundle, type DCAEVerdict, DCAE_PREIMAGE_VERSION, DSE_FORMULA_VERSION, type DeletionReceipt, type DeterminismDiff, type DeterminismRun, EXECUTE_FIELDS, type ExecuteBatchResponse, type ExecuteBatchResult, type ExecuteRequest, type ExecuteResponse, type ExecutionManifest, type ExportRow, type ExportRunsParams, type FormulaEra, type Health, type Hex64, type JobLink, type JobStepOptions, type KeyTier, type ListAttestationsParams, type ListRunsParams, MAX_BATCH_ROWS, NotFoundError, type PDFFormat, type Policy, type PolicyActivation, type PolicyActivationSignature, type PolicyActorType, type PolicyClassification, type PolicyContext, type PolicyList, type PolicyObjectType, type PolicyTestRequest, type PolicyTestResult, type PolicyWithDocument, RUN_STATUSES, type ReproducibilityContext, type RequestKeyChange, type RequestKeyPair, type RequestKeyRegistration, type RequestKeyRegistrationBody, type RequestSignature, type RequestSignatureV2, type Run, type RunStats, type RunStatus, type RunSummary, type RunsExport, type RunsExportCSV, type SearchRunsParams, type SignedRequestBody, VerdifaxClient, VerdifaxError, VerdifaxJob, type VerdifaxVersion, type VerifyDeterminismResponse, type VerifyResponse, captureEnvironment, cresReceiptHash, dcaeClosureHash, dseDispatchHash, formatProviderPayload, fromEnv, generateRequestKeyPair, policyActivationApproval, policyActivationPreimage, requestKeyChangeApproval, requestKeyChangePreimage, requestKeyRegistrationBody, requestSignaturePreimage, requestSignaturePreimageV2, signRequest, signRequestV2, validateHex64, validateJobName, validateRouteId };
|
|
1643
|
+
export { APIError, APIKeyRequiredError, type Account, type AccountInfo, type AccountKey, type AccountKeyList, type AccountKeyRevocation, type AccountRunsExport, type AdminAPIKey, type AdminCreateAccountOptions, type AdminCreateAccountResponse, type AdminCreateKeyOptions, type AdminCreateKeyResponse, type AdminEraseRequest, type Approver, type ApproverKeyProof, type ApproverList, type ApproverRegistration, type ApproverRevocation, type AsOwnerOptions, type AttestRequest, type AttestationList, AuthError, CRES_ENGINE_VERSION, CRES_PREIMAGE_VERSION, type ClientConfig, type CreateAccountKeyOptions, type CreatedAccountKey, type DCAEProofBundle, type DCAEVerdict, DCAE_PREIMAGE_VERSION, DSE_FORMULA_VERSION, type DeletionReceipt, type DeterminismDiff, type DeterminismRun, EXECUTE_FIELDS, type ExecuteBatchResponse, type ExecuteBatchResult, type ExecuteRequest, type ExecuteResponse, type ExecutionManifest, type ExportRow, type ExportRunsParams, type FormulaEra, type Health, type Hex64, type JobLink, type JobStepOptions, type KeyTier, type ListAttestationsParams, type ListRunsParams, MAX_BATCH_ROWS, type NewestSignOff, NotFoundError, type PDFFormat, type Policy, type PolicyActivation, type PolicyActivationSignature, type PolicyActorType, type PolicyClassification, type PolicyContext, type PolicyList, type PolicyObjectType, type PolicyTestRequest, type PolicyTestResult, type PolicyWithDocument, RUN_STATUSES, type ReproducibilityContext, type RequestKeyChange, type RequestKeyPair, type RequestKeyRegistration, type RequestKeyRegistrationBody, type RequestSignature, type RequestSignatureV2, type Run, type RunStats, type RunStatus, type RunSummary, type RunsExport, type RunsExportCSV, type SearchRunsParams, type SignOffDecision, type SignOffOptions, type SignOffRecord, type SignOffSignature, type SignOffTarget, type SignOffTargetKind, type SignedRequestBody, VerdifaxClient, VerdifaxError, VerdifaxJob, type VerdifaxVersion, type VerifyDeterminismResponse, type VerifyResponse, approverKeyProof, captureEnvironment, cresReceiptHash, dcaeClosureHash, dseDispatchHash, formatProviderPayload, fromEnv, generateRequestKeyPair, policyActivationApproval, policyActivationPreimage, requestKeyChangeApproval, requestKeyChangePreimage, requestKeyRegistrationBody, requestSignaturePreimage, requestSignaturePreimageV2, signOffPreimage, signRequest, signRequestV2, validateHex64, validateJobName, validateRouteId };
|
package/dist/index.d.ts
CHANGED
|
@@ -569,6 +569,11 @@ interface RunsExport {
|
|
|
569
569
|
* key in the account, with the account's policy documents and changes. */
|
|
570
570
|
interface AccountRunsExport extends RunsExport {
|
|
571
571
|
account_id: string;
|
|
572
|
+
/** The account's whole chain of sign-offs, oldest first, as
|
|
573
|
+
* {@link VerdifaxClient.getSignOffHistory} returns it. Only the owner
|
|
574
|
+
* key's export carries it, and the SDK returns it as the server sent
|
|
575
|
+
* it, without checking it: check it with `verdifax-verify`. */
|
|
576
|
+
signoffs?: SignOffRecord[];
|
|
572
577
|
}
|
|
573
578
|
/** Returned by {@link VerdifaxClient.exportRunsCSV}. */
|
|
574
579
|
interface RunsExportCSV {
|
|
@@ -755,6 +760,127 @@ interface AdminAPIKey {
|
|
|
755
760
|
run_count: number;
|
|
756
761
|
revoked: boolean;
|
|
757
762
|
}
|
|
763
|
+
/** The kind of result a sign-off names: a completed run's record, a
|
|
764
|
+
* refused or halted run's receipt, or a whole job by its final step. */
|
|
765
|
+
type SignOffTargetKind = "record" | "receipt" | "job";
|
|
766
|
+
/** An approver's decision on one result. */
|
|
767
|
+
type SignOffDecision = "approve" | "reject";
|
|
768
|
+
/** The exact result a sign-off names, by what its public-log entry
|
|
769
|
+
* covers. */
|
|
770
|
+
interface SignOffTarget {
|
|
771
|
+
kind: SignOffTargetKind;
|
|
772
|
+
/** The run: for a job, the run of its final step. */
|
|
773
|
+
run_id: number;
|
|
774
|
+
/** What the result's public-log entry covers. For a record, its
|
|
775
|
+
* `bundle_anchor.anchored_digest` in the bundle
|
|
776
|
+
* {@link VerdifaxClient.getRunArtifacts} returns. For a receipt, its
|
|
777
|
+
* hash. For a job, the final step's record digest, read the same way
|
|
778
|
+
* as a record's. 64 lowercase hex characters. */
|
|
779
|
+
digest: Hex64;
|
|
780
|
+
/** The job, for a job target only. */
|
|
781
|
+
job_id?: string;
|
|
782
|
+
}
|
|
783
|
+
/** One sign-off, verdifax.signoff.v1, as the server records it and
|
|
784
|
+
* `verdifax-verify --signoff` and `--signoff-history` read it. */
|
|
785
|
+
interface SignOffRecord {
|
|
786
|
+
format: string;
|
|
787
|
+
/** The account's chain, `account-N`. */
|
|
788
|
+
chain_id: string;
|
|
789
|
+
sequence: number;
|
|
790
|
+
/** The sign-off this one follows, or 64 zeros for the account's first. */
|
|
791
|
+
previous_signoff_hash: Hex64;
|
|
792
|
+
target: SignOffTarget;
|
|
793
|
+
decision: SignOffDecision;
|
|
794
|
+
/** SHA-256 of the approver's note, when there was one. The note's text
|
|
795
|
+
* never reaches Verdifax. */
|
|
796
|
+
note_hash?: string;
|
|
797
|
+
/** The base64 Ed25519 public key of the approver who signed. */
|
|
798
|
+
approver_key: string;
|
|
799
|
+
/** Base64 Ed25519 signature by the approver key. */
|
|
800
|
+
signature: string;
|
|
801
|
+
signed_at: string;
|
|
802
|
+
nonce: string;
|
|
803
|
+
recorded_at: string;
|
|
804
|
+
signoff_hash: Hex64;
|
|
805
|
+
/** Where the sign-off was logged on the public log. */
|
|
806
|
+
anchor?: Record<string, unknown>;
|
|
807
|
+
}
|
|
808
|
+
/** What an approver signs against: the account's chain and its newest
|
|
809
|
+
* sign-off. Returned by {@link VerdifaxClient.getNewestSignOff}. */
|
|
810
|
+
interface NewestSignOff {
|
|
811
|
+
ok: boolean;
|
|
812
|
+
/** The account's chain, `account-N`. */
|
|
813
|
+
chain_id: string;
|
|
814
|
+
/** The newest sign-off's hash, or 64 zeros when there is none yet. */
|
|
815
|
+
newest_signoff_hash: Hex64;
|
|
816
|
+
/** The newest sign-off's place in the chain, 0 when there is none. */
|
|
817
|
+
sequence: number;
|
|
818
|
+
}
|
|
819
|
+
/** The approver key's part of a sign-off. */
|
|
820
|
+
interface SignOffSignature {
|
|
821
|
+
/** The base64 Ed25519 public key of the approver. */
|
|
822
|
+
approver_key: string;
|
|
823
|
+
/** Base64 Ed25519 signature by the approver key. */
|
|
824
|
+
signature: string;
|
|
825
|
+
/** RFC 3339 UTC with whole seconds. */
|
|
826
|
+
signed_at: string;
|
|
827
|
+
/** 16 random bytes as 32 lowercase hex characters, fresh per sign-off. */
|
|
828
|
+
nonce: string;
|
|
829
|
+
}
|
|
830
|
+
/** Options for {@link VerdifaxClient.signOff}. */
|
|
831
|
+
interface SignOffOptions {
|
|
832
|
+
/** The approver's note. It is hashed on this machine and only its
|
|
833
|
+
* SHA-256 is sent and signed: the text never reaches Verdifax. Keep the
|
|
834
|
+
* text yourself to show it matches the hash. */
|
|
835
|
+
note?: string;
|
|
836
|
+
/** Use the owner key's routes, /account/signoffs/newest and
|
|
837
|
+
* /account/signoffs, instead of a member key's. */
|
|
838
|
+
asOwner?: boolean;
|
|
839
|
+
}
|
|
840
|
+
/** For a method that serves both a member key's route and the owner
|
|
841
|
+
* key's /account route. */
|
|
842
|
+
interface AsOwnerOptions {
|
|
843
|
+
/** Use the owner key's /account route. */
|
|
844
|
+
asOwner?: boolean;
|
|
845
|
+
}
|
|
846
|
+
/** An approver key's public key and the proof that its holder has the
|
|
847
|
+
* private key, from {@link approverKeyProof}. */
|
|
848
|
+
interface ApproverKeyProof {
|
|
849
|
+
/** Base64 of the raw 32-byte public key. */
|
|
850
|
+
public_key: string;
|
|
851
|
+
/** Base64 Ed25519 signature over verdifax.approver-key.v1, a zero byte
|
|
852
|
+
* and the base64 public key. */
|
|
853
|
+
proof: string;
|
|
854
|
+
}
|
|
855
|
+
/** An approver key registered to an account. A key is not an identity:
|
|
856
|
+
* `name` is the label the owner gave it. */
|
|
857
|
+
interface Approver {
|
|
858
|
+
id: number;
|
|
859
|
+
public_key: string;
|
|
860
|
+
name: string;
|
|
861
|
+
/** The account, `account-N`. */
|
|
862
|
+
account_id: string;
|
|
863
|
+
registered_at: string;
|
|
864
|
+
revoked: boolean;
|
|
865
|
+
revoked_at?: string;
|
|
866
|
+
}
|
|
867
|
+
/** Returned by {@link VerdifaxClient.registerApprover}. */
|
|
868
|
+
interface ApproverRegistration extends Approver {
|
|
869
|
+
ok: boolean;
|
|
870
|
+
}
|
|
871
|
+
/** Returned by {@link VerdifaxClient.listApprovers}: revoked keys
|
|
872
|
+
* included. */
|
|
873
|
+
interface ApproverList {
|
|
874
|
+
ok: boolean;
|
|
875
|
+
account_id: string;
|
|
876
|
+
approvers: Approver[];
|
|
877
|
+
}
|
|
878
|
+
/** Returned by {@link VerdifaxClient.revokeApprover}. */
|
|
879
|
+
interface ApproverRevocation {
|
|
880
|
+
ok: boolean;
|
|
881
|
+
id: number;
|
|
882
|
+
revoked: boolean;
|
|
883
|
+
}
|
|
758
884
|
|
|
759
885
|
/**
|
|
760
886
|
* The job helper: runs the steps of one job and keeps the map of step ids
|
|
@@ -869,6 +995,13 @@ declare const MAX_BATCH_ROWS = 1000;
|
|
|
869
995
|
* | `testAccountPolicy(req)` | POST /account/policies/test | owner key |
|
|
870
996
|
* | `getAccountPolicyHistory()` | GET /account/policies/history | owner key |
|
|
871
997
|
* | `activateAccountPolicy(documentHash)` | POST /account/policies/{document_hash}/activate | owner key |
|
|
998
|
+
* | `registerApprover(name, approver)` | POST /account/approvers | owner key |
|
|
999
|
+
* | `listApprovers()` | GET /account/approvers | owner key |
|
|
1000
|
+
* | `revokeApprover(id)` | DELETE /account/approvers/{id} | owner key |
|
|
1001
|
+
* | `signOff(target, decision, approverKey, options)` | POST /signoffs, or POST /account/signoffs with asOwner | required |
|
|
1002
|
+
* | `getNewestSignOff(options)` | GET /signoffs/newest, or GET /account/signoffs/newest with asOwner | required |
|
|
1003
|
+
* | `getSignOffs(runId, options)` | GET /runs/{id}/signoffs, or GET /account/runs/{id}/signoffs with asOwner | required |
|
|
1004
|
+
* | `getSignOffHistory()` | GET /account/signoffs/history | owner key |
|
|
872
1005
|
* | `job(jobId)` | none, returns a {@link VerdifaxJob} | none |
|
|
873
1006
|
*
|
|
874
1007
|
* With the `signingKey` option set, every path to POST /execute carries
|
|
@@ -1037,6 +1170,56 @@ declare class VerdifaxClient {
|
|
|
1037
1170
|
* `account-N`. The server refuses an unsigned account change, so this
|
|
1038
1171
|
* needs the `signingKey` option and refuses to send without it. */
|
|
1039
1172
|
activateAccountPolicy(documentHash: Hex64): Promise<PolicyActivation>;
|
|
1173
|
+
/** Registers an approver key to the owner's account. `approver` is the
|
|
1174
|
+
* approver's signing key, from which the proof is made on this machine,
|
|
1175
|
+
* or the `{ public_key, proof }` that {@link approverKeyProof} made on
|
|
1176
|
+
* the approver's own machine, so the owner need not hold the private
|
|
1177
|
+
* key. A proof that does not verify is refused before sending. The
|
|
1178
|
+
* server refuses a key that is a request key in the same account.
|
|
1179
|
+
* `name` is a label, not an identity. */
|
|
1180
|
+
registerApprover(name: string, approver: Uint8Array | KeyObject | ApproverKeyProof): Promise<ApproverRegistration>;
|
|
1181
|
+
/** The account's approver keys, revoked ones included. */
|
|
1182
|
+
listApprovers(): Promise<ApproverList>;
|
|
1183
|
+
/** Revokes one of the account's approver keys. Its earlier sign-offs
|
|
1184
|
+
* stay on the record. A key that is not an active approver in the
|
|
1185
|
+
* account throws {@link NotFoundError}. */
|
|
1186
|
+
revokeApprover(approverId: number): Promise<ApproverRevocation>;
|
|
1187
|
+
/** What an approver signs against: the account's chain `account-N` and
|
|
1188
|
+
* its newest sign-off (64 zeros when there is none yet). With
|
|
1189
|
+
* `asOwner` it reads the owner key's route. */
|
|
1190
|
+
getNewestSignOff(options?: AsOwnerOptions): Promise<NewestSignOff>;
|
|
1191
|
+
/** Signs an approver's decision, approve or reject, on one exact result
|
|
1192
|
+
* and submits it. It first reads {@link getNewestSignOff} for the
|
|
1193
|
+
* account's chain and its newest sign-off, then signs over them with
|
|
1194
|
+
* `approverKey` on this machine. The approver's private key is never
|
|
1195
|
+
* sent.
|
|
1196
|
+
*
|
|
1197
|
+
* `target.digest` is what the result's public-log entry covers: for a
|
|
1198
|
+
* record, the `bundle_anchor.anchored_digest` of the bundle
|
|
1199
|
+
* {@link getRunArtifacts} returns; for a receipt, its hash; for a job,
|
|
1200
|
+
* the final step's record digest, read as a record's, with `job_id`.
|
|
1201
|
+
*
|
|
1202
|
+
* `note` is hashed here and only its SHA-256 is sent and signed: the
|
|
1203
|
+
* text never reaches Verdifax. With `asOwner` it uses the owner key's
|
|
1204
|
+
* routes. Bad input is refused before anything is sent.
|
|
1205
|
+
*
|
|
1206
|
+
* The record the server returns must carry the signature,
|
|
1207
|
+
* approver_key, decision, previous_signoff_hash and target that were
|
|
1208
|
+
* sent, or this throws {@link VerdifaxError}.
|
|
1209
|
+
*
|
|
1210
|
+
* A later sign-off on the same result supersedes this one, and neither
|
|
1211
|
+
* is deleted. An HTTP 409 means the chain moved or the target is not
|
|
1212
|
+
* this account's result: it throws the usual {@link APIError} and is
|
|
1213
|
+
* not retried. */
|
|
1214
|
+
signOff(target: SignOffTarget, decision: SignOffDecision, approverKey: Uint8Array | KeyObject, options?: SignOffOptions): Promise<SignOffRecord>;
|
|
1215
|
+
/** The sign-offs on one run, oldest first, in the form
|
|
1216
|
+
* `verdifax-verify --signoff FILE` reads: for a member key, a run it
|
|
1217
|
+
* made; with `asOwner`, any run of the account. The latest is the
|
|
1218
|
+
* decision in force. */
|
|
1219
|
+
getSignOffs(runId: number, options?: AsOwnerOptions): Promise<SignOffRecord[]>;
|
|
1220
|
+
/** The account's whole chain of sign-offs, oldest first, in the form
|
|
1221
|
+
* `verdifax-verify --signoff-history FILE` reads. Needs the owner key. */
|
|
1222
|
+
getSignOffHistory(): Promise<SignOffRecord[]>;
|
|
1040
1223
|
getRun(runId: number): Promise<Run>;
|
|
1041
1224
|
verifyRun(runId: number): Promise<VerifyResponse>;
|
|
1042
1225
|
listRuns(params?: ListRunsParams): Promise<{
|
|
@@ -1349,6 +1532,29 @@ declare function policyActivationApproval(signingKey: Uint8Array | KeyObject, fi
|
|
|
1349
1532
|
signedAt?: string;
|
|
1350
1533
|
nonce?: string;
|
|
1351
1534
|
}): PolicyActivationSignature;
|
|
1535
|
+
/** The bytes an approver key signs for one sign-off, matching the
|
|
1536
|
+
* orchestrator's SignOffPreimage: the fields joined with single zero
|
|
1537
|
+
* bytes. `chain_id` and `previous_signoff_hash` come from GET
|
|
1538
|
+
* /signoffs/newest (or /account/signoffs/newest for the owner key), so a
|
|
1539
|
+
* signature cannot be moved to another account, another result or
|
|
1540
|
+
* another point in the chain. `target.job_id` is empty unless the target
|
|
1541
|
+
* is a job, and `note_hash` is empty when there is no note. */
|
|
1542
|
+
declare function signOffPreimage(fields: {
|
|
1543
|
+
chain_id: string;
|
|
1544
|
+
previous_signoff_hash: string;
|
|
1545
|
+
target: SignOffTarget;
|
|
1546
|
+
decision: SignOffDecision;
|
|
1547
|
+
note_hash: string;
|
|
1548
|
+
signed_at: string;
|
|
1549
|
+
nonce: string;
|
|
1550
|
+
}): Buffer;
|
|
1551
|
+
/** The approver key's base64 public key and its proof: its signature over
|
|
1552
|
+
* verdifax.approver-key.v1, a zero byte and the base64 public key, which
|
|
1553
|
+
* shows its holder has the private key. The approver can run this on
|
|
1554
|
+
* their own machine and give the result to the account's owner, who
|
|
1555
|
+
* registers it with {@link VerdifaxClient.registerApprover}, so the owner
|
|
1556
|
+
* never holds the approver's private key. */
|
|
1557
|
+
declare function approverKeyProof(approverKey: Uint8Array | KeyObject): ApproverKeyProof;
|
|
1352
1558
|
|
|
1353
1559
|
/**
|
|
1354
1560
|
* Input-validation helpers, mirrors the Python SDK's
|
|
@@ -1434,4 +1640,4 @@ declare class APIKeyRequiredError extends VerdifaxError {
|
|
|
1434
1640
|
constructor();
|
|
1435
1641
|
}
|
|
1436
1642
|
|
|
1437
|
-
export { APIError, APIKeyRequiredError, type Account, type AccountInfo, type AccountKey, type AccountKeyList, type AccountKeyRevocation, type AccountRunsExport, type AdminAPIKey, type AdminCreateAccountOptions, type AdminCreateAccountResponse, type AdminCreateKeyOptions, type AdminCreateKeyResponse, type AdminEraseRequest, type AttestRequest, type AttestationList, AuthError, CRES_ENGINE_VERSION, CRES_PREIMAGE_VERSION, type ClientConfig, type CreateAccountKeyOptions, type CreatedAccountKey, type DCAEProofBundle, type DCAEVerdict, DCAE_PREIMAGE_VERSION, DSE_FORMULA_VERSION, type DeletionReceipt, type DeterminismDiff, type DeterminismRun, EXECUTE_FIELDS, type ExecuteBatchResponse, type ExecuteBatchResult, type ExecuteRequest, type ExecuteResponse, type ExecutionManifest, type ExportRow, type ExportRunsParams, type FormulaEra, type Health, type Hex64, type JobLink, type JobStepOptions, type KeyTier, type ListAttestationsParams, type ListRunsParams, MAX_BATCH_ROWS, NotFoundError, type PDFFormat, type Policy, type PolicyActivation, type PolicyActivationSignature, type PolicyActorType, type PolicyClassification, type PolicyContext, type PolicyList, type PolicyObjectType, type PolicyTestRequest, type PolicyTestResult, type PolicyWithDocument, RUN_STATUSES, type ReproducibilityContext, type RequestKeyChange, type RequestKeyPair, type RequestKeyRegistration, type RequestKeyRegistrationBody, type RequestSignature, type RequestSignatureV2, type Run, type RunStats, type RunStatus, type RunSummary, type RunsExport, type RunsExportCSV, type SearchRunsParams, type SignedRequestBody, VerdifaxClient, VerdifaxError, VerdifaxJob, type VerdifaxVersion, type VerifyDeterminismResponse, type VerifyResponse, captureEnvironment, cresReceiptHash, dcaeClosureHash, dseDispatchHash, formatProviderPayload, fromEnv, generateRequestKeyPair, policyActivationApproval, policyActivationPreimage, requestKeyChangeApproval, requestKeyChangePreimage, requestKeyRegistrationBody, requestSignaturePreimage, requestSignaturePreimageV2, signRequest, signRequestV2, validateHex64, validateJobName, validateRouteId };
|
|
1643
|
+
export { APIError, APIKeyRequiredError, type Account, type AccountInfo, type AccountKey, type AccountKeyList, type AccountKeyRevocation, type AccountRunsExport, type AdminAPIKey, type AdminCreateAccountOptions, type AdminCreateAccountResponse, type AdminCreateKeyOptions, type AdminCreateKeyResponse, type AdminEraseRequest, type Approver, type ApproverKeyProof, type ApproverList, type ApproverRegistration, type ApproverRevocation, type AsOwnerOptions, type AttestRequest, type AttestationList, AuthError, CRES_ENGINE_VERSION, CRES_PREIMAGE_VERSION, type ClientConfig, type CreateAccountKeyOptions, type CreatedAccountKey, type DCAEProofBundle, type DCAEVerdict, DCAE_PREIMAGE_VERSION, DSE_FORMULA_VERSION, type DeletionReceipt, type DeterminismDiff, type DeterminismRun, EXECUTE_FIELDS, type ExecuteBatchResponse, type ExecuteBatchResult, type ExecuteRequest, type ExecuteResponse, type ExecutionManifest, type ExportRow, type ExportRunsParams, type FormulaEra, type Health, type Hex64, type JobLink, type JobStepOptions, type KeyTier, type ListAttestationsParams, type ListRunsParams, MAX_BATCH_ROWS, type NewestSignOff, NotFoundError, type PDFFormat, type Policy, type PolicyActivation, type PolicyActivationSignature, type PolicyActorType, type PolicyClassification, type PolicyContext, type PolicyList, type PolicyObjectType, type PolicyTestRequest, type PolicyTestResult, type PolicyWithDocument, RUN_STATUSES, type ReproducibilityContext, type RequestKeyChange, type RequestKeyPair, type RequestKeyRegistration, type RequestKeyRegistrationBody, type RequestSignature, type RequestSignatureV2, type Run, type RunStats, type RunStatus, type RunSummary, type RunsExport, type RunsExportCSV, type SearchRunsParams, type SignOffDecision, type SignOffOptions, type SignOffRecord, type SignOffSignature, type SignOffTarget, type SignOffTargetKind, type SignedRequestBody, VerdifaxClient, VerdifaxError, VerdifaxJob, type VerdifaxVersion, type VerifyDeterminismResponse, type VerifyResponse, approverKeyProof, captureEnvironment, cresReceiptHash, dcaeClosureHash, dseDispatchHash, formatProviderPayload, fromEnv, generateRequestKeyPair, policyActivationApproval, policyActivationPreimage, requestKeyChangeApproval, requestKeyChangePreimage, requestKeyRegistrationBody, requestSignaturePreimage, requestSignaturePreimageV2, signOffPreimage, signRequest, signRequestV2, validateHex64, validateJobName, validateRouteId };
|
package/dist/index.js
CHANGED
|
@@ -34,6 +34,7 @@ __export(index_exports, {
|
|
|
34
34
|
VerdifaxClient: () => VerdifaxClient,
|
|
35
35
|
VerdifaxError: () => VerdifaxError,
|
|
36
36
|
VerdifaxJob: () => VerdifaxJob,
|
|
37
|
+
approverKeyProof: () => approverKeyProof,
|
|
37
38
|
captureEnvironment: () => captureEnvironment,
|
|
38
39
|
cresReceiptHash: () => cresReceiptHash,
|
|
39
40
|
dcaeClosureHash: () => dcaeClosureHash,
|
|
@@ -48,6 +49,7 @@ __export(index_exports, {
|
|
|
48
49
|
requestKeyRegistrationBody: () => requestKeyRegistrationBody,
|
|
49
50
|
requestSignaturePreimage: () => requestSignaturePreimage,
|
|
50
51
|
requestSignaturePreimageV2: () => requestSignaturePreimageV2,
|
|
52
|
+
signOffPreimage: () => signOffPreimage,
|
|
51
53
|
signRequest: () => signRequest,
|
|
52
54
|
signRequestV2: () => signRequestV2,
|
|
53
55
|
validateHex64: () => validateHex64,
|
|
@@ -389,7 +391,10 @@ var JOB_FORM = "verdifax.job.v1";
|
|
|
389
391
|
var REQUEST_KEY_PROOF_FORM = "verdifax.request-key.v1";
|
|
390
392
|
var REQUEST_KEY_CHANGE_FORM = "verdifax.request-key-change.v1";
|
|
391
393
|
var POLICY_ACTIVATION_FORM = "verdifax.policy-activation.v1";
|
|
394
|
+
var SIGNOFF_FORM = "verdifax.signoff.v1";
|
|
395
|
+
var APPROVER_KEY_PROOF_FORM = "verdifax.approver-key.v1";
|
|
392
396
|
var SIGNING_KEY_BYTES = 32;
|
|
397
|
+
var SPKI_ED25519_HEADER = Buffer.from("302a300506032b6570032100", "hex");
|
|
393
398
|
var PKCS8_ED25519_HEADER = Buffer.from(
|
|
394
399
|
"302e020100300506032b657004220420",
|
|
395
400
|
"hex"
|
|
@@ -573,6 +578,71 @@ function policyActivationApproval(signingKey, fields, options = {}) {
|
|
|
573
578
|
nonce
|
|
574
579
|
};
|
|
575
580
|
}
|
|
581
|
+
function signOffPreimage(fields) {
|
|
582
|
+
const { target } = fields;
|
|
583
|
+
return Buffer.from(
|
|
584
|
+
[
|
|
585
|
+
SIGNOFF_FORM,
|
|
586
|
+
fields.chain_id,
|
|
587
|
+
fields.previous_signoff_hash,
|
|
588
|
+
target.kind,
|
|
589
|
+
String(target.run_id),
|
|
590
|
+
target.digest,
|
|
591
|
+
target.job_id ?? "",
|
|
592
|
+
fields.decision,
|
|
593
|
+
fields.note_hash,
|
|
594
|
+
fields.signed_at,
|
|
595
|
+
fields.nonce
|
|
596
|
+
].join("\0"),
|
|
597
|
+
"utf8"
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
function signOffSignature(approverKey, fields, options = {}) {
|
|
601
|
+
const key = loadSigningKey(approverKey);
|
|
602
|
+
const signed_at = options.signedAt ?? signedAtNow();
|
|
603
|
+
const nonce = options.nonce ?? (0, import_node_crypto.randomBytes)(16).toString("hex");
|
|
604
|
+
const preimage = signOffPreimage({ ...fields, signed_at, nonce });
|
|
605
|
+
return {
|
|
606
|
+
approver_key: rawPublicKey((0, import_node_crypto.createPublicKey)(key)).toString("base64"),
|
|
607
|
+
signature: (0, import_node_crypto.sign)(null, preimage, key).toString("base64"),
|
|
608
|
+
signed_at,
|
|
609
|
+
nonce
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
function approverKeyProof(approverKey) {
|
|
613
|
+
const key = loadSigningKey(approverKey);
|
|
614
|
+
const public_key = rawPublicKey((0, import_node_crypto.createPublicKey)(key)).toString("base64");
|
|
615
|
+
const proof = (0, import_node_crypto.sign)(
|
|
616
|
+
null,
|
|
617
|
+
Buffer.from(`${APPROVER_KEY_PROOF_FORM}\0${public_key}`, "utf8"),
|
|
618
|
+
key
|
|
619
|
+
).toString("base64");
|
|
620
|
+
return { public_key, proof };
|
|
621
|
+
}
|
|
622
|
+
function approverKeyProofVerifies(proof) {
|
|
623
|
+
if (typeof proof?.public_key !== "string" || typeof proof?.proof !== "string") {
|
|
624
|
+
return false;
|
|
625
|
+
}
|
|
626
|
+
const raw = Buffer.from(proof.public_key, "base64");
|
|
627
|
+
const signature = Buffer.from(proof.proof, "base64");
|
|
628
|
+
if (raw.length !== 32 || raw.toString("base64") !== proof.public_key || signature.length !== 64 || signature.toString("base64") !== proof.proof) {
|
|
629
|
+
return false;
|
|
630
|
+
}
|
|
631
|
+
const publicKey = (0, import_node_crypto.createPublicKey)({
|
|
632
|
+
key: Buffer.concat([SPKI_ED25519_HEADER, raw]),
|
|
633
|
+
format: "der",
|
|
634
|
+
type: "spki"
|
|
635
|
+
});
|
|
636
|
+
return (0, import_node_crypto.verify)(
|
|
637
|
+
null,
|
|
638
|
+
Buffer.from(`${APPROVER_KEY_PROOF_FORM}\0${proof.public_key}`, "utf8"),
|
|
639
|
+
publicKey,
|
|
640
|
+
signature
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
function publicKeyOf(key) {
|
|
644
|
+
return rawPublicKey((0, import_node_crypto.createPublicKey)(key)).toString("base64");
|
|
645
|
+
}
|
|
576
646
|
|
|
577
647
|
// src/job.ts
|
|
578
648
|
var STEP_OPTIONS = {
|
|
@@ -674,9 +744,10 @@ var VerdifaxJob = class {
|
|
|
674
744
|
};
|
|
675
745
|
|
|
676
746
|
// src/version.ts
|
|
677
|
-
var SDK_VERSION = "1.
|
|
747
|
+
var SDK_VERSION = "1.4.0";
|
|
678
748
|
|
|
679
749
|
// src/client.ts
|
|
750
|
+
var import_node_crypto2 = require("crypto");
|
|
680
751
|
var DEFAULT_BASE_URL = "https://api.verdifax.com";
|
|
681
752
|
var DEFAULT_TIMEOUT_MS = 9e4;
|
|
682
753
|
var DEFAULT_MAX_RETRIES = 3;
|
|
@@ -689,6 +760,12 @@ var ACCOUNT_PATH = "/account";
|
|
|
689
760
|
var ACCOUNT_REQUEST_KEY_PATH = "/account/request-key";
|
|
690
761
|
var ACCOUNT_POLICIES_PATH = "/account/policies";
|
|
691
762
|
var ACCOUNT_CHAIN_ID = /^account-[1-9][0-9]*$/;
|
|
763
|
+
var SIGNOFFS_PATH = "/signoffs";
|
|
764
|
+
var ACCOUNT_SIGNOFFS_PATH = "/account/signoffs";
|
|
765
|
+
var APPROVERS_PATH = "/account/approvers";
|
|
766
|
+
var SIGNOFF_TARGET_KINDS = /* @__PURE__ */ new Set(["record", "receipt", "job"]);
|
|
767
|
+
var SIGNOFF_DECISIONS = /* @__PURE__ */ new Set(["approve", "reject"]);
|
|
768
|
+
var MAX_APPROVER_NAME_BYTES = 200;
|
|
692
769
|
var HEADER_EXPORT_TOTAL = "X-Verdifax-Total";
|
|
693
770
|
var HEADER_EXPORT_TRUNCATED = "X-Verdifax-Truncated";
|
|
694
771
|
var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
|
|
@@ -1195,6 +1272,174 @@ var VerdifaxClient = class {
|
|
|
1195
1272
|
}
|
|
1196
1273
|
return this.activatePolicyAt(ACCOUNT_POLICIES_PATH, documentHash, chain);
|
|
1197
1274
|
}
|
|
1275
|
+
// ── Sign-off ─────────────────────────────────────────────────
|
|
1276
|
+
//
|
|
1277
|
+
// An account's owner registers approver keys. An approver signs a
|
|
1278
|
+
// decision, approve or reject, on one exact result on their own machine,
|
|
1279
|
+
// and any key in the account, or the owner key with asOwner, submits it.
|
|
1280
|
+
// A sign-off shows that the holder of a registered approver key signed
|
|
1281
|
+
// that decision on that exact result and that Verdifax logged it on the
|
|
1282
|
+
// public log. It does not show the review was careful, the result right,
|
|
1283
|
+
// or who the person is, and it gates nothing on its own.
|
|
1284
|
+
/** Registers an approver key to the owner's account. `approver` is the
|
|
1285
|
+
* approver's signing key, from which the proof is made on this machine,
|
|
1286
|
+
* or the `{ public_key, proof }` that {@link approverKeyProof} made on
|
|
1287
|
+
* the approver's own machine, so the owner need not hold the private
|
|
1288
|
+
* key. A proof that does not verify is refused before sending. The
|
|
1289
|
+
* server refuses a key that is a request key in the same account.
|
|
1290
|
+
* `name` is a label, not an identity. */
|
|
1291
|
+
async registerApprover(name, approver) {
|
|
1292
|
+
this.requireAPIKey();
|
|
1293
|
+
if (typeof name !== "string" || name.trim() === "" || Buffer.byteLength(name.trim(), "utf8") > MAX_APPROVER_NAME_BYTES) {
|
|
1294
|
+
throw new VerdifaxError(
|
|
1295
|
+
"verdifax: registerApprover needs a name of at most 200 bytes"
|
|
1296
|
+
);
|
|
1297
|
+
}
|
|
1298
|
+
const body = approver instanceof Uint8Array || approver instanceof import_node_crypto2.KeyObject ? approverKeyProof(approver) : { public_key: approver?.public_key, proof: approver?.proof };
|
|
1299
|
+
if (!approverKeyProofVerifies(body)) {
|
|
1300
|
+
throw new VerdifaxError(
|
|
1301
|
+
"verdifax: registerApprover needs the approver's signing key, or a public_key and proof from approverKeyProof that verify"
|
|
1302
|
+
);
|
|
1303
|
+
}
|
|
1304
|
+
if (this.signingKey && publicKeyOf(this.signingKey) === body.public_key) {
|
|
1305
|
+
throw new VerdifaxError(
|
|
1306
|
+
"verdifax: an approver key must not be a request key in the same account, and this is the client's signingKey"
|
|
1307
|
+
);
|
|
1308
|
+
}
|
|
1309
|
+
return this.request("POST", APPROVERS_PATH, {
|
|
1310
|
+
public_key: body.public_key,
|
|
1311
|
+
name,
|
|
1312
|
+
proof: body.proof
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
/** The account's approver keys, revoked ones included. */
|
|
1316
|
+
async listApprovers() {
|
|
1317
|
+
this.requireAPIKey();
|
|
1318
|
+
const out = await this.request("GET", APPROVERS_PATH);
|
|
1319
|
+
return { ...out, approvers: out?.approvers ?? [] };
|
|
1320
|
+
}
|
|
1321
|
+
/** Revokes one of the account's approver keys. Its earlier sign-offs
|
|
1322
|
+
* stay on the record. A key that is not an active approver in the
|
|
1323
|
+
* account throws {@link NotFoundError}. */
|
|
1324
|
+
async revokeApprover(approverId) {
|
|
1325
|
+
this.requireAPIKey();
|
|
1326
|
+
requirePositiveId(approverId, "approverId");
|
|
1327
|
+
return this.request(
|
|
1328
|
+
"DELETE",
|
|
1329
|
+
`${APPROVERS_PATH}/${approverId}`
|
|
1330
|
+
);
|
|
1331
|
+
}
|
|
1332
|
+
/** What an approver signs against: the account's chain `account-N` and
|
|
1333
|
+
* its newest sign-off (64 zeros when there is none yet). With
|
|
1334
|
+
* `asOwner` it reads the owner key's route. */
|
|
1335
|
+
async getNewestSignOff(options = {}) {
|
|
1336
|
+
this.requireAPIKey();
|
|
1337
|
+
return this.request(
|
|
1338
|
+
"GET",
|
|
1339
|
+
`${signOffsBase(options, "getNewestSignOff")}/newest`
|
|
1340
|
+
);
|
|
1341
|
+
}
|
|
1342
|
+
/** Signs an approver's decision, approve or reject, on one exact result
|
|
1343
|
+
* and submits it. It first reads {@link getNewestSignOff} for the
|
|
1344
|
+
* account's chain and its newest sign-off, then signs over them with
|
|
1345
|
+
* `approverKey` on this machine. The approver's private key is never
|
|
1346
|
+
* sent.
|
|
1347
|
+
*
|
|
1348
|
+
* `target.digest` is what the result's public-log entry covers: for a
|
|
1349
|
+
* record, the `bundle_anchor.anchored_digest` of the bundle
|
|
1350
|
+
* {@link getRunArtifacts} returns; for a receipt, its hash; for a job,
|
|
1351
|
+
* the final step's record digest, read as a record's, with `job_id`.
|
|
1352
|
+
*
|
|
1353
|
+
* `note` is hashed here and only its SHA-256 is sent and signed: the
|
|
1354
|
+
* text never reaches Verdifax. With `asOwner` it uses the owner key's
|
|
1355
|
+
* routes. Bad input is refused before anything is sent.
|
|
1356
|
+
*
|
|
1357
|
+
* The record the server returns must carry the signature,
|
|
1358
|
+
* approver_key, decision, previous_signoff_hash and target that were
|
|
1359
|
+
* sent, or this throws {@link VerdifaxError}.
|
|
1360
|
+
*
|
|
1361
|
+
* A later sign-off on the same result supersedes this one, and neither
|
|
1362
|
+
* is deleted. An HTTP 409 means the chain moved or the target is not
|
|
1363
|
+
* this account's result: it throws the usual {@link APIError} and is
|
|
1364
|
+
* not retried. */
|
|
1365
|
+
async signOff(target, decision, approverKey, options = {}) {
|
|
1366
|
+
this.requireAPIKey();
|
|
1367
|
+
const base = signOffsBase(options, "signOff");
|
|
1368
|
+
const exact = signOffTargetOf(target);
|
|
1369
|
+
if (!SIGNOFF_DECISIONS.has(decision)) {
|
|
1370
|
+
throw new VerdifaxError(
|
|
1371
|
+
"verdifax: signOff decision must be approve or reject"
|
|
1372
|
+
);
|
|
1373
|
+
}
|
|
1374
|
+
const note = options?.note;
|
|
1375
|
+
if (note !== void 0 && (typeof note !== "string" || note === "")) {
|
|
1376
|
+
throw new VerdifaxError(
|
|
1377
|
+
"verdifax: signOff note must be non-empty text when given"
|
|
1378
|
+
);
|
|
1379
|
+
}
|
|
1380
|
+
const key = loadSigningKey(approverKey);
|
|
1381
|
+
const note_hash = note === void 0 ? "" : sha256Hex(note);
|
|
1382
|
+
const newest = await this.getNewestSignOff(options);
|
|
1383
|
+
if (typeof newest?.chain_id !== "string" || !ACCOUNT_CHAIN_ID.test(newest.chain_id)) {
|
|
1384
|
+
throw new VerdifaxError(
|
|
1385
|
+
"verdifax: the server did not report an account chain_id (account-N), so the sign-off cannot be signed"
|
|
1386
|
+
);
|
|
1387
|
+
}
|
|
1388
|
+
const previous = newest.newest_signoff_hash;
|
|
1389
|
+
if (typeof previous !== "string" || !/^[0-9a-f]{64}$/.test(previous)) {
|
|
1390
|
+
throw new VerdifaxError(
|
|
1391
|
+
"verdifax: the server did not report newest_signoff_hash, so the sign-off cannot name the one it follows"
|
|
1392
|
+
);
|
|
1393
|
+
}
|
|
1394
|
+
const signed = signOffSignature(key, {
|
|
1395
|
+
chain_id: newest.chain_id,
|
|
1396
|
+
previous_signoff_hash: previous,
|
|
1397
|
+
target: exact,
|
|
1398
|
+
decision,
|
|
1399
|
+
note_hash
|
|
1400
|
+
});
|
|
1401
|
+
const body = {
|
|
1402
|
+
previous_signoff_hash: previous,
|
|
1403
|
+
target: exact,
|
|
1404
|
+
decision,
|
|
1405
|
+
...note_hash ? { note_hash } : {},
|
|
1406
|
+
...signed
|
|
1407
|
+
};
|
|
1408
|
+
const out = await this.request(
|
|
1409
|
+
"POST",
|
|
1410
|
+
base,
|
|
1411
|
+
body
|
|
1412
|
+
);
|
|
1413
|
+
const record = out?.signoff;
|
|
1414
|
+
if (!recordMatchesSent(record, exact, decision, previous, signed)) {
|
|
1415
|
+
throw new VerdifaxError(
|
|
1416
|
+
"verdifax: the sign-off the server returned does not carry the signature, approver_key, decision, previous_signoff_hash and target that were sent"
|
|
1417
|
+
);
|
|
1418
|
+
}
|
|
1419
|
+
return record;
|
|
1420
|
+
}
|
|
1421
|
+
/** The sign-offs on one run, oldest first, in the form
|
|
1422
|
+
* `verdifax-verify --signoff FILE` reads: for a member key, a run it
|
|
1423
|
+
* made; with `asOwner`, any run of the account. The latest is the
|
|
1424
|
+
* decision in force. */
|
|
1425
|
+
async getSignOffs(runId, options = {}) {
|
|
1426
|
+
this.requireAPIKey();
|
|
1427
|
+
requirePositiveId(runId, "runId");
|
|
1428
|
+
const base = asOwnerOf(options, "getSignOffs") ? `${ACCOUNT_PATH}/runs` : "/runs";
|
|
1429
|
+
return await this.request(
|
|
1430
|
+
"GET",
|
|
1431
|
+
`${base}/${runId}/signoffs`
|
|
1432
|
+
) ?? [];
|
|
1433
|
+
}
|
|
1434
|
+
/** The account's whole chain of sign-offs, oldest first, in the form
|
|
1435
|
+
* `verdifax-verify --signoff-history FILE` reads. Needs the owner key. */
|
|
1436
|
+
async getSignOffHistory() {
|
|
1437
|
+
this.requireAPIKey();
|
|
1438
|
+
return await this.request(
|
|
1439
|
+
"GET",
|
|
1440
|
+
`${ACCOUNT_SIGNOFFS_PATH}/history`
|
|
1441
|
+
) ?? [];
|
|
1442
|
+
}
|
|
1198
1443
|
// ── Runs ──────────────────────────────────────────────────────
|
|
1199
1444
|
async getRun(runId) {
|
|
1200
1445
|
this.requireAPIKey();
|
|
@@ -1648,6 +1893,52 @@ var VerdifaxClient = class {
|
|
|
1648
1893
|
return this.signingKey ? signRequestV2(this.signingKey, req) : withExecuteFlags(req);
|
|
1649
1894
|
}
|
|
1650
1895
|
};
|
|
1896
|
+
function signOffsBase(options, method) {
|
|
1897
|
+
return asOwnerOf(options, method) ? ACCOUNT_SIGNOFFS_PATH : SIGNOFFS_PATH;
|
|
1898
|
+
}
|
|
1899
|
+
function asOwnerOf(options, method) {
|
|
1900
|
+
const asOwner = options?.asOwner;
|
|
1901
|
+
if (asOwner !== void 0 && typeof asOwner !== "boolean") {
|
|
1902
|
+
throw new VerdifaxError(`verdifax: ${method} asOwner must be true or false`);
|
|
1903
|
+
}
|
|
1904
|
+
return asOwner === true;
|
|
1905
|
+
}
|
|
1906
|
+
function signOffTargetOf(target) {
|
|
1907
|
+
if (typeof target !== "object" || target === null || Array.isArray(target)) {
|
|
1908
|
+
throw new VerdifaxError("verdifax: signOff needs a target object");
|
|
1909
|
+
}
|
|
1910
|
+
for (const name of Object.keys(target)) {
|
|
1911
|
+
if (!["kind", "run_id", "digest", "job_id"].includes(name)) {
|
|
1912
|
+
throw new VerdifaxError(`verdifax: signOff target has an unknown field: ${name}`);
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
if (!SIGNOFF_TARGET_KINDS.has(target.kind)) {
|
|
1916
|
+
throw new VerdifaxError(
|
|
1917
|
+
"verdifax: signOff target kind must be record, receipt or job"
|
|
1918
|
+
);
|
|
1919
|
+
}
|
|
1920
|
+
requirePositiveId(target.run_id, "target.run_id");
|
|
1921
|
+
validateHex64(target.digest, "target.digest");
|
|
1922
|
+
if (target.kind === "job") {
|
|
1923
|
+
validateJobName(target.job_id, "target.job_id");
|
|
1924
|
+
return {
|
|
1925
|
+
kind: target.kind,
|
|
1926
|
+
run_id: target.run_id,
|
|
1927
|
+
digest: target.digest,
|
|
1928
|
+
job_id: target.job_id
|
|
1929
|
+
};
|
|
1930
|
+
}
|
|
1931
|
+
if (target.job_id !== void 0) {
|
|
1932
|
+
throw new VerdifaxError(
|
|
1933
|
+
"verdifax: signOff target job_id is for a job target only"
|
|
1934
|
+
);
|
|
1935
|
+
}
|
|
1936
|
+
return { kind: target.kind, run_id: target.run_id, digest: target.digest };
|
|
1937
|
+
}
|
|
1938
|
+
function recordMatchesSent(record, target, decision, previous, signed) {
|
|
1939
|
+
const got = record?.target;
|
|
1940
|
+
return typeof record === "object" && record !== null && record.signature === signed.signature && record.approver_key === signed.approver_key && record.decision === decision && record.previous_signoff_hash === previous && typeof got === "object" && got !== null && got.kind === target.kind && got.run_id === target.run_id && got.digest === target.digest && (got.job_id ?? "") === (target.job_id ?? "");
|
|
1941
|
+
}
|
|
1651
1942
|
function requirePositiveId(id, name) {
|
|
1652
1943
|
if (!Number.isSafeInteger(id) || id <= 0) {
|
|
1653
1944
|
throw new VerdifaxError(`verdifax: ${name} must be a whole number > 0`);
|
|
@@ -1778,13 +2069,13 @@ var RUN_STATUSES = [
|
|
|
1778
2069
|
];
|
|
1779
2070
|
|
|
1780
2071
|
// src/section0.ts
|
|
1781
|
-
var
|
|
2072
|
+
var import_node_crypto3 = require("crypto");
|
|
1782
2073
|
var DSE_FORMULA_VERSION = "dse.tcu.dispatch.v1";
|
|
1783
2074
|
var DCAE_PREIMAGE_VERSION = "verdifax.dcae.v1";
|
|
1784
2075
|
var CRES_PREIMAGE_VERSION = "verdifax.cres.v1";
|
|
1785
2076
|
var CRES_ENGINE_VERSION = "verdifax-cres/1.0.0";
|
|
1786
2077
|
function sha256Hex2(preimage) {
|
|
1787
|
-
return (0,
|
|
2078
|
+
return (0, import_node_crypto3.createHash)("sha256").update(Buffer.from(preimage, "utf8")).digest("hex");
|
|
1788
2079
|
}
|
|
1789
2080
|
function requireHex64(value, name) {
|
|
1790
2081
|
try {
|
|
@@ -1854,6 +2145,7 @@ function cresReceiptHash(fields) {
|
|
|
1854
2145
|
VerdifaxClient,
|
|
1855
2146
|
VerdifaxError,
|
|
1856
2147
|
VerdifaxJob,
|
|
2148
|
+
approverKeyProof,
|
|
1857
2149
|
captureEnvironment,
|
|
1858
2150
|
cresReceiptHash,
|
|
1859
2151
|
dcaeClosureHash,
|
|
@@ -1868,6 +2160,7 @@ function cresReceiptHash(fields) {
|
|
|
1868
2160
|
requestKeyRegistrationBody,
|
|
1869
2161
|
requestSignaturePreimage,
|
|
1870
2162
|
requestSignaturePreimageV2,
|
|
2163
|
+
signOffPreimage,
|
|
1871
2164
|
signRequest,
|
|
1872
2165
|
signRequestV2,
|
|
1873
2166
|
validateHex64,
|
package/dist/index.mjs
CHANGED
|
@@ -57,7 +57,8 @@ import {
|
|
|
57
57
|
generateKeyPairSync,
|
|
58
58
|
KeyObject,
|
|
59
59
|
randomBytes,
|
|
60
|
-
sign
|
|
60
|
+
sign,
|
|
61
|
+
verify
|
|
61
62
|
} from "crypto";
|
|
62
63
|
|
|
63
64
|
// src/validation.ts
|
|
@@ -339,7 +340,10 @@ var JOB_FORM = "verdifax.job.v1";
|
|
|
339
340
|
var REQUEST_KEY_PROOF_FORM = "verdifax.request-key.v1";
|
|
340
341
|
var REQUEST_KEY_CHANGE_FORM = "verdifax.request-key-change.v1";
|
|
341
342
|
var POLICY_ACTIVATION_FORM = "verdifax.policy-activation.v1";
|
|
343
|
+
var SIGNOFF_FORM = "verdifax.signoff.v1";
|
|
344
|
+
var APPROVER_KEY_PROOF_FORM = "verdifax.approver-key.v1";
|
|
342
345
|
var SIGNING_KEY_BYTES = 32;
|
|
346
|
+
var SPKI_ED25519_HEADER = Buffer.from("302a300506032b6570032100", "hex");
|
|
343
347
|
var PKCS8_ED25519_HEADER = Buffer.from(
|
|
344
348
|
"302e020100300506032b657004220420",
|
|
345
349
|
"hex"
|
|
@@ -523,6 +527,71 @@ function policyActivationApproval(signingKey, fields, options = {}) {
|
|
|
523
527
|
nonce
|
|
524
528
|
};
|
|
525
529
|
}
|
|
530
|
+
function signOffPreimage(fields) {
|
|
531
|
+
const { target } = fields;
|
|
532
|
+
return Buffer.from(
|
|
533
|
+
[
|
|
534
|
+
SIGNOFF_FORM,
|
|
535
|
+
fields.chain_id,
|
|
536
|
+
fields.previous_signoff_hash,
|
|
537
|
+
target.kind,
|
|
538
|
+
String(target.run_id),
|
|
539
|
+
target.digest,
|
|
540
|
+
target.job_id ?? "",
|
|
541
|
+
fields.decision,
|
|
542
|
+
fields.note_hash,
|
|
543
|
+
fields.signed_at,
|
|
544
|
+
fields.nonce
|
|
545
|
+
].join("\0"),
|
|
546
|
+
"utf8"
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
function signOffSignature(approverKey, fields, options = {}) {
|
|
550
|
+
const key = loadSigningKey(approverKey);
|
|
551
|
+
const signed_at = options.signedAt ?? signedAtNow();
|
|
552
|
+
const nonce = options.nonce ?? randomBytes(16).toString("hex");
|
|
553
|
+
const preimage = signOffPreimage({ ...fields, signed_at, nonce });
|
|
554
|
+
return {
|
|
555
|
+
approver_key: rawPublicKey(createPublicKey(key)).toString("base64"),
|
|
556
|
+
signature: sign(null, preimage, key).toString("base64"),
|
|
557
|
+
signed_at,
|
|
558
|
+
nonce
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
function approverKeyProof(approverKey) {
|
|
562
|
+
const key = loadSigningKey(approverKey);
|
|
563
|
+
const public_key = rawPublicKey(createPublicKey(key)).toString("base64");
|
|
564
|
+
const proof = sign(
|
|
565
|
+
null,
|
|
566
|
+
Buffer.from(`${APPROVER_KEY_PROOF_FORM}\0${public_key}`, "utf8"),
|
|
567
|
+
key
|
|
568
|
+
).toString("base64");
|
|
569
|
+
return { public_key, proof };
|
|
570
|
+
}
|
|
571
|
+
function approverKeyProofVerifies(proof) {
|
|
572
|
+
if (typeof proof?.public_key !== "string" || typeof proof?.proof !== "string") {
|
|
573
|
+
return false;
|
|
574
|
+
}
|
|
575
|
+
const raw = Buffer.from(proof.public_key, "base64");
|
|
576
|
+
const signature = Buffer.from(proof.proof, "base64");
|
|
577
|
+
if (raw.length !== 32 || raw.toString("base64") !== proof.public_key || signature.length !== 64 || signature.toString("base64") !== proof.proof) {
|
|
578
|
+
return false;
|
|
579
|
+
}
|
|
580
|
+
const publicKey = createPublicKey({
|
|
581
|
+
key: Buffer.concat([SPKI_ED25519_HEADER, raw]),
|
|
582
|
+
format: "der",
|
|
583
|
+
type: "spki"
|
|
584
|
+
});
|
|
585
|
+
return verify(
|
|
586
|
+
null,
|
|
587
|
+
Buffer.from(`${APPROVER_KEY_PROOF_FORM}\0${proof.public_key}`, "utf8"),
|
|
588
|
+
publicKey,
|
|
589
|
+
signature
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
function publicKeyOf(key) {
|
|
593
|
+
return rawPublicKey(createPublicKey(key)).toString("base64");
|
|
594
|
+
}
|
|
526
595
|
|
|
527
596
|
// src/job.ts
|
|
528
597
|
var STEP_OPTIONS = {
|
|
@@ -624,9 +693,10 @@ var VerdifaxJob = class {
|
|
|
624
693
|
};
|
|
625
694
|
|
|
626
695
|
// src/version.ts
|
|
627
|
-
var SDK_VERSION = "1.
|
|
696
|
+
var SDK_VERSION = "1.4.0";
|
|
628
697
|
|
|
629
698
|
// src/client.ts
|
|
699
|
+
import { KeyObject as KeyObject2 } from "crypto";
|
|
630
700
|
var DEFAULT_BASE_URL = "https://api.verdifax.com";
|
|
631
701
|
var DEFAULT_TIMEOUT_MS = 9e4;
|
|
632
702
|
var DEFAULT_MAX_RETRIES = 3;
|
|
@@ -639,6 +709,12 @@ var ACCOUNT_PATH = "/account";
|
|
|
639
709
|
var ACCOUNT_REQUEST_KEY_PATH = "/account/request-key";
|
|
640
710
|
var ACCOUNT_POLICIES_PATH = "/account/policies";
|
|
641
711
|
var ACCOUNT_CHAIN_ID = /^account-[1-9][0-9]*$/;
|
|
712
|
+
var SIGNOFFS_PATH = "/signoffs";
|
|
713
|
+
var ACCOUNT_SIGNOFFS_PATH = "/account/signoffs";
|
|
714
|
+
var APPROVERS_PATH = "/account/approvers";
|
|
715
|
+
var SIGNOFF_TARGET_KINDS = /* @__PURE__ */ new Set(["record", "receipt", "job"]);
|
|
716
|
+
var SIGNOFF_DECISIONS = /* @__PURE__ */ new Set(["approve", "reject"]);
|
|
717
|
+
var MAX_APPROVER_NAME_BYTES = 200;
|
|
642
718
|
var HEADER_EXPORT_TOTAL = "X-Verdifax-Total";
|
|
643
719
|
var HEADER_EXPORT_TRUNCATED = "X-Verdifax-Truncated";
|
|
644
720
|
var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
|
|
@@ -1145,6 +1221,174 @@ var VerdifaxClient = class {
|
|
|
1145
1221
|
}
|
|
1146
1222
|
return this.activatePolicyAt(ACCOUNT_POLICIES_PATH, documentHash, chain);
|
|
1147
1223
|
}
|
|
1224
|
+
// ── Sign-off ─────────────────────────────────────────────────
|
|
1225
|
+
//
|
|
1226
|
+
// An account's owner registers approver keys. An approver signs a
|
|
1227
|
+
// decision, approve or reject, on one exact result on their own machine,
|
|
1228
|
+
// and any key in the account, or the owner key with asOwner, submits it.
|
|
1229
|
+
// A sign-off shows that the holder of a registered approver key signed
|
|
1230
|
+
// that decision on that exact result and that Verdifax logged it on the
|
|
1231
|
+
// public log. It does not show the review was careful, the result right,
|
|
1232
|
+
// or who the person is, and it gates nothing on its own.
|
|
1233
|
+
/** Registers an approver key to the owner's account. `approver` is the
|
|
1234
|
+
* approver's signing key, from which the proof is made on this machine,
|
|
1235
|
+
* or the `{ public_key, proof }` that {@link approverKeyProof} made on
|
|
1236
|
+
* the approver's own machine, so the owner need not hold the private
|
|
1237
|
+
* key. A proof that does not verify is refused before sending. The
|
|
1238
|
+
* server refuses a key that is a request key in the same account.
|
|
1239
|
+
* `name` is a label, not an identity. */
|
|
1240
|
+
async registerApprover(name, approver) {
|
|
1241
|
+
this.requireAPIKey();
|
|
1242
|
+
if (typeof name !== "string" || name.trim() === "" || Buffer.byteLength(name.trim(), "utf8") > MAX_APPROVER_NAME_BYTES) {
|
|
1243
|
+
throw new VerdifaxError(
|
|
1244
|
+
"verdifax: registerApprover needs a name of at most 200 bytes"
|
|
1245
|
+
);
|
|
1246
|
+
}
|
|
1247
|
+
const body = approver instanceof Uint8Array || approver instanceof KeyObject2 ? approverKeyProof(approver) : { public_key: approver?.public_key, proof: approver?.proof };
|
|
1248
|
+
if (!approverKeyProofVerifies(body)) {
|
|
1249
|
+
throw new VerdifaxError(
|
|
1250
|
+
"verdifax: registerApprover needs the approver's signing key, or a public_key and proof from approverKeyProof that verify"
|
|
1251
|
+
);
|
|
1252
|
+
}
|
|
1253
|
+
if (this.signingKey && publicKeyOf(this.signingKey) === body.public_key) {
|
|
1254
|
+
throw new VerdifaxError(
|
|
1255
|
+
"verdifax: an approver key must not be a request key in the same account, and this is the client's signingKey"
|
|
1256
|
+
);
|
|
1257
|
+
}
|
|
1258
|
+
return this.request("POST", APPROVERS_PATH, {
|
|
1259
|
+
public_key: body.public_key,
|
|
1260
|
+
name,
|
|
1261
|
+
proof: body.proof
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
/** The account's approver keys, revoked ones included. */
|
|
1265
|
+
async listApprovers() {
|
|
1266
|
+
this.requireAPIKey();
|
|
1267
|
+
const out = await this.request("GET", APPROVERS_PATH);
|
|
1268
|
+
return { ...out, approvers: out?.approvers ?? [] };
|
|
1269
|
+
}
|
|
1270
|
+
/** Revokes one of the account's approver keys. Its earlier sign-offs
|
|
1271
|
+
* stay on the record. A key that is not an active approver in the
|
|
1272
|
+
* account throws {@link NotFoundError}. */
|
|
1273
|
+
async revokeApprover(approverId) {
|
|
1274
|
+
this.requireAPIKey();
|
|
1275
|
+
requirePositiveId(approverId, "approverId");
|
|
1276
|
+
return this.request(
|
|
1277
|
+
"DELETE",
|
|
1278
|
+
`${APPROVERS_PATH}/${approverId}`
|
|
1279
|
+
);
|
|
1280
|
+
}
|
|
1281
|
+
/** What an approver signs against: the account's chain `account-N` and
|
|
1282
|
+
* its newest sign-off (64 zeros when there is none yet). With
|
|
1283
|
+
* `asOwner` it reads the owner key's route. */
|
|
1284
|
+
async getNewestSignOff(options = {}) {
|
|
1285
|
+
this.requireAPIKey();
|
|
1286
|
+
return this.request(
|
|
1287
|
+
"GET",
|
|
1288
|
+
`${signOffsBase(options, "getNewestSignOff")}/newest`
|
|
1289
|
+
);
|
|
1290
|
+
}
|
|
1291
|
+
/** Signs an approver's decision, approve or reject, on one exact result
|
|
1292
|
+
* and submits it. It first reads {@link getNewestSignOff} for the
|
|
1293
|
+
* account's chain and its newest sign-off, then signs over them with
|
|
1294
|
+
* `approverKey` on this machine. The approver's private key is never
|
|
1295
|
+
* sent.
|
|
1296
|
+
*
|
|
1297
|
+
* `target.digest` is what the result's public-log entry covers: for a
|
|
1298
|
+
* record, the `bundle_anchor.anchored_digest` of the bundle
|
|
1299
|
+
* {@link getRunArtifacts} returns; for a receipt, its hash; for a job,
|
|
1300
|
+
* the final step's record digest, read as a record's, with `job_id`.
|
|
1301
|
+
*
|
|
1302
|
+
* `note` is hashed here and only its SHA-256 is sent and signed: the
|
|
1303
|
+
* text never reaches Verdifax. With `asOwner` it uses the owner key's
|
|
1304
|
+
* routes. Bad input is refused before anything is sent.
|
|
1305
|
+
*
|
|
1306
|
+
* The record the server returns must carry the signature,
|
|
1307
|
+
* approver_key, decision, previous_signoff_hash and target that were
|
|
1308
|
+
* sent, or this throws {@link VerdifaxError}.
|
|
1309
|
+
*
|
|
1310
|
+
* A later sign-off on the same result supersedes this one, and neither
|
|
1311
|
+
* is deleted. An HTTP 409 means the chain moved or the target is not
|
|
1312
|
+
* this account's result: it throws the usual {@link APIError} and is
|
|
1313
|
+
* not retried. */
|
|
1314
|
+
async signOff(target, decision, approverKey, options = {}) {
|
|
1315
|
+
this.requireAPIKey();
|
|
1316
|
+
const base = signOffsBase(options, "signOff");
|
|
1317
|
+
const exact = signOffTargetOf(target);
|
|
1318
|
+
if (!SIGNOFF_DECISIONS.has(decision)) {
|
|
1319
|
+
throw new VerdifaxError(
|
|
1320
|
+
"verdifax: signOff decision must be approve or reject"
|
|
1321
|
+
);
|
|
1322
|
+
}
|
|
1323
|
+
const note = options?.note;
|
|
1324
|
+
if (note !== void 0 && (typeof note !== "string" || note === "")) {
|
|
1325
|
+
throw new VerdifaxError(
|
|
1326
|
+
"verdifax: signOff note must be non-empty text when given"
|
|
1327
|
+
);
|
|
1328
|
+
}
|
|
1329
|
+
const key = loadSigningKey(approverKey);
|
|
1330
|
+
const note_hash = note === void 0 ? "" : sha256Hex(note);
|
|
1331
|
+
const newest = await this.getNewestSignOff(options);
|
|
1332
|
+
if (typeof newest?.chain_id !== "string" || !ACCOUNT_CHAIN_ID.test(newest.chain_id)) {
|
|
1333
|
+
throw new VerdifaxError(
|
|
1334
|
+
"verdifax: the server did not report an account chain_id (account-N), so the sign-off cannot be signed"
|
|
1335
|
+
);
|
|
1336
|
+
}
|
|
1337
|
+
const previous = newest.newest_signoff_hash;
|
|
1338
|
+
if (typeof previous !== "string" || !/^[0-9a-f]{64}$/.test(previous)) {
|
|
1339
|
+
throw new VerdifaxError(
|
|
1340
|
+
"verdifax: the server did not report newest_signoff_hash, so the sign-off cannot name the one it follows"
|
|
1341
|
+
);
|
|
1342
|
+
}
|
|
1343
|
+
const signed = signOffSignature(key, {
|
|
1344
|
+
chain_id: newest.chain_id,
|
|
1345
|
+
previous_signoff_hash: previous,
|
|
1346
|
+
target: exact,
|
|
1347
|
+
decision,
|
|
1348
|
+
note_hash
|
|
1349
|
+
});
|
|
1350
|
+
const body = {
|
|
1351
|
+
previous_signoff_hash: previous,
|
|
1352
|
+
target: exact,
|
|
1353
|
+
decision,
|
|
1354
|
+
...note_hash ? { note_hash } : {},
|
|
1355
|
+
...signed
|
|
1356
|
+
};
|
|
1357
|
+
const out = await this.request(
|
|
1358
|
+
"POST",
|
|
1359
|
+
base,
|
|
1360
|
+
body
|
|
1361
|
+
);
|
|
1362
|
+
const record = out?.signoff;
|
|
1363
|
+
if (!recordMatchesSent(record, exact, decision, previous, signed)) {
|
|
1364
|
+
throw new VerdifaxError(
|
|
1365
|
+
"verdifax: the sign-off the server returned does not carry the signature, approver_key, decision, previous_signoff_hash and target that were sent"
|
|
1366
|
+
);
|
|
1367
|
+
}
|
|
1368
|
+
return record;
|
|
1369
|
+
}
|
|
1370
|
+
/** The sign-offs on one run, oldest first, in the form
|
|
1371
|
+
* `verdifax-verify --signoff FILE` reads: for a member key, a run it
|
|
1372
|
+
* made; with `asOwner`, any run of the account. The latest is the
|
|
1373
|
+
* decision in force. */
|
|
1374
|
+
async getSignOffs(runId, options = {}) {
|
|
1375
|
+
this.requireAPIKey();
|
|
1376
|
+
requirePositiveId(runId, "runId");
|
|
1377
|
+
const base = asOwnerOf(options, "getSignOffs") ? `${ACCOUNT_PATH}/runs` : "/runs";
|
|
1378
|
+
return await this.request(
|
|
1379
|
+
"GET",
|
|
1380
|
+
`${base}/${runId}/signoffs`
|
|
1381
|
+
) ?? [];
|
|
1382
|
+
}
|
|
1383
|
+
/** The account's whole chain of sign-offs, oldest first, in the form
|
|
1384
|
+
* `verdifax-verify --signoff-history FILE` reads. Needs the owner key. */
|
|
1385
|
+
async getSignOffHistory() {
|
|
1386
|
+
this.requireAPIKey();
|
|
1387
|
+
return await this.request(
|
|
1388
|
+
"GET",
|
|
1389
|
+
`${ACCOUNT_SIGNOFFS_PATH}/history`
|
|
1390
|
+
) ?? [];
|
|
1391
|
+
}
|
|
1148
1392
|
// ── Runs ──────────────────────────────────────────────────────
|
|
1149
1393
|
async getRun(runId) {
|
|
1150
1394
|
this.requireAPIKey();
|
|
@@ -1598,6 +1842,52 @@ var VerdifaxClient = class {
|
|
|
1598
1842
|
return this.signingKey ? signRequestV2(this.signingKey, req) : withExecuteFlags(req);
|
|
1599
1843
|
}
|
|
1600
1844
|
};
|
|
1845
|
+
function signOffsBase(options, method) {
|
|
1846
|
+
return asOwnerOf(options, method) ? ACCOUNT_SIGNOFFS_PATH : SIGNOFFS_PATH;
|
|
1847
|
+
}
|
|
1848
|
+
function asOwnerOf(options, method) {
|
|
1849
|
+
const asOwner = options?.asOwner;
|
|
1850
|
+
if (asOwner !== void 0 && typeof asOwner !== "boolean") {
|
|
1851
|
+
throw new VerdifaxError(`verdifax: ${method} asOwner must be true or false`);
|
|
1852
|
+
}
|
|
1853
|
+
return asOwner === true;
|
|
1854
|
+
}
|
|
1855
|
+
function signOffTargetOf(target) {
|
|
1856
|
+
if (typeof target !== "object" || target === null || Array.isArray(target)) {
|
|
1857
|
+
throw new VerdifaxError("verdifax: signOff needs a target object");
|
|
1858
|
+
}
|
|
1859
|
+
for (const name of Object.keys(target)) {
|
|
1860
|
+
if (!["kind", "run_id", "digest", "job_id"].includes(name)) {
|
|
1861
|
+
throw new VerdifaxError(`verdifax: signOff target has an unknown field: ${name}`);
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
if (!SIGNOFF_TARGET_KINDS.has(target.kind)) {
|
|
1865
|
+
throw new VerdifaxError(
|
|
1866
|
+
"verdifax: signOff target kind must be record, receipt or job"
|
|
1867
|
+
);
|
|
1868
|
+
}
|
|
1869
|
+
requirePositiveId(target.run_id, "target.run_id");
|
|
1870
|
+
validateHex64(target.digest, "target.digest");
|
|
1871
|
+
if (target.kind === "job") {
|
|
1872
|
+
validateJobName(target.job_id, "target.job_id");
|
|
1873
|
+
return {
|
|
1874
|
+
kind: target.kind,
|
|
1875
|
+
run_id: target.run_id,
|
|
1876
|
+
digest: target.digest,
|
|
1877
|
+
job_id: target.job_id
|
|
1878
|
+
};
|
|
1879
|
+
}
|
|
1880
|
+
if (target.job_id !== void 0) {
|
|
1881
|
+
throw new VerdifaxError(
|
|
1882
|
+
"verdifax: signOff target job_id is for a job target only"
|
|
1883
|
+
);
|
|
1884
|
+
}
|
|
1885
|
+
return { kind: target.kind, run_id: target.run_id, digest: target.digest };
|
|
1886
|
+
}
|
|
1887
|
+
function recordMatchesSent(record, target, decision, previous, signed) {
|
|
1888
|
+
const got = record?.target;
|
|
1889
|
+
return typeof record === "object" && record !== null && record.signature === signed.signature && record.approver_key === signed.approver_key && record.decision === decision && record.previous_signoff_hash === previous && typeof got === "object" && got !== null && got.kind === target.kind && got.run_id === target.run_id && got.digest === target.digest && (got.job_id ?? "") === (target.job_id ?? "");
|
|
1890
|
+
}
|
|
1601
1891
|
function requirePositiveId(id, name) {
|
|
1602
1892
|
if (!Number.isSafeInteger(id) || id <= 0) {
|
|
1603
1893
|
throw new VerdifaxError(`verdifax: ${name} must be a whole number > 0`);
|
|
@@ -1803,6 +2093,7 @@ export {
|
|
|
1803
2093
|
VerdifaxClient,
|
|
1804
2094
|
VerdifaxError,
|
|
1805
2095
|
VerdifaxJob,
|
|
2096
|
+
approverKeyProof,
|
|
1806
2097
|
captureEnvironment,
|
|
1807
2098
|
cresReceiptHash,
|
|
1808
2099
|
dcaeClosureHash,
|
|
@@ -1817,6 +2108,7 @@ export {
|
|
|
1817
2108
|
requestKeyRegistrationBody,
|
|
1818
2109
|
requestSignaturePreimage,
|
|
1819
2110
|
requestSignaturePreimageV2,
|
|
2111
|
+
signOffPreimage,
|
|
1820
2112
|
signRequest,
|
|
1821
2113
|
signRequestV2,
|
|
1822
2114
|
validateHex64,
|