@mikeargento/bitgraph-verify 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/types.ts ADDED
@@ -0,0 +1,778 @@
1
+ // Copyright (c) 2024-2026 Mike Argento. Licensed under the MIT License. See LICENSE.
2
+
3
+ /**
4
+ * bitgraph-core types
5
+ *
6
+ * All public-facing data structures for the BitGraph proof system.
7
+ * This file intentionally contains no logic.
8
+ */
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Enforcement tier
12
+ // ---------------------------------------------------------------------------
13
+
14
+ /**
15
+ * Declares the enforcement tier of the host adapter that produced a proof.
16
+ *
17
+ * This field is SIGNED and therefore tamper-evident in transit.
18
+ * However, it is self-reported by the adapter and MUST NOT be treated as
19
+ * self-authenticating by verifiers.
20
+ *
21
+ * Trust is established by verifier policy (allowedMeasurements +
22
+ * attestation chain validation), not by this field alone.
23
+ *
24
+ * Tier semantics:
25
+ *
26
+ * "stub"
27
+ * Software-only. Key lives in process memory. No hardware protection.
28
+ * Suitable for development, testing, and local demos only.
29
+ * measurement MAY be a synthetic sentinel value.
30
+ *
31
+ * "hw-key"
32
+ * Hardware-protected signing key (Secure Enclave / TPM class).
33
+ * The key is non-exportable from the hardware boundary.
34
+ * However, the commit gate runs outside the measured boundary —
35
+ * the host feeds digests to the hardware for signing.
36
+ * This provides hardware-bound identity but NOT causal enforcement.
37
+ * measurement SHOULD identify the key environment.
38
+ *
39
+ * "measured-tee"
40
+ * Full causal enforcement. The commit gate, key management, nonce
41
+ * generation, monotonic counter, and signing all execute inside the
42
+ * attested enclave boundary. The host is treated as untrusted.
43
+ * Satisfies BitGraph's atomic causality invariant when combined with a
44
+ * verifier that pins allowedMeasurements to a known-good enclave image.
45
+ * measurement MUST identify the attested enclave image.
46
+ * attestation SHOULD be present and verified by the relying party.
47
+ *
48
+ * IMPORTANT: Signing this field prevents downgrade tampering in transit
49
+ * but does NOT prevent a malicious adapter from lying about its tier.
50
+ * A verifier requiring "measured-tee" guarantees MUST independently
51
+ * validate measurement and attestation — not rely on this field alone.
52
+ */
53
+ export type EnforcementTier = "stub" | "hw-key" | "measured-tee";
54
+
55
+ // ---------------------------------------------------------------------------
56
+ // Proof schema (v1)
57
+ // ---------------------------------------------------------------------------
58
+
59
+ /**
60
+ * A fully self-contained, verifiable commit proof.
61
+ *
62
+ * Two orthogonal facts are encoded:
63
+ *
64
+ * (A) Cryptographic fact — proven by signer.publicKeyB64 + signer.signatureB64
65
+ * Answers: "Who signed?" — objective, machine-checkable.
66
+ *
67
+ * (B) Enforcement fact — proven by environment.measurement + environment.attestation
68
+ * + verifier allowlist policy.
69
+ * Answers: "Under what enforced conditions was signing allowed?"
70
+ * Atomic causality lives here, not in (A).
71
+ *
72
+ * Signed body covers:
73
+ * version, artifact, commit, signer.publicKeyB64,
74
+ * environment.enforcement, environment.measurement,
75
+ * environment.attestation.format (when present),
76
+ * agency.actor identity (when agency is present)
77
+ *
78
+ * Outside the signature (advisory / vendor-signed / independently verifiable):
79
+ * signer.signatureB64, environment.attestation.reportB64,
80
+ * agency.authorization (P-256 signed, independently verifiable), metadata
81
+ */
82
+ export interface BitGraphProof {
83
+ /** Schema version. Hard-coded for forward-compatibility detection. */
84
+ version: "bitgraph/1";
85
+
86
+ /** Describes the committed artifact. */
87
+ artifact: {
88
+ /**
89
+ * Hash algorithm applied to the raw input bytes.
90
+ * Only "sha256" is defined for bitgraph/1.
91
+ */
92
+ hashAlg: "sha256";
93
+ /** Base64-standard (RFC 4648 §4) encoded SHA-256 digest of the input bytes. */
94
+ digestB64: string;
95
+ };
96
+
97
+ /** Describes the commit context that ensures uniqueness and ordering. */
98
+ commit: {
99
+ /** Base64-encoded boundary-fresh nonce produced by the host. */
100
+ nonceB64: string;
101
+ /**
102
+ * Monotonic counter value at commit time.
103
+ * Decimal string (no leading zeros unless value is "0", no leading +,
104
+ * ASCII digits only) to avoid IEEE-754 precision loss.
105
+ * Compared as BigInt by the verifier.
106
+ * Present only when the host implements nextCounter().
107
+ */
108
+ counter?: string;
109
+ /**
110
+ * Unix epoch milliseconds at commit time from a TEE-trusted clock.
111
+ * Advisory — software clocks may be skewed. Use counter for ordering.
112
+ * Present only when the host implements secureTime().
113
+ */
114
+ time?: number;
115
+ /**
116
+ * Base64-encoded hash of a previous BitGraphProof's canonical form,
117
+ * allowing callers to chain proofs into a verifiable sequence.
118
+ */
119
+ prevB64?: string;
120
+ /**
121
+ * Opaque identifier for the enclave lifecycle (epoch).
122
+ * Generated at enclave boot as SHA-256(publicKeyB64 + ":" + bootNonceB64).
123
+ * Unique per enclave lifecycle. Changes on every restart.
124
+ *
125
+ * Verifiers use this to detect epoch boundaries: when epochId changes,
126
+ * a new enclave lifecycle has begun (new keypair, counter may reset).
127
+ *
128
+ * Included in the signed body (via commit) — tamper-evident.
129
+ */
130
+ epochId?: string;
131
+ /**
132
+ * Counter value of the consumed slot allocation.
133
+ *
134
+ * When present, proves causal ordering: slotCounter < counter means
135
+ * the slot was allocated before the commit occurred. Combined with
136
+ * slotHashB64 and the embedded slotAllocation record, this provides
137
+ * structural evidence of nonce-first causality.
138
+ *
139
+ * Decimal string, compared as BigInt by the verifier.
140
+ */
141
+ slotCounter?: string;
142
+ /**
143
+ * SHA-256 hash of the canonical slot allocation body (Base64-encoded).
144
+ *
145
+ * Cryptographically binds the commit to the exact slot allocation record.
146
+ * The slot body is canonicalized (sorted keys, compact JSON, UTF-8) and
147
+ * hashed. This field is SIGNED (included in the signed body), so the
148
+ * Ed25519 commit signature covers the binding.
149
+ *
150
+ * A verifier reconstructs the slot body from slotAllocation, canonicalizes,
151
+ * hashes, and checks: SHA-256(canonicalize(slotBody)) === slotHashB64.
152
+ * This prevents slot swapping — any change to the slot record would
153
+ * break the commit signature.
154
+ */
155
+ slotHashB64?: string;
156
+
157
+ /**
158
+ * Epoch lineage link — present only on the FIRST proof of a new epoch.
159
+ *
160
+ * Establishes cryptographic succession: this epoch consumed the final
161
+ * proof of the previous epoch. The referenced proof's hash, signer,
162
+ * epochId, and counter are all included so a verifier can:
163
+ * 1. Confirm the predecessor proof is valid
164
+ * 2. Confirm prevProofHashB64 matches its canonical hash
165
+ * 3. Confirm prevPublicKeyB64 matches its signer
166
+ * 4. Detect forks (same predecessor consumed by multiple successors)
167
+ *
168
+ * New epochs continue lineage, not counters. Each epoch maintains its
169
+ * own local monotonic counter starting fresh.
170
+ *
171
+ * Single-successor invariant: a given (prevEpochId, prevCounter,
172
+ * prevProofHashB64) tuple MUST be consumed by AT MOST ONE successor
173
+ * epoch. Multiple epoch-genesis proofs referencing the same predecessor
174
+ * constitute a detectable fork.
175
+ */
176
+ epochLink?: {
177
+ /** EpochId of the predecessor epoch. */
178
+ prevEpochId: string;
179
+ /** Ed25519 public key of the predecessor epoch's signer. */
180
+ prevPublicKeyB64: string;
181
+ /** Final counter value from the predecessor epoch. */
182
+ prevCounter: string;
183
+ /** SHA-256 hash of the full predecessor proof (canonicalized). */
184
+ prevProofHashB64: string;
185
+ /** EpochId of THIS (successor) epoch — must match proof's commit.epochId. */
186
+ toEpochId: string;
187
+ /** Ed25519 public key of THIS (successor) epoch — must match proof's signer.publicKeyB64. */
188
+ toPublicKeyB64: string;
189
+ };
190
+ };
191
+
192
+ /**
193
+ * Cryptographic identity of the signer.
194
+ * Contains only the signing keypair — who signed.
195
+ * Enforcement context lives in environment, not here.
196
+ */
197
+ signer: {
198
+ /** Base64-encoded Ed25519 public key (32 bytes). */
199
+ publicKeyB64: string;
200
+ /**
201
+ * Base64-encoded Ed25519 signature (64 bytes) over the canonical
202
+ * serialization of the signed body.
203
+ * NOT included in the signed body itself.
204
+ */
205
+ signatureB64: string;
206
+ };
207
+
208
+ /**
209
+ * Enforcement context — under what conditions signing was allowed.
210
+ *
211
+ * enforcement and measurement are included in the signed body,
212
+ * making them tamper-evident. attestation.reportB64 is excluded
213
+ * from the signature because it is a vendor-signed document that
214
+ * self-authenticates.
215
+ */
216
+ environment: {
217
+ /**
218
+ * Self-reported enforcement tier.
219
+ * SIGNED — tamper-evident in transit but not self-authenticating.
220
+ * Verifiers MUST NOT treat this as sufficient evidence of tier.
221
+ */
222
+ enforcement: EnforcementTier;
223
+ /**
224
+ * Platform-specific measurement string.
225
+ * - stub: MAY be a synthetic sentinel
226
+ * - hw-key: SHOULD identify the key environment
227
+ * - measured-tee: MUST identify the attested enclave image
228
+ * SIGNED — pinned by verifier allowedMeasurements policy.
229
+ */
230
+ measurement: string;
231
+ /**
232
+ * Optional platform attestation report.
233
+ * If present, format MUST be a non-empty string.
234
+ * reportB64 is excluded from the signature (vendor-signed).
235
+ * attestation.format IS included in the signed body.
236
+ */
237
+ attestation?: {
238
+ /**
239
+ * Identifies the attestation document format.
240
+ * e.g. "aws-nitro", "sgx-dcap", "amd-sev-snp"
241
+ * SIGNED — prevents semantic ambiguity via format rewrite.
242
+ */
243
+ format: string;
244
+ /**
245
+ * Base64-encoded raw attestation report bytes.
246
+ * NOT signed — vendor-signed and self-authenticating.
247
+ * Platform-specific verifiers must parse and validate this.
248
+ */
249
+ reportB64: string;
250
+ };
251
+ };
252
+
253
+ /**
254
+ * Optional external timestamps (e.g. RFC 3161 TSA tokens).
255
+ * NOT included in the signed body. Independently verifiable.
256
+ *
257
+ * When present, each entry contains a TSA authority, ISO 8601 time,
258
+ * DER-encoded token (base64), digest algorithm, and the digest
259
+ * that was timestamped. The "artifact" timestamp covers the artifact
260
+ * digest; the "proof" timestamp covers the proof's canonical hash.
261
+ */
262
+ timestamps?: {
263
+ artifact?: {
264
+ authority: string;
265
+ time: string;
266
+ tokenB64: string;
267
+ digestAlg: string;
268
+ digestB64: string;
269
+ };
270
+ proof?: {
271
+ authority: string;
272
+ time: string;
273
+ tokenB64: string;
274
+ digestAlg: string;
275
+ digestB64: string;
276
+ };
277
+ };
278
+
279
+ /**
280
+ * Optional agency envelope — binds actor identity to this proof.
281
+ *
282
+ * When present, proves WHO authorized the commitment:
283
+ * - actor: device-bound identity (Secure Enclave P-256 key)
284
+ * - authorization: the possession commitment the actor signed
285
+ * (artifact hash + enclave-issued challenge + purpose + timestamp)
286
+ *
287
+ * Two independent signatures in the proof:
288
+ * - P-256 ECDSA (device Secure Enclave) → proves WHO authorized it
289
+ * - Ed25519 (Nitro Enclave) → proves it was committed inside the TEE
290
+ *
291
+ * The actor identity summary (keyId, publicKeyB64, algorithm, provider)
292
+ * is also included in the signed body (via SignedBody.actor), making it
293
+ * tamper-evident under the enclave's Ed25519 signature.
294
+ *
295
+ * The full authorization envelope (including the device's P-256 signature)
296
+ * lives here, outside the Ed25519 signed body — like attestation.reportB64,
297
+ * it is independently verifiable.
298
+ *
299
+ * NOT included in the Ed25519 signed body (independently verifiable).
300
+ */
301
+ agency?: AgencyEnvelope;
302
+
303
+ /**
304
+ * Embedded slot allocation record proving nonce-first causality.
305
+ *
306
+ * When present, this is a self-contained signed record that the enclave
307
+ * created BEFORE any artifact hash was known. The verifier checks:
308
+ * 1. Slot signature valid (enclave created it)
309
+ * 2. Slot body has no artifact hash (causal independence)
310
+ * 3. Slot nonce == commit nonce (binding)
311
+ * 4. Slot counter < commit counter (ordering)
312
+ * 5. Same key and epoch (same enclave)
313
+ *
314
+ * NOT included in the Ed25519 signed commit body — independently
315
+ * verifiable via its own Ed25519 signature (same pattern as
316
+ * attestation.reportB64 and agency.authorization).
317
+ */
318
+ slotAllocation?: SlotAllocation;
319
+
320
+ /**
321
+ * Optional policy binding — cryptographic proof that a specific policy
322
+ * document governed this action.
323
+ *
324
+ * Contains the SHA-256 hash of the policy document, plus optional
325
+ * human-readable name and version. INCLUDED in the Ed25519 signed body
326
+ * so the policy binding is tamper-evident and cryptographically sealed.
327
+ *
328
+ * Verifiers can confirm the policy hash matches a known-good policy
329
+ * document, establishing what rules the agent was operating under.
330
+ */
331
+ policy?: PolicyBinding;
332
+
333
+ /**
334
+ * Optional human-readable attribution claim.
335
+ *
336
+ * A free-form human claim associated with the artifact and commit event.
337
+ * INCLUDED in the Ed25519 signed body — cryptographically sealed and
338
+ * tamper-evident. Cannot be modified after the proof is created.
339
+ *
340
+ * This is a claim, not a guaranteed identity. It complements (does not
341
+ * replace) the cryptographic actor key in agency.
342
+ *
343
+ * All fields are optional. If no fields are provided, omit the object.
344
+ */
345
+ attribution?: Attribution;
346
+
347
+ /**
348
+ * Caller-supplied metadata key/value pairs.
349
+ * NOT included in the signed body. Treat as advisory only.
350
+ */
351
+ metadata?: Record<string, unknown>;
352
+ }
353
+
354
+ // ---------------------------------------------------------------------------
355
+ // Policy types
356
+ // ---------------------------------------------------------------------------
357
+
358
+ /**
359
+ * Policy applied during Constructor initialization.
360
+ */
361
+ export interface BitGraphPolicy {
362
+ /**
363
+ * If true, commits are rejected when host.nextCounter() is unavailable.
364
+ * Defaults to false (counter is advisory).
365
+ */
366
+ requireCounter?: boolean;
367
+
368
+ /**
369
+ * If true, commits are rejected when host.secureTime() is unavailable.
370
+ * Defaults to false.
371
+ */
372
+ requireTime?: boolean;
373
+ }
374
+
375
+ /**
376
+ * Constraints checked by the verifier against a BitGraphProof.
377
+ * All fields are optional; omitting a field skips that check.
378
+ *
379
+ * Trust model:
380
+ * - Cryptographic identity is proven by signature verification.
381
+ * - Enforcement guarantees are proven by measurement + attestation policy.
382
+ * - requireEnforcement alone is NOT sufficient for security — it must
383
+ * be combined with allowedMeasurements (and requireAttestation for
384
+ * measured-tee) to establish actual trust.
385
+ */
386
+ export interface VerificationPolicy {
387
+ /**
388
+ * Require a specific enforcement tier.
389
+ * MUST be combined with allowedMeasurements for security.
390
+ * This field is tamper-evident (signed) but not self-authenticating.
391
+ */
392
+ requireEnforcement?: EnforcementTier;
393
+
394
+ /**
395
+ * Accepted measurement strings. The proof's environment.measurement
396
+ * must exactly match one of the listed values.
397
+ * This is the primary trust anchor for enforcement verification.
398
+ */
399
+ allowedMeasurements?: string[];
400
+
401
+ /**
402
+ * Accepted public keys (Base64-encoded). The proof's
403
+ * signer.publicKeyB64 must exactly match one of the listed values.
404
+ */
405
+ allowedPublicKeys?: string[];
406
+
407
+ /**
408
+ * If true, the proof must contain environment.attestation.
409
+ * Required for full measured-tee verification.
410
+ */
411
+ requireAttestation?: boolean;
412
+
413
+ /**
414
+ * If provided, environment.attestation.format must match one of
415
+ * the listed format strings (e.g. ["aws-nitro", "sgx-dcap"]).
416
+ * Only checked when attestation is present.
417
+ */
418
+ requireAttestationFormat?: string[];
419
+
420
+ /**
421
+ * If set, proof.commit.counter must be >= this value (as BigInt).
422
+ * Decimal string, compared as BigInt to avoid precision loss.
423
+ */
424
+ minCounter?: string;
425
+
426
+ /**
427
+ * If set, proof.commit.counter must be <= this value (as BigInt).
428
+ * Use with minCounter to enforce a sliding replay-resistance window.
429
+ */
430
+ maxCounter?: string;
431
+
432
+ /**
433
+ * If set, proof.commit.time must be >= this Unix ms value.
434
+ */
435
+ minTime?: number;
436
+
437
+ /**
438
+ * If set, proof.commit.time must be <= this Unix ms value.
439
+ * Use with minTime to enforce a validity window.
440
+ */
441
+ maxTime?: number;
442
+
443
+ /**
444
+ * If true, proof.commit.epochId must be present.
445
+ * Required for production deployments using the epoch model.
446
+ * Verifiers should track epochId to detect epoch boundaries
447
+ * (enclave restarts) and cross-reference with an external
448
+ * monotonic anchor (e.g., DynamoDB) for cross-epoch continuity.
449
+ */
450
+ requireEpochId?: boolean;
451
+
452
+ /**
453
+ * If true, proof.agency must be present with a valid actor identity
454
+ * and authorization signature. The proof must answer WHO authorized
455
+ * the commitment, not just WHAT and WHERE.
456
+ */
457
+ requireActor?: boolean;
458
+
459
+ /**
460
+ * Accepted actor key IDs. The proof's agency.actor.keyId must
461
+ * exactly match one of the listed values.
462
+ * Key ID is hex(SHA-256(SPKI DER public key bytes)) — stable,
463
+ * deterministic, derived from the raw key material.
464
+ */
465
+ allowedActorKeyIds?: string[];
466
+
467
+ /**
468
+ * Accepted actor key providers. The proof's agency.actor.provider
469
+ * must exactly match one of the listed values.
470
+ * e.g. ["apple-secure-enclave", "android-strongbox", "webauthn"]
471
+ */
472
+ allowedActorProviders?: string[];
473
+
474
+ /**
475
+ * If true, proof must contain a valid slotAllocation proving
476
+ * nonce-first atomic causality (BitGraph causal commit model).
477
+ *
478
+ * When enabled, the verifier checks:
479
+ * - slotAllocation is present with valid Ed25519 signature
480
+ * - slotAllocation contains no artifact data (causal independence)
481
+ * - slotAllocation.nonceB64 === commit.nonceB64 (binding)
482
+ * - slotAllocation.counter < commit.counter (ordering)
483
+ * - slotAllocation.publicKeyB64 === signer.publicKeyB64 (same enclave)
484
+ * - slotAllocation.epochId === commit.epochId (same lifecycle)
485
+ *
486
+ * Required for verifiers that need to confirm true BitGraph causality,
487
+ * not just TEE-enforced signing with freshness.
488
+ */
489
+ requireSlot?: boolean;
490
+ }
491
+
492
+ // ---------------------------------------------------------------------------
493
+ // Policy binding — cryptographic link between proof and governing policy
494
+ // ---------------------------------------------------------------------------
495
+
496
+ /**
497
+ * Binds a BitGraphProof to the policy document that governed the action.
498
+ *
499
+ * The digestB64 is the SHA-256 hash of the raw policy document bytes
500
+ * (UTF-8 encoded). Any verifier can recompute this from the original
501
+ * policy document to confirm the binding.
502
+ *
503
+ * Included in the Ed25519 signed body — tamper-evident and
504
+ * cryptographically sealed by the signer.
505
+ */
506
+ export interface PolicyBinding {
507
+ /** SHA-256 hash of the policy document (Base64-standard, RFC 4648 §4). */
508
+ digestB64: string;
509
+ /**
510
+ * Digest of the BitGraph proof that committed this policy document.
511
+ * When present, the policy was authored through BitGraph (typically with
512
+ * biometric/passkey signing), making the authorship tamper-evident.
513
+ * Verifiers can look up this proof to confirm WHO authored the rules
514
+ * and WHEN they were signed — not just what the rules say.
515
+ */
516
+ authorProofDigestB64?: string;
517
+ /** Human-readable name of the policy (e.g. "Financial Agent"). */
518
+ name?: string;
519
+ /** Version identifier of the policy document. */
520
+ version?: string;
521
+ }
522
+
523
+ // ---------------------------------------------------------------------------
524
+ // Attribution — human-readable claim sealed into the proof
525
+ // ---------------------------------------------------------------------------
526
+
527
+ /**
528
+ * Human-readable attribution claim associated with the artifact.
529
+ *
530
+ * All fields are optional. Included in the Ed25519 signed body so that
531
+ * the text is cryptographically sealed and tamper-evident.
532
+ *
533
+ * This is a claim, not a guaranteed identity. It complements the
534
+ * cryptographic actor key in agency.
535
+ */
536
+ export interface Attribution {
537
+ /** Human name of the person or entity making the claim. */
538
+ name?: string;
539
+ /** Short description of the claim (e.g. "Original capture"). */
540
+ title?: string;
541
+ /** Free-form message associated with the artifact (e.g. "Shot at sunset in Buffalo."). */
542
+ message?: string;
543
+ }
544
+
545
+ // ---------------------------------------------------------------------------
546
+ // Agency types — actor identity + authorization
547
+ // ---------------------------------------------------------------------------
548
+
549
+ /**
550
+ * Actor identity — a device-bound key that identifies WHO authorized
551
+ * a commitment.
552
+ *
553
+ * This structure appears in two places:
554
+ * 1. SignedBody.actor — signed by the TEE's Ed25519 key (tamper-evident)
555
+ * 2. BitGraphProof.agency.actor — in the full agency envelope
556
+ *
557
+ * The keyId is deterministic: hex(SHA-256(SPKI DER public key bytes)).
558
+ * Any verifier can recompute it from the raw public key to confirm
559
+ * the binding.
560
+ */
561
+ export interface ActorIdentity {
562
+ /** Stable device key ID: hex(SHA-256(SPKI DER public key bytes)). */
563
+ keyId: string;
564
+ /** Base64 SPKI DER P-256 public key (standard format, any platform can parse). */
565
+ publicKeyB64: string;
566
+ /** Signature algorithm the device used. */
567
+ algorithm: "ES256";
568
+ /** Key origin: "apple-secure-enclave", "android-strongbox", "webauthn". */
569
+ provider: string;
570
+ }
571
+
572
+ /**
573
+ * The canonical payload that the actor's device signs.
574
+ *
575
+ * Serialized as canonical JSON (sorted keys, compact, UTF-8) for signing.
576
+ * signatureB64 is excluded from the signed bytes (it IS the signature).
577
+ *
578
+ * Fields:
579
+ * - purpose: domain separation — prevents cross-context signature reuse
580
+ * - actorKeyId: must match actor.keyId (binds signature to specific key)
581
+ * - artifactHash: must match proof.artifact.digestB64 (binds to specific artifact)
582
+ * - challenge: enclave-issued nonce (prevents replay)
583
+ * - timestamp: Unix epoch ms (enclave checks freshness)
584
+ */
585
+ export interface AuthorizationPayload {
586
+ /** Domain separation: prevents cross-context signature reuse. */
587
+ purpose: "bitgraph/commit-authorize/v1";
588
+ /** Must match actor.keyId. */
589
+ actorKeyId: string;
590
+ /** Base64 SHA-256 of artifact — must match proof.artifact.digestB64. */
591
+ artifactHash: string;
592
+ /** Enclave-issued challenge (prevents replay). */
593
+ challenge: string;
594
+ /** Unix epoch ms — enclave checks freshness. */
595
+ timestamp: number;
596
+ /**
597
+ * Optional protocol version. When present, included in the canonical
598
+ * JSON payload that is P-256-signed. Allows versioning the authorization
599
+ * format while maintaining backward compatibility (old payloads without
600
+ * this field remain valid).
601
+ */
602
+ protocolVersion?: string;
603
+ }
604
+
605
+ /**
606
+ * WebAuthn authorization payload — sent when the device signs via
607
+ * navigator.credentials.get() (passkey / Face ID / Touch ID).
608
+ *
609
+ * WebAuthn signs `authenticatorData || SHA-256(clientDataJSON)`, not
610
+ * arbitrary data. The enclave-issued challenge is embedded in
611
+ * clientDataJSON.challenge. The enclave must verify the WebAuthn
612
+ * signature over the standard WebAuthn signed data format.
613
+ */
614
+ export interface WebAuthnAuthorization {
615
+ /** Domain separation — same as direct. */
616
+ purpose: "bitgraph/commit-authorize/v1";
617
+ /** Discriminator for verification path. */
618
+ format: "webauthn";
619
+ /** Must match actor.keyId. */
620
+ actorKeyId: string;
621
+ /** Base64 SHA-256 of artifact — must match proof.artifact.digestB64. */
622
+ artifactHash: string;
623
+ /** Enclave-issued challenge (base64). Embedded in clientDataJSON. */
624
+ challenge: string;
625
+ /** Unix epoch ms — checked for freshness. */
626
+ timestamp: number;
627
+ /** Base64-encoded raw authenticator data bytes. */
628
+ authenticatorDataB64: string;
629
+ /** Full clientDataJSON string (UTF-8). Contains challenge, origin, type. */
630
+ clientDataJSON: string;
631
+ /** Base64 DER ECDSA P-256 signature from the authenticator. */
632
+ signatureB64: string;
633
+ }
634
+
635
+ /**
636
+ * Full agency envelope — lives in BitGraphProof.agency.
637
+ *
638
+ * Contains the actor identity and the authorization payload
639
+ * (including the device's P-256 signature). Independently verifiable:
640
+ * any verifier can check the P-256 signature over the authorization
641
+ * payload without needing any server or API.
642
+ *
643
+ * Two authorization formats:
644
+ * - Direct: P-256 signature over canonical JSON (native apps, test scripts)
645
+ * - WebAuthn: Standard WebAuthn assertion (browser passkeys, Face ID / Touch ID)
646
+ */
647
+ export interface AgencyEnvelope {
648
+ /** Actor identity (matches SignedBody.actor when present). */
649
+ actor: ActorIdentity;
650
+ /** The possession commitment the actor signed. */
651
+ authorization: (AuthorizationPayload & {
652
+ /**
653
+ * Base64 DER ECDSA signature over canonical JSON of the
654
+ * AuthorizationPayload (excluding signatureB64 itself).
655
+ * P-256 / ES256 — verifiable with actor.publicKeyB64.
656
+ */
657
+ signatureB64: string;
658
+ }) | WebAuthnAuthorization;
659
+ /**
660
+ * Present on batch proofs. The P-256 signature binds to the first digest;
661
+ * batchContext maps every proof in the batch back to that authorization.
662
+ */
663
+ batchContext?: {
664
+ batchSize: number;
665
+ batchIndex: number;
666
+ batchDigests: string[];
667
+ };
668
+ }
669
+
670
+ // ---------------------------------------------------------------------------
671
+ // Slot allocation — causal slot proving nonce-first ordering
672
+ // ---------------------------------------------------------------------------
673
+
674
+ /**
675
+ * Signed slot allocation record.
676
+ *
677
+ * Created by the enclave's allocateSlot endpoint BEFORE any artifact hash
678
+ * is known. The slot body deliberately contains NO artifact data — this is
679
+ * the structural proof of causal independence.
680
+ *
681
+ * The enclave signs this record at allocation time and stores the nonce as
682
+ * a single-use resource in pendingSlots. When a client later calls commit
683
+ * with the slotId, the enclave consumes the slot and embeds this record in
684
+ * the proof. A verifier can then confirm:
685
+ *
686
+ * 1. The slot signature is valid (enclave created it)
687
+ * 2. The slot body contains no artifact hash (causal independence)
688
+ * 3. SHA-256(canonicalize(slotBody)) === commit.slotHashB64 (signed binding)
689
+ * 4. slotAllocation.nonceB64 === commit.nonceB64 (nonce binding)
690
+ * 5. slotAllocation.counter < commit.counter (ordering)
691
+ * 6. Same publicKeyB64 and epochId (same enclave lifecycle)
692
+ *
693
+ * Together these checks prove the nonce existed before the artifact was
694
+ * bound to it — the BitGraph atomic causality invariant.
695
+ */
696
+ export interface SlotAllocation {
697
+ /** Schema version for slot records. Domain separation from proof bodies. */
698
+ version: "bitgraph/slot/1";
699
+ /** NSM-generated nonce. Becomes commit.nonceB64 when consumed. */
700
+ nonceB64: string;
701
+ /** Monotonic counter at allocation time. Must be < commit counter. */
702
+ counter: string;
703
+ /** Advisory timestamp (Unix epoch ms) at allocation time. Optional — BitGraph is causal, not temporal. */
704
+ time?: number;
705
+ /** Enclave lifecycle identifier. Must match commit.epochId. */
706
+ epochId: string;
707
+ /** Enclave Ed25519 public key. Must match signer.publicKeyB64. */
708
+ publicKeyB64: string;
709
+ /** Chain isolation identifier. Omitted for the default "global" chain. */
710
+ chainId?: string;
711
+ /** Ed25519 signature over canonical slot body (all fields above). */
712
+ signatureB64: string;
713
+ }
714
+
715
+ // ---------------------------------------------------------------------------
716
+ // Internal canonical body type
717
+ // ---------------------------------------------------------------------------
718
+
719
+ /**
720
+ * The object that is serialized and signed.
721
+ * Constructed internally by Constructor.commit() and reconstructed by
722
+ * verify() for signature validation.
723
+ *
724
+ * Exported so external adapters can construct test proofs without
725
+ * re-implementing the signing body layout.
726
+ *
727
+ * Signed fields:
728
+ * version, artifact, commit, publicKeyB64,
729
+ * enforcement, measurement, attestationFormat (when present),
730
+ * actor (when agency is present),
731
+ * attribution (when provided)
732
+ */
733
+ export interface SignedBody {
734
+ version: "bitgraph/1";
735
+ artifact: BitGraphProof["artifact"];
736
+ commit: BitGraphProof["commit"];
737
+ /** Public key included to bind cryptographic identity to the body. */
738
+ publicKeyB64: string;
739
+ /** Enforcement tier — signed to prevent downgrade attacks. */
740
+ enforcement: EnforcementTier;
741
+ /** Measurement — primary trust anchor for enforcement verification. */
742
+ measurement: string;
743
+ /**
744
+ * Attestation format string — signed to prevent semantic ambiguity.
745
+ * Present only when environment.attestation is present.
746
+ * reportB64 is intentionally excluded (vendor-signed).
747
+ */
748
+ attestationFormat?: string;
749
+
750
+ /**
751
+ * Actor identity summary — included in the signed body when agency
752
+ * is present.
753
+ *
754
+ * Follows the same pattern as attestationFormat: identity summary is
755
+ * signed (tamper-evident under Ed25519), while the full signature
756
+ * envelope (agency.authorization.signatureB64) lives outside the
757
+ * signed body and is independently verifiable.
758
+ *
759
+ * The TEE verifies the actor's P-256 signature BEFORE including this
760
+ * in the signed body, so its presence means the TEE confirmed the
761
+ * actor authorized this specific commitment.
762
+ */
763
+ actor?: ActorIdentity;
764
+
765
+ /**
766
+ * Policy binding — sealed into the signed body.
767
+ * Present only when a policy document was active at commit time.
768
+ * Canonical serialization includes this when present.
769
+ */
770
+ policy?: PolicyBinding;
771
+
772
+ /**
773
+ * Human-readable attribution claim — sealed into the signed body.
774
+ * Present only when the user provided attribution fields at commit time.
775
+ * Canonical serialization includes this when present.
776
+ */
777
+ attribution?: Attribution;
778
+ }