@learncard/lca-api-service 1.3.2 → 1.5.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.
Files changed (240) hide show
  1. package/.env.example +99 -2
  2. package/AGENTS.md +155 -44
  3. package/CHANGELOG.md +84 -0
  4. package/bootstrapLambda.test.ts +76 -0
  5. package/compose.env +5 -0
  6. package/config/config.dev.json +23 -0
  7. package/config/config.example.json +22 -0
  8. package/config/config.json +1 -0
  9. package/config/config.production.json +12 -0
  10. package/config/config.scouts.dev.json +11 -0
  11. package/config/config.scouts.production.json +11 -0
  12. package/dist/accesslayer/signing-authority/index.d.ts +8 -2
  13. package/dist/app.d.ts +361 -8
  14. package/dist/cache/challenges.d.ts +2 -0
  15. package/dist/cache/getDel.d.ts +2 -0
  16. package/dist/cache/getDel.test.d.ts +1 -0
  17. package/dist/cache/login-tickets.d.ts +12 -0
  18. package/dist/cache/recoverySessions.d.ts +42 -0
  19. package/dist/config/applyDockerStageConfig.d.ts +1 -0
  20. package/dist/config/cacheEnvironment.d.ts +16 -0
  21. package/dist/config/environment.d.ts +253 -2
  22. package/dist/config/environment.test.d.ts +1 -0
  23. package/dist/config/escrowBundleMode.test.d.ts +1 -0
  24. package/dist/config/escrowDeployment.test.d.ts +1 -0
  25. package/dist/config/oidcEnvironment.d.ts +74 -0
  26. package/dist/config/serverlessFunctionEnv.test.d.ts +1 -0
  27. package/dist/config/stageConfig.d.ts +62 -0
  28. package/dist/config/stageConfig.test.d.ts +1 -0
  29. package/dist/didkit_wasm_bg.wasm +0 -0
  30. package/dist/docker-entry.d.ts +1 -1
  31. package/dist/helpers/auth.helpers.d.ts +1 -1
  32. package/dist/helpers/escrowCancelLink.d.ts +7 -0
  33. package/dist/helpers/escrowCancelLink.test.d.ts +1 -0
  34. package/dist/helpers/escrowCancelToken.d.ts +4 -0
  35. package/dist/helpers/escrowCancelToken.test.d.ts +1 -0
  36. package/dist/helpers/keycloak.helpers.d.ts +23 -0
  37. package/dist/helpers/learnCard.signingAuthority.test.d.ts +1 -0
  38. package/dist/helpers/login-ticket.helpers.d.ts +31 -0
  39. package/dist/helpers/login-ticket.helpers.test.d.ts +1 -0
  40. package/dist/helpers/oidc.helpers.d.ts +47 -0
  41. package/dist/helpers/rate-limit.helpers.d.ts +6 -0
  42. package/dist/helpers/rate-limit.helpers.test.d.ts +1 -0
  43. package/dist/helpers/rateLimit.helpers.d.ts +4 -0
  44. package/dist/helpers/seedEncryption.helpers.d.ts +23 -0
  45. package/dist/helpers/seedEncryption.helpers.test.d.ts +1 -0
  46. package/dist/helpers/social-token.helpers.d.ts +19 -0
  47. package/dist/index.d.ts +1 -0
  48. package/dist/index.js +2 -2
  49. package/dist/jobs/escrowBlobRewrap.d.ts +9 -0
  50. package/dist/jobs/escrowHoldReminders.d.ts +12 -0
  51. package/dist/migrations/signingAuthoritySeeds.d.ts +13 -0
  52. package/dist/migrations/signingAuthoritySeedsCli.test.d.ts +1 -0
  53. package/dist/models/AuthSubject.d.ts +20 -0
  54. package/dist/models/EscrowHold.d.ts +153 -0
  55. package/dist/models/Notification.d.ts +5 -0
  56. package/dist/models/SigningAuthority.d.ts +7 -1
  57. package/dist/models/UserKey.d.ts +326 -3
  58. package/dist/models/UserKey.escrowPin.test.d.ts +1 -0
  59. package/dist/models/index.d.ts +2 -0
  60. package/dist/oidc.d.ts +5 -0
  61. package/dist/routes/auth.d.ts +31 -0
  62. package/dist/routes/escrow.d.ts +164 -0
  63. package/dist/routes/index.d.ts +6 -0
  64. package/dist/routes/keys.d.ts +182 -7
  65. package/dist/routes/notifications.d.ts +5 -0
  66. package/dist/routes/qr-login.d.ts +1 -1
  67. package/dist/routes/signing-authority.d.ts +14 -0
  68. package/dist/services/escrow-enclave/blobIdentity.d.ts +10 -0
  69. package/dist/services/escrow-enclave/blobIdentity.test.d.ts +1 -0
  70. package/dist/services/escrow-enclave/cancellation.d.ts +3 -0
  71. package/dist/services/escrow-enclave/enclaveTrustAnchors.d.ts +1 -0
  72. package/dist/services/escrow-enclave/index.d.ts +25 -0
  73. package/dist/services/escrow-enclave/notifications.d.ts +24 -0
  74. package/dist/services/escrow-enclave/notifications.test.d.ts +1 -0
  75. package/dist/services/escrow-enclave/production.test.d.ts +1 -0
  76. package/dist/services/escrow-enclave/remoteEnclave.contract.test.d.ts +1 -0
  77. package/dist/services/escrow-enclave/remoteEnclave.d.ts +20 -0
  78. package/dist/services/escrow-enclave/remoteEnclave.test.d.ts +1 -0
  79. package/dist/services/escrow-enclave/remoteEnclave.tls.test.d.ts +1 -0
  80. package/dist/services/escrow-enclave/softwareEnclave.d.ts +27 -0
  81. package/dist/services/escrow-enclave/softwareEnclave.test.d.ts +1 -0
  82. package/dist/services/escrow-enclave/types.d.ts +111 -0
  83. package/dist/types/notifications.d.ts +5 -0
  84. package/dist/types/seed-encryption.d.ts +31 -0
  85. package/dist/types/seed-migration.d.ts +49 -0
  86. package/esbuildPlugins.cjs +1 -0
  87. package/lambda.ts +27 -112
  88. package/lambdaApp.ts +146 -0
  89. package/oidcLambda.test.ts +38 -0
  90. package/oidcLambda.ts +18 -0
  91. package/oidcLambdaApp.ts +11 -0
  92. package/package.json +30 -14
  93. package/scripts/README.md +88 -0
  94. package/scripts/check-seed-encryption-infrastructure.mjs +139 -0
  95. package/scripts/check-signing-authority-redis.mjs +93 -0
  96. package/scripts/escrow-blob-mode-report.test.ts +227 -0
  97. package/scripts/escrow-blob-mode-report.ts +465 -0
  98. package/scripts/keycloak-admin.ts +108 -0
  99. package/scripts/keycloak-provider-links.ts +147 -0
  100. package/scripts/migrate-signing-authority-seeds-local.sh +51 -0
  101. package/scripts/migrate-signing-authority-seeds.mjs +105 -0
  102. package/scripts/print-seed-rollout-flags.ts +21 -0
  103. package/scripts/provision-keycloak-users.ts +419 -0
  104. package/seedMigrationApp.test.ts +145 -0
  105. package/seedMigrationApp.ts +50 -0
  106. package/seedMigrationLambda.test.ts +40 -0
  107. package/seedMigrationLambda.ts +19 -0
  108. package/serverless-local.yml +28 -1
  109. package/serverless.function-env.cjs +58 -0
  110. package/serverless.yml +294 -26
  111. package/src/accesslayer/notifications/index.ts +9 -2
  112. package/src/accesslayer/signing-authority/create.ts +11 -7
  113. package/src/accesslayer/signing-authority/index.ts +4 -1
  114. package/src/app.ts +4 -0
  115. package/src/cache/challenges.ts +12 -2
  116. package/src/cache/getDel.test.ts +34 -0
  117. package/src/cache/getDel.ts +32 -0
  118. package/src/cache/in-memory-lru.ts +1 -0
  119. package/src/cache/index.ts +1 -1
  120. package/src/cache/login-tickets.ts +26 -0
  121. package/src/cache/recoverySessions.ts +252 -0
  122. package/src/config/applyDockerStageConfig.ts +6 -0
  123. package/src/config/cacheEnvironment.ts +34 -0
  124. package/src/config/environment.test.ts +159 -0
  125. package/src/config/environment.ts +164 -0
  126. package/src/config/escrowBundleMode.test.ts +52 -0
  127. package/src/config/escrowDeployment.test.ts +114 -0
  128. package/src/config/oidcEnvironment.ts +59 -0
  129. package/src/config/serverlessFunctionEnv.test.ts +98 -0
  130. package/src/config/stageConfig.test.ts +26 -0
  131. package/src/config/stageConfig.ts +24 -0
  132. package/src/docker-entry.ts +13 -0
  133. package/src/helpers/auth.helpers.ts +68 -30
  134. package/src/helpers/escrowCancelLink.test.ts +60 -0
  135. package/src/helpers/escrowCancelLink.ts +38 -0
  136. package/src/helpers/escrowCancelToken.test.ts +105 -0
  137. package/src/helpers/escrowCancelToken.ts +47 -0
  138. package/src/helpers/keycloak.helpers.ts +120 -0
  139. package/src/helpers/learnCard.helpers.test.ts +11 -1
  140. package/src/helpers/learnCard.helpers.ts +57 -45
  141. package/src/helpers/learnCard.signingAuthority.test.ts +194 -0
  142. package/src/helpers/login-ticket.helpers.test.ts +45 -0
  143. package/src/helpers/login-ticket.helpers.ts +91 -0
  144. package/src/helpers/oidc.helpers.ts +354 -0
  145. package/src/helpers/rate-limit.helpers.test.ts +61 -0
  146. package/src/helpers/rate-limit.helpers.ts +73 -0
  147. package/src/helpers/rateLimit.helpers.ts +112 -0
  148. package/src/helpers/seedEncryption.helpers.test.ts +427 -0
  149. package/src/helpers/seedEncryption.helpers.ts +372 -0
  150. package/src/helpers/signingAuthority.helpers.ts +4 -1
  151. package/src/helpers/social-token.helpers.ts +144 -0
  152. package/src/index.ts +1 -0
  153. package/src/jobs/escrowBlobRewrap.ts +121 -0
  154. package/src/jobs/escrowHoldReminders.ts +177 -0
  155. package/src/migrations/signingAuthoritySeeds.ts +430 -0
  156. package/src/migrations/signingAuthoritySeedsCli.test.ts +70 -0
  157. package/src/models/AuthSubject.ts +147 -0
  158. package/src/models/EscrowHold.ts +541 -0
  159. package/src/models/SigningAuthority.ts +14 -3
  160. package/src/models/UserKey.escrowPin.test.ts +49 -0
  161. package/src/models/UserKey.ts +1109 -40
  162. package/src/models/index.ts +2 -0
  163. package/src/models/pruneOrphanedRecoveryMethods.ts +3 -1
  164. package/src/oidc.ts +371 -0
  165. package/src/routes/auth.ts +173 -0
  166. package/src/routes/credentials.ts +8 -0
  167. package/src/routes/escrow.ts +1073 -0
  168. package/src/routes/firebase.ts +90 -48
  169. package/src/routes/index.ts +98 -5
  170. package/src/routes/keys.ts +1022 -174
  171. package/src/routes/qr-login.ts +51 -53
  172. package/src/routes/signing-authority.ts +39 -1
  173. package/src/services/delivery/adapters/postmark.adapter.ts +48 -1
  174. package/src/services/escrow-enclave/README.md +122 -0
  175. package/src/services/escrow-enclave/__fixtures__/tls/pinned-ca.pem +11 -0
  176. package/src/services/escrow-enclave/__fixtures__/tls/pinned-leaf.key +5 -0
  177. package/src/services/escrow-enclave/__fixtures__/tls/pinned-leaf.pem +12 -0
  178. package/src/services/escrow-enclave/__fixtures__/tls/rogue-ca.pem +11 -0
  179. package/src/services/escrow-enclave/__fixtures__/tls/rogue-leaf.key +5 -0
  180. package/src/services/escrow-enclave/__fixtures__/tls/rogue-leaf.pem +12 -0
  181. package/src/services/escrow-enclave/blobIdentity.test.ts +42 -0
  182. package/src/services/escrow-enclave/blobIdentity.ts +55 -0
  183. package/src/services/escrow-enclave/cancellation.ts +59 -0
  184. package/src/services/escrow-enclave/enclaveTrustAnchors.ts +34 -0
  185. package/src/services/escrow-enclave/index.ts +174 -0
  186. package/src/services/escrow-enclave/notifications.test.ts +377 -0
  187. package/src/services/escrow-enclave/notifications.ts +348 -0
  188. package/src/services/escrow-enclave/production.test.ts +34 -0
  189. package/src/services/escrow-enclave/remoteEnclave.contract.test.ts +344 -0
  190. package/src/services/escrow-enclave/remoteEnclave.test.ts +490 -0
  191. package/src/services/escrow-enclave/remoteEnclave.tls.test.ts +62 -0
  192. package/src/services/escrow-enclave/remoteEnclave.ts +263 -0
  193. package/src/services/escrow-enclave/softwareEnclave.test.ts +450 -0
  194. package/src/services/escrow-enclave/softwareEnclave.ts +254 -0
  195. package/src/services/escrow-enclave/types.ts +122 -0
  196. package/src/types/seed-encryption.ts +51 -0
  197. package/src/types/seed-migration.ts +61 -0
  198. package/test/auth-tickets.spec.ts +480 -0
  199. package/test/credentials.spec.ts +2 -0
  200. package/test/helpers/keycloak-broker.ts +181 -0
  201. package/test/helpers/live-mongo.ts +9 -0
  202. package/test/jobs/escrowBlobRewrap.spec.ts +262 -0
  203. package/test/jobs/escrowHoldReminders.spec.ts +319 -0
  204. package/test/keycloak-admin.spec.ts +58 -0
  205. package/test/keycloak-broker-roundtrip.integration.spec.ts +40 -0
  206. package/test/keycloak-migration.integration.spec.ts +266 -0
  207. package/test/keycloak-provider-links.spec.ts +201 -0
  208. package/test/keycloak-social-broker.integration.spec.ts +228 -0
  209. package/test/keycloak-verify.integration.spec.ts +100 -0
  210. package/test/keycloak-verify.spec.ts +307 -0
  211. package/test/keys-escrow.spec.ts +2328 -0
  212. package/test/keys-identity-rebind.spec.ts +456 -0
  213. package/test/keys-lifecycle-invariants.spec.ts +785 -0
  214. package/test/keys-recovery-confirmation.spec.ts +586 -0
  215. package/test/keys-recovery-rate-limit.spec.ts +123 -0
  216. package/test/keys-secret-leakage.spec.ts +265 -0
  217. package/test/models/authSubjectIndexes.spec.ts +132 -0
  218. package/test/models/escrow.spec.ts +572 -0
  219. package/test/notifications.spec.ts +4 -0
  220. package/test/oidc-e2e/broker.spec.ts +116 -0
  221. package/test/oidc-e2e/compose.yaml +33 -0
  222. package/test/oidc-e2e/fixtures.ts +245 -0
  223. package/test/oidc-e2e/lock.test.mjs +73 -0
  224. package/test/oidc-e2e/playwright.config.ts +21 -0
  225. package/test/oidc-e2e/run.mjs +312 -0
  226. package/test/oidc-e2e/tsconfig.json +10 -0
  227. package/test/oidc-e2e/types.ts +21 -0
  228. package/test/oidc.integration.spec.ts +54 -0
  229. package/test/oidc.spec.ts +697 -0
  230. package/test/oidcLambdaApp.boot.spec.ts +105 -0
  231. package/test/provision-keycloak-users.spec.ts +299 -0
  232. package/test/signing-authority-http.spec.ts +161 -0
  233. package/test/signing-authority-seed-migration.spec.ts +414 -0
  234. package/test/signing-authority.spec.ts +90 -0
  235. package/tsconfig.build.json +1 -1
  236. package/tsconfig.json +1 -0
  237. package/vitest.config.ts +24 -1
  238. package/vitest.integration.config.ts +25 -3
  239. package/dist/cache/signing-authority.d.ts +0 -6
  240. package/src/cache/signing-authority.ts +0 -37
@@ -0,0 +1,263 @@
1
+ import { Agent } from 'node:https';
2
+ import axios from 'axios';
3
+ import { z } from 'zod';
4
+ import {
5
+ ESCROW_ALGORITHM,
6
+ ESCROW_ENVELOPE_VERSION,
7
+ type EscrowEnvelope,
8
+ } from '@learncard/sss-key-manager';
9
+ import {
10
+ EscrowBlobError,
11
+ EscrowPinMismatchError,
12
+ EscrowPolicyError,
13
+ EscrowUnavailableError,
14
+ type CarryPinVerifierInput,
15
+ type EnclaveAttestation,
16
+ type EscrowEnclave,
17
+ type ReleaseRequest,
18
+ type ReleaseResult,
19
+ type RewrapEscrowBlobInput,
20
+ type VerifyEscrowBlobInput,
21
+ type VerifyEscrowBlobResult,
22
+ } from './types';
23
+
24
+ // Mirrors services/escrow-enclave-app/src/wire.rs camelCase field names exactly,
25
+ // minus the vsock wire's `method` tag (the HTTP paths below take its place).
26
+ const measurementsValidator = z.object({
27
+ imageSha384: z.string().optional(),
28
+ pcr0: z.string().optional(),
29
+ pcr1: z.string().optional(),
30
+ pcr2: z.string().optional(),
31
+ });
32
+
33
+ const attestResponseValidator = z.object({
34
+ mode: z.enum(['software', 'nitro']),
35
+ keyId: z.string(),
36
+ // Optional/defaulted (not required, unlike the host's own wire.rs field) so
37
+ // a not-yet-upgraded host during a rolling deploy still parses; P9.1.
38
+ previousKeyIds: z.array(z.string()).optional().default([]),
39
+ publicKey: z.string(),
40
+ measurements: measurementsValidator,
41
+ document: z.string(),
42
+ issuedAt: z.string(),
43
+ });
44
+
45
+ const envelopeValidator = z.object({
46
+ version: z.literal(ESCROW_ENVELOPE_VERSION),
47
+ algorithm: z.literal(ESCROW_ALGORITHM),
48
+ keyId: z.string(),
49
+ ephemeralPublicKey: z.string(),
50
+ salt: z.string(),
51
+ iv: z.string(),
52
+ ciphertext: z.string(),
53
+ });
54
+
55
+ const verifyBlobResponseValidator = z.union([
56
+ z.object({ ok: z.literal(true), hasPin: z.boolean() }),
57
+ z.object({ ok: z.literal(false), hasPin: z.boolean(), reason: z.string() }),
58
+ ]);
59
+
60
+ const carryPinVerifierResponseValidator = z.object({ envelope: envelopeValidator });
61
+ const rewrapEscrowBlobResponseValidator = z.object({ envelope: envelopeValidator });
62
+
63
+ // Opaque SignedHoldRecord JSON: validate shape while preserving signed extensions.
64
+ const holdRecordValidator = z
65
+ .object({
66
+ hold: z
67
+ .object({
68
+ holdId: z.string().min(1),
69
+ did: z.string().min(1),
70
+ shareVersion: z.number().int().positive(),
71
+ blobHash: z.string().regex(/^[0-9a-f]{64}$/),
72
+ enrollmentEpoch: z.number().int().positive(),
73
+ releasePolicy: z.enum(['hold', 'pin']),
74
+ clientEphemeralPublicKey: z.string().min(1),
75
+ createdLo: z.number().int().nonnegative(),
76
+ createdHi: z.number().int().nonnegative(),
77
+ policyVersion: z.number().int().positive(),
78
+ signature: z.string().min(1),
79
+ })
80
+ .passthrough(),
81
+ holdDurationMs: z.number().int().nonnegative(),
82
+ ledgerSeq: z.number().int().nonnegative(),
83
+ })
84
+ .passthrough();
85
+ const validatedHold = (hold: ReleaseRequest['hold']): ReleaseRequest['hold'] => {
86
+ if (!holdRecordValidator.safeParse(hold).success) throw new EscrowUnavailableError();
87
+ return hold;
88
+ };
89
+ const releaseResponseValidator = z.object({ sealed: envelopeValidator });
90
+
91
+ const errorCodeValidator = z.enum([
92
+ 'policy',
93
+ 'pinMismatch',
94
+ 'blob',
95
+ 'unavailable',
96
+ 'ledger',
97
+ 'time',
98
+ ]);
99
+ const errorResponseValidator = z.object({ code: errorCodeValidator, message: z.string() });
100
+
101
+ const errorForCode = (code: z.infer<typeof errorCodeValidator>): Error => {
102
+ if (code === 'policy') return new EscrowPolicyError();
103
+ if (code === 'pinMismatch') return new EscrowPinMismatchError();
104
+ if (code === 'blob') return new EscrowBlobError();
105
+ return new EscrowUnavailableError(); // 'unavailable' | 'ledger' | 'time'
106
+ };
107
+
108
+ export interface RemoteEnclaveTransportResponse {
109
+ status: number;
110
+ data: unknown;
111
+ }
112
+
113
+ export interface RemoteEnclaveTransport {
114
+ post(
115
+ url: string,
116
+ body: unknown,
117
+ config: { headers: Record<string, string>; timeout: number }
118
+ ): Promise<RemoteEnclaveTransportResponse>;
119
+ }
120
+
121
+ /** With `caPem`, the connection trusts only that CA (system roots are not consulted). */
122
+ export const createHttpTransport = (caPem?: string): RemoteEnclaveTransport => {
123
+ const httpsAgent = caPem ? new Agent({ ca: caPem, keepAlive: true }) : undefined;
124
+ return {
125
+ post: async (url, body, config) => {
126
+ const response = await axios.post(url, body, {
127
+ ...config,
128
+ httpsAgent,
129
+ validateStatus: () => true,
130
+ });
131
+ return { status: response.status, data: response.data };
132
+ },
133
+ };
134
+ };
135
+
136
+ export interface RemoteEnclaveConfig {
137
+ /** Origin of the enclave-host parent service (P3.3), e.g. `https://escrow.internal`. */
138
+ baseUrl: string;
139
+ token: string;
140
+ timeoutMs: number;
141
+ /** Private CA pinned for this host (see enclaveTrustAnchors.ts); system roots otherwise. */
142
+ caPem?: string;
143
+ /** Injectable for tests; defaults to a real HTTP (axios) transport. */
144
+ transport?: RemoteEnclaveTransport;
145
+ }
146
+
147
+ /**
148
+ * `EscrowEnclave` backend for `ESCROW_ENCLAVE_MODE=remote`: an HTTP client to the
149
+ * enclave-host parent service fronting a real Nitro Enclave. See issues.md
150
+ * "P4.1 report" for the full contract (paths, bodies, error format).
151
+ *
152
+ * Fail-closed: any transport failure (network error, timeout, non-2xx without a
153
+ * recognized error code, or a response failing its zod schema) becomes
154
+ * `EscrowUnavailableError`. Request/response bodies (sealed envelopes, PIN
155
+ * proofs) are never logged anywhere in this module.
156
+ */
157
+ export const createRemoteEnclave = (config: RemoteEnclaveConfig): EscrowEnclave => {
158
+ const transport = config.transport ?? createHttpTransport(config.caPem);
159
+
160
+ const call = async <T>(path: string, body: unknown, validator: z.ZodType<T>): Promise<T> => {
161
+ let status: number;
162
+ let data: unknown;
163
+ try {
164
+ const response = await transport.post(`${config.baseUrl}${path}`, body, {
165
+ headers: { Authorization: `Bearer ${config.token}` },
166
+ timeout: config.timeoutMs,
167
+ });
168
+ status = response.status;
169
+ data = response.data;
170
+ } catch {
171
+ throw new EscrowUnavailableError();
172
+ }
173
+ if (status < 200 || status >= 300) {
174
+ const parsedError = errorResponseValidator.safeParse(data);
175
+ if (parsedError.success) throw errorForCode(parsedError.data.code);
176
+ throw new EscrowUnavailableError();
177
+ }
178
+ const parsed = validator.safeParse(data);
179
+ if (!parsed.success) throw new EscrowUnavailableError();
180
+ return parsed.data;
181
+ };
182
+
183
+ return {
184
+ getAttestation: (nonce?: Uint8Array): Promise<EnclaveAttestation> =>
185
+ call('/v1/attest', { nonce: Array.from(nonce ?? []) }, attestResponseValidator),
186
+ verifyEscrowBlob: (input: VerifyEscrowBlobInput): Promise<VerifyEscrowBlobResult> =>
187
+ call(
188
+ '/v1/verify-blob',
189
+ {
190
+ envelope: input.envelope,
191
+ expectedDid: input.expectedDid,
192
+ expectedShareVersion: input.expectedShareVersion,
193
+ },
194
+ verifyBlobResponseValidator
195
+ ),
196
+ carryPinVerifier: (input: CarryPinVerifierInput): Promise<{ envelope: EscrowEnvelope }> =>
197
+ call(
198
+ '/v1/carry-pin-verifier',
199
+ {
200
+ sourceEnvelope: input.sourceEnvelope,
201
+ targetEnvelope: input.targetEnvelope,
202
+ expectedDid: input.expectedDid,
203
+ sourceShareVersion: input.sourceShareVersion,
204
+ targetShareVersion: input.targetShareVersion,
205
+ sourceEnrollmentEpoch: input.sourceEnrollmentEpoch,
206
+ targetEnrollmentEpoch: input.targetEnrollmentEpoch,
207
+ },
208
+ carryPinVerifierResponseValidator
209
+ ),
210
+ rewrapEscrowBlob: (input: RewrapEscrowBlobInput): Promise<{ envelope: EscrowEnvelope }> =>
211
+ call(
212
+ '/v1/rewrap-escrow-blob',
213
+ {
214
+ envelope: input.envelope,
215
+ expectedDid: input.expectedDid,
216
+ expectedShareVersion: input.expectedShareVersion,
217
+ sourceEnrollmentEpoch: input.sourceEnrollmentEpoch,
218
+ },
219
+ rewrapEscrowBlobResponseValidator
220
+ ),
221
+ createHold: async input => ({
222
+ holdRecord: await call(
223
+ '/v1/create-hold',
224
+ {
225
+ envelope: input.envelope,
226
+ holdId: input.holdId,
227
+ expectedDid: input.expectedDid,
228
+ expectedShareVersion: input.expectedShareVersion,
229
+ enrollmentEpoch: input.enrollmentEpoch,
230
+ releasePolicy: input.releasePolicy,
231
+ clientEphemeralPublicKey: input.clientEphemeralPublicKey,
232
+ },
233
+ holdRecordValidator
234
+ ),
235
+ }),
236
+ cancelHold: async input => {
237
+ await call(
238
+ '/v1/cancel-hold',
239
+ {
240
+ envelope: input.envelope,
241
+ hold: validatedHold(input.hold),
242
+ clientEphemeralPublicKey: input.clientEphemeralPublicKey,
243
+ expectedDid: input.expectedDid,
244
+ },
245
+ z.object({ ok: z.literal(true) })
246
+ );
247
+ },
248
+ releaseEscrow: async (input: ReleaseRequest): Promise<ReleaseResult> => {
249
+ validatedHold(input.hold);
250
+ return call(
251
+ '/v1/release',
252
+ {
253
+ envelope: input.envelope,
254
+ hold: input.hold,
255
+ clientEphemeralPublicKey: input.clientEphemeralPublicKey,
256
+ expectedDid: input.expectedDid,
257
+ ...(input.pinProof !== undefined ? { pinProof: input.pinProof } : {}),
258
+ },
259
+ releaseResponseValidator
260
+ );
261
+ },
262
+ };
263
+ };