@seams/wallet-server 0.22.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD_PARTY_NOTICES.md +186 -6
- package/artifact-manifest.json +23 -13
- package/cloudflare-router-ab/build/deriver-a/build-stamp.json +1 -1
- package/cloudflare-router-ab/build/deriver-b/build-stamp.json +1 -1
- package/cloudflare-router-ab/build/router/build-stamp.json +1 -1
- package/cloudflare-router-ab/build/signing-worker/build-stamp.json +1 -1
- package/cloudflare-router-ab/build/tenant-root-control-plane/build-stamp.json +1 -1
- package/dist/esm/authorization/voiceIdDeviceProof.js +32 -0
- package/dist/esm/core/ThresholdService/validation.js +17 -1
- package/dist/esm/core/d1WalletAuthMethodStore.js +42 -1
- package/dist/esm/core/deviceLinking/linkedDeviceManagement.js +5 -0
- package/dist/esm/core/signingLanes/WalletExecutionLaneProjection.js +11 -2
- package/dist/esm/router/cloudflare/d1/auth/d1RouterApiAuthService.js +23 -6
- package/dist/esm/router/cloudflare/d1/deviceLinking/d1LinkedDeviceAuthorityInstallService.js +5 -5
- package/dist/esm/router/cloudflare/d1/emailOtp/d1GoogleEmailOtpRegistrationAttemptStore.js +1 -1
- package/dist/esm/router/cloudflare/d1/passkeyCustody/d1PasskeyCustodyEnvelopeStore.js +6 -0
- package/dist/esm/router/cloudflare/d1/wallet/d1VoiceIdActivation.js +139 -0
- package/dist/esm/router/cloudflare/d1/wallet/d1VoiceIdSessions.js +138 -0
- package/dist/esm/router/cloudflare/d1/wallet/d1VoiceIdWalletSessionChallenges.js +88 -0
- package/dist/esm/router/cloudflare/d1/wallet/d1WalletAuthMethodService.js +12 -4
- package/dist/esm/router/cloudflare/d1/wallet/d1WalletAuthorityStore.js +14 -0
- package/dist/esm/router/cloudflare/d1/webauthn/d1WebAuthnRecords.js +1 -0
- package/dist/esm/router/cloudflare.js +576 -10
- package/dist/esm/router/domains/ed25519Yao/export/routerAbEd25519YaoExport.js +5 -0
- package/dist/esm/router/domains/passkeyCustody/walletRecoveryFinalization.js +1 -0
- package/dist/esm/router/domains/signingOperations/routerAbNormalSigningAdmissionCore.js +1 -0
- package/dist/esm/router/domains/signingOperations/walletExecutionAdmission.js +1 -0
- package/dist/esm/router/domains/voiceId/voiceIdActivation.js +12 -0
- package/dist/esm/router/domains/voiceId/voiceIdSessions.js +0 -0
- package/dist/esm/router/framework/routeDefinitions.js +13 -0
- package/dist/esm/router/transport/fetch/createFetchRouter.js +5 -0
- package/dist/esm/router/transport/fetch/routes/deviceLinkingOwnerAuthorization.js +12 -1
- package/dist/esm/router/transport/fetch/routes/walletExecutionLanePreflight.js +1 -0
- package/dist/esm/router/transport/fetch/routes/walletVoiceIdActivation.js +139 -0
- package/dist/esm/router/transport/fetch/routes/walletVoiceIdNear.js +36 -0
- package/dist/esm/router/transport/fetch/routes/walletVoiceIdSessions.js +127 -0
- package/dist/esm/shared-ts/src/authorization/voiceIdDeviceAssertion.js +110 -0
- package/dist/esm/shared-ts/src/device-linking/authorityActivation.js +1 -0
- package/dist/esm/shared-ts/src/device-linking/committedSignerPackages.js +1 -1
- package/dist/esm/shared-ts/src/passkey-custody/custodyEnvelope.js +30 -3
- package/dist/esm/shared-ts/src/passkey-custody/custodySecretBinding.js +1 -1
- package/dist/esm/shared-ts/src/signing-lanes/recordParsers.js +15 -1
- package/dist/esm/shared-ts/src/threshold/sessionPolicy.js +2 -1
- package/dist/esm/shared-ts/src/utils/registrationIntent.js +1 -3
- package/dist/esm/shared-ts/src/utils/walletAuthAuthority.js +46 -2
- package/dist/esm/shared-ts/src/utils/walletAuthMethodRecord.js +116 -7
- package/dist/esm/shared-ts/src/wallet-recovery/walletRecoveryCommittedProjection.js +1 -0
- package/dist/esm/wasm/near_signer/pkg/wasm_signer_worker_bg.wasm +0 -0
- package/dist/types/shared-ts/src/authorization/voiceIdDeviceAssertion.d.ts +32 -0
- package/dist/types/shared-ts/src/device-linking/committedSignerPackages.d.ts +3 -1
- package/dist/types/shared-ts/src/device-linking/contracts.d.ts +8 -0
- package/dist/types/shared-ts/src/passkey-custody/custodyEnvelope.d.ts +29 -0
- package/dist/types/shared-ts/src/passkey-custody/custodySecretBinding.d.ts +1 -0
- package/dist/types/shared-ts/src/signing-lanes/execution.d.ts +2 -2
- package/dist/types/shared-ts/src/signing-lanes/recordParsers.d.ts +2 -1
- package/dist/types/shared-ts/src/signing-lanes/records.d.ts +10 -2
- package/dist/types/shared-ts/src/signing-lanes/rotation.d.ts +2 -2
- package/dist/types/shared-ts/src/threshold/sessionPolicy.d.ts +9 -0
- package/dist/types/shared-ts/src/utils/addWalletAuthMethod.d.ts +3 -3
- package/dist/types/shared-ts/src/utils/domainIds.d.ts +4 -5
- package/dist/types/shared-ts/src/utils/registrationIntent.d.ts +0 -1
- package/dist/types/shared-ts/src/utils/signerDomain.d.ts +3 -0
- package/dist/types/shared-ts/src/utils/walletAuthAuthority.d.ts +32 -2
- package/dist/types/shared-ts/src/utils/walletAuthMethodRecord.d.ts +15 -1
- package/dist/types/shared-ts/src/utils/walletCapabilityBindings.d.ts +20 -0
- package/dist/types/wallet-server/src/authorization/voiceIdDeviceProof.d.ts +29 -0
- package/dist/types/wallet-server/src/core/d1WalletAuthMethodStore.d.ts +13 -2
- package/dist/types/wallet-server/src/core/registrationContracts.d.ts +8 -0
- package/dist/types/wallet-server/src/core/signingLanes/WalletExecutionLaneProjection.d.ts +4 -0
- package/dist/types/wallet-server/src/router/cloudflare/d1/passkeyCustody/d1PasskeyCustodyEnvelopeStore.d.ts +4 -0
- package/dist/types/wallet-server/src/router/cloudflare/d1/wallet/d1VoiceIdActivation.d.ts +14 -0
- package/dist/types/wallet-server/src/router/cloudflare/d1/wallet/d1VoiceIdSessions.d.ts +19 -0
- package/dist/types/wallet-server/src/router/cloudflare/d1/wallet/d1VoiceIdWalletSessionChallenges.d.ts +26 -0
- package/dist/types/wallet-server/src/router/cloudflare/d1/wallet/d1WalletAuthMethodService.d.ts +1 -0
- package/dist/types/wallet-server/src/router/cloudflare/d1/wallet/d1WalletRelocationOwner.d.ts +25 -0
- package/dist/types/wallet-server/src/router/domains/voiceId/voiceIdActivation.d.ts +29 -0
- package/dist/types/wallet-server/src/router/domains/voiceId/voiceIdSessions.d.ts +35 -0
- package/dist/types/wallet-server/src/router/framework/authServicePort.d.ts +3 -0
- package/dist/types/wallet-server/src/router/transport/fetch/routes/walletVoiceIdActivation.d.ts +12 -0
- package/dist/types/wallet-server/src/router/transport/fetch/routes/walletVoiceIdNear.d.ts +14 -0
- package/dist/types/wallet-server/src/router/transport/fetch/routes/walletVoiceIdSessions.d.ts +19 -0
- package/migrations/d1-signer/0083_voiceid_wallet_session_challenges.sql +29 -0
- package/migrations/d1-signer/0084_voiceid_wallet_auth_methods.sql +373 -0
- package/package.json +1 -1
|
@@ -891,6 +891,11 @@ async function recordFreshExportProof(args) {
|
|
|
891
891
|
let challengeId = null;
|
|
892
892
|
let assertionDigest = null;
|
|
893
893
|
switch (args.authority.factor.kind) {
|
|
894
|
+
case "voiceid": return authorizationFailure({
|
|
895
|
+
status: 403,
|
|
896
|
+
code: "export_authorization_invalid",
|
|
897
|
+
message: "Use a passkey or Email OTP to export keys."
|
|
898
|
+
});
|
|
894
899
|
case "passkey":
|
|
895
900
|
factorId = parseAuthFactorId(`passkey:${args.authority.factor.credentialIdB64u}`);
|
|
896
901
|
try {
|
|
@@ -21,6 +21,7 @@ function sameWalletCustodyRecoveryReplacementEnvelopeV1(left, right) {
|
|
|
21
21
|
switch (left.factor.kind) {
|
|
22
22
|
case "passkey": return right.factor.kind === "passkey" && left.factor.rpId === right.factor.rpId && left.factor.credentialIdB64u === right.factor.credentialIdB64u && left.factor.kekVersion === right.factor.kekVersion;
|
|
23
23
|
case "email_otp": return right.factor.kind === "email_otp" && left.factor.enrollmentId === right.factor.enrollmentId && left.factor.enrollmentSealKeyVersion === right.factor.enrollmentSealKeyVersion && left.factor.kekVersion === right.factor.kekVersion;
|
|
24
|
+
case "voiceid": return right.factor.kind === "voiceid" && left.factor.deviceKeyId === right.factor.deviceKeyId && left.factor.kekVersion === right.factor.kekVersion;
|
|
24
25
|
default: return assertNeverWalletRecoveryFinalizationBranch(left.factor, "custody envelope factor");
|
|
25
26
|
}
|
|
26
27
|
}
|
|
@@ -75,6 +75,7 @@ function runtimePolicyScopeKey(scope) {
|
|
|
75
75
|
}
|
|
76
76
|
function ed25519AdmissionAuthorityScopeKey(scope) {
|
|
77
77
|
switch (scope.kind) {
|
|
78
|
+
case "voiceid": return `voiceid:${scope.deviceKeyId}`;
|
|
78
79
|
case "passkey_rp": return `passkey_rp:${scope.rpId}`;
|
|
79
80
|
case "email_otp": return `email_otp:${scope.provider}:${scope.providerUserId}`;
|
|
80
81
|
}
|
|
@@ -199,6 +199,7 @@ function isActiveOwnerLane(lane) {
|
|
|
199
199
|
if (lane.lifecycle.state !== "active") return false;
|
|
200
200
|
switch (lane.laneKind) {
|
|
201
201
|
case "owner_passkey":
|
|
202
|
+
case "owner_voiceid":
|
|
202
203
|
case "owner_email_otp":
|
|
203
204
|
case "recovery":
|
|
204
205
|
case "break_glass": return true;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { isVoiceIdSeedEnvelope, parsePasskeyCustodyEnvelopeRecord } from "../../../shared-ts/src/passkey-custody/custodyEnvelope.js";
|
|
2
|
+
//#region src/router/domains/voiceId/voiceIdActivation.ts
|
|
3
|
+
/** Accepts only a method-bound owner seed envelope for the new VoiceID method. */
|
|
4
|
+
function parseVoiceIdActivationEnvelope(raw) {
|
|
5
|
+
const envelope = parsePasskeyCustodyEnvelopeRecord(raw);
|
|
6
|
+
if (!isVoiceIdSeedEnvelope(envelope) || Number(envelope.envelopeRevision) !== 1) throw new Error("VoiceID activation requires an active, method-bound wallet seed envelope");
|
|
7
|
+
return envelope;
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
export { parseVoiceIdActivationEnvelope };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=voiceIdActivation.js.map
|
|
File without changes
|
|
@@ -45,6 +45,7 @@ const ROUTER_API_PASSKEY_CUSTODY_SERVICES = [
|
|
|
45
45
|
];
|
|
46
46
|
const ROUTER_API_LINKED_DEVICE_PASSKEY_SERVICES = ["publishableKeyAuth"];
|
|
47
47
|
const ROUTER_API_WALLET_UNLOCK_SERVICES = ["walletUnlock"];
|
|
48
|
+
const ROUTER_API_VOICEID_ACTIVATION_SERVICES = ["session", "walletAuthMethods"];
|
|
48
49
|
const ROUTER_API_HOSTED_SESSION_EXCHANGE_SERVICES = ["session"];
|
|
49
50
|
const ROUTER_API_EMAIL_OTP_PUBLIC_SERVICES = ["emailOtp"];
|
|
50
51
|
const ROUTER_API_EMAIL_OTP_CHALLENGE_SERVICES = ["emailOtp", "walletAuthMethods"];
|
|
@@ -420,6 +421,18 @@ function createRouterApiRouteDefinitions(options = {}) {
|
|
|
420
421
|
plane: "public",
|
|
421
422
|
proof: "challenge_exchange",
|
|
422
423
|
rationale: "Wallet unlock verification is intentionally public because the challenge proof is the gate."
|
|
424
|
+
}, ROUTER_API_WALLET_UNLOCK_SERVICES), publicRoute("wallet_voiceid_activate", "POST", "/wallet/voiceid/activate", "Activate a device-bound VoiceID method under an owner Wallet Session", {
|
|
425
|
+
plane: "public",
|
|
426
|
+
proof: "challenge_exchange",
|
|
427
|
+
rationale: "An active full-owner Wallet Session and the hosted wallet origin authorize activation."
|
|
428
|
+
}, ROUTER_API_VOICEID_ACTIVATION_SERVICES), publicRoute("wallet_voiceid_challenge", "POST", "/wallet/voiceid/challenge", "Issue a device-bound VoiceID Wallet Session challenge", {
|
|
429
|
+
plane: "public",
|
|
430
|
+
proof: "challenge_exchange",
|
|
431
|
+
rationale: "An active method receives a short-lived challenge bound to the hosted wallet origin."
|
|
432
|
+
}, ROUTER_API_WALLET_UNLOCK_SERVICES), publicRoute("wallet_voiceid_verify", "POST", "/wallet/voiceid/verify", "Verify a VoiceID device assertion and issue a Wallet Session", {
|
|
433
|
+
plane: "public",
|
|
434
|
+
proof: "signed_payload",
|
|
435
|
+
rationale: "The registered device signature and single-use server challenge authorize issuance."
|
|
423
436
|
}, ROUTER_API_WALLET_UNLOCK_SERVICES), publicRoute("wallet_email_otp_challenge", "POST", "/wallet/email-otp/challenge", "Create an operation-bound Email OTP challenge", {
|
|
424
437
|
plane: "public",
|
|
425
438
|
proof: "challenge_exchange",
|
|
@@ -10,6 +10,8 @@ import { coerceRouterLogger } from "../../framework/logger.js";
|
|
|
10
10
|
import { json, jsonFailure, withCors } from "../../framework/http.js";
|
|
11
11
|
import { handleHealth, handleReady } from "./routes/health.js";
|
|
12
12
|
import { handleWalletRegistration } from "./routes/walletRegistration.js";
|
|
13
|
+
import { handleWalletVoiceIdActivation } from "./routes/walletVoiceIdActivation.js";
|
|
14
|
+
import { handleWalletVoiceIdChallenge, handleWalletVoiceIdVerify } from "./routes/walletVoiceIdSessions.js";
|
|
13
15
|
import { handlePasskeyCustody, handleWalletCustodyCredentialLabel, handleWalletCustodyCredentialsList, handleWalletCustodyEmailOtpChallenge, handleWalletCustodyEnvelopeOwnershipUpgrade, handleWalletRecoveryBackupAcknowledge, handleWalletRecoveryEmailOtpRelease, handleWalletRecoveryEmailOtpVerify, handleWalletRecoveryFinalize, handleWalletRecoveryGoogleEmailOtpFinalize, handleWalletRecoveryGoogleVerify, handleWalletRecoveryPrepare, handleWalletRecoveryRead, handleWalletRecoveryRotate, handleWalletRecoveryStatus } from "./routes/passkeyCustody.js";
|
|
14
16
|
import { handleThresholdEcdsa } from "./routes/thresholdEcdsa.js";
|
|
15
17
|
import { handleExactWalletSessionStatus, handleHostedWalletSessionExchangeIssue, handleHostedWalletSessionExchangeRedeem, handleWalletEmailOtpChallenge, handleWalletEmailOtpDevCleanupGoogleRegistration, handleWalletEmailOtpDevOutbox, handleWalletEmailOtpFactorRelease, handleWalletEmailOtpRegistrationSeal, handleWalletUnlockChallenge, handleWalletUnlockVerify } from "./routes/sessions.js";
|
|
@@ -234,6 +236,9 @@ function createFetchRouter(service, opts, runtime) {
|
|
|
234
236
|
handleHostedWalletSessionExchangeRedeem,
|
|
235
237
|
handleWalletUnlockChallenge,
|
|
236
238
|
handleWalletUnlockVerify,
|
|
239
|
+
handleWalletVoiceIdActivation,
|
|
240
|
+
handleWalletVoiceIdChallenge,
|
|
241
|
+
handleWalletVoiceIdVerify,
|
|
237
242
|
handleWalletEmailOtpChallenge,
|
|
238
243
|
handleWalletEmailOtpFactorRelease,
|
|
239
244
|
handleWalletEmailOtpDevCleanupGoogleRegistration,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parseProviderSubject } from "../../../../shared-ts/src/utils/domainIds.js";
|
|
2
2
|
import { buildDelegatedWalletAuthorityV1 } from "../../../../shared-ts/src/authorization/delegatedAuthority.js";
|
|
3
3
|
import { sha256DigestB64u } from "../../../../shared-ts/src/utils/canonicalPrimitives.js";
|
|
4
|
-
import { buildEmailOtpWalletAuthAuthority, buildPasskeyWalletAuthAuthority, walletAuthAuthorityRef } from "../../../../shared-ts/src/utils/walletAuthAuthority.js";
|
|
4
|
+
import { buildEmailOtpWalletAuthAuthority, buildPasskeyWalletAuthAuthority, buildVoiceIdWalletAuthAuthority, walletAuthAuthorityRef } from "../../../../shared-ts/src/utils/walletAuthAuthority.js";
|
|
5
5
|
import { parseLinkedDeviceOwnerAuthorizationRequestV1 } from "../../../../shared-ts/src/device-linking/parsers.js";
|
|
6
6
|
import { buildWalletSessionCapabilitySubjectsV1, walletSessionCapabilitySubjectsV1Equal } from "../../../../authorization/domain.js";
|
|
7
7
|
import { extractBearerCredential } from "../../../auth/routerApiKeyAuth.js";
|
|
@@ -203,6 +203,17 @@ function walletAuthAuthorityFromExactV2MethodV1(input) {
|
|
|
203
203
|
} catch {
|
|
204
204
|
return null;
|
|
205
205
|
}
|
|
206
|
+
if (input.authMethod.kind === "voiceid") return {
|
|
207
|
+
authority: buildVoiceIdWalletAuthAuthority({
|
|
208
|
+
walletId: input.walletId,
|
|
209
|
+
walletAuthMethodId: input.authMethod.walletAuthMethodId,
|
|
210
|
+
deviceKeyId: input.authMethod.deviceKeyId
|
|
211
|
+
}),
|
|
212
|
+
authSource: {
|
|
213
|
+
kind: "voiceid",
|
|
214
|
+
deviceKeyId: input.authMethod.deviceKeyId
|
|
215
|
+
}
|
|
216
|
+
};
|
|
206
217
|
const providerSubject = parseProviderSubject(input.principalId);
|
|
207
218
|
if (!providerSubject.ok) return null;
|
|
208
219
|
try {
|
|
@@ -116,6 +116,7 @@ function serializeProjection(value) {
|
|
|
116
116
|
const lane = value.lane;
|
|
117
117
|
switch (lane.laneKind) {
|
|
118
118
|
case "owner_passkey":
|
|
119
|
+
case "owner_voiceid":
|
|
119
120
|
case "owner_email_otp": return {
|
|
120
121
|
kind: value.kind,
|
|
121
122
|
walletKey: serializeWalletKey(value.walletKey),
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { parseWalletAuthMethodId } from "../../../../shared-ts/src/utils/domainIds.js";
|
|
2
|
+
import { requireRecord } from "../../../../shared-ts/src/utils/validation.js";
|
|
3
|
+
import { rejectUnknownFields } from "../../../../shared-ts/src/utils/exactRecord.js";
|
|
4
|
+
import { parseVoiceIdDevicePublicKeyB64u } from "../../../../shared-ts/src/authorization/voiceIdDeviceAssertion.js";
|
|
5
|
+
import { parsePrimaryWalletSessionOperationCredentialToken, parseSessionOrigin } from "../../../../authorization/domain.js";
|
|
6
|
+
import { extractBearerCredential } from "../../../auth/routerApiKeyAuth.js";
|
|
7
|
+
import { json, readJson } from "../../../framework/http.js";
|
|
8
|
+
import { parseVoiceIdActivationEnvelope } from "../../../domains/voiceId/voiceIdActivation.js";
|
|
9
|
+
//#region src/router/transport/fetch/routes/walletVoiceIdActivation.ts
|
|
10
|
+
const ACTIVATION_FIELDS = [
|
|
11
|
+
"targetWalletAuthMethodId",
|
|
12
|
+
"devicePublicKeyB64u",
|
|
13
|
+
"custodyEnvelope"
|
|
14
|
+
];
|
|
15
|
+
function hostedWalletOriginAllowed(request, allowedOrigins) {
|
|
16
|
+
const rawOrigin = request.headers.get("origin");
|
|
17
|
+
let requestOrigin;
|
|
18
|
+
try {
|
|
19
|
+
requestOrigin = parseSessionOrigin(rawOrigin);
|
|
20
|
+
} catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
for (const allowed of allowedOrigins) try {
|
|
24
|
+
if (parseSessionOrigin(allowed) === requestOrigin) return true;
|
|
25
|
+
} catch {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
function parseActivationBody(raw) {
|
|
31
|
+
const body = requireRecord(raw, "VoiceID activation");
|
|
32
|
+
rejectUnknownFields(body, ACTIVATION_FIELDS, "VoiceID activation");
|
|
33
|
+
const target = parseWalletAuthMethodId(body.targetWalletAuthMethodId);
|
|
34
|
+
if (!target.ok) throw new Error(target.error.message);
|
|
35
|
+
return {
|
|
36
|
+
targetWalletAuthMethodId: target.value,
|
|
37
|
+
devicePublicKeyB64u: parseVoiceIdDevicePublicKeyB64u(body.devicePublicKeyB64u),
|
|
38
|
+
envelope: parseVoiceIdActivationEnvelope(body.custodyEnvelope)
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function rejectionStatus(code) {
|
|
42
|
+
switch (code) {
|
|
43
|
+
case "invalid_source": return 403;
|
|
44
|
+
case "invalid_target": return 400;
|
|
45
|
+
case "conflict": return 409;
|
|
46
|
+
default: {
|
|
47
|
+
const unexpected = code;
|
|
48
|
+
throw new Error(`Unsupported VoiceID activation rejection: ${unexpected}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function handleWalletVoiceIdActivation(ctx) {
|
|
53
|
+
if (ctx.method !== "POST" || ctx.pathname !== "/wallet/voiceid/activate") return null;
|
|
54
|
+
return await handleVoiceIdActivationRequest(ctx.request, {
|
|
55
|
+
tenantId: ctx.service.authorizationSessions.tenantId,
|
|
56
|
+
readWalletSessionAuthorizationV2ByOperationCredential: ctx.service.authorizationSessions.readWalletSessionAuthorizationV2ByOperationCredential,
|
|
57
|
+
activateVoiceId: ctx.service.walletAuthMethods.activateVoiceId,
|
|
58
|
+
hostedWalletOrigins: ctx.opts.hostedWalletOrigins ?? []
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
async function handleVoiceIdActivationRequest(request, deps) {
|
|
62
|
+
if (!hostedWalletOriginAllowed(request, deps.hostedWalletOrigins)) return json({
|
|
63
|
+
ok: false,
|
|
64
|
+
code: "origin_mismatch"
|
|
65
|
+
}, { status: 403 });
|
|
66
|
+
const credential = extractBearerCredential(request.headers);
|
|
67
|
+
if (!credential) return json({
|
|
68
|
+
ok: false,
|
|
69
|
+
code: "unauthorized"
|
|
70
|
+
}, { status: 401 });
|
|
71
|
+
let token;
|
|
72
|
+
try {
|
|
73
|
+
token = parsePrimaryWalletSessionOperationCredentialToken(credential);
|
|
74
|
+
} catch {
|
|
75
|
+
return json({
|
|
76
|
+
ok: false,
|
|
77
|
+
code: "unauthorized"
|
|
78
|
+
}, { status: 401 });
|
|
79
|
+
}
|
|
80
|
+
const nowMs = Date.now();
|
|
81
|
+
let source;
|
|
82
|
+
try {
|
|
83
|
+
source = await deps.readWalletSessionAuthorizationV2ByOperationCredential({
|
|
84
|
+
tenantId: deps.tenantId,
|
|
85
|
+
token,
|
|
86
|
+
nowMs
|
|
87
|
+
});
|
|
88
|
+
} catch {
|
|
89
|
+
return json({
|
|
90
|
+
ok: false,
|
|
91
|
+
code: "wallet_session_unavailable"
|
|
92
|
+
}, { status: 503 });
|
|
93
|
+
}
|
|
94
|
+
if (!source || source.retiredAtMs !== null || source.authMethod.status !== "active" || source.authMethod.kind !== "passkey" && source.authMethod.kind !== "email_otp") return json({
|
|
95
|
+
ok: false,
|
|
96
|
+
code: "unauthorized"
|
|
97
|
+
}, { status: 401 });
|
|
98
|
+
let parsedBody;
|
|
99
|
+
try {
|
|
100
|
+
parsedBody = parseActivationBody(await readJson(request));
|
|
101
|
+
} catch (error) {
|
|
102
|
+
return json({
|
|
103
|
+
ok: false,
|
|
104
|
+
code: "invalid_body",
|
|
105
|
+
message: error instanceof Error ? error.message : "VoiceID activation body is invalid"
|
|
106
|
+
}, { status: 400 });
|
|
107
|
+
}
|
|
108
|
+
let result;
|
|
109
|
+
try {
|
|
110
|
+
result = await deps.activateVoiceId({
|
|
111
|
+
source: {
|
|
112
|
+
authorization: source.authorization,
|
|
113
|
+
authority: source.authority,
|
|
114
|
+
authMethod: source.authMethod
|
|
115
|
+
},
|
|
116
|
+
targetWalletAuthMethodId: parsedBody.targetWalletAuthMethodId,
|
|
117
|
+
devicePublicKeyB64u: parsedBody.devicePublicKeyB64u,
|
|
118
|
+
envelope: parsedBody.envelope,
|
|
119
|
+
nowMs
|
|
120
|
+
});
|
|
121
|
+
} catch {
|
|
122
|
+
return json({
|
|
123
|
+
ok: false,
|
|
124
|
+
code: "activation_unavailable"
|
|
125
|
+
}, { status: 503 });
|
|
126
|
+
}
|
|
127
|
+
if (result.kind === "rejected") return json({
|
|
128
|
+
ok: false,
|
|
129
|
+
code: result.code
|
|
130
|
+
}, { status: rejectionStatus(result.code) });
|
|
131
|
+
return json({
|
|
132
|
+
ok: true,
|
|
133
|
+
authMethod: result.authMethod
|
|
134
|
+
}, { status: 200 });
|
|
135
|
+
}
|
|
136
|
+
//#endregion
|
|
137
|
+
export { handleVoiceIdActivationRequest, handleWalletVoiceIdActivation, hostedWalletOriginAllowed };
|
|
138
|
+
|
|
139
|
+
//# sourceMappingURL=walletVoiceIdActivation.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { routerAbMpcMaterialActivationRefToWire, sameRouterAbMpcMaterialActivationRef } from "../../../../shared-ts/src/utils/routerAbNormalSigningIdentity.js";
|
|
2
|
+
//#region src/router/transport/fetch/routes/walletVoiceIdNear.ts
|
|
3
|
+
async function prepareVoiceIdNearInstallation(ctx, verified) {
|
|
4
|
+
const session = verified.authorization.session;
|
|
5
|
+
const activation = session.capabilitySubjects.find(isNearSignSubject);
|
|
6
|
+
if (!activation || activation.kind !== "sign") return { kind: "unavailable" };
|
|
7
|
+
const resolved = await ctx.service.walletRegistration.resolveEd25519MaterialActivation({
|
|
8
|
+
walletId: session.walletId,
|
|
9
|
+
materialActivation: routerAbMpcMaterialActivationRefToWire(activation.materialActivation)
|
|
10
|
+
});
|
|
11
|
+
if (!resolved.ok) throw new Error(resolved.message);
|
|
12
|
+
const product = ctx.opts.routerAbEd25519YaoProduct;
|
|
13
|
+
if (!product) throw new Error("NEAR signing is not configured");
|
|
14
|
+
const capability = await product.resolveActiveCapability({
|
|
15
|
+
kind: "router_ab_ed25519_yao_active_capability_lookup_v1",
|
|
16
|
+
walletId: session.walletId,
|
|
17
|
+
nearEd25519SigningKeyId: resolved.exportIdentity.application_binding.near_ed25519_signing_key_id,
|
|
18
|
+
signerSlot: resolved.signerSlot,
|
|
19
|
+
signingWorkerId: resolved.signingWorkerId,
|
|
20
|
+
participantIds: resolved.participantIds
|
|
21
|
+
});
|
|
22
|
+
if (!capability.ok) throw new Error(capability.message);
|
|
23
|
+
if (!sameRouterAbMpcMaterialActivationRef(capability.capability.materialActivation, resolved.materialActivation)) throw new Error("NEAR capability does not belong to this Wallet Session");
|
|
24
|
+
return {
|
|
25
|
+
kind: "ready",
|
|
26
|
+
capability: capability.capability,
|
|
27
|
+
remainingUses: verified.authorization.quota.remainingUses
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function isNearSignSubject(subject) {
|
|
31
|
+
return subject.kind === "sign" && subject.keyFamily === "ed25519";
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
export { prepareVoiceIdNearInstallation };
|
|
35
|
+
|
|
36
|
+
//# sourceMappingURL=walletVoiceIdNear.js.map
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { parseWalletAuthMethodId, parseWalletId } from "../../../../shared-ts/src/utils/domainIds.js";
|
|
2
|
+
import { requireRecord } from "../../../../shared-ts/src/utils/validation.js";
|
|
3
|
+
import { rejectUnknownFields } from "../../../../shared-ts/src/utils/exactRecord.js";
|
|
4
|
+
import { parseVoiceIdDeviceAssertion } from "../../../../shared-ts/src/authorization/voiceIdDeviceAssertion.js";
|
|
5
|
+
import { parseSessionOrigin, projectActiveWalletSession } from "../../../../authorization/domain.js";
|
|
6
|
+
import { json, readJson } from "../../../framework/http.js";
|
|
7
|
+
import { hostedWalletOriginAllowed } from "./walletVoiceIdActivation.js";
|
|
8
|
+
import { prepareVoiceIdNearInstallation } from "./walletVoiceIdNear.js";
|
|
9
|
+
//#region src/router/transport/fetch/routes/walletVoiceIdSessions.ts
|
|
10
|
+
function hostedOrigin(request, allowedOrigins) {
|
|
11
|
+
if (!hostedWalletOriginAllowed(request, allowedOrigins)) return null;
|
|
12
|
+
return parseSessionOrigin(request.headers.get("origin"));
|
|
13
|
+
}
|
|
14
|
+
function parseChallengeBody(raw) {
|
|
15
|
+
const body = requireRecord(raw, "VoiceID challenge");
|
|
16
|
+
rejectUnknownFields(body, ["walletId", "walletAuthMethodId"], "VoiceID challenge");
|
|
17
|
+
const walletId = parseWalletId(body.walletId);
|
|
18
|
+
if (!walletId.ok) throw new Error(walletId.error.message);
|
|
19
|
+
const walletAuthMethodId = parseWalletAuthMethodId(body.walletAuthMethodId);
|
|
20
|
+
if (!walletAuthMethodId.ok) throw new Error(walletAuthMethodId.error.message);
|
|
21
|
+
return {
|
|
22
|
+
walletId: walletId.value,
|
|
23
|
+
walletAuthMethodId: walletAuthMethodId.value
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
async function handleWalletVoiceIdChallenge(ctx) {
|
|
27
|
+
if (ctx.method !== "POST" || ctx.pathname !== "/wallet/voiceid/challenge") return null;
|
|
28
|
+
return await handleVoiceIdChallengeRequest(ctx.request, {
|
|
29
|
+
issueVoiceIdChallenge: ctx.service.walletUnlock.issueVoiceIdChallenge,
|
|
30
|
+
hostedWalletOrigins: ctx.opts.hostedWalletOrigins ?? []
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
async function handleWalletVoiceIdVerify(ctx) {
|
|
34
|
+
if (ctx.method !== "POST" || ctx.pathname !== "/wallet/voiceid/verify") return null;
|
|
35
|
+
return await handleVoiceIdVerifyRequest(ctx.request, {
|
|
36
|
+
verifyAndIssueVoiceId: ctx.service.walletUnlock.verifyAndIssueVoiceId,
|
|
37
|
+
prepareNear: prepareVoiceIdNearInstallation.bind(null, ctx),
|
|
38
|
+
hostedWalletOrigins: ctx.opts.hostedWalletOrigins ?? []
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
async function handleVoiceIdChallengeRequest(request, deps) {
|
|
42
|
+
const requestOrigin = hostedOrigin(request, deps.hostedWalletOrigins);
|
|
43
|
+
if (!requestOrigin) return json({
|
|
44
|
+
ok: false,
|
|
45
|
+
code: "origin_mismatch"
|
|
46
|
+
}, { status: 403 });
|
|
47
|
+
let body;
|
|
48
|
+
try {
|
|
49
|
+
body = parseChallengeBody(await readJson(request));
|
|
50
|
+
} catch (error) {
|
|
51
|
+
return json({
|
|
52
|
+
ok: false,
|
|
53
|
+
code: "invalid_body",
|
|
54
|
+
message: error instanceof Error ? error.message : "VoiceID challenge body is invalid"
|
|
55
|
+
}, { status: 400 });
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
const result = await deps.issueVoiceIdChallenge({
|
|
59
|
+
walletId: body.walletId,
|
|
60
|
+
walletAuthMethodId: body.walletAuthMethodId,
|
|
61
|
+
requestOrigin,
|
|
62
|
+
nowMs: Date.now()
|
|
63
|
+
});
|
|
64
|
+
if (result.kind === "rejected") return json({
|
|
65
|
+
ok: false,
|
|
66
|
+
code: result.code
|
|
67
|
+
}, { status: 403 });
|
|
68
|
+
return json({
|
|
69
|
+
ok: true,
|
|
70
|
+
challenge: result.challenge
|
|
71
|
+
}, { status: 200 });
|
|
72
|
+
} catch {
|
|
73
|
+
return json({
|
|
74
|
+
ok: false,
|
|
75
|
+
code: "challenge_unavailable"
|
|
76
|
+
}, { status: 503 });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async function handleVoiceIdVerifyRequest(request, deps) {
|
|
80
|
+
const requestOrigin = hostedOrigin(request, deps.hostedWalletOrigins);
|
|
81
|
+
if (!requestOrigin) return json({
|
|
82
|
+
ok: false,
|
|
83
|
+
code: "origin_mismatch"
|
|
84
|
+
}, { status: 403 });
|
|
85
|
+
let assertion;
|
|
86
|
+
try {
|
|
87
|
+
const body = requireRecord(await readJson(request), "VoiceID verification");
|
|
88
|
+
rejectUnknownFields(body, ["assertion"], "VoiceID verification");
|
|
89
|
+
assertion = parseVoiceIdDeviceAssertion(body.assertion);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
return json({
|
|
92
|
+
ok: false,
|
|
93
|
+
code: "invalid_body",
|
|
94
|
+
message: error instanceof Error ? error.message : "VoiceID assertion is invalid"
|
|
95
|
+
}, { status: 400 });
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const result = await deps.verifyAndIssueVoiceId({
|
|
99
|
+
assertion,
|
|
100
|
+
requestOrigin,
|
|
101
|
+
nowMs: Date.now()
|
|
102
|
+
});
|
|
103
|
+
if (result.kind === "rejected") {
|
|
104
|
+
const status = result.code === "issuance_unavailable" ? 503 : 403;
|
|
105
|
+
return json({
|
|
106
|
+
ok: false,
|
|
107
|
+
code: result.code
|
|
108
|
+
}, { status });
|
|
109
|
+
}
|
|
110
|
+
return json({
|
|
111
|
+
ok: true,
|
|
112
|
+
walletSession: projectActiveWalletSession(result.authorization),
|
|
113
|
+
operationCredential: result.operationCredential,
|
|
114
|
+
custodyEnvelope: result.custodyEnvelope,
|
|
115
|
+
near: await deps.prepareNear(result)
|
|
116
|
+
}, { status: 200 });
|
|
117
|
+
} catch {
|
|
118
|
+
return json({
|
|
119
|
+
ok: false,
|
|
120
|
+
code: "verification_unavailable"
|
|
121
|
+
}, { status: 503 });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
//#endregion
|
|
125
|
+
export { handleVoiceIdChallengeRequest, handleVoiceIdVerifyRequest, handleWalletVoiceIdChallenge, handleWalletVoiceIdVerify };
|
|
126
|
+
|
|
127
|
+
//# sourceMappingURL=walletVoiceIdSessions.js.map
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { parseWalletAuthMethodId, parseWalletAuthorityId, parseWalletId } from "../utils/domainIds.js";
|
|
2
|
+
import { parseWalletSessionMintId } from "./capabilityKinds.js";
|
|
3
|
+
import { isPlainObject } from "../utils/validation.js";
|
|
4
|
+
import { base64UrlDecode, base64UrlEncode } from "../utils/base64.js";
|
|
5
|
+
import { alphabetizeStringify } from "../utils/digests.js";
|
|
6
|
+
import { parseDigestB64u } from "../utils/canonicalPrimitives.js";
|
|
7
|
+
//#region ../shared-ts/src/authorization/voiceIdDeviceAssertion.ts
|
|
8
|
+
const CHALLENGE_DOMAIN = "seams:wallet-session:voiceid-device:v1";
|
|
9
|
+
const MAXIMUM_CHALLENGE_MS = 6e4;
|
|
10
|
+
function parseVoiceIdDevicePublicKeyB64u(value) {
|
|
11
|
+
if (typeof value !== "string") throw new Error("VoiceID public key is invalid");
|
|
12
|
+
const bytes = base64UrlDecode(value);
|
|
13
|
+
if (bytes.length !== 32 || base64UrlEncode(bytes) !== value) throw new Error("VoiceID public key must be canonical Ed25519 bytes");
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
function exactKeys(value, keys) {
|
|
17
|
+
if (Object.keys(value).length !== keys.length) return false;
|
|
18
|
+
for (const key of keys) if (!Object.hasOwn(value, key)) return false;
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
function requireOrigin(value, label) {
|
|
22
|
+
if (typeof value !== "string") throw new Error(`${label} must be an origin`);
|
|
23
|
+
let origin;
|
|
24
|
+
let protocol;
|
|
25
|
+
try {
|
|
26
|
+
const parsed = new URL(value);
|
|
27
|
+
origin = parsed.origin;
|
|
28
|
+
protocol = parsed.protocol;
|
|
29
|
+
} catch {
|
|
30
|
+
throw new Error(`${label} must be an origin`);
|
|
31
|
+
}
|
|
32
|
+
if (origin !== value || protocol !== "http:" && protocol !== "https:") throw new Error(`${label} must be a canonical HTTP origin`);
|
|
33
|
+
return origin;
|
|
34
|
+
}
|
|
35
|
+
function requireWindow(issuedAtMs, expiresAtMs) {
|
|
36
|
+
if (!Number.isSafeInteger(issuedAtMs) || !Number.isSafeInteger(expiresAtMs) || Number(issuedAtMs) <= 0 || Number(expiresAtMs) <= Number(issuedAtMs) || Number(expiresAtMs) - Number(issuedAtMs) > MAXIMUM_CHALLENGE_MS) throw new Error("VoiceID challenge has an invalid validity window");
|
|
37
|
+
}
|
|
38
|
+
function buildVoiceIdWalletSessionChallenge(fields) {
|
|
39
|
+
requireWindow(fields.issuedAtMs, fields.expiresAtMs);
|
|
40
|
+
requireOrigin(fields.requestOrigin, "VoiceID requestOrigin");
|
|
41
|
+
requireOrigin(fields.audience, "VoiceID audience");
|
|
42
|
+
return {
|
|
43
|
+
kind: "voiceid_wallet_session_challenge_v1",
|
|
44
|
+
walletId: fields.walletId,
|
|
45
|
+
walletAuthMethodId: fields.walletAuthMethodId,
|
|
46
|
+
walletAuthorityId: fields.walletAuthorityId,
|
|
47
|
+
deviceKeyId: fields.deviceKeyId,
|
|
48
|
+
challengeId: fields.challengeId,
|
|
49
|
+
nonce: fields.nonce,
|
|
50
|
+
requestOrigin: fields.requestOrigin,
|
|
51
|
+
audience: fields.audience,
|
|
52
|
+
issuedAtMs: fields.issuedAtMs,
|
|
53
|
+
expiresAtMs: fields.expiresAtMs
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function parseVoiceIdWalletSessionChallenge(raw) {
|
|
57
|
+
if (!isPlainObject(raw) || !exactKeys(raw, [
|
|
58
|
+
"kind",
|
|
59
|
+
"walletId",
|
|
60
|
+
"walletAuthMethodId",
|
|
61
|
+
"walletAuthorityId",
|
|
62
|
+
"deviceKeyId",
|
|
63
|
+
"challengeId",
|
|
64
|
+
"nonce",
|
|
65
|
+
"requestOrigin",
|
|
66
|
+
"audience",
|
|
67
|
+
"issuedAtMs",
|
|
68
|
+
"expiresAtMs"
|
|
69
|
+
]) || raw.kind !== "voiceid_wallet_session_challenge_v1") throw new Error("Invalid VoiceID Wallet Session challenge");
|
|
70
|
+
const walletId = parseWalletId(raw.walletId);
|
|
71
|
+
const walletAuthMethodId = parseWalletAuthMethodId(raw.walletAuthMethodId);
|
|
72
|
+
const walletAuthorityId = parseWalletAuthorityId(raw.walletAuthorityId);
|
|
73
|
+
const challengeId = parseWalletSessionMintId(raw.challengeId);
|
|
74
|
+
if (!walletId.ok || !walletAuthMethodId.ok || !walletAuthorityId.ok || !challengeId.ok) throw new Error("VoiceID challenge identity is invalid");
|
|
75
|
+
if (typeof raw.issuedAtMs !== "number" || typeof raw.expiresAtMs !== "number") throw new Error("VoiceID challenge times must be numbers");
|
|
76
|
+
return buildVoiceIdWalletSessionChallenge({
|
|
77
|
+
walletId: walletId.value,
|
|
78
|
+
walletAuthMethodId: walletAuthMethodId.value,
|
|
79
|
+
walletAuthorityId: walletAuthorityId.value,
|
|
80
|
+
deviceKeyId: parseDigestB64u(raw.deviceKeyId),
|
|
81
|
+
challengeId: challengeId.value,
|
|
82
|
+
nonce: parseDigestB64u(raw.nonce),
|
|
83
|
+
requestOrigin: requireOrigin(raw.requestOrigin, "VoiceID requestOrigin"),
|
|
84
|
+
audience: requireOrigin(raw.audience, "VoiceID audience"),
|
|
85
|
+
issuedAtMs: raw.issuedAtMs,
|
|
86
|
+
expiresAtMs: raw.expiresAtMs
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
function parseVoiceIdDeviceAssertion(raw) {
|
|
90
|
+
if (!isPlainObject(raw) || !exactKeys(raw, [
|
|
91
|
+
"kind",
|
|
92
|
+
"challenge",
|
|
93
|
+
"signatureB64u"
|
|
94
|
+
]) || raw.kind !== "voiceid_device_assertion_v1" || typeof raw.signatureB64u !== "string") throw new Error("Invalid VoiceID device assertion");
|
|
95
|
+
const signature = base64UrlDecode(raw.signatureB64u);
|
|
96
|
+
if (signature.length !== 64 || base64UrlEncode(signature) !== raw.signatureB64u) throw new Error("VoiceID signature must be canonical Ed25519 bytes");
|
|
97
|
+
return {
|
|
98
|
+
kind: "voiceid_device_assertion_v1",
|
|
99
|
+
challenge: parseVoiceIdWalletSessionChallenge(raw.challenge),
|
|
100
|
+
signatureB64u: raw.signatureB64u
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function voiceIdWalletSessionChallengeBytes(challenge) {
|
|
104
|
+
const payload = alphabetizeStringify(challenge);
|
|
105
|
+
return new TextEncoder().encode(`${CHALLENGE_DOMAIN}|${payload}`);
|
|
106
|
+
}
|
|
107
|
+
//#endregion
|
|
108
|
+
export { buildVoiceIdWalletSessionChallenge, parseVoiceIdDeviceAssertion, parseVoiceIdDevicePublicKeyB64u, parseVoiceIdWalletSessionChallenge, voiceIdWalletSessionChallengeBytes };
|
|
109
|
+
|
|
110
|
+
//# sourceMappingURL=voiceIdDeviceAssertion.js.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { wireLiteral, wireObject } from "../utils/wireSchema.js";
|
|
2
2
|
import { authorizationDeviceId, digest, keyLabeled, parseUnixTime, sessionId, walletAuthMethodId, walletAuthorityId, walletId, walletSessionAuthorizationId, walletSessionId } from "./wireFields.js";
|
|
3
3
|
import "./activeWalletSession.js";
|
|
4
|
+
import "../utils/walletAuthMethodRecord.js";
|
|
4
5
|
import { parseWalletSignerActivationSetV1 } from "../authorization/walletAuthority.js";
|
|
5
6
|
import "./walletSessionCredentialDelivery.js";
|
|
6
7
|
//#region ../shared-ts/src/device-linking/authorityActivation.ts
|
|
@@ -16,7 +16,7 @@ function parseCommittedAuthorityPackagesV1(raw) {
|
|
|
16
16
|
const authority = parseWalletAuthorityV1(record.authority);
|
|
17
17
|
if (!authority.ok || authority.value.state !== "pending_local_install") throw new Error("CommittedAuthorityPackagesV1.authority is not pending");
|
|
18
18
|
const authMethod = parseWalletAuthMethodRecordV2(record.authMethod);
|
|
19
|
-
if (!authMethod || authMethod.status !== "pending_local_install") throw new Error("CommittedAuthorityPackagesV1.authMethod is not pending");
|
|
19
|
+
if (!authMethod || authMethod.status !== "pending_local_install" || authMethod.kind === "voiceid") throw new Error("CommittedAuthorityPackagesV1.authMethod is not pending");
|
|
20
20
|
if (authMethod.walletId !== authority.value.walletId || authMethod.walletAuthorityId !== authority.value.authorityId) throw new Error("CommittedAuthorityPackagesV1 authority and auth method identities differ");
|
|
21
21
|
const signerPackages = parseCommittedSignerPackageSetV1(record.signerPackages);
|
|
22
22
|
assertSignerPackagesMatchAuthority(signerPackages, authority.value.signerActivations);
|
|
@@ -7,6 +7,13 @@ import { parsePasskeyCustodySecretBinding } from "./custodySecretBinding.js";
|
|
|
7
7
|
const WALLET_CUSTODY_ENVELOPE_VERSION_V2 = "wallet_custody_envelope_v2";
|
|
8
8
|
const PASSKEY_PRF_KEK_VERSION_V1 = "passkey_prf_kek_hkdf_sha256_v1";
|
|
9
9
|
const EMAIL_OTP_FACTOR_KEK_VERSION_V1 = "email_otp_factor_kek_hkdf_sha256_v1";
|
|
10
|
+
const VOICEID_DEVICE_FACTOR_KEK_VERSION_V1 = "voiceid_device_factor_kek_hkdf_sha256_v1";
|
|
11
|
+
function isActiveMethodBoundWalletSeedEnvelope(envelope) {
|
|
12
|
+
return envelope.ownership.kind === "method_bound" && envelope.binding.kind === "wallet_custody_seed_v1" && envelope.lifecycle.state === "active";
|
|
13
|
+
}
|
|
14
|
+
function isVoiceIdSeedEnvelope(envelope) {
|
|
15
|
+
return isActiveMethodBoundWalletSeedEnvelope(envelope) && envelope.factor.kind === "voiceid";
|
|
16
|
+
}
|
|
10
17
|
const ENVELOPE_OWNERSHIP_FIELDS = ["kind", "walletAuthMethodId"];
|
|
11
18
|
function parseWalletCustodyEnvelopeOwnership(raw, label) {
|
|
12
19
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) throw new Error(`${label} must be an object`);
|
|
@@ -67,6 +74,13 @@ function buildEmailOtpEnvelopeFactor(args) {
|
|
|
67
74
|
kekVersion: EMAIL_OTP_FACTOR_KEK_VERSION_V1
|
|
68
75
|
};
|
|
69
76
|
}
|
|
77
|
+
function buildVoiceIdEnvelopeFactor(args) {
|
|
78
|
+
return {
|
|
79
|
+
kind: "voiceid",
|
|
80
|
+
deviceKeyId: args.deviceKeyId,
|
|
81
|
+
kekVersion: VOICEID_DEVICE_FACTOR_KEK_VERSION_V1
|
|
82
|
+
};
|
|
83
|
+
}
|
|
70
84
|
function buildActiveEnvelopeLifecycle(args) {
|
|
71
85
|
return {
|
|
72
86
|
state: "active",
|
|
@@ -153,7 +167,16 @@ const EMAIL_OTP_FACTOR_FIELDS = [
|
|
|
153
167
|
"enrollmentSealKeyVersion",
|
|
154
168
|
"kekVersion"
|
|
155
169
|
];
|
|
156
|
-
const
|
|
170
|
+
const VOICEID_FACTOR_FIELDS = [
|
|
171
|
+
"kind",
|
|
172
|
+
"deviceKeyId",
|
|
173
|
+
"kekVersion"
|
|
174
|
+
];
|
|
175
|
+
const ALL_FACTOR_FIELDS = Array.from(new Set([
|
|
176
|
+
...PASSKEY_FACTOR_FIELDS,
|
|
177
|
+
...EMAIL_OTP_FACTOR_FIELDS,
|
|
178
|
+
...VOICEID_FACTOR_FIELDS
|
|
179
|
+
]));
|
|
157
180
|
function requireNonEmptyString(value, label) {
|
|
158
181
|
if (typeof value !== "string" || !value.trim()) throw new Error(`${label} is required`);
|
|
159
182
|
return value;
|
|
@@ -180,7 +203,11 @@ function parseWalletCustodyEnvelopeFactor(raw, label = "factor") {
|
|
|
180
203
|
enrollmentId: requireNonEmptyString(record.enrollmentId, `${label}.enrollmentId`),
|
|
181
204
|
enrollmentSealKeyVersion: requireNonEmptyString(record.enrollmentSealKeyVersion, `${label}.enrollmentSealKeyVersion`)
|
|
182
205
|
});
|
|
183
|
-
|
|
206
|
+
case "voiceid":
|
|
207
|
+
rejectUnknownFields(record, VOICEID_FACTOR_FIELDS, label, ALL_FACTOR_FIELDS);
|
|
208
|
+
if (record.kekVersion !== VOICEID_DEVICE_FACTOR_KEK_VERSION_V1) throw new Error(`${label}.kekVersion must be ${VOICEID_DEVICE_FACTOR_KEK_VERSION_V1}`);
|
|
209
|
+
return buildVoiceIdEnvelopeFactor({ deviceKeyId: parseDigestField(record.deviceKeyId, `${label}.deviceKeyId`) });
|
|
210
|
+
default: throw new Error(`${label}.kind must be passkey, email_otp, or voiceid`);
|
|
184
211
|
}
|
|
185
212
|
}
|
|
186
213
|
const ENVELOPE_RECORD_FIELDS = [
|
|
@@ -269,6 +296,6 @@ function admitWalletCustodyEnvelopeOwnershipReplacementV1(input) {
|
|
|
269
296
|
}
|
|
270
297
|
}
|
|
271
298
|
//#endregion
|
|
272
|
-
export { WALLET_CUSTODY_ENVELOPE_VERSION_V2, admitWalletCustodyEnvelopeOwnershipReplacementV1, buildActiveEnvelopeLifecycle, buildEmailOtpEnvelopeFactor, buildPasskeyCustodyEnvelopeRecord, buildPasskeyEnvelopeFactor, buildRevokedEnvelopeLifecycle, parsePasskeyCustodyEnvelopeRecord, parseWalletCustodyEnvelopeFactor, parseWalletCustodyEnvelopeOwnershipWireV1, sameWalletCustodyEnvelopeOwnership };
|
|
299
|
+
export { WALLET_CUSTODY_ENVELOPE_VERSION_V2, admitWalletCustodyEnvelopeOwnershipReplacementV1, buildActiveEnvelopeLifecycle, buildEmailOtpEnvelopeFactor, buildPasskeyCustodyEnvelopeRecord, buildPasskeyEnvelopeFactor, buildRevokedEnvelopeLifecycle, buildVoiceIdEnvelopeFactor, isActiveMethodBoundWalletSeedEnvelope, isVoiceIdSeedEnvelope, parsePasskeyCustodyEnvelopeRecord, parseWalletCustodyEnvelopeFactor, parseWalletCustodyEnvelopeOwnershipWireV1, sameWalletCustodyEnvelopeOwnership };
|
|
273
300
|
|
|
274
301
|
//# sourceMappingURL=custodyEnvelope.js.map
|
|
@@ -172,6 +172,6 @@ function parsePasskeyCustodySecretBinding(raw, label = "passkeyCustodySecretBind
|
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
174
|
//#endregion
|
|
175
|
-
export { buildEd25519YaoClientRootBinding, parsePasskeyCustodySecretBinding };
|
|
175
|
+
export { buildEd25519YaoClientRootBinding, buildWalletCustodySeedBinding, parsePasskeyCustodySecretBinding };
|
|
176
176
|
|
|
177
177
|
//# sourceMappingURL=custodySecretBinding.js.map
|