@seams/wallet-server 0.43.0 → 0.45.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 (35) hide show
  1. package/README.md +4 -4
  2. package/artifact-manifest.json +11 -11
  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/router/build-stamp.json +1 -1
  6. package/cloudflare-router-ab/build/signing-worker/build-stamp.json +1 -1
  7. package/cloudflare-router-ab/build/tenant-root-control-plane/build-stamp.json +1 -1
  8. package/dist/esm/authorization/factorEvidence.js +37 -1
  9. package/dist/esm/authorization/voiceIdDeviceProof.js +32 -6
  10. package/dist/esm/hosted-wallet-gateway.js +1 -1
  11. package/dist/esm/router/cloudflare/d1/auth/d1RouterApiAuthService.js +1 -0
  12. package/dist/esm/router/cloudflare/d1/wallet/d1VoiceIdSessions.js +34 -1
  13. package/dist/esm/router/cloudflare.js +317 -100
  14. package/dist/esm/router/domains/ed25519Yao/export/routerAbEd25519YaoExport.js +181 -14
  15. package/dist/esm/router/domains/ed25519Yao/export/routerAbEd25519YaoExportRequestScopedCloudflare.js +21 -5
  16. package/dist/esm/router/domains/signingOperations/routerAbOperationStepUp.js +4 -0
  17. package/dist/esm/router/transport/fetch/routes/ecdsaOperationStepUpFactor.js +51 -1
  18. package/dist/esm/router/transport/fetch/routes/thresholdEcdsa.js +3 -1
  19. package/dist/esm/shared-ts/src/authorization/capabilityKinds.js +2 -1
  20. package/dist/esm/shared-ts/src/authorization/voiceIdDeviceAssertion.js +70 -1
  21. package/dist/esm/shared-ts/src/utils/routerAbEcdsaDerivation.js +18 -1
  22. package/dist/esm/shared-ts/src/utils/routerAbEd25519YaoDigests.js +8 -1
  23. package/dist/types/shared-ts/src/authorization/capabilityKinds.d.ts +2 -1
  24. package/dist/types/shared-ts/src/authorization/voiceIdDeviceAssertion.d.ts +30 -0
  25. package/dist/types/shared-ts/src/utils/routerAbEcdsaDerivation.d.ts +11 -1
  26. package/dist/types/shared-ts/src/utils/routerAbEd25519YaoDigests.d.ts +3 -0
  27. package/dist/types/wallet-server/src/authorization/domain.d.ts +3 -0
  28. package/dist/types/wallet-server/src/authorization/factorEvidence.d.ts +11 -1
  29. package/dist/types/wallet-server/src/authorization/voiceIdDeviceProof.d.ts +27 -1
  30. package/dist/types/wallet-server/src/router/cloudflare/d1/wallet/d1VoiceIdSessions.d.ts +3 -1
  31. package/dist/types/wallet-server/src/router/domains/ed25519Yao/export/routerAbEd25519YaoExport.d.ts +36 -2
  32. package/dist/types/wallet-server/src/router/domains/voiceId/voiceIdSessions.d.ts +23 -1
  33. package/dist/types/wallet-server/src/router/framework/authServicePort.d.ts +1 -0
  34. package/dist/types/wallet-server/src/router/transport/fetch/routes/ecdsaOperationStepUpFactor.d.ts +2 -1
  35. package/package.json +1 -1
@@ -967,7 +967,8 @@ const EVM_ECDSA_MPC_OPERATION_KINDS = {
967
967
  const AUTHORIZATION_EVIDENCE_KINDS = {
968
968
  seamsSession: "seams_session",
969
969
  passkeyAssertion: "passkey_assertion",
970
- emailOtp: "email_otp"
970
+ emailOtp: "email_otp",
971
+ voiceIdDevice: "voiceid_device"
971
972
  };
972
973
  const AUTHORIZATION_GRANT_KINDS = { walletSession: "wallet_session_authorization" };
973
974
  const CAPABILITY_KIND_VALUES = Object.values(CAPABILITY_KINDS);
@@ -1561,6 +1562,19 @@ function isPasskeyWalletAuthAuthority(authority) {
1561
1562
  function isEmailOtpWalletAuthAuthority(authority) {
1562
1563
  return authority.factor.kind === "email_otp";
1563
1564
  }
1565
+ function walletAuthAuthoritiesMatch(left, right) {
1566
+ if (left.walletId !== right.walletId || left.bindingId !== right.bindingId) return false;
1567
+ if (isPasskeyWalletAuthAuthority(left)) {
1568
+ if (!isPasskeyWalletAuthAuthority(right)) return false;
1569
+ return left.factor.credentialIdB64u === right.factor.credentialIdB64u && left.verifier.rpId === right.verifier.rpId;
1570
+ }
1571
+ if (isEmailOtpWalletAuthAuthority(left)) {
1572
+ if (!isEmailOtpWalletAuthAuthority(right)) return false;
1573
+ return left.factor.provider === right.factor.provider && left.factor.providerUserId === right.factor.providerUserId && left.verifier.emailHashHex === right.verifier.emailHashHex;
1574
+ }
1575
+ if (isVoiceIdWalletAuthAuthority(left)) return isVoiceIdWalletAuthAuthority(right) && left.factor.deviceKeyId === right.factor.deviceKeyId;
1576
+ return false;
1577
+ }
1564
1578
  function parseWalletAuthAuthorityRef(raw) {
1565
1579
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
1566
1580
  const record = raw;
@@ -1766,6 +1780,166 @@ async function walletAuthAuthorityRef(args) {
1766
1780
  walletAuthMethodId: args.authority.bindingId
1767
1781
  };
1768
1782
  }
1783
+ //#endregion
1784
+ //#region ../shared-ts/src/authorization/voiceIdDeviceAssertion.ts
1785
+ const MAXIMUM_CHALLENGE_MS = 6e4;
1786
+ function parseVoiceIdDevicePublicKeyB64u(value) {
1787
+ if (typeof value !== "string") throw new Error("VoiceID public key is invalid");
1788
+ const bytes = base64UrlDecode(value);
1789
+ if (bytes.length !== 32 || base64UrlEncode(bytes) !== value) throw new Error("VoiceID public key must be canonical Ed25519 bytes");
1790
+ return value;
1791
+ }
1792
+ function exactKeys(value, keys) {
1793
+ if (Object.keys(value).length !== keys.length) return false;
1794
+ for (const key of keys) if (!Object.hasOwn(value, key)) return false;
1795
+ return true;
1796
+ }
1797
+ function requireOrigin(value, label) {
1798
+ if (typeof value !== "string") throw new Error(`${label} must be an origin`);
1799
+ let origin;
1800
+ let protocol;
1801
+ try {
1802
+ const parsed = new URL(value);
1803
+ origin = parsed.origin;
1804
+ protocol = parsed.protocol;
1805
+ } catch {
1806
+ throw new Error(`${label} must be an origin`);
1807
+ }
1808
+ if (origin !== value || protocol !== "http:" && protocol !== "https:") throw new Error(`${label} must be a canonical HTTP origin`);
1809
+ return origin;
1810
+ }
1811
+ function requireWindow(issuedAtMs, expiresAtMs) {
1812
+ 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");
1813
+ }
1814
+ function buildVoiceIdWalletSessionChallenge(fields) {
1815
+ requireWindow(fields.issuedAtMs, fields.expiresAtMs);
1816
+ requireOrigin(fields.requestOrigin, "VoiceID requestOrigin");
1817
+ requireOrigin(fields.audience, "VoiceID audience");
1818
+ return {
1819
+ kind: "voiceid_wallet_session_challenge_v1",
1820
+ walletId: fields.walletId,
1821
+ walletAuthMethodId: fields.walletAuthMethodId,
1822
+ walletAuthorityId: fields.walletAuthorityId,
1823
+ deviceKeyId: fields.deviceKeyId,
1824
+ challengeId: fields.challengeId,
1825
+ nonce: fields.nonce,
1826
+ requestOrigin: fields.requestOrigin,
1827
+ audience: fields.audience,
1828
+ issuedAtMs: fields.issuedAtMs,
1829
+ expiresAtMs: fields.expiresAtMs
1830
+ };
1831
+ }
1832
+ function parseVoiceIdWalletSessionChallenge(raw) {
1833
+ if (!isPlainObject(raw) || !exactKeys(raw, [
1834
+ "kind",
1835
+ "walletId",
1836
+ "walletAuthMethodId",
1837
+ "walletAuthorityId",
1838
+ "deviceKeyId",
1839
+ "challengeId",
1840
+ "nonce",
1841
+ "requestOrigin",
1842
+ "audience",
1843
+ "issuedAtMs",
1844
+ "expiresAtMs"
1845
+ ]) || raw.kind !== "voiceid_wallet_session_challenge_v1") throw new Error("Invalid VoiceID Wallet Session challenge");
1846
+ const walletId = parseWalletId(raw.walletId);
1847
+ const walletAuthMethodId = parseWalletAuthMethodId(raw.walletAuthMethodId);
1848
+ const walletAuthorityId = parseWalletAuthorityId(raw.walletAuthorityId);
1849
+ const challengeId = parseWalletSessionMintId(raw.challengeId);
1850
+ if (!walletId.ok || !walletAuthMethodId.ok || !walletAuthorityId.ok || !challengeId.ok) throw new Error("VoiceID challenge identity is invalid");
1851
+ if (typeof raw.issuedAtMs !== "number" || typeof raw.expiresAtMs !== "number") throw new Error("VoiceID challenge times must be numbers");
1852
+ return buildVoiceIdWalletSessionChallenge({
1853
+ walletId: walletId.value,
1854
+ walletAuthMethodId: walletAuthMethodId.value,
1855
+ walletAuthorityId: walletAuthorityId.value,
1856
+ deviceKeyId: parseDigestB64u(raw.deviceKeyId),
1857
+ challengeId: challengeId.value,
1858
+ nonce: parseDigestB64u(raw.nonce),
1859
+ requestOrigin: requireOrigin(raw.requestOrigin, "VoiceID requestOrigin"),
1860
+ audience: requireOrigin(raw.audience, "VoiceID audience"),
1861
+ issuedAtMs: raw.issuedAtMs,
1862
+ expiresAtMs: raw.expiresAtMs
1863
+ });
1864
+ }
1865
+ function parseVoiceIdDeviceAssertion(raw) {
1866
+ if (!isPlainObject(raw) || !exactKeys(raw, [
1867
+ "kind",
1868
+ "challenge",
1869
+ "signatureB64u"
1870
+ ]) || raw.kind !== "voiceid_device_assertion_v1" || typeof raw.signatureB64u !== "string") throw new Error("Invalid VoiceID device assertion");
1871
+ const signature = base64UrlDecode(raw.signatureB64u);
1872
+ if (signature.length !== 64 || base64UrlEncode(signature) !== raw.signatureB64u) throw new Error("VoiceID signature must be canonical Ed25519 bytes");
1873
+ return {
1874
+ kind: "voiceid_device_assertion_v1",
1875
+ challenge: parseVoiceIdWalletSessionChallenge(raw.challenge),
1876
+ signatureB64u: raw.signatureB64u
1877
+ };
1878
+ }
1879
+ function parseVoiceIdOperationKind(value) {
1880
+ if (value === "near.export_key" || value === "evm.export_key") return value;
1881
+ throw new Error("VoiceID operation kind is unsupported");
1882
+ }
1883
+ function buildVoiceIdOperationStatement(fields) {
1884
+ requireWindow(fields.issuedAtMs, fields.expiresAtMs);
1885
+ requireOrigin(fields.requestOrigin, "VoiceID requestOrigin");
1886
+ return {
1887
+ kind: "voiceid_operation_statement_v1",
1888
+ walletId: fields.walletId,
1889
+ walletAuthMethodId: fields.walletAuthMethodId,
1890
+ walletAuthorityId: fields.walletAuthorityId,
1891
+ deviceKeyId: fields.deviceKeyId,
1892
+ operationKind: parseVoiceIdOperationKind(fields.operationKind),
1893
+ operationChallenge: parseDigestB64u(fields.operationChallenge),
1894
+ requestOrigin: fields.requestOrigin,
1895
+ issuedAtMs: fields.issuedAtMs,
1896
+ expiresAtMs: fields.expiresAtMs
1897
+ };
1898
+ }
1899
+ function parseVoiceIdOperationStatement(raw) {
1900
+ if (!isPlainObject(raw) || !exactKeys(raw, [
1901
+ "kind",
1902
+ "walletId",
1903
+ "walletAuthMethodId",
1904
+ "walletAuthorityId",
1905
+ "deviceKeyId",
1906
+ "operationKind",
1907
+ "operationChallenge",
1908
+ "requestOrigin",
1909
+ "issuedAtMs",
1910
+ "expiresAtMs"
1911
+ ]) || raw.kind !== "voiceid_operation_statement_v1") throw new Error("Invalid VoiceID operation statement");
1912
+ const walletId = parseWalletId(raw.walletId);
1913
+ const walletAuthMethodId = parseWalletAuthMethodId(raw.walletAuthMethodId);
1914
+ const walletAuthorityId = parseWalletAuthorityId(raw.walletAuthorityId);
1915
+ if (!walletId.ok || !walletAuthMethodId.ok || !walletAuthorityId.ok) throw new Error("VoiceID operation identity is invalid");
1916
+ if (typeof raw.issuedAtMs !== "number" || typeof raw.expiresAtMs !== "number") throw new Error("VoiceID operation times must be numbers");
1917
+ return buildVoiceIdOperationStatement({
1918
+ walletId: walletId.value,
1919
+ walletAuthMethodId: walletAuthMethodId.value,
1920
+ walletAuthorityId: walletAuthorityId.value,
1921
+ deviceKeyId: parseDigestB64u(raw.deviceKeyId),
1922
+ operationKind: parseVoiceIdOperationKind(raw.operationKind),
1923
+ operationChallenge: parseDigestB64u(raw.operationChallenge),
1924
+ requestOrigin: requireOrigin(raw.requestOrigin, "VoiceID requestOrigin"),
1925
+ issuedAtMs: raw.issuedAtMs,
1926
+ expiresAtMs: raw.expiresAtMs
1927
+ });
1928
+ }
1929
+ function parseVoiceIdOperationAssertion(raw) {
1930
+ if (!isPlainObject(raw) || !exactKeys(raw, [
1931
+ "kind",
1932
+ "statement",
1933
+ "signatureB64u"
1934
+ ]) || raw.kind !== "voiceid_operation_assertion_v1" || typeof raw.signatureB64u !== "string") throw new Error("Invalid VoiceID operation assertion");
1935
+ const signature = base64UrlDecode(raw.signatureB64u);
1936
+ if (signature.length !== 64 || base64UrlEncode(signature) !== raw.signatureB64u) throw new Error("VoiceID signature must be canonical Ed25519 bytes");
1937
+ return {
1938
+ kind: "voiceid_operation_assertion_v1",
1939
+ statement: parseVoiceIdOperationStatement(raw.statement),
1940
+ signatureB64u: raw.signatureB64u
1941
+ };
1942
+ }
1769
1943
  const ROUTER_AB_ECDSA_DERIVATION_NORMAL_SIGNING_STATE_KIND_V1 = "router_ab_ecdsa_derivation_normal_signing_v1";
1770
1944
  const ROUTER_AB_ECDSA_DERIVATION_HEALTH_PATH = "/router-ab/ecdsa-derivation/healthz";
1771
1945
  const ROUTER_AB_ECDSA_DERIVATION_EXPORT_PATH = "/router-ab/ecdsa-derivation/export";
@@ -2909,6 +3083,22 @@ function parseRouterAbEcdsaOperationStepUpAuthorizationRequestV1(value) {
2909
3083
  otp_code: requireAsciiNonEmptyString(proof.otp_code, "operationStepUpAuthorizationRequest.proof.otp_code")
2910
3084
  };
2911
3085
  break;
3086
+ case "voiceid": {
3087
+ requireExactKeys$5(proof, "operationStepUpAuthorizationRequest.proof", [
3088
+ "kind",
3089
+ "authority",
3090
+ "assertion"
3091
+ ]);
3092
+ if (!authority || !isVoiceIdWalletAuthAuthority(authority)) throw new Error("operationStepUpAuthorizationRequest.proof requires an exact VoiceID authority");
3093
+ const assertion = parseVoiceIdOperationAssertion(proof.assertion);
3094
+ if (assertion.statement.walletId !== authority.walletId || assertion.statement.walletAuthMethodId !== authority.bindingId || assertion.statement.deviceKeyId !== authority.factor.deviceKeyId) throw new Error("operationStepUpAuthorizationRequest.proof names another VoiceID method");
3095
+ parsedProof = {
3096
+ kind: "voiceid",
3097
+ authority,
3098
+ assertion
3099
+ };
3100
+ break;
3101
+ }
2912
3102
  default: throw new Error("operationStepUpAuthorizationRequest.proof.kind is invalid");
2913
3103
  }
2914
3104
  return {
@@ -6358,102 +6548,6 @@ function exactRecord$2(raw, keys, label) {
6358
6548
  return record;
6359
6549
  }
6360
6550
  //#endregion
6361
- //#region ../shared-ts/src/authorization/voiceIdDeviceAssertion.ts
6362
- const MAXIMUM_CHALLENGE_MS = 6e4;
6363
- function parseVoiceIdDevicePublicKeyB64u(value) {
6364
- if (typeof value !== "string") throw new Error("VoiceID public key is invalid");
6365
- const bytes = base64UrlDecode(value);
6366
- if (bytes.length !== 32 || base64UrlEncode(bytes) !== value) throw new Error("VoiceID public key must be canonical Ed25519 bytes");
6367
- return value;
6368
- }
6369
- function exactKeys(value, keys) {
6370
- if (Object.keys(value).length !== keys.length) return false;
6371
- for (const key of keys) if (!Object.hasOwn(value, key)) return false;
6372
- return true;
6373
- }
6374
- function requireOrigin(value, label) {
6375
- if (typeof value !== "string") throw new Error(`${label} must be an origin`);
6376
- let origin;
6377
- let protocol;
6378
- try {
6379
- const parsed = new URL(value);
6380
- origin = parsed.origin;
6381
- protocol = parsed.protocol;
6382
- } catch {
6383
- throw new Error(`${label} must be an origin`);
6384
- }
6385
- if (origin !== value || protocol !== "http:" && protocol !== "https:") throw new Error(`${label} must be a canonical HTTP origin`);
6386
- return origin;
6387
- }
6388
- function requireWindow(issuedAtMs, expiresAtMs) {
6389
- 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");
6390
- }
6391
- function buildVoiceIdWalletSessionChallenge(fields) {
6392
- requireWindow(fields.issuedAtMs, fields.expiresAtMs);
6393
- requireOrigin(fields.requestOrigin, "VoiceID requestOrigin");
6394
- requireOrigin(fields.audience, "VoiceID audience");
6395
- return {
6396
- kind: "voiceid_wallet_session_challenge_v1",
6397
- walletId: fields.walletId,
6398
- walletAuthMethodId: fields.walletAuthMethodId,
6399
- walletAuthorityId: fields.walletAuthorityId,
6400
- deviceKeyId: fields.deviceKeyId,
6401
- challengeId: fields.challengeId,
6402
- nonce: fields.nonce,
6403
- requestOrigin: fields.requestOrigin,
6404
- audience: fields.audience,
6405
- issuedAtMs: fields.issuedAtMs,
6406
- expiresAtMs: fields.expiresAtMs
6407
- };
6408
- }
6409
- function parseVoiceIdWalletSessionChallenge(raw) {
6410
- if (!isPlainObject(raw) || !exactKeys(raw, [
6411
- "kind",
6412
- "walletId",
6413
- "walletAuthMethodId",
6414
- "walletAuthorityId",
6415
- "deviceKeyId",
6416
- "challengeId",
6417
- "nonce",
6418
- "requestOrigin",
6419
- "audience",
6420
- "issuedAtMs",
6421
- "expiresAtMs"
6422
- ]) || raw.kind !== "voiceid_wallet_session_challenge_v1") throw new Error("Invalid VoiceID Wallet Session challenge");
6423
- const walletId = parseWalletId(raw.walletId);
6424
- const walletAuthMethodId = parseWalletAuthMethodId(raw.walletAuthMethodId);
6425
- const walletAuthorityId = parseWalletAuthorityId(raw.walletAuthorityId);
6426
- const challengeId = parseWalletSessionMintId(raw.challengeId);
6427
- if (!walletId.ok || !walletAuthMethodId.ok || !walletAuthorityId.ok || !challengeId.ok) throw new Error("VoiceID challenge identity is invalid");
6428
- if (typeof raw.issuedAtMs !== "number" || typeof raw.expiresAtMs !== "number") throw new Error("VoiceID challenge times must be numbers");
6429
- return buildVoiceIdWalletSessionChallenge({
6430
- walletId: walletId.value,
6431
- walletAuthMethodId: walletAuthMethodId.value,
6432
- walletAuthorityId: walletAuthorityId.value,
6433
- deviceKeyId: parseDigestB64u(raw.deviceKeyId),
6434
- challengeId: challengeId.value,
6435
- nonce: parseDigestB64u(raw.nonce),
6436
- requestOrigin: requireOrigin(raw.requestOrigin, "VoiceID requestOrigin"),
6437
- audience: requireOrigin(raw.audience, "VoiceID audience"),
6438
- issuedAtMs: raw.issuedAtMs,
6439
- expiresAtMs: raw.expiresAtMs
6440
- });
6441
- }
6442
- function parseVoiceIdDeviceAssertion(raw) {
6443
- if (!isPlainObject(raw) || !exactKeys(raw, [
6444
- "kind",
6445
- "challenge",
6446
- "signatureB64u"
6447
- ]) || raw.kind !== "voiceid_device_assertion_v1" || typeof raw.signatureB64u !== "string") throw new Error("Invalid VoiceID device assertion");
6448
- const signature = base64UrlDecode(raw.signatureB64u);
6449
- if (signature.length !== 64 || base64UrlEncode(signature) !== raw.signatureB64u) throw new Error("VoiceID signature must be canonical Ed25519 bytes");
6450
- return {
6451
- kind: "voiceid_device_assertion_v1",
6452
- challenge: parseVoiceIdWalletSessionChallenge(raw.challenge),
6453
- signatureB64u: raw.signatureB64u
6454
- };
6455
- }
6456
- //#endregion
6457
6551
  //#region ../shared-ts/src/utils/registrationAuthMethodInput.ts
6458
6552
  function inspectRawObject$2(value) {
6459
6553
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -9003,6 +9097,26 @@ function buildVerifiedWalletOperationEmailOtpFactorResult(fields) {
9003
9097
  ...fields
9004
9098
  };
9005
9099
  }
9100
+ /** Only a verified operation statement yields this factor; its proof supplies the key and digest. */
9101
+ function buildVerifiedWalletOperationVoiceIdFactorResult(fields) {
9102
+ requireVerificationWindow(fields.verifiedAtMs, fields.expiresAtMs);
9103
+ if (fields.expiresAtMs > fields.proof.statement.expiresAtMs) throw new Error("VoiceID factor cannot outlive its operation statement");
9104
+ return {
9105
+ kind: "verified_wallet_operation_voiceid_factor",
9106
+ tenantId: fields.tenantId,
9107
+ principalId: fields.principalId,
9108
+ walletId: fields.walletId,
9109
+ authorityRef: fields.authorityRef,
9110
+ requestOrigin: fields.requestOrigin,
9111
+ audience: fields.audience,
9112
+ factorId: fields.factorId,
9113
+ operation: fields.operation,
9114
+ deviceKeyId: fields.proof.deviceKeyId,
9115
+ assertionDigest: fields.proof.assertionDigest,
9116
+ verifiedAtMs: fields.verifiedAtMs,
9117
+ expiresAtMs: fields.expiresAtMs
9118
+ };
9119
+ }
9006
9120
  function buildVerifiedWalletSessionPasskeyFactorResult(fields) {
9007
9121
  requireVerificationWindow(fields.verifiedAtMs, fields.expiresAtMs);
9008
9122
  return {
@@ -9109,6 +9223,16 @@ async function buildWalletOperationFactorEvidence(evidenceId, factor) {
9109
9223
  verificationReceiptDigest: factor.verificationReceiptDigest
9110
9224
  })
9111
9225
  };
9226
+ case "verified_wallet_operation_voiceid_factor": return {
9227
+ evidenceId,
9228
+ evidenceKind: AUTHORIZATION_EVIDENCE_KINDS.voiceIdDevice,
9229
+ evidenceDigest: await digestCanonicalEvidence({
9230
+ kind: factor.kind,
9231
+ ...common,
9232
+ deviceKeyId: factor.deviceKeyId,
9233
+ assertionDigest: factor.assertionDigest
9234
+ })
9235
+ };
9112
9236
  }
9113
9237
  }
9114
9238
  function requireVerificationWindow(verifiedAtMs, expiresAtMs) {
@@ -19459,6 +19583,10 @@ function walletExecutionLaneAuthSourceFromAuthority(authority) {
19459
19583
  kind: "passkey",
19460
19584
  credentialIdB64u: authority.factor.credentialIdB64u
19461
19585
  };
19586
+ if (authority.factor.kind === "voiceid") return {
19587
+ kind: "voiceid",
19588
+ deviceKeyId: authority.factor.deviceKeyId
19589
+ };
19462
19590
  const providerSubject = parseProviderSubject(authority.factor.providerUserId);
19463
19591
  return providerSubject.ok ? {
19464
19592
  kind: "oidc_provider",
@@ -21051,6 +21179,55 @@ async function verifyEcdsaOperationStepUpFactor(input) {
21051
21179
  } : { kind: "not_requested" }
21052
21180
  };
21053
21181
  }
21182
+ case "voiceid": {
21183
+ if (input.operation.operation.operationKind !== "evm.export_key" || input.authSource.kind !== "voiceid" || proof.authority.factor.deviceKeyId !== input.authSource.deviceKeyId) return {
21184
+ ok: false,
21185
+ response: json$3({
21186
+ ok: false,
21187
+ code: "scope_mismatch",
21188
+ message: "VoiceID authority changed"
21189
+ }, { status: 403 })
21190
+ };
21191
+ const verified = await input.voiceId.verifyVoiceIdOperation({
21192
+ walletId: input.walletId,
21193
+ walletAuthMethodId: proof.authority.bindingId,
21194
+ assertion: proof.assertion,
21195
+ operationKind: "evm.export_key",
21196
+ operationChallenge: parseDigestB64u(input.expectedChallenge),
21197
+ requestOrigin: input.requestOrigin,
21198
+ nowMs: Date.now()
21199
+ });
21200
+ if (verified.kind === "rejected" || !walletAuthAuthoritiesMatch(verified.authority, proof.authority)) return {
21201
+ ok: false,
21202
+ response: json$3({
21203
+ ok: false,
21204
+ code: "not_verified",
21205
+ message: "VoiceID operation proof was not verified"
21206
+ }, { status: 401 })
21207
+ };
21208
+ const verifiedAtMs = Date.now();
21209
+ const expiresAtMs = Math.min(input.expiresAtMs, verified.proof.statement.expiresAtMs);
21210
+ if (expiresAtMs <= verifiedAtMs) return expiredStepUpFactor();
21211
+ const factorId = parseAuthFactorId(`voiceid:${proof.authority.factor.deviceKeyId}`);
21212
+ if (!factorId.ok) throw new Error(factorId.error.message);
21213
+ return {
21214
+ ok: true,
21215
+ factor: buildVerifiedWalletOperationVoiceIdFactorResult({
21216
+ tenantId: input.operation.tenantId,
21217
+ principalId: input.operation.principalId,
21218
+ walletId: input.walletId,
21219
+ requestOrigin: input.requestOrigin,
21220
+ audience: input.requestOrigin,
21221
+ factorId: factorId.value,
21222
+ authorityRef: input.authorityRef,
21223
+ operation: input.operation,
21224
+ proof: verified.proof,
21225
+ verifiedAtMs,
21226
+ expiresAtMs
21227
+ }),
21228
+ unseal: { kind: "not_requested" }
21229
+ };
21230
+ }
21054
21231
  default: throw new Error("Unsupported ECDSA operation step-up proof");
21055
21232
  }
21056
21233
  }
@@ -21326,6 +21503,7 @@ async function verifyActiveEcdsaOperationStepUpAuthority(walletAuthMethods, proo
21326
21503
  switch (proof.kind) {
21327
21504
  case "passkey": return await walletAuthMethods.verifyActivePasskeyAuthority(proof.authority);
21328
21505
  case "email_otp": return await walletAuthMethods.verifyActiveEmailOtpAuthority(proof.authority);
21506
+ case "voiceid": return { ok: true };
21329
21507
  default: throw new Error("Unsupported ECDSA operation step-up proof");
21330
21508
  }
21331
21509
  }
@@ -21864,7 +22042,8 @@ async function issueEcdsaOperationStepUpAuthorization(input) {
21864
22042
  expectedChallenge,
21865
22043
  expiresAtMs,
21866
22044
  webAuthn: input.ctx.service.webAuthn,
21867
- emailOtp: input.ctx.service.emailOtp
22045
+ emailOtp: input.ctx.service.emailOtp,
22046
+ voiceId: input.ctx.service.walletUnlock
21868
22047
  });
21869
22048
  if (!verifiedFactor.ok) return verifiedFactor.response;
21870
22049
  const evidenceSet = await input.ctx.service.authorizedOperations.recordVerifiedWalletOperationFactorEvidenceSet({
@@ -39753,7 +39932,7 @@ const ROUTES = Object.freeze([defineRoute({
39753
39932
  auth: {
39754
39933
  plane: "public",
39755
39934
  proof: "challenge_exchange",
39756
- rationale: "Exact-seed export requires fresh passkey assertion or Email OTP factor possession."
39935
+ rationale: "Exact-seed export requires a fresh passkey assertion, Email OTP factor possession or VoiceID device proof."
39757
39936
  },
39758
39937
  metering: { kind: "none" },
39759
39938
  requiredServices: [],
@@ -39839,6 +40018,42 @@ function parseEmailOtpExportAdmissionAuthorization(authorization) {
39839
40018
  }
39840
40019
  };
39841
40020
  }
40021
+ function parseVoiceIdExportAdmissionAuthorization(authorization) {
40022
+ const unexpectedField = firstUnexpectedField(authorization, [
40023
+ "kind",
40024
+ "walletAuthMethodId",
40025
+ "assertion"
40026
+ ]);
40027
+ if (unexpectedField) return {
40028
+ ok: false,
40029
+ message: `VoiceID export authorization has unknown field: ${unexpectedField}`
40030
+ };
40031
+ let assertion;
40032
+ try {
40033
+ assertion = parseVoiceIdOperationAssertion(authorization.assertion);
40034
+ } catch (error) {
40035
+ return {
40036
+ ok: false,
40037
+ message: error instanceof Error ? error.message : "VoiceID export assertion is invalid"
40038
+ };
40039
+ }
40040
+ if (typeof authorization.walletAuthMethodId !== "string" || authorization.walletAuthMethodId !== assertion.statement.walletAuthMethodId) return {
40041
+ ok: false,
40042
+ message: "walletAuthMethodId must name the signing VoiceID method"
40043
+ };
40044
+ if (assertion.statement.operationKind !== "near.export_key") return {
40045
+ ok: false,
40046
+ message: "VoiceID export assertion names another operation"
40047
+ };
40048
+ return {
40049
+ ok: true,
40050
+ value: {
40051
+ kind: "voiceid_device",
40052
+ walletAuthMethodId: authorization.walletAuthMethodId,
40053
+ assertion
40054
+ }
40055
+ };
40056
+ }
39842
40057
  function parseExportAdmissionAuthorization(value) {
39843
40058
  if (!isPlainObject(value)) return {
39844
40059
  ok: false,
@@ -39847,6 +40062,7 @@ function parseExportAdmissionAuthorization(value) {
39847
40062
  switch (value.kind) {
39848
40063
  case "passkey": return parsePasskeyExportAdmissionAuthorization(value);
39849
40064
  case "email_otp_factor": return parseEmailOtpExportAdmissionAuthorization(value);
40065
+ case "voiceid_device": return parseVoiceIdExportAdmissionAuthorization(value);
39850
40066
  default: return {
39851
40067
  ok: false,
39852
40068
  message: "export admission authorization kind is invalid"
@@ -39937,7 +40153,8 @@ var RouterAbEd25519YaoExportRouteExtension = class {
39937
40153
  const result = await this.service.admitExport(parsed.protocol, authorized.authorizationIdentity, authorized.ownershipGeneration, authorized.admission, authorized.factorRelease ?? null, traceContext.value);
39938
40154
  return result.ok ? json$3({
39939
40155
  protocol: result.value,
39940
- ...authorized.factorRelease ? { factorRelease: authorized.factorRelease } : {}
40156
+ ...authorized.factorRelease ? { factorRelease: authorized.factorRelease } : {},
40157
+ ...authorized.custodyRelease ? { voiceIdCustodyEnvelope: authorized.custodyRelease.envelope } : {}
39941
40158
  }, { status: result.status }) : json$3({
39942
40159
  ok: false,
39943
40160
  code: result.code,