@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.
- package/README.md +4 -4
- package/artifact-manifest.json +11 -11
- 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/factorEvidence.js +37 -1
- package/dist/esm/authorization/voiceIdDeviceProof.js +32 -6
- package/dist/esm/hosted-wallet-gateway.js +1 -1
- package/dist/esm/router/cloudflare/d1/auth/d1RouterApiAuthService.js +1 -0
- package/dist/esm/router/cloudflare/d1/wallet/d1VoiceIdSessions.js +34 -1
- package/dist/esm/router/cloudflare.js +317 -100
- package/dist/esm/router/domains/ed25519Yao/export/routerAbEd25519YaoExport.js +181 -14
- package/dist/esm/router/domains/ed25519Yao/export/routerAbEd25519YaoExportRequestScopedCloudflare.js +21 -5
- package/dist/esm/router/domains/signingOperations/routerAbOperationStepUp.js +4 -0
- package/dist/esm/router/transport/fetch/routes/ecdsaOperationStepUpFactor.js +51 -1
- package/dist/esm/router/transport/fetch/routes/thresholdEcdsa.js +3 -1
- package/dist/esm/shared-ts/src/authorization/capabilityKinds.js +2 -1
- package/dist/esm/shared-ts/src/authorization/voiceIdDeviceAssertion.js +70 -1
- package/dist/esm/shared-ts/src/utils/routerAbEcdsaDerivation.js +18 -1
- package/dist/esm/shared-ts/src/utils/routerAbEd25519YaoDigests.js +8 -1
- package/dist/types/shared-ts/src/authorization/capabilityKinds.d.ts +2 -1
- package/dist/types/shared-ts/src/authorization/voiceIdDeviceAssertion.d.ts +30 -0
- package/dist/types/shared-ts/src/utils/routerAbEcdsaDerivation.d.ts +11 -1
- package/dist/types/shared-ts/src/utils/routerAbEd25519YaoDigests.d.ts +3 -0
- package/dist/types/wallet-server/src/authorization/domain.d.ts +3 -0
- package/dist/types/wallet-server/src/authorization/factorEvidence.d.ts +11 -1
- package/dist/types/wallet-server/src/authorization/voiceIdDeviceProof.d.ts +27 -1
- package/dist/types/wallet-server/src/router/cloudflare/d1/wallet/d1VoiceIdSessions.d.ts +3 -1
- package/dist/types/wallet-server/src/router/domains/ed25519Yao/export/routerAbEd25519YaoExport.d.ts +36 -2
- package/dist/types/wallet-server/src/router/domains/voiceId/voiceIdSessions.d.ts +23 -1
- package/dist/types/wallet-server/src/router/framework/authServicePort.d.ts +1 -0
- package/dist/types/wallet-server/src/router/transport/fetch/routes/ecdsaOperationStepUpFactor.d.ts +2 -1
- 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
|
|
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,
|