@seams/wallet-server 0.20.0 → 0.21.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 (71) hide show
  1. package/artifact-manifest.json +22 -21
  2. package/cloudflare-router-ab/build/deriver-a/build-stamp.json +1 -1
  3. package/cloudflare-router-ab/build/deriver-a/index_bg.wasm +0 -0
  4. package/cloudflare-router-ab/build/deriver-b/build-stamp.json +1 -1
  5. package/cloudflare-router-ab/build/deriver-b/index_bg.wasm +0 -0
  6. package/cloudflare-router-ab/build/local-tools/linux-x64/router_ab_local_init +0 -0
  7. package/cloudflare-router-ab/build/router/build-stamp.json +1 -1
  8. package/cloudflare-router-ab/build/router/index_bg.wasm +0 -0
  9. package/cloudflare-router-ab/build/signing-worker/build-stamp.json +1 -1
  10. package/cloudflare-router-ab/build/signing-worker/index_bg.wasm +0 -0
  11. package/cloudflare-router-ab/build/tenant-root-control-plane/build-stamp.json +1 -1
  12. package/cloudflare-router-ab/build/tenant-root-control-plane/index_bg.wasm +0 -0
  13. package/cloudflare-router-ab/scripts/prepare-local-runtime-config.mjs +16 -2
  14. package/dist/esm/core/deviceLinking/linkedDeviceManagement.js +11 -30
  15. package/dist/esm/core/emailOtpD1Statements.js +14 -2
  16. package/dist/esm/core/nearRelayerAccountProvisioning.js +2 -7
  17. package/dist/esm/hosted-wallet-gateway.js +10 -7
  18. package/dist/esm/router/auth/webAuthnCredentialCodecs.js +16 -1
  19. package/dist/esm/router/cloudflare/d1/auth/d1AuthorizationAssembly.js +1 -0
  20. package/dist/esm/router/cloudflare/d1/auth/d1RouterApiAuthService.js +0 -1
  21. package/dist/esm/router/cloudflare/d1/deviceLinking/d1LinkedDeviceEmailOtpTargetFactor.js +4 -1
  22. package/dist/esm/router/cloudflare/d1/deviceLinking/d1LinkedDeviceManagementService.js +6 -11
  23. package/dist/esm/router/cloudflare/d1/deviceLinking/d1LinkedDeviceRevocation.js +7 -0
  24. package/dist/esm/router/cloudflare/d1/emailOtp/d1EmailOtpGrantStore.js +5 -2
  25. package/dist/esm/router/cloudflare/d1/emailOtp/d1EmailOtpRateLimitStore.js +18 -0
  26. package/dist/esm/router/cloudflare/d1/emailOtp/d1EmailOtpRecoveryService.js +28 -8
  27. package/dist/esm/router/cloudflare/d1/emailOtp/d1EmailOtpServerSealRuntime.js +12 -8
  28. package/dist/esm/router/cloudflare/d1/oidc/d1OidcBoundary.js +1 -0
  29. package/dist/esm/router/cloudflare/d1/passkeyCustody/d1PasskeyCustodyRouteService.js +19 -5
  30. package/dist/esm/router/cloudflare/d1/passkeyCustody/d1WalletCustodyCommitStore.js +26 -10
  31. package/dist/esm/router/cloudflare/d1/passkeyCustody/d1WalletRecoveryGoogleEmailOtpService.js +4 -1
  32. package/dist/esm/router/cloudflare/d1/wallet/d1WalletAuthMethodService.js +6 -0
  33. package/dist/esm/router/cloudflare/d1/wallet/d1WalletRelocationApprovals.js +12 -0
  34. package/dist/esm/router/cloudflare.js +252 -150
  35. package/dist/esm/router/domains/ecdsa/routerAbEcdsaStrictRegistration.js +1 -1
  36. package/dist/esm/router/domains/passkeyCustody/passkeyCustodyEnvelopeRetrieval.js +4 -22
  37. package/dist/esm/router/domains/passkeyCustody/registrationCustodyOutcome.js +8 -2
  38. package/dist/esm/router/framework/routeDefinitions.js +3 -2
  39. package/dist/esm/router/transport/fetch/routes/auth.js +5 -0
  40. package/dist/esm/router/transport/fetch/routes/passkeyCustody.js +78 -88
  41. package/dist/esm/router/transport/fetch/routes/sessions.js +4 -1
  42. package/dist/esm/router/transport/fetch/routes/thresholdEd25519.js +4 -1
  43. package/dist/esm/router/transport/fetch/routes/walletCustodyRequestBoundary.js +80 -0
  44. package/dist/esm/shared-ts/src/passkey-custody/ceremonyCommitPayload.js +5 -1
  45. package/dist/esm/shared-ts/src/utils/evmRpcResults.js +5 -3
  46. package/dist/esm/shared-ts/src/utils/routerAbEcdsaDerivation.js +2 -1
  47. package/dist/esm/threshold/session/signingSessionSeal/crypto/cipher.js +160 -40
  48. package/dist/types/shared-ts/src/authorization/walletCustodyOperation.d.ts +1 -1
  49. package/dist/types/shared-ts/src/device-linking/contracts.d.ts +1 -7
  50. package/dist/types/shared-ts/src/passkey-custody/ceremonyCommitPayload.d.ts +5 -0
  51. package/dist/types/shared-ts/src/utils/evmRpcResults.d.ts +3 -3
  52. package/dist/types/shared-ts/src/utils/routerAbEcdsaDerivation.d.ts +1 -1
  53. package/dist/types/wallet-server/src/core/authService/emailOtpSeal.d.ts +5 -1
  54. package/dist/types/wallet-server/src/core/deviceLinking/linkedDeviceManagement.d.ts +1 -9
  55. package/dist/types/wallet-server/src/core/emailOtpD1Statements.d.ts +2 -1
  56. package/dist/types/wallet-server/src/hosted-wallet-gateway.d.ts +1 -0
  57. package/dist/types/wallet-server/src/router/auth/webAuthnCredentialCodecs.d.ts +1 -0
  58. package/dist/types/wallet-server/src/router/cloudflare/d1/deviceLinking/d1LinkedDeviceManagementService.d.ts +0 -3
  59. package/dist/types/wallet-server/src/router/cloudflare/d1/deviceLinking/d1LinkedDeviceRevocation.d.ts +1 -1
  60. package/dist/types/wallet-server/src/router/cloudflare/d1/emailOtp/d1EmailOtpGrantStore.d.ts +3 -1
  61. package/dist/types/wallet-server/src/router/cloudflare/d1/emailOtp/d1EmailOtpRateLimitStore.d.ts +15 -2
  62. package/dist/types/wallet-server/src/router/cloudflare/d1/emailOtp/d1EmailOtpServerSealRuntime.d.ts +3 -3
  63. package/dist/types/wallet-server/src/router/cloudflare/d1/passkeyCustody/d1PasskeyCustodyRouteService.d.ts +3 -0
  64. package/dist/types/wallet-server/src/router/cloudflare/d1/passkeyCustody/d1WalletCustodyCommitStore.d.ts +5 -6
  65. package/dist/types/wallet-server/src/router/domains/passkeyCustody/walletCustodyRegistrationCommit.d.ts +4 -6
  66. package/dist/types/wallet-server/src/router/framework/authServicePort.d.ts +7 -1
  67. package/dist/types/wallet-server/src/router/transport/fetch/routes/passkeyCustody.d.ts +1 -10
  68. package/dist/types/wallet-server/src/router/transport/fetch/routes/walletCustodyRequestBoundary.d.ts +28 -0
  69. package/dist/types/wallet-server/src/threshold/session/signingSessionSeal/crypto/cipher.d.ts +3 -0
  70. package/package.json +1 -1
  71. package/src/README.md +11 -4
@@ -73,6 +73,17 @@ function normalizeConfig(config) {
73
73
  };
74
74
  }
75
75
  function encodeSigningSessionSealServerLockContext(input) {
76
+ return encodeSigningSessionHkdfTuple([
77
+ "seams/router-ab/signing-session-seal",
78
+ input.protocol.algorithm,
79
+ input.protocol.groupId,
80
+ input.keyVersion,
81
+ "server-lock/session/v1",
82
+ input.walletId,
83
+ input.thresholdSessionId
84
+ ]);
85
+ }
86
+ function encodeEmailOtpEnrollmentServerLockContext(input) {
76
87
  return encodeSigningSessionHkdfTuple([
77
88
  "seams/router-ab/signing-session-seal",
78
89
  input.protocol.algorithm,
@@ -89,14 +100,14 @@ function defaultRuntime() {
89
100
  destroyLockKeyHandle: destroySigningSessionSealLockKeyHandle
90
101
  };
91
102
  }
92
- async function deriveAcceptedKeyHandles(input) {
103
+ async function deriveEmailOtpEnrollmentKeyHandles(input) {
93
104
  const handles = /* @__PURE__ */ new Map();
94
105
  try {
95
106
  for (const keyVersion of input.config.acceptedWarmKeyVersions) {
96
107
  const handle = await input.runtime.deriveLockKeyHandle({
97
108
  groupId: input.config.protocol.groupId,
98
109
  rootSecret32: input.config.rootSecret32,
99
- context: encodeSigningSessionSealServerLockContext({
110
+ context: encodeEmailOtpEnrollmentServerLockContext({
100
111
  protocol: input.config.protocol,
101
112
  keyVersion
102
113
  })
@@ -111,14 +122,14 @@ async function deriveAcceptedKeyHandles(input) {
111
122
  input.config.rootSecret32.fill(0);
112
123
  }
113
124
  }
114
- var SigningSessionSealKeyHandles = class {
125
+ var EmailOtpEnrollmentKeyHandles = class {
115
126
  initialization = null;
116
127
  constructor(config, runtime) {
117
128
  this.config = config;
118
129
  this.runtime = runtime;
119
130
  }
120
131
  get() {
121
- if (!this.initialization) this.initialization = deriveAcceptedKeyHandles({
132
+ if (!this.initialization) this.initialization = deriveEmailOtpEnrollmentKeyHandles({
122
133
  config: this.config,
123
134
  runtime: this.runtime
124
135
  });
@@ -132,50 +143,159 @@ function mapCipherError(error, defaultMessage) {
132
143
  if (lowered.includes("ciphertext") || lowered.includes("base64url") || lowered.includes("group")) return failure("invalid_ciphertext", message);
133
144
  return failure("internal", message);
134
145
  }
135
- function createSigningSessionSealShamir3PassCipherAdapter(options) {
136
- const config = normalizeConfig(options.config);
137
- options.config.rootSecret32.fill(0);
138
- const runtime = options.runtime ?? defaultRuntime();
139
- const handles = new SigningSessionSealKeyHandles(config, runtime);
140
- return createSigningSessionSealCipherAdapter({
141
- applyServerSeal: async (input) => {
146
+ function copyToArrayBuffer(bytes) {
147
+ const copy = new ArrayBuffer(bytes.length);
148
+ new Uint8Array(copy).set(bytes);
149
+ return copy;
150
+ }
151
+ async function importSigningSessionSealRootKey(rootSecret32) {
152
+ const rootCopy = copyToArrayBuffer(rootSecret32);
153
+ try {
154
+ return await crypto.subtle.importKey("raw", rootCopy, {
155
+ name: "HMAC",
156
+ hash: "SHA-256"
157
+ }, false, ["sign"]);
158
+ } finally {
159
+ rootSecret32.fill(0);
160
+ new Uint8Array(rootCopy).fill(0);
161
+ }
162
+ }
163
+ async function deriveScopedSigningSessionHandle(input) {
164
+ const context = encodeSigningSessionSealServerLockContext({
165
+ protocol: input.state.config.protocol,
166
+ keyVersion: input.keyVersion,
167
+ walletId: input.walletId,
168
+ thresholdSessionId: input.thresholdSessionId
169
+ });
170
+ let sessionRoot32 = null;
171
+ try {
172
+ const rootKey = await input.state.rootKey;
173
+ sessionRoot32 = new Uint8Array(await crypto.subtle.sign("HMAC", rootKey, copyToArrayBuffer(context)));
174
+ return await input.state.runtime.deriveLockKeyHandle({
175
+ groupId: input.state.config.protocol.groupId,
176
+ rootSecret32: sessionRoot32,
177
+ context
178
+ });
179
+ } finally {
180
+ sessionRoot32?.fill(0);
181
+ context.fill(0);
182
+ }
183
+ }
184
+ async function acquireLockHandle(state, input, keyVersion) {
185
+ switch (state.kind) {
186
+ case "email_otp_enrollment": {
187
+ const handle = (await state.handles.get()).get(keyVersion);
188
+ if (handle === void 0) throw new Error("Email OTP enrollment lock key handle is unavailable");
189
+ return {
190
+ kind: "borrowed",
191
+ handle
192
+ };
193
+ }
194
+ case "signing_session": return {
195
+ kind: "owned",
196
+ handle: await deriveScopedSigningSessionHandle({
197
+ state,
198
+ keyVersion,
199
+ walletId: input.auth.userId,
200
+ thresholdSessionId: input.thresholdSessionId
201
+ })
202
+ };
203
+ default: return assertNeverShamir3PassCipherState(state);
204
+ }
205
+ }
206
+ function assertNeverShamir3PassCipherState(value) {
207
+ throw new Error(`Unsupported Shamir 3-pass cipher state: ${String(value)}`);
208
+ }
209
+ function assertNeverSigningSessionSealOperation(value) {
210
+ throw new Error(`Unsupported signing-session seal operation: ${String(value)}`);
211
+ }
212
+ async function runShamir3PassCipher(state, operation, input) {
213
+ if (state.kind === "signing_session") {
214
+ if (!normalizeOptionalTrimmedString(input.auth.userId) || !normalizeOptionalTrimmedString(input.thresholdSessionId)) return failure("invalid_scope", "Authenticated wallet and threshold session are required");
215
+ }
216
+ let keyVersion;
217
+ switch (operation) {
218
+ case "apply-server-seal": {
142
219
  const requestedKeyVersion = normalizeOptionalTrimmedString(input.keyVersion);
143
- if (requestedKeyVersion && requestedKeyVersion !== config.currentKeyVersion) return failure("invalid_key_version", `Requested keyVersion "${requestedKeyVersion}" does not match active keyVersion "${config.currentKeyVersion}"`);
220
+ if (requestedKeyVersion && requestedKeyVersion !== state.config.currentKeyVersion) return failure("invalid_key_version", `Requested keyVersion "${requestedKeyVersion}" does not match active keyVersion "${state.config.currentKeyVersion}"`);
221
+ keyVersion = state.config.currentKeyVersion;
222
+ break;
223
+ }
224
+ case "remove-server-seal":
144
225
  try {
145
- const handle = (await handles.get()).get(config.currentKeyVersion);
146
- if (handle === void 0) throw new Error("currentKeyVersion handle is unavailable");
147
- return {
148
- ok: true,
149
- ciphertext: await runtime.addLock({
150
- handle,
151
- ciphertextB64u: input.ciphertext
152
- }),
153
- keyVersion: config.currentKeyVersion
154
- };
155
- } catch (error) {
156
- return mapCipherError(error, "Failed to apply server seal");
226
+ keyVersion = normalizeKeyVersion(input.keyVersion, "keyVersion");
227
+ } catch {
228
+ return failure("invalid_key_version", "keyVersion is invalid");
157
229
  }
158
- },
159
- removeServerSeal: async (input) => {
160
- const keyVersion = normalizeKeyVersion(input.keyVersion, "keyVersion");
161
- try {
162
- const handle = (await handles.get()).get(keyVersion);
163
- if (handle === void 0) return failure("invalid_key_version", `Unknown keyVersion "${keyVersion}"`);
164
- return {
165
- ok: true,
166
- ciphertext: await runtime.removeLock({
167
- handle,
230
+ if (!state.config.acceptedWarmKeyVersions.includes(keyVersion)) return failure("invalid_key_version", `Unknown keyVersion "${keyVersion}"`);
231
+ break;
232
+ default: return assertNeverSigningSessionSealOperation(operation);
233
+ }
234
+ try {
235
+ const lease = await acquireLockHandle(state, input, keyVersion);
236
+ try {
237
+ let ciphertext;
238
+ switch (operation) {
239
+ case "apply-server-seal":
240
+ ciphertext = await state.runtime.addLock({
241
+ handle: lease.handle,
168
242
  ciphertextB64u: input.ciphertext
169
- }),
170
- keyVersion
171
- };
172
- } catch (error) {
173
- return mapCipherError(error, "Failed to remove server seal");
243
+ });
244
+ break;
245
+ case "remove-server-seal":
246
+ ciphertext = await state.runtime.removeLock({
247
+ handle: lease.handle,
248
+ ciphertextB64u: input.ciphertext
249
+ });
250
+ break;
251
+ default: return assertNeverSigningSessionSealOperation(operation);
174
252
  }
253
+ return {
254
+ ok: true,
255
+ ciphertext,
256
+ keyVersion
257
+ };
258
+ } finally {
259
+ if (lease.kind === "owned") state.runtime.destroyLockKeyHandle(lease.handle);
175
260
  }
261
+ } catch (error) {
262
+ return mapCipherError(error, "Failed to process server seal");
263
+ }
264
+ }
265
+ function applyShamir3PassCipher(state, input) {
266
+ return runShamir3PassCipher(state, "apply-server-seal", input);
267
+ }
268
+ function removeShamir3PassCipher(state, input) {
269
+ return runShamir3PassCipher(state, "remove-server-seal", input);
270
+ }
271
+ function createShamir3PassCipherAdapter(state) {
272
+ return createSigningSessionSealCipherAdapter({
273
+ applyServerSeal: applyShamir3PassCipher.bind(null, state),
274
+ removeServerSeal: removeShamir3PassCipher.bind(null, state)
275
+ });
276
+ }
277
+ function createEmailOtpEnrollmentShamir3PassCipherAdapter(options) {
278
+ const config = normalizeConfig(options.config);
279
+ options.config.rootSecret32.fill(0);
280
+ const runtime = options.runtime ?? defaultRuntime();
281
+ return createShamir3PassCipherAdapter({
282
+ kind: "email_otp_enrollment",
283
+ config,
284
+ runtime,
285
+ handles: new EmailOtpEnrollmentKeyHandles(config, runtime)
286
+ });
287
+ }
288
+ function createSigningSessionSealShamir3PassCipherAdapter(options) {
289
+ const config = normalizeConfig(options.config);
290
+ options.config.rootSecret32.fill(0);
291
+ return createShamir3PassCipherAdapter({
292
+ kind: "signing_session",
293
+ config,
294
+ runtime: options.runtime ?? defaultRuntime(),
295
+ rootKey: importSigningSessionSealRootKey(config.rootSecret32)
176
296
  });
177
297
  }
178
298
  //#endregion
179
- export { createPassthroughSigningSessionSealCipherAdapter, createSigningSessionSealCipherAdapter, createSigningSessionSealShamir3PassCipherAdapter, encodeSigningSessionSealServerLockContext };
299
+ export { createEmailOtpEnrollmentShamir3PassCipherAdapter, createPassthroughSigningSessionSealCipherAdapter, createSigningSessionSealCipherAdapter, createSigningSessionSealShamir3PassCipherAdapter, encodeSigningSessionSealServerLockContext };
180
300
 
181
301
  //# sourceMappingURL=cipher.js.map
@@ -1,5 +1,5 @@
1
1
  import { type DigestB64u } from '../utils/canonicalPrimitives';
2
- export type WalletCustodyAdminOperation = 'credentials_list' | 'credential_label' | 'recovery_rotate' | 'recovery_read';
2
+ export type WalletCustodyAdminOperation = 'credentials_list' | 'credential_label' | 'recovery_rotate' | 'recovery_read' | 'recovery_status' | 'recovery_backup_acknowledge';
3
3
  /** Public challenge digest for a single wallet-administration factor proof. */
4
4
  export declare function computeWalletCustodyAdminChallengeDigest(input: {
5
5
  readonly walletId: string;
@@ -429,13 +429,7 @@ export type LinkedOwnerCredentialMetadataV1 = {
429
429
  } | {
430
430
  readonly kind: 'email_otp';
431
431
  readonly walletAuthMethodId: WalletAuthMethodId;
432
- /**
433
- * The verified address behind this factor. The management projection is
434
- * served only to an authenticated owner session, and the owner already
435
- * knows the address they enrolled — this exists so the settings surface
436
- * can name the factor. Local persistence keeps only the email hash.
437
- */
438
- readonly email: VerifiedEmailAddress;
432
+ readonly email?: never;
439
433
  readonly device?: never;
440
434
  readonly credentialIdB64u?: never;
441
435
  };
@@ -135,8 +135,10 @@ export type WalletCustodyRegistrationOutcome =
135
135
  /** No custody payload rode this leg. Registration is unaffected. */
136
136
  {
137
137
  readonly status: 'not_requested';
138
+ readonly issuedAtMs?: never;
138
139
  } | {
139
140
  readonly status: 'committed';
141
+ readonly issuedAtMs: number;
140
142
  }
141
143
  /**
142
144
  * A joining run: this key set's manifest digest, with no custody records
@@ -145,6 +147,7 @@ export type WalletCustodyRegistrationOutcome =
145
147
  | {
146
148
  readonly status: 'joined';
147
149
  readonly keyManifestDigestB64u: string;
150
+ readonly issuedAtMs?: never;
148
151
  }
149
152
  /**
150
153
  * Another ceremony established this wallet's custody first. The client must
@@ -152,11 +155,13 @@ export type WalletCustodyRegistrationOutcome =
152
155
  */
153
156
  | {
154
157
  readonly status: 'custody_already_established';
158
+ readonly issuedAtMs?: never;
155
159
  }
156
160
  /** The payload was refused. Nothing was written. */
157
161
  | {
158
162
  readonly status: 'rejected';
159
163
  readonly reason: string;
164
+ readonly issuedAtMs?: never;
160
165
  };
161
166
  /**
162
167
  * Reads a commit payload off the wire without judging it.
@@ -1,8 +1,8 @@
1
1
  export type EvmTransactionReceipt = {
2
- transactionHash?: string | null;
2
+ transactionHash: string;
3
3
  from?: string | null;
4
- blockNumber?: string | null;
5
- status?: string | null;
4
+ blockNumber: string;
5
+ status: string;
6
6
  gasUsed?: string | null;
7
7
  effectiveGasPrice?: string | null;
8
8
  gasPrice?: string | null;
@@ -441,7 +441,7 @@ type RouterAbEcdsaOperationStepUpWebAuthnCredentialV1Wire = {
441
441
  readonly signature: string;
442
442
  readonly userHandle: string | null;
443
443
  };
444
- readonly clientExtensionResults: unknown | null;
444
+ readonly clientExtensionResults: null;
445
445
  };
446
446
  export type RouterAbEcdsaOperationStepUpPreparationV1Wire = {
447
447
  readonly wallet_id: string;
@@ -8,9 +8,13 @@ export type EmailOtpShamirCipherResult = {
8
8
  readonly code: 'not_configured';
9
9
  readonly message: string;
10
10
  };
11
- export type EmailOtpServerSealRequest = {
11
+ export type EmailOtpServerSealApplyRequest = {
12
12
  readonly wrappedCiphertext?: unknown;
13
13
  };
14
+ export type EmailOtpServerSealRemoveRequest = {
15
+ readonly wrappedCiphertext?: unknown;
16
+ readonly enrollmentSealKeyVersion: string;
17
+ };
14
18
  export type EmailOtpServerSealResult = {
15
19
  ok: true;
16
20
  ciphertext: string;
@@ -108,15 +108,6 @@ type LinkedDeviceManagementCredentialMetadataPortV1 = {
108
108
  readonly walletId: WalletId;
109
109
  readonly credentialIdB64u: string;
110
110
  }): Promise<WebAuthnAuthenticatorDeviceInfo | null>;
111
- /**
112
- * The wallet's verified Email OTP address, from its canonical enrollment.
113
- * Served only inside the owner-scoped management projection, which is why the
114
- * plaintext address (not the hash) is acceptable here: the reader is the
115
- * wallet owner who enrolled it.
116
- */
117
- readEmailOtpAddressV1(input: {
118
- readonly walletId: WalletId;
119
- }): Promise<string | null>;
120
111
  };
121
112
  type LinkedDeviceManagementServiceOptionsV1 = {
122
113
  readonly tenantId: TenantId;
@@ -151,5 +142,6 @@ export declare class LinkedDeviceManagementServiceV1 {
151
142
  private buildLinkedDeviceSummaryV1;
152
143
  private buildOwnerDeviceSummaryV1;
153
144
  }
145
+ export declare const MAX_FRESH_REVOCATION_PROOF_AGE_MS_V1: number;
154
146
  export {};
155
147
  //# sourceMappingURL=linkedDeviceManagement.d.ts.map
@@ -11,7 +11,8 @@ export declare const emailOtpChallengeRows: {
11
11
  /** Statements on `email_otp_grants`. */
12
12
  export declare const emailOtpGrantRows: {
13
13
  insert: (prepare: ScopedD1Prepare, record: EmailOtpGrantRecord) => D1PreparedStatementLike;
14
- consume: (prepare: ScopedD1Prepare, grantToken: string) => D1PreparedStatementLike;
14
+ read: (prepare: ScopedD1Prepare, grantToken: string) => D1PreparedStatementLike;
15
+ consumeMatching: (prepare: ScopedD1Prepare, record: EmailOtpGrantRecord, nowMs: number) => D1PreparedStatementLike;
15
16
  };
16
17
  /** Statements on `email_otp_wallet_enrollments`. */
17
18
  export declare const emailOtpWalletEnrollmentRows: {
@@ -52,6 +52,7 @@ export interface CloudflareD1GatewayBaseEnv extends Readonly<Record<string, unkn
52
52
  readonly SIGNING_SESSION_SEAL_ROOT_SECRET_B64U?: string;
53
53
  readonly SIGNING_SESSION_SEAL_CURRENT_KEY_VERSION?: string;
54
54
  readonly SIGNING_SESSION_SEAL_ACCEPTED_WARM_KEY_VERSIONS?: string;
55
+ readonly EMAIL_OTP_SERVER_SEAL_ACCEPTED_WARM_KEY_VERSIONS?: string;
55
56
  readonly EMAIL_OTP_DELIVERY_MODE?: string;
56
57
  readonly EMAIL_OTP_RUNTIME_PROFILE?: string;
57
58
  readonly EMAIL_OTP_PROVIDER?: string;
@@ -12,6 +12,7 @@ type WebAuthnClientDataJson = {
12
12
  readonly origin: string;
13
13
  readonly type: string;
14
14
  };
15
+ export declare function unredactedWebAuthnExtensionField(input: unknown): string | null;
15
16
  export declare function decodeWebAuthnBase64UrlOrBase64(input: string, fieldName: string): Uint8Array<ArrayBuffer>;
16
17
  export declare function parseWebAuthnClientDataJsonBase64url(clientDataJSONB64u: string): WebAuthnClientDataJson;
17
18
  export declare function webAuthnOriginHostnameOrEmpty(origin: string): string;
@@ -6,7 +6,6 @@ import type { OrdinaryInactiveSignerMaterialDeactivationPortV1 } from '../../../
6
6
  import { D1WalletAuthorityStore, type D1WalletAuthorityStoreScope } from '../wallet/d1WalletAuthorityStore';
7
7
  import { CloudflareD1WebAuthnStore } from '../webauthn/d1WebAuthnStore';
8
8
  import type { CloudflareD1AuthorizationStore } from '../authorization/d1AuthorizationStore';
9
- import type { CloudflareD1EmailOtpEnrollmentStore } from '../emailOtp/d1EmailOtpEnrollmentStore';
10
9
  export declare function createD1LinkedDeviceManagementServiceV1(input: {
11
10
  readonly scope: D1WalletAuthorityStoreScope;
12
11
  readonly tenantId: TenantId;
@@ -15,8 +14,6 @@ export declare function createD1LinkedDeviceManagementServiceV1(input: {
15
14
  readonly authorizationService: AuthorizationService;
16
15
  readonly walletSessionAuthorizations: Pick<CloudflareD1AuthorizationStore, 'readActiveWalletSessionAuthorizationV2ByIdentity' | 'prepareRetireWalletSessionAuthorizationsV2ForAuthority'>;
17
16
  readonly webAuthnStore: CloudflareD1WebAuthnStore;
18
- /** Absent when Email OTP is not deployed; an email_otp method then fails closed. */
19
- readonly emailOtpEnrollments?: Pick<CloudflareD1EmailOtpEnrollmentStore, 'readEnrollment'>;
20
17
  readonly materialDeactivation?: OrdinaryInactiveSignerMaterialDeactivationPortV1;
21
18
  }): LinkedDeviceManagementServiceV1;
22
19
  //# sourceMappingURL=d1LinkedDeviceManagementService.d.ts.map
@@ -1,7 +1,7 @@
1
1
  import type { LinkedDeviceRevokeRequestV1, LinkedDeviceRevokeResultV1 } from '../../../../../../shared-ts/src/device-linking/contracts';
2
2
  import type { DigestB64u } from '../../../../../../shared-ts/src/utils/canonicalPrimitives';
3
3
  import type { WalletAuthMethodId, WalletId } from '../../../../../../shared-ts/src/utils/domainIds';
4
- import type { LinkedDeviceManagementServiceV1, LinkedDeviceManagementSourceV1 } from '../../../../core/deviceLinking/linkedDeviceManagement';
4
+ import { type LinkedDeviceManagementServiceV1, type LinkedDeviceManagementSourceV1 } from '../../../../core/deviceLinking/linkedDeviceManagement';
5
5
  import type { D1WalletAuthMethodStore } from '../../../../core/d1WalletAuthMethodStore';
6
6
  import type { DeviceLinkingAuthDeniedV1 } from '../../../transport/fetch/routes/deviceLinking';
7
7
  import type { D1LinkedDeviceFreshRevokeProofV1, verifyD1LinkedDeviceFreshRevokeProofV1 } from '../wallet/d1WalletAuthMethodBoundary';
@@ -1,11 +1,13 @@
1
1
  import type { EmailOtpGrantRecord } from '../../../../core/EmailOtpStores';
2
2
  import { type ScopedD1Prepare } from '../../../../core/emailOtpD1Statements';
3
+ import type { D1PreparedStatementLike } from '../../../../storage/tenantRoute';
3
4
  export declare class CloudflareD1EmailOtpGrantStore {
4
5
  private readonly prepare;
5
6
  constructor(input: {
6
7
  readonly prepare: ScopedD1Prepare;
7
8
  });
8
9
  put(record: EmailOtpGrantRecord): Promise<void>;
9
- consume(grantToken: string): Promise<EmailOtpGrantRecord | null>;
10
+ read(grantToken: string): Promise<EmailOtpGrantRecord | null>;
11
+ prepareConsumeMatching(record: EmailOtpGrantRecord, nowMs: number): D1PreparedStatementLike;
10
12
  }
11
13
  //# sourceMappingURL=d1EmailOtpGrantStore.d.ts.map
@@ -1,8 +1,8 @@
1
- import type { D1DatabaseLike } from '../../../../storage/tenantRoute';
1
+ import type { D1DatabaseLike, D1PreparedStatementLike } from '../../../../storage/tenantRoute';
2
2
  import type { ScopedD1Prepare } from '../../../../core/emailOtpD1Statements';
3
3
  import type { RateLimitResult } from '../../../../core/authService/rateLimits';
4
4
  import type { EmailOtpRateLimitPolicy } from '../auth/d1RouterApiAuthConfig';
5
- import { type EmailOtpRateLimitScope, type EmailOtpRateLimitConsumeInput } from './d1EmailOtpRecords';
5
+ import { emailOtpRateLimitExceeded, type EmailOtpRateLimitScope, type EmailOtpRateLimitConsumeInput } from './d1EmailOtpRecords';
6
6
  type EmailOtpRateLimitPolicies = {
7
7
  readonly [K in EmailOtpRateLimitScope]: EmailOtpRateLimitPolicy;
8
8
  };
@@ -10,11 +10,24 @@ type EmailOtpRateLimitConsumeResult = Readonly<RateLimitResult>;
10
10
  export declare class CloudflareD1EmailOtpRateLimitStore {
11
11
  private readonly input;
12
12
  constructor(input: {
13
+ readonly database: D1DatabaseLike;
13
14
  readonly prepare: ScopedD1Prepare;
14
15
  readonly batch: D1DatabaseLike['batch'];
15
16
  readonly rateLimits: EmailOtpRateLimitPolicies;
16
17
  });
17
18
  consume(input: EmailOtpRateLimitConsumeInput): Promise<EmailOtpRateLimitConsumeResult>;
19
+ consumeVerifiedGrant(input: {
20
+ readonly grantDelete: D1PreparedStatementLike;
21
+ readonly rateLimit: EmailOtpRateLimitConsumeInput & {
22
+ readonly kind: 'wallet';
23
+ readonly scope: 'grant';
24
+ };
25
+ readonly nowMs: number;
26
+ }): Promise<void>;
27
+ readExceededGrantRateLimit(input: EmailOtpRateLimitConsumeInput & {
28
+ readonly kind: 'wallet';
29
+ readonly scope: 'grant';
30
+ }, nowMs: number): Promise<ReturnType<typeof emailOtpRateLimitExceeded> | null>;
18
31
  private prepareConsume;
19
32
  private readCounter;
20
33
  }
@@ -1,12 +1,12 @@
1
1
  import type { EmailOtpServerSealRuntimeConfig } from '../auth/d1RouterApiAuthConfig';
2
- import type { EmailOtpServerSealRequest, EmailOtpServerSealResult as CoreEmailOtpServerSealResult } from '../../../../core/authService/emailOtpSeal';
2
+ import type { EmailOtpServerSealApplyRequest, EmailOtpServerSealRemoveRequest, EmailOtpServerSealResult as CoreEmailOtpServerSealResult } from '../../../../core/authService/emailOtpSeal';
3
3
  type EmailOtpServerSealResult = Readonly<CoreEmailOtpServerSealResult>;
4
4
  export declare class CloudflareD1EmailOtpServerSealRuntime {
5
5
  private readonly config;
6
6
  private readonly cipherResult;
7
7
  constructor(config: EmailOtpServerSealRuntimeConfig);
8
- removeEmailOtpServerSeal(input: EmailOtpServerSealRequest): Promise<EmailOtpServerSealResult>;
9
- applyEmailOtpServerSeal(input: EmailOtpServerSealRequest): Promise<EmailOtpServerSealResult>;
8
+ removeEmailOtpServerSeal(input: EmailOtpServerSealRemoveRequest): Promise<EmailOtpServerSealResult>;
9
+ applyEmailOtpServerSeal(input: EmailOtpServerSealApplyRequest): Promise<EmailOtpServerSealResult>;
10
10
  private runServerSealOperation;
11
11
  private createCipher;
12
12
  }
@@ -236,11 +236,14 @@ export interface RouterApiPasskeyCustodyService {
236
236
  */
237
237
  acknowledgeRecoveryBackup(request: {
238
238
  readonly walletId: string;
239
+ readonly expectedIssuedAtMs: number;
239
240
  }): Promise<{
240
241
  kind: 'acknowledged';
241
242
  issuedAtMs: number;
242
243
  } | {
243
244
  kind: 'no_recovery_set';
245
+ } | {
246
+ kind: 'conflict';
244
247
  }>;
245
248
  /**
246
249
  * Replaces the wallet's recovery codes with a freshly wrapped set.
@@ -43,9 +43,11 @@ export type WalletRecoveryCodeLocatorRecord = {
43
43
  */
44
44
  export type WalletCustodyRegistrationReservation = {
45
45
  readonly kind: 'reserved';
46
+ readonly issuedAtMs: number;
46
47
  readonly statements: readonly D1PreparedStatementLike[];
47
48
  } | {
48
49
  readonly kind: 'already_exists';
50
+ readonly issuedAtMs: number;
49
51
  } | {
50
52
  readonly kind: 'custody_already_established';
51
53
  } | {
@@ -148,12 +150,9 @@ export declare class CloudflareD1WalletCustodyCommitStore {
148
150
  */
149
151
  readBackupAcknowledgement(walletId: WalletId): Promise<WalletRecoveryBackupAcknowledgementV1 | null>;
150
152
  /**
151
- * Records that the owner confirmed saving their codes.
152
- *
153
- * Written with no expected version, unlike the recovery set: this row is
154
- * cosmetic, a repeat acknowledgement is not a conflict worth surfacing to
155
- * someone pressing a button, and it shares no record with the wraps a spend
156
- * updates — which is exactly why it is a separate key.
153
+ * Records the issuance that the owner confirmed saving. The caller selects
154
+ * that issuance before this write, so a concurrent rotation cannot make this
155
+ * acknowledgement apply to newer codes the owner did not see.
157
156
  */
158
157
  writeBackupAcknowledgement(record: WalletRecoveryBackupAcknowledgementV1): Promise<{
159
158
  kind: 'stored';
@@ -3,6 +3,7 @@ export type { WalletCustodyCeremonyCommitPayload };
3
3
  import type { CloudflareD1WalletCustodyCommitStore, WalletCustodyRegistrationReservation } from '../../cloudflare/d1/passkeyCustody/d1WalletCustodyCommitStore';
4
4
  export type WalletCustodyRegistrationCommitOutcome = {
5
5
  readonly kind: 'already_exists';
6
+ readonly issuedAtMs: number;
6
7
  } | {
7
8
  readonly kind: 'custody_already_established';
8
9
  } | {
@@ -10,12 +11,9 @@ export type WalletCustodyRegistrationCommitOutcome = {
10
11
  readonly reason: string;
11
12
  };
12
13
  /** The write that a reservation left for after the registration's wallet commit. */
13
- export type WalletCustodyRegistrationReserved = {
14
- readonly kind: 'reserved';
15
- readonly statements: Extract<WalletCustodyRegistrationReservation, {
16
- kind: 'reserved';
17
- }>['statements'];
18
- };
14
+ export type WalletCustodyRegistrationReserved = Extract<WalletCustodyRegistrationReservation, {
15
+ kind: 'reserved';
16
+ }>;
19
17
  /**
20
18
  * Builds the records and claims their recovery-code routes.
21
19
  *
@@ -701,7 +701,13 @@ type RouterApiMethodTypes = {
701
701
  readonly expiresAtMs: number;
702
702
  } | RouterApiOkFailure;
703
703
  };
704
- removeEmailOtpServerSeal: RouterApiMethodTypes['applyEmailOtpServerSeal'];
704
+ removeEmailOtpServerSeal: {
705
+ readonly input: {
706
+ readonly wrappedCiphertext?: unknown;
707
+ readonly enrollmentSealKeyVersion: string;
708
+ };
709
+ readonly result: RouterApiMethodTypes['applyEmailOtpServerSeal']['result'];
710
+ };
705
711
  respondWalletAddSignerEcdsaDerivation: {
706
712
  readonly input: WalletAddSignerEcdsaDerivationRespondRequest;
707
713
  readonly result: WalletAddSignerEcdsaDerivationRespondResponse;
@@ -52,15 +52,6 @@ export declare function handleWalletRecoveryBackupAcknowledge(ctx: FetchRouterAp
52
52
  */
53
53
  export declare function handleWalletRecoveryRotate(ctx: FetchRouterApiContext): Promise<Response | null>;
54
54
  export declare function handleWalletRecoveryRead(ctx: FetchRouterApiContext): Promise<Response | null>;
55
- /**
56
- * Reporting recovery status to the wallet's owner.
57
- *
58
- * The wallet comes from the path. Origin binding keeps this count endpoint
59
- * scoped to browser callers without exposing recovery identifiers.
60
- *
61
- * Counts only, never identifiers. Which codes remain is not something even
62
- * the owner's browser needs, and a list would be one leak away from being
63
- * useful to someone else.
64
- */
55
+ /** Reports current recovery-code counts after fresh owner proof. */
65
56
  export declare function handleWalletRecoveryStatus(ctx: FetchRouterApiContext): Promise<Response | null>;
66
57
  //# sourceMappingURL=passkeyCustody.d.ts.map
@@ -0,0 +1,28 @@
1
+ import type { WebAuthnAuthenticationCredential } from '../../../../core/types';
2
+ export type WalletCustodyOwnerProofWire = {
3
+ readonly kind: 'passkey';
4
+ readonly walletId: string;
5
+ readonly rpId: string;
6
+ readonly credentialIdB64u: string;
7
+ readonly challenge_digest: string;
8
+ readonly webauthn_authentication: WebAuthnAuthenticationCredential;
9
+ } | {
10
+ readonly kind: 'email_otp';
11
+ readonly provider_subject_id: string;
12
+ readonly challenge_id: string;
13
+ readonly otp_code: string;
14
+ readonly challenge_digest: string;
15
+ };
16
+ export declare function requireExactObjectFields(value: Record<string, unknown>, fields: readonly string[], label: string): void;
17
+ export declare function parseCustodyOwnerProof(value: unknown): WalletCustodyOwnerProofWire;
18
+ export declare function parseRecoveryBackupAcknowledgementRequest(body: unknown): {
19
+ readonly walletId: string;
20
+ readonly requestId: string;
21
+ readonly expectedIssuedAtMs: number;
22
+ readonly factorProof: unknown;
23
+ };
24
+ export declare function parseRecoveryStatusRequest(body: unknown): {
25
+ readonly requestId: string;
26
+ readonly factorProof: unknown;
27
+ };
28
+ //# sourceMappingURL=walletCustodyRequestBoundary.d.ts.map
@@ -39,7 +39,10 @@ export declare function createPassthroughSigningSessionSealCipherAdapter(): Sign
39
39
  export declare function encodeSigningSessionSealServerLockContext(input: {
40
40
  readonly protocol: SigningSessionSealProtocol;
41
41
  readonly keyVersion: string;
42
+ readonly walletId: string;
43
+ readonly thresholdSessionId: string;
42
44
  }): Uint8Array;
45
+ export declare function createEmailOtpEnrollmentShamir3PassCipherAdapter(options: CreateSigningSessionSealShamir3PassCipherAdapterOptions): SigningSessionSealCipherAdapter;
43
46
  export declare function createSigningSessionSealShamir3PassCipherAdapter(options: CreateSigningSessionSealShamir3PassCipherAdapterOptions): SigningSessionSealCipherAdapter;
44
47
  export {};
45
48
  //# sourceMappingURL=cipher.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seams/wallet-server",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
4
4
  "type": "module",
5
5
  "description": "Seams server TypeScript SDK for NEAR and EVM-family wallets",
6
6
  "main": "./dist/esm/index.js",