@seams/wallet-server 0.23.0 → 0.25.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 (86) hide show
  1. package/THIRD_PARTY_NOTICES.md +182 -5
  2. package/artifact-manifest.json +23 -13
  3. package/cloudflare-router-ab/build/deriver-a/build-stamp.json +1 -1
  4. package/cloudflare-router-ab/build/deriver-b/build-stamp.json +1 -1
  5. package/cloudflare-router-ab/build/local-tools/darwin-arm64/router_ab_local_init +0 -0
  6. package/cloudflare-router-ab/build/local-tools/darwin-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/signing-worker/build-stamp.json +1 -1
  9. package/cloudflare-router-ab/build/tenant-root-control-plane/build-stamp.json +1 -1
  10. package/dist/esm/authorization/voiceIdDeviceProof.js +32 -0
  11. package/dist/esm/core/ThresholdService/validation.js +17 -1
  12. package/dist/esm/core/d1WalletAuthMethodStore.js +42 -1
  13. package/dist/esm/core/deviceLinking/linkedDeviceManagement.js +5 -0
  14. package/dist/esm/core/signingLanes/WalletExecutionLaneProjection.js +11 -2
  15. package/dist/esm/router/cloudflare/d1/auth/d1RouterApiAuthService.js +23 -6
  16. package/dist/esm/router/cloudflare/d1/deviceLinking/d1LinkedDeviceAuthorityInstallService.js +5 -5
  17. package/dist/esm/router/cloudflare/d1/emailOtp/d1GoogleEmailOtpRegistrationAttemptStore.js +1 -1
  18. package/dist/esm/router/cloudflare/d1/passkeyCustody/d1PasskeyCustodyEnvelopeStore.js +6 -0
  19. package/dist/esm/router/cloudflare/d1/wallet/d1VoiceIdActivation.js +139 -0
  20. package/dist/esm/router/cloudflare/d1/wallet/d1VoiceIdSessions.js +138 -0
  21. package/dist/esm/router/cloudflare/d1/wallet/d1VoiceIdWalletSessionChallenges.js +88 -0
  22. package/dist/esm/router/cloudflare/d1/wallet/d1WalletAuthMethodService.js +12 -4
  23. package/dist/esm/router/cloudflare/d1/wallet/d1WalletAuthorityStore.js +14 -0
  24. package/dist/esm/router/cloudflare/d1/webauthn/d1WebAuthnRecords.js +1 -0
  25. package/dist/esm/router/cloudflare.js +576 -10
  26. package/dist/esm/router/domains/ed25519Yao/export/routerAbEd25519YaoExport.js +5 -0
  27. package/dist/esm/router/domains/passkeyCustody/walletRecoveryFinalization.js +1 -0
  28. package/dist/esm/router/domains/signingOperations/routerAbNormalSigningAdmissionCore.js +1 -0
  29. package/dist/esm/router/domains/signingOperations/walletExecutionAdmission.js +1 -0
  30. package/dist/esm/router/domains/voiceId/voiceIdActivation.js +12 -0
  31. package/dist/esm/router/domains/voiceId/voiceIdSessions.js +0 -0
  32. package/dist/esm/router/framework/routeDefinitions.js +13 -0
  33. package/dist/esm/router/transport/fetch/createFetchRouter.js +5 -0
  34. package/dist/esm/router/transport/fetch/routes/deviceLinkingOwnerAuthorization.js +12 -1
  35. package/dist/esm/router/transport/fetch/routes/walletExecutionLanePreflight.js +1 -0
  36. package/dist/esm/router/transport/fetch/routes/walletVoiceIdActivation.js +139 -0
  37. package/dist/esm/router/transport/fetch/routes/walletVoiceIdNear.js +36 -0
  38. package/dist/esm/router/transport/fetch/routes/walletVoiceIdSessions.js +127 -0
  39. package/dist/esm/shared-ts/src/authorization/voiceIdDeviceAssertion.js +110 -0
  40. package/dist/esm/shared-ts/src/device-linking/authorityActivation.js +1 -0
  41. package/dist/esm/shared-ts/src/device-linking/committedSignerPackages.js +1 -1
  42. package/dist/esm/shared-ts/src/passkey-custody/custodyEnvelope.js +30 -3
  43. package/dist/esm/shared-ts/src/passkey-custody/custodySecretBinding.js +1 -1
  44. package/dist/esm/shared-ts/src/signing-lanes/recordParsers.js +15 -1
  45. package/dist/esm/shared-ts/src/threshold/sessionPolicy.js +2 -1
  46. package/dist/esm/shared-ts/src/utils/registrationIntent.js +1 -3
  47. package/dist/esm/shared-ts/src/utils/walletAuthAuthority.js +46 -2
  48. package/dist/esm/shared-ts/src/utils/walletAuthMethodRecord.js +116 -7
  49. package/dist/esm/shared-ts/src/wallet-recovery/walletRecoveryCommittedProjection.js +1 -0
  50. package/dist/esm/wasm/near_signer/pkg/wasm_signer_worker_bg.wasm +0 -0
  51. package/dist/types/shared-ts/src/authorization/voiceIdDeviceAssertion.d.ts +32 -0
  52. package/dist/types/shared-ts/src/device-linking/committedSignerPackages.d.ts +3 -1
  53. package/dist/types/shared-ts/src/device-linking/contracts.d.ts +8 -0
  54. package/dist/types/shared-ts/src/passkey-custody/custodyEnvelope.d.ts +29 -0
  55. package/dist/types/shared-ts/src/passkey-custody/custodySecretBinding.d.ts +1 -0
  56. package/dist/types/shared-ts/src/signing-lanes/execution.d.ts +2 -2
  57. package/dist/types/shared-ts/src/signing-lanes/recordParsers.d.ts +2 -1
  58. package/dist/types/shared-ts/src/signing-lanes/records.d.ts +10 -2
  59. package/dist/types/shared-ts/src/signing-lanes/rotation.d.ts +2 -2
  60. package/dist/types/shared-ts/src/threshold/sessionPolicy.d.ts +9 -0
  61. package/dist/types/shared-ts/src/utils/addWalletAuthMethod.d.ts +3 -3
  62. package/dist/types/shared-ts/src/utils/domainIds.d.ts +4 -5
  63. package/dist/types/shared-ts/src/utils/registrationIntent.d.ts +0 -1
  64. package/dist/types/shared-ts/src/utils/signerDomain.d.ts +3 -0
  65. package/dist/types/shared-ts/src/utils/walletAuthAuthority.d.ts +32 -2
  66. package/dist/types/shared-ts/src/utils/walletAuthMethodRecord.d.ts +15 -1
  67. package/dist/types/shared-ts/src/utils/walletCapabilityBindings.d.ts +20 -0
  68. package/dist/types/wallet-server/src/authorization/voiceIdDeviceProof.d.ts +29 -0
  69. package/dist/types/wallet-server/src/core/d1WalletAuthMethodStore.d.ts +13 -2
  70. package/dist/types/wallet-server/src/core/registrationContracts.d.ts +8 -0
  71. package/dist/types/wallet-server/src/core/signingLanes/WalletExecutionLaneProjection.d.ts +4 -0
  72. package/dist/types/wallet-server/src/router/cloudflare/d1/passkeyCustody/d1PasskeyCustodyEnvelopeStore.d.ts +4 -0
  73. package/dist/types/wallet-server/src/router/cloudflare/d1/wallet/d1VoiceIdActivation.d.ts +14 -0
  74. package/dist/types/wallet-server/src/router/cloudflare/d1/wallet/d1VoiceIdSessions.d.ts +19 -0
  75. package/dist/types/wallet-server/src/router/cloudflare/d1/wallet/d1VoiceIdWalletSessionChallenges.d.ts +26 -0
  76. package/dist/types/wallet-server/src/router/cloudflare/d1/wallet/d1WalletAuthMethodService.d.ts +1 -0
  77. package/dist/types/wallet-server/src/router/cloudflare/d1/wallet/d1WalletRelocationOwner.d.ts +25 -0
  78. package/dist/types/wallet-server/src/router/domains/voiceId/voiceIdActivation.d.ts +29 -0
  79. package/dist/types/wallet-server/src/router/domains/voiceId/voiceIdSessions.d.ts +35 -0
  80. package/dist/types/wallet-server/src/router/framework/authServicePort.d.ts +3 -0
  81. package/dist/types/wallet-server/src/router/transport/fetch/routes/walletVoiceIdActivation.d.ts +12 -0
  82. package/dist/types/wallet-server/src/router/transport/fetch/routes/walletVoiceIdNear.d.ts +14 -0
  83. package/dist/types/wallet-server/src/router/transport/fetch/routes/walletVoiceIdSessions.d.ts +19 -0
  84. package/migrations/d1-signer/0083_voiceid_wallet_session_challenges.sql +29 -0
  85. package/migrations/d1-signer/0084_voiceid_wallet_auth_methods.sql +373 -0
  86. package/package.json +1 -1
@@ -9,7 +9,7 @@ import { executeSignedDelegateWithRelayer } from "../../../../delegateAction/ind
9
9
  import { parseWalletUnlockIssuanceRejectionCode } from "../../../framework/authServicePort.js";
10
10
  import { parseWebAuthnAuthenticationCredential } from "../../../auth/webAuthnCredentialCodecs.js";
11
11
  import { resolveActiveOwnerWalletExecutionLane, resolveWalletAuthMethodIdForAuthority } from "../../../../core/signingLanes/WalletExecutionLaneProjection.js";
12
- import { DEFAULT_WALLET_SESSION_TTL_MS } from "../../../../shared-ts/src/threshold/sessionPolicy.js";
12
+ import { DEFAULT_WALLET_SESSION_TTL_MS, LINKED_DEVICE_WALLET_SESSION_TTL_MS } from "../../../../shared-ts/src/threshold/sessionPolicy.js";
13
13
  import { createDeviceLinkingOwnerRequestAuthenticatorV1 } from "../../../transport/fetch/routes/deviceLinkingOwnerAuthorization.js";
14
14
  import { sha256BytesPortable, toRecordValue } from "./d1RouterApiAuthBoundary.js";
15
15
  import { verifyD1LinkedDeviceFreshRevokeProofV1 } from "../wallet/d1WalletAuthMethodBoundary.js";
@@ -31,6 +31,11 @@ import { dispatchWalletRegistrationSetup } from "../../../framework/registration
31
31
  import { readActiveWalletSessionCredential, readEd25519WalletSessionSigningRead, readExhaustedWalletSessionCredential, readWalletSessionSigningCandidate } from "./walletSessionCredentialReaders.js";
32
32
  import { createD1AuthorizedOperationRouteService } from "./d1AuthorizedOperationRouteService.js";
33
33
  import { parseRegistrationEstablishedSessionProjectionV2 } from "../../../../shared-ts/src/utils/registrationEstablishedSession.js";
34
+ import { D1WalletAuthMethodRevocationReplayStoreV1 } from "../wallet/d1WalletAuthMethodRevocationReplayStore.js";
35
+ import { CloudflareD1WalletAuthMethodService } from "../wallet/d1WalletAuthMethodService.js";
36
+ import { D1VoiceIdActivation } from "../wallet/d1VoiceIdActivation.js";
37
+ import { D1VoiceIdSessions } from "../wallet/d1VoiceIdSessions.js";
38
+ import { D1VoiceIdWalletSessionChallenges } from "../wallet/d1VoiceIdWalletSessionChallenges.js";
34
39
  import { signGasRelayerNearTransactionWithDeps } from "../../../../core/authService/nearTransactions.js";
35
40
  import { ensureSignerWasmRuntime } from "../../../../core/authService/wasm.js";
36
41
  import { CloudflareD1PasskeyCustodyEnvelopeStore } from "../passkeyCustody/d1PasskeyCustodyEnvelopeStore.js";
@@ -49,8 +54,6 @@ import { CloudflareD1EmailOtpRecoveryService } from "../emailOtp/d1EmailOtpRecov
49
54
  import { CloudflareD1GoogleEmailOtpSessionResolver } from "../emailOtp/d1GoogleEmailOtpSessionResolver.js";
50
55
  import { CloudflareD1IdentityService } from "../identity/d1IdentityService.js";
51
56
  import { CloudflareD1OidcVerificationService } from "../oidc/d1OidcVerificationService.js";
52
- import { D1WalletAuthMethodRevocationReplayStoreV1 } from "../wallet/d1WalletAuthMethodRevocationReplayStore.js";
53
- import { CloudflareD1WalletAuthMethodService } from "../wallet/d1WalletAuthMethodService.js";
54
57
  import { parseWalletRegistrationSessionCommitReceiptV2 } from "../registration/walletRegistrationSessionCommitReceipt.js";
55
58
  import { CloudflareD1WalletRegistrationService } from "../registration/d1WalletRegistrationService.js";
56
59
  import { CloudflareD1WalletRegistrationCommitStore } from "../registration/d1WalletRegistrationCommitStore.js";
@@ -1054,7 +1057,14 @@ function createD1WalletRegistrationRouteService(assembly) {
1054
1057
  };
1055
1058
  }
1056
1059
  function createD1WalletAuthMethodRouteService(assembly) {
1060
+ const voiceIdActivation = new D1VoiceIdActivation(assembly.options.database, {
1061
+ namespace: assembly.options.namespace,
1062
+ orgId: assembly.options.orgId,
1063
+ projectId: assembly.options.projectId,
1064
+ envId: assembly.options.envId
1065
+ }, assembly.walletAuthMethodStore, assembly.passkeyCustodyEnvelopes);
1057
1066
  return {
1067
+ activateVoiceId: voiceIdActivation.activate.bind(voiceIdActivation),
1058
1068
  resolveActiveWalletSessionAuthority: assembly.walletAuthMethods.resolveActiveWalletSessionAuthority.bind(assembly.walletAuthMethods),
1059
1069
  revokeWalletAuthMethodWithFreshProof: assembly.walletAuthMethods.revokeWalletAuthMethodWithFreshProof.bind(assembly.walletAuthMethods),
1060
1070
  verifyActivePasskeyAuthority: assembly.walletAuthMethods.verifyActivePasskeyAuthority.bind(assembly.walletAuthMethods),
@@ -1075,8 +1085,6 @@ function createD1WalletAuthMethodRouteService(assembly) {
1075
1085
  startWalletAddSigner: assembly.walletAddSigners.startWalletAddSigner.bind(assembly.walletAddSigners)
1076
1086
  };
1077
1087
  }
1078
- const LINKED_DEVICE_WALLET_SESSION_TTL_MS = 900 * 1e3;
1079
- const LINKED_DEVICE_WALLET_SESSION_REMAINING_USES = 100;
1080
1088
  async function resolveEmailOtpAuthorityForUnlock(input) {
1081
1089
  try {
1082
1090
  const enrollment = await input.emailOtpRecoveryService.readEmailOtpEnrollment({
@@ -1127,7 +1135,7 @@ async function issueWalletSessionForActiveAuthority(input) {
1127
1135
  authority: input.resolved.authority,
1128
1136
  walletAuthMethodId: input.resolved.authMethod.walletAuthMethodId,
1129
1137
  mintId: mintId.value,
1130
- remainingUses: deviceLinked ? LINKED_DEVICE_WALLET_SESSION_REMAINING_USES : 3,
1138
+ remainingUses: deviceLinked ? 100 : 3,
1131
1139
  issuedAtMs,
1132
1140
  expiresAtMs: issuedAtMs + (deviceLinked ? LINKED_DEVICE_WALLET_SESSION_TTL_MS : DEFAULT_WALLET_SESSION_TTL_MS)
1133
1141
  });
@@ -1195,7 +1203,16 @@ async function issueWalletSessionForEmailOtpUnlock(input) {
1195
1203
  });
1196
1204
  }
1197
1205
  function createD1WalletUnlockRouteService(assembly) {
1206
+ const scope = {
1207
+ namespace: assembly.options.namespace,
1208
+ orgId: assembly.options.orgId,
1209
+ projectId: assembly.options.projectId,
1210
+ envId: assembly.options.envId
1211
+ };
1212
+ const voiceIdSessions = new D1VoiceIdSessions(assembly.walletAuthMethodStore, assembly.walletAuthorityStore, assembly.passkeyCustodyEnvelopes, new D1VoiceIdWalletSessionChallenges(assembly.options.database, scope), assembly.authorizationService, assembly.options.orgId);
1198
1213
  return {
1214
+ issueVoiceIdChallenge: voiceIdSessions.issueChallenge.bind(voiceIdSessions),
1215
+ verifyAndIssueVoiceId: voiceIdSessions.verifyAndIssue.bind(voiceIdSessions),
1199
1216
  createEmailOtpUnlockChallenge: assembly.emailOtpRecoveryService.createEmailOtpUnlockChallenge.bind(assembly.emailOtpRecoveryService),
1200
1217
  createWebAuthnLoginOptions: assembly.webAuthnAuthService.createWebAuthnLoginOptions.bind(assembly.webAuthnAuthService),
1201
1218
  markEmailOtpStrongAuthSatisfied: assembly.emailOtpRecoveryService.markEmailOtpStrongAuthSatisfied.bind(assembly.emailOtpRecoveryService),
@@ -217,7 +217,7 @@ var D1LinkedDeviceAuthorityInstallServiceV1 = class {
217
217
  };
218
218
  const authority = await this.options.authorityStore.readById(stored.authorityId);
219
219
  const authMethod = await this.options.authMethodStore.readByIdV2({ walletAuthMethodId: stored.authMethodId });
220
- if (!authority || !authMethod) return {
220
+ if (!authority || !authMethod || authMethod.kind === "voiceid") return {
221
221
  kind: "integrity_error",
222
222
  message: "pending authority records are missing"
223
223
  };
@@ -1295,7 +1295,7 @@ function buildPendingAuthMethod(input, authorityId, nowMs) {
1295
1295
  createdAtMs: draft.createdAtMs,
1296
1296
  updatedAtMs: nowMs
1297
1297
  });
1298
- if (record.status !== "pending_local_install") throw new Error("auth method builder returned a non-pending record");
1298
+ if (record.status !== "pending_local_install" || record.kind !== "passkey") throw new Error("auth method builder returned a non-pending record");
1299
1299
  return record;
1300
1300
  }
1301
1301
  const record = buildWalletAuthMethodRecordV2({
@@ -1310,7 +1310,7 @@ function buildPendingAuthMethod(input, authorityId, nowMs) {
1310
1310
  createdAtMs: draft.createdAtMs,
1311
1311
  updatedAtMs: nowMs
1312
1312
  });
1313
- if (record.status !== "pending_local_install") throw new Error("auth method builder returned a non-pending record");
1313
+ if (record.status !== "pending_local_install" || record.kind !== "email_otp") throw new Error("auth method builder returned a non-pending record");
1314
1314
  return record;
1315
1315
  }
1316
1316
  function buildActivationSet(manifest, reservations) {
@@ -1399,7 +1399,7 @@ function buildActiveAuthMethod(pending, activatedAtMs) {
1399
1399
  updatedAtMs: activatedAtMs,
1400
1400
  activatedAtMs
1401
1401
  });
1402
- if (record.status !== "active") throw new Error("auth method builder returned a non-active record");
1402
+ if (record.status !== "active" || record.kind !== "passkey") throw new Error("auth method builder returned a non-active record");
1403
1403
  return record;
1404
1404
  }
1405
1405
  const record = buildWalletAuthMethodRecordV2({
@@ -1415,7 +1415,7 @@ function buildActiveAuthMethod(pending, activatedAtMs) {
1415
1415
  updatedAtMs: activatedAtMs,
1416
1416
  activatedAtMs
1417
1417
  });
1418
- if (record.status !== "active") throw new Error("auth method builder returned a non-active record");
1418
+ if (record.status !== "active" || record.kind !== "email_otp") throw new Error("auth method builder returned a non-active record");
1419
1419
  return record;
1420
1420
  }
1421
1421
  async function buildPasskeyCredentialPromotionStatements(input) {
@@ -1,10 +1,10 @@
1
1
  import { normalizeOptionalTrimmedString } from "../../../../shared-ts/src/utils/normalize.js";
2
2
  import { secureRandomBase64Url } from "../../../../shared-ts/src/utils/secureRandomId.js";
3
3
  import { d1MutationChanges } from "../auth/d1RouterApiAuthBoundary.js";
4
+ import { abandonedGoogleEmailOtpRegistrationAttemptRecord, parseGoogleEmailOtpRegistrationAttemptRow } from "./d1GoogleEmailOtpRegistrationRecords.js";
4
5
  import { registrationAttemptMatchesReplacementScope, registrationAttemptMatchesStartedScope } from "../../../../core/EmailOtpRecords.js";
5
6
  import { completeGoogleRegistration } from "./d1GoogleRegistrationCompletion.js";
6
7
  import { emailOtpRegistrationAttemptRows } from "../../../../core/emailOtpD1Statements.js";
7
- import { abandonedGoogleEmailOtpRegistrationAttemptRecord, parseGoogleEmailOtpRegistrationAttemptRow } from "./d1GoogleEmailOtpRegistrationRecords.js";
8
8
  //#region src/router/cloudflare/d1/emailOtp/d1GoogleEmailOtpRegistrationAttemptStore.ts
9
9
  var CloudflareD1GoogleEmailOtpRegistrationAttemptStore = class {
10
10
  prepare;
@@ -43,6 +43,10 @@ function envelopeFactorRef(envelope) {
43
43
  enrollmentId: envelope.factor.enrollmentId,
44
44
  enrollmentSealKeyVersion: envelope.factor.enrollmentSealKeyVersion
45
45
  };
46
+ case "voiceid": return {
47
+ kind: "voiceid",
48
+ deviceKeyId: envelope.factor.deviceKeyId
49
+ };
46
50
  }
47
51
  }
48
52
  /**
@@ -62,6 +66,7 @@ function factorKeyPart(factor) {
62
66
  factor.enrollmentId,
63
67
  factor.enrollmentSealKeyVersion
64
68
  ];
69
+ case "voiceid": return ["voiceid", String(factor.deviceKeyId)];
65
70
  }
66
71
  }
67
72
  function factorRefsMatch(left, right) {
@@ -73,6 +78,7 @@ function factorRefsMatch(left, right) {
73
78
  case "email_otp":
74
79
  if (right.kind !== "email_otp") return false;
75
80
  return left.enrollmentId === right.enrollmentId && left.enrollmentSealKeyVersion === right.enrollmentSealKeyVersion;
81
+ case "voiceid": return right.kind === "voiceid" && left.deviceKeyId === right.deviceKeyId;
76
82
  }
77
83
  return false;
78
84
  }
@@ -0,0 +1,139 @@
1
+ import { base64UrlDecode } from "../../../../shared-ts/src/utils/base64.js";
2
+ import { alphabetizeStringify } from "../../../../shared-ts/src/utils/digests.js";
3
+ import { sha256DigestB64u } from "../../../../shared-ts/src/utils/canonicalPrimitives.js";
4
+ import { isFullOwnerAuthorityV1 } from "./d1WalletAuthMethodService.js";
5
+ //#region src/router/cloudflare/d1/wallet/d1VoiceIdActivation.ts
6
+ function ownerSessionIsCurrent(source, nowMs) {
7
+ const session = source.authorization.session;
8
+ const quota = source.authorization.quota;
9
+ return source.authority.state === "active" && source.authMethod.status === "active" && source.authority.walletId === session.walletId && source.authority.authorityId === session.authorityId && source.authority.authorityDigestB64u === session.authorityDigestB64u && source.authority.revocationEpoch === session.authorityRevocationEpoch && source.authMethod.walletId === session.walletId && source.authMethod.walletAuthorityId === session.authorityId && source.authMethod.walletAuthMethodId === session.walletAuthMethodId && quota.walletSessionId === session.walletSessionId && quota.quotaId === session.quotaId && quota.tenantId === session.tenantId && quota.principalId === session.principalId && quota.remainingUses > 0 && session.createdAtMs <= nowMs && session.expiresAtMs > nowMs && quota.expiresAtMs > nowMs && isFullOwnerAuthorityV1(source.authority);
10
+ }
11
+ /** Aborts the D1 batch if the source Wallet Session lost its live quota or expired. */
12
+ function prepareOwnerSessionGuard(input) {
13
+ const session = input.source.authorization.session;
14
+ return input.database.prepare(`
15
+ INSERT INTO wallet_authority_cas_guard (guard_id)
16
+ SELECT 1 WHERE NOT EXISTS (
17
+ SELECT 1 FROM wallet_session_authorizations_v2 AS source_session
18
+ JOIN authorization_wallet_session_quotas AS quota
19
+ ON quota.namespace = source_session.namespace
20
+ AND quota.tenant_id = source_session.tenant_id
21
+ AND quota.quota_id = source_session.quota_id
22
+ AND quota.wallet_session_id = source_session.wallet_session_id
23
+ WHERE source_session.namespace = ? AND source_session.org_id = ?
24
+ AND source_session.project_id = ? AND source_session.env_id = ?
25
+ AND source_session.tenant_id = ? AND source_session.authorization_id = ?
26
+ AND source_session.wallet_id = ? AND source_session.authority_id = ?
27
+ AND source_session.wallet_auth_method_id = ?
28
+ AND source_session.authority_digest_b64u = ?
29
+ AND source_session.authority_revocation_epoch = ?
30
+ AND source_session.wallet_session_id = ? AND source_session.quota_id = ?
31
+ AND source_session.retired_at_ms IS NULL
32
+ AND source_session.issued_at_ms <= ? AND source_session.expires_at_ms > ?
33
+ AND quota.lifecycle_kind = 'active' AND quota.remaining_uses > 0
34
+ AND quota.expires_at_ms > ?
35
+ )
36
+ `).bind(input.scope.namespace, input.scope.orgId, input.scope.projectId, input.scope.envId, String(session.tenantId), String(session.authorizationId), String(session.walletId), String(session.authorityId), String(session.walletAuthMethodId), String(session.authorityDigestB64u), session.authorityRevocationEpoch, String(session.walletSessionId), String(session.quotaId), input.nowMs, input.nowMs, input.nowMs);
37
+ }
38
+ function buildActiveVoiceIdMethod(input) {
39
+ return {
40
+ version: "wallet_auth_method_v2",
41
+ walletAuthMethodId: input.walletAuthMethodId,
42
+ walletId: input.walletId,
43
+ walletAuthorityId: input.walletAuthorityId,
44
+ kind: "voiceid",
45
+ status: "active",
46
+ deviceKeyId: input.deviceKeyId,
47
+ devicePublicKeyB64u: input.devicePublicKeyB64u,
48
+ createdAtMs: input.nowMs,
49
+ updatedAtMs: input.nowMs,
50
+ activatedAtMs: input.nowMs
51
+ };
52
+ }
53
+ async function readCommittedActivation(input) {
54
+ const storedMethod = await input.authMethods.readByIdV2({ walletAuthMethodId: input.command.targetWalletAuthMethodId });
55
+ const session = input.command.source.authorization.session;
56
+ if (!storedMethod || storedMethod.kind !== "voiceid" || storedMethod.status !== "active" || storedMethod.walletId !== session.walletId || storedMethod.walletAuthorityId !== session.authorityId || storedMethod.deviceKeyId !== input.deviceKeyId || storedMethod.devicePublicKeyB64u !== input.command.devicePublicKeyB64u) return null;
57
+ const storedEnvelope = await input.envelopes.lookupEnvelopeForWalletAuthMethod({
58
+ walletId: session.walletId,
59
+ walletAuthMethodId: input.command.targetWalletAuthMethodId,
60
+ factor: {
61
+ kind: "voiceid",
62
+ deviceKeyId: input.deviceKeyId
63
+ }
64
+ });
65
+ if (storedEnvelope.kind !== "active" || alphabetizeStringify(storedEnvelope.envelope) !== alphabetizeStringify(input.expectedEnvelope)) return null;
66
+ return storedMethod;
67
+ }
68
+ var D1VoiceIdActivation = class {
69
+ constructor(database, scope, authMethods, envelopes) {
70
+ this.database = database;
71
+ this.scope = scope;
72
+ this.authMethods = authMethods;
73
+ this.envelopes = envelopes;
74
+ }
75
+ async activate(input) {
76
+ if (!Number.isSafeInteger(input.nowMs) || !ownerSessionIsCurrent(input.source, input.nowMs)) return {
77
+ kind: "rejected",
78
+ code: "invalid_source"
79
+ };
80
+ const session = input.source.authorization.session;
81
+ const deviceKeyId = await sha256DigestB64u(base64UrlDecode(input.devicePublicKeyB64u));
82
+ const ciphertextDigest = await sha256DigestB64u(base64UrlDecode(input.envelope.sealedCustodySecretB64u));
83
+ if (!String(input.targetWalletAuthMethodId).startsWith("wallet-auth-method:") || input.targetWalletAuthMethodId === input.source.authMethod.walletAuthMethodId || input.envelope.walletId !== session.walletId || input.envelope.ownership.walletAuthMethodId !== input.targetWalletAuthMethodId || input.envelope.factor.deviceKeyId !== deviceKeyId || input.envelope.createdAtMs > input.nowMs || input.envelope.lifecycle.activatedAtMs > input.nowMs || ciphertextDigest !== input.envelope.ciphertextDigestB64u) return {
84
+ kind: "rejected",
85
+ code: "invalid_target"
86
+ };
87
+ const authMethod = buildActiveVoiceIdMethod({
88
+ walletAuthMethodId: input.targetWalletAuthMethodId,
89
+ walletId: session.walletId,
90
+ walletAuthorityId: session.authorityId,
91
+ deviceKeyId,
92
+ devicePublicKeyB64u: input.devicePublicKeyB64u,
93
+ nowMs: input.nowMs
94
+ });
95
+ if ((await this.envelopes.linkWalletCustodyFactorAtomically({
96
+ envelope: input.envelope,
97
+ additionalStatements: [
98
+ prepareOwnerSessionGuard({
99
+ database: this.database,
100
+ scope: this.scope,
101
+ source: input.source,
102
+ nowMs: input.nowMs
103
+ }),
104
+ ...this.authMethods.prepareActiveV2SourceGuardStatements({
105
+ walletId: session.walletId,
106
+ walletAuthMethodId: input.source.authMethod.walletAuthMethodId,
107
+ walletAuthorityId: session.authorityId,
108
+ authorityDigestB64u: session.authorityDigestB64u,
109
+ authorityRevocationEpoch: session.authorityRevocationEpoch
110
+ }),
111
+ ...this.authMethods.prepareV2InsertStatements(authMethod)
112
+ ]
113
+ })).kind !== "stored") {
114
+ const committed = await readCommittedActivation({
115
+ authMethods: this.authMethods,
116
+ envelopes: this.envelopes,
117
+ command: input,
118
+ deviceKeyId,
119
+ expectedEnvelope: input.envelope
120
+ });
121
+ if (committed) return {
122
+ kind: "activated",
123
+ authMethod: committed
124
+ };
125
+ return {
126
+ kind: "rejected",
127
+ code: "conflict"
128
+ };
129
+ }
130
+ return {
131
+ kind: "activated",
132
+ authMethod
133
+ };
134
+ }
135
+ };
136
+ //#endregion
137
+ export { D1VoiceIdActivation };
138
+
139
+ //# sourceMappingURL=d1VoiceIdActivation.js.map
@@ -0,0 +1,138 @@
1
+ import { parsePrincipalId, parseTenantId } from "../../../../shared-ts/src/authorization/capabilityKinds.js";
2
+ import { verifyVoiceIdWalletSessionAssertion } from "../../../../authorization/voiceIdDeviceProof.js";
3
+ import { isVoiceIdSeedEnvelope } from "../../../../shared-ts/src/passkey-custody/custodyEnvelope.js";
4
+ import { DEFAULT_WALLET_SESSION_TTL_MS, LINKED_DEVICE_WALLET_SESSION_TTL_MS } from "../../../../shared-ts/src/threshold/sessionPolicy.js";
5
+ import { isFullOwnerAuthorityV1 } from "./d1WalletAuthMethodService.js";
6
+ //#region src/router/cloudflare/d1/wallet/d1VoiceIdSessions.ts
7
+ var D1VoiceIdSessions = class {
8
+ tenantId;
9
+ constructor(authMethods, authorities, envelopes, challenges, authorization, orgId) {
10
+ this.authMethods = authMethods;
11
+ this.authorities = authorities;
12
+ this.envelopes = envelopes;
13
+ this.challenges = challenges;
14
+ this.authorization = authorization;
15
+ const tenantId = parseTenantId(orgId);
16
+ if (!tenantId.ok) throw new Error(tenantId.error.message);
17
+ this.tenantId = tenantId.value;
18
+ }
19
+ async resolveActiveMethod(walletId, walletAuthMethodId) {
20
+ const method = await this.authMethods.readByIdV2({ walletAuthMethodId });
21
+ if (!method || method.kind !== "voiceid" || method.status !== "active" || method.walletId !== walletId) return null;
22
+ const authority = await this.authorities.readById(method.walletAuthorityId);
23
+ if (!authority || authority.state !== "active" || authority.walletId !== walletId || authority.authorityId !== method.walletAuthorityId || !isFullOwnerAuthorityV1(authority)) return null;
24
+ const custody = await this.envelopes.lookupEnvelopeForWalletAuthMethod({
25
+ walletId,
26
+ walletAuthMethodId,
27
+ factor: {
28
+ kind: "voiceid",
29
+ deviceKeyId: method.deviceKeyId
30
+ }
31
+ });
32
+ if (custody.kind !== "active" || !isVoiceIdSeedEnvelope(custody.envelope)) return null;
33
+ return {
34
+ authMethod: method,
35
+ authority,
36
+ registered: {
37
+ walletId,
38
+ walletAuthMethodId,
39
+ walletAuthorityId: method.walletAuthorityId,
40
+ deviceKeyId: method.deviceKeyId,
41
+ publicKeyB64u: method.devicePublicKeyB64u,
42
+ status: "active"
43
+ },
44
+ custodyEnvelope: custody.envelope
45
+ };
46
+ }
47
+ async issueChallenge(input) {
48
+ const active = await this.resolveActiveMethod(input.walletId, input.walletAuthMethodId);
49
+ if (!active) return {
50
+ kind: "rejected",
51
+ code: "method_unavailable"
52
+ };
53
+ return {
54
+ kind: "issued",
55
+ challenge: await this.challenges.issue({
56
+ registered: active.registered,
57
+ requestOrigin: input.requestOrigin,
58
+ audience: input.requestOrigin,
59
+ nowMs: input.nowMs
60
+ })
61
+ };
62
+ }
63
+ async verifyAndIssue(input) {
64
+ const supplied = input.assertion.challenge;
65
+ const expected = await this.challenges.read({
66
+ challengeId: supplied.challengeId,
67
+ requestOrigin: input.requestOrigin,
68
+ audience: input.requestOrigin,
69
+ nowMs: input.nowMs
70
+ });
71
+ if (!expected) return {
72
+ kind: "rejected",
73
+ code: "challenge_unavailable"
74
+ };
75
+ const active = await this.resolveActiveMethod(expected.walletId, expected.walletAuthMethodId);
76
+ if (!active) return {
77
+ kind: "rejected",
78
+ code: "method_unavailable"
79
+ };
80
+ let verified;
81
+ try {
82
+ verified = await verifyVoiceIdWalletSessionAssertion({
83
+ registered: active.registered,
84
+ expectedChallenge: expected,
85
+ assertion: input.assertion,
86
+ nowMs: input.nowMs,
87
+ consumeChallenge: this.challenges.consume.bind(this.challenges)
88
+ });
89
+ } catch {
90
+ return {
91
+ kind: "rejected",
92
+ code: "invalid_assertion"
93
+ };
94
+ }
95
+ const principalId = parsePrincipalId(String(active.authority.walletId));
96
+ if (!principalId.ok) return {
97
+ kind: "rejected",
98
+ code: "issuance_unavailable"
99
+ };
100
+ const deviceLinked = active.authority.provenance.kind === "device_link";
101
+ let issued;
102
+ try {
103
+ issued = await this.authorization.issueDirectWalletSessionAuthorizationV2({
104
+ tenantId: this.tenantId,
105
+ principalId: principalId.value,
106
+ walletId: active.authority.walletId,
107
+ authority: active.authority,
108
+ walletAuthMethodId: active.authMethod.walletAuthMethodId,
109
+ mintId: verified.challenge.challengeId,
110
+ remainingUses: deviceLinked ? 100 : 3,
111
+ issuedAtMs: input.nowMs,
112
+ expiresAtMs: input.nowMs + (deviceLinked ? LINKED_DEVICE_WALLET_SESSION_TTL_MS : DEFAULT_WALLET_SESSION_TTL_MS)
113
+ });
114
+ } catch {
115
+ return {
116
+ kind: "rejected",
117
+ code: "issuance_unavailable"
118
+ };
119
+ }
120
+ if (issued.kind !== "issued") return {
121
+ kind: "rejected",
122
+ code: "issuance_unavailable"
123
+ };
124
+ return {
125
+ kind: "issued",
126
+ authorization: {
127
+ session: issued.session,
128
+ quota: issued.quota
129
+ },
130
+ operationCredential: issued.operationCredential,
131
+ custodyEnvelope: active.custodyEnvelope
132
+ };
133
+ }
134
+ };
135
+ //#endregion
136
+ export { D1VoiceIdSessions };
137
+
138
+ //# sourceMappingURL=d1VoiceIdSessions.js.map
@@ -0,0 +1,88 @@
1
+ import { parseWalletSessionMintId } from "../../../../shared-ts/src/authorization/capabilityKinds.js";
2
+ import { alphabetizeStringify } from "../../../../shared-ts/src/utils/digests.js";
3
+ import { parseDigestB64u } from "../../../../shared-ts/src/utils/canonicalPrimitives.js";
4
+ import { buildVoiceIdWalletSessionChallenge, parseVoiceIdWalletSessionChallenge } from "../../../../shared-ts/src/authorization/voiceIdDeviceAssertion.js";
5
+ import { secureRandomBase64Url } from "../../../../shared-ts/src/utils/secureRandomId.js";
6
+ import { d1ChangedRows, parseD1JsonColumn } from "../../../../storage/d1Sql.js";
7
+ //#region src/router/cloudflare/d1/wallet/d1VoiceIdWalletSessionChallenges.ts
8
+ const CHALLENGE_LIFETIME_MS = 6e4;
9
+ const EXPIRY_CLEANUP_LIMIT = 64;
10
+ var D1VoiceIdWalletSessionChallenges = class {
11
+ constructor(database, scope) {
12
+ this.database = database;
13
+ this.scope = scope;
14
+ }
15
+ async issue(input) {
16
+ requireTime(input.nowMs);
17
+ const expiresAtMs = input.nowMs + CHALLENGE_LIFETIME_MS;
18
+ requireTime(expiresAtMs);
19
+ const challengeId = parseWalletSessionMintId(`voiceid:${secureRandomBase64Url(32, "VoiceID Wallet Session challenge")}`);
20
+ if (!challengeId.ok) throw new Error(challengeId.error.message);
21
+ const challenge = buildVoiceIdWalletSessionChallenge({
22
+ walletId: input.registered.walletId,
23
+ walletAuthMethodId: input.registered.walletAuthMethodId,
24
+ walletAuthorityId: input.registered.walletAuthorityId,
25
+ deviceKeyId: input.registered.deviceKeyId,
26
+ challengeId: challengeId.value,
27
+ nonce: parseDigestB64u(secureRandomBase64Url(32, "VoiceID Wallet Session nonce")),
28
+ requestOrigin: input.requestOrigin,
29
+ audience: input.audience,
30
+ issuedAtMs: input.nowMs,
31
+ expiresAtMs
32
+ });
33
+ await this.pruneExpired(input.nowMs);
34
+ if (d1ChangedRows(await this.database.prepare(`INSERT INTO voiceid_wallet_session_challenges (
35
+ namespace, org_id, project_id, env_id, challenge_id, wallet_id,
36
+ wallet_auth_method_id, challenge_json, issued_at_ms, expires_at_ms
37
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).bind(...this.scopeValues(), String(challenge.challengeId), String(challenge.walletId), String(challenge.walletAuthMethodId), alphabetizeStringify(challenge), challenge.issuedAtMs, challenge.expiresAtMs).run()) !== 1) throw new Error("VoiceID challenge was not stored");
38
+ return challenge;
39
+ }
40
+ async read(input) {
41
+ requireTime(input.nowMs);
42
+ const row = await this.database.prepare(`SELECT challenge_json FROM voiceid_wallet_session_challenges
43
+ WHERE namespace = ? AND org_id = ? AND project_id = ? AND env_id = ?
44
+ AND challenge_id = ? AND consumed_at_ms IS NULL
45
+ AND issued_at_ms <= ? AND expires_at_ms > ?
46
+ LIMIT 1`).bind(...this.scopeValues(), String(input.challengeId), input.nowMs, input.nowMs).first();
47
+ if (!row) return null;
48
+ const challenge = parseVoiceIdWalletSessionChallenge(parseD1JsonColumn(row.challenge_json));
49
+ if (challenge.challengeId !== input.challengeId) throw new Error("Stored VoiceID challenge identity changed");
50
+ if (challenge.requestOrigin !== input.requestOrigin || challenge.audience !== input.audience) return null;
51
+ return challenge;
52
+ }
53
+ async consume(challenge, nowMs) {
54
+ requireTime(nowMs);
55
+ const parsed = parseVoiceIdWalletSessionChallenge(challenge);
56
+ if (nowMs < parsed.issuedAtMs || nowMs >= parsed.expiresAtMs) return false;
57
+ return d1ChangedRows(await this.database.prepare(`UPDATE voiceid_wallet_session_challenges SET consumed_at_ms = ?
58
+ WHERE namespace = ? AND org_id = ? AND project_id = ? AND env_id = ?
59
+ AND challenge_id = ? AND wallet_id = ? AND wallet_auth_method_id = ?
60
+ AND challenge_json = ? AND consumed_at_ms IS NULL
61
+ AND issued_at_ms <= ? AND expires_at_ms > ?`).bind(nowMs, ...this.scopeValues(), String(parsed.challengeId), String(parsed.walletId), String(parsed.walletAuthMethodId), alphabetizeStringify(parsed), nowMs, nowMs).run()) === 1;
62
+ }
63
+ async pruneExpired(nowMs) {
64
+ await this.database.prepare(`WITH expired AS (
65
+ SELECT rowid FROM voiceid_wallet_session_challenges
66
+ WHERE namespace = ? AND org_id = ? AND project_id = ? AND env_id = ?
67
+ AND expires_at_ms <= ?
68
+ ORDER BY expires_at_ms ASC, challenge_id ASC
69
+ LIMIT ?
70
+ ) DELETE FROM voiceid_wallet_session_challenges
71
+ WHERE rowid IN (SELECT rowid FROM expired)`).bind(...this.scopeValues(), nowMs, EXPIRY_CLEANUP_LIMIT).run();
72
+ }
73
+ scopeValues() {
74
+ return [
75
+ this.scope.namespace,
76
+ this.scope.orgId,
77
+ this.scope.projectId,
78
+ this.scope.envId
79
+ ];
80
+ }
81
+ };
82
+ function requireTime(value) {
83
+ if (!Number.isSafeInteger(value) || value <= 0) throw new Error("VoiceID challenge time is invalid");
84
+ }
85
+ //#endregion
86
+ export { D1VoiceIdWalletSessionChallenges };
87
+
88
+ //# sourceMappingURL=d1VoiceIdWalletSessionChallenges.js.map
@@ -22,9 +22,9 @@ import { resolveWalletAuthMethodIdForAuthority } from "../../../../core/signingL
22
22
  import { toRecordValue } from "../auth/d1RouterApiAuthBoundary.js";
23
23
  import { d1HostIsWithinWebAuthnRpId, resolveD1AddAuthMethodExistingAuth, resolveD1AddSignerExistingAuth, verifyD1LinkedDeviceFreshRevokeProofV1, walletAuthAuthorityFromRegistrationAuthority } from "./d1WalletAuthMethodBoundary.js";
24
24
  import { parseWalletIdForIntent } from "../registration/d1RegistrationCeremonyRecords.js";
25
- import { runtimePolicyScopeKey } from "../../../../core/EmailOtpRecords.js";
26
- import { expiredGoogleEmailOtpRegistrationAttemptRecord } from "../emailOtp/d1GoogleEmailOtpRegistrationRecords.js";
27
25
  import { admitAddWalletAuthMethod } from "../../../../shared-ts/src/utils/addWalletAuthMethod.js";
26
+ import { expiredGoogleEmailOtpRegistrationAttemptRecord } from "../emailOtp/d1GoogleEmailOtpRegistrationRecords.js";
27
+ import { runtimePolicyScopeKey } from "../../../../core/EmailOtpRecords.js";
28
28
  import { computeWalletAuthMethodRevocationProofDigestV1 } from "./d1WalletAuthMethodRevocationReplayStore.js";
29
29
  //#region src/router/cloudflare/d1/wallet/d1WalletAuthMethodService.ts
30
30
  /** Long enough for a device to retry across a reload, short of ceremony scale. */
@@ -141,6 +141,14 @@ function walletAuthMethodRevokedResponse(walletId, record) {
141
141
  status: "revoked"
142
142
  }
143
143
  };
144
+ case "voiceid": return {
145
+ ok: true,
146
+ walletId,
147
+ authMethod: {
148
+ kind: "voiceid",
149
+ status: "revoked"
150
+ }
151
+ };
144
152
  default: return unreachableRevokedMethodKind(record);
145
153
  }
146
154
  }
@@ -506,7 +514,7 @@ var CloudflareD1WalletAuthMethodService = class {
506
514
  walletId,
507
515
  auth: storedAuth.auth
508
516
  });
509
- if (!sourceMethod) return {
517
+ if (!sourceMethod || sourceMethod.kind === "voiceid") return {
510
518
  ok: false,
511
519
  code: "unauthorized",
512
520
  message: "Add-auth-method source is not an active V2 wallet auth method"
@@ -2114,6 +2122,6 @@ var CloudflareD1WalletAuthMethodService = class {
2114
2122
  }
2115
2123
  };
2116
2124
  //#endregion
2117
- export { CloudflareD1WalletAuthMethodService };
2125
+ export { CloudflareD1WalletAuthMethodService, isFullOwnerAuthorityV1 };
2118
2126
 
2119
2127
  //# sourceMappingURL=d1WalletAuthMethodService.js.map
@@ -724,6 +724,20 @@ function buildRevokedAuthMethod(method, revokedAtMs) {
724
724
  activatedAtMs: method.activatedAtMs,
725
725
  revokedAtMs
726
726
  };
727
+ if (method.kind === "voiceid") return {
728
+ version: "wallet_auth_method_v2",
729
+ walletAuthMethodId: method.walletAuthMethodId,
730
+ walletId: method.walletId,
731
+ walletAuthorityId: method.walletAuthorityId,
732
+ kind: "voiceid",
733
+ status: "revoked",
734
+ deviceKeyId: method.deviceKeyId,
735
+ devicePublicKeyB64u: method.devicePublicKeyB64u,
736
+ createdAtMs: method.createdAtMs,
737
+ updatedAtMs: revokedAtMs,
738
+ activatedAtMs: method.activatedAtMs,
739
+ revokedAtMs
740
+ };
727
741
  return {
728
742
  version: "wallet_auth_method_v2",
729
743
  walletAuthMethodId: method.walletAuthMethodId,
@@ -243,6 +243,7 @@ function buildWebAuthnRecoveryContinuityEnvelopeAnchor(envelope) {
243
243
  updatedAtMs: envelope.updatedAtMs,
244
244
  bindingKind: "wallet_custody_seed_v1"
245
245
  };
246
+ case "voiceid": throw new Error("WebAuthn recovery continuity does not accept a VoiceID envelope");
246
247
  }
247
248
  }
248
249
  function parseWebAuthnRecoveryContinuityEnvelopeAnchor(input) {