Skip to content

Commit

Permalink
passkey connector apis
Browse files Browse the repository at this point in the history
  • Loading branch information
himanshuchawla009 committed Nov 1, 2024
1 parent bfaa56e commit 0c741e5
Show file tree
Hide file tree
Showing 7 changed files with 350 additions and 31 deletions.
13 changes: 8 additions & 5 deletions src/TorusUtilsExtraParams.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
export interface TorusUtilsExtraParams {
nonce?: string; // farcaster

message?: string; // farcaster

export interface TorusUtilsPasskeyExtraParams {
signature?: string; // farcaster, passkey, webauthn

clientDataJson?: string; // passkey, webauthn
Expand All @@ -16,6 +12,13 @@ export interface TorusUtilsExtraParams {
rpOrigin?: string; // passkey, webauthn

rpId?: string; // passkey, webauthn
}
export interface TorusUtilsExtraParams extends TorusUtilsPasskeyExtraParams {
nonce?: string; // farcaster

message?: string; // farcaster

signature?: string; // farcaster, passkey, webauthn

session_token_exp_second?: number;

Expand Down
5 changes: 5 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ export const JRPC_METHODS = {
COMMITMENT_REQUEST: "CommitmentRequest",
IMPORT_SHARES: "ImportShares",
GET_SHARE_OR_KEY_ASSIGN: "GetShareOrKeyAssign",
RETRIEVE_SHARES_WITH_LINKED_PASSKEY: "RetrieveSharesWithLinkedPasskey",
GENERATE_AUTH_MESSAGE: "GenerateAuthMessage",
LINK_PASSKEY: "LinkPasskey",
UNLINK_PASSKEY: "UnlinkPasskey",
GET_LINKED_PASSKEYS: "GetLinkedPasskeys",
};

export const SAPPHIRE_METADATA_URL = "https://node-1.node.web3auth.io/metadata";
Expand Down
82 changes: 57 additions & 25 deletions src/helpers/nodeUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
} from "../interfaces";
import log from "../loglevel";
import { Some } from "../some";
import { TorusUtilsExtraParams } from "../TorusUtilsExtraParams";
import { TorusUtilsExtraParams, TorusUtilsPasskeyExtraParams } from "../TorusUtilsExtraParams";
import {
calculateMedian,
generatePrivateKey,
Expand Down Expand Up @@ -368,6 +368,7 @@ export async function retrieveOrImportShare(params: {
extraParams: TorusUtilsExtraParams;
newImportedShares?: ImportedShare[];
checkCommitment?: boolean;
useLinkedPasskey?: boolean;
}): Promise<TorusKey> {
const {
legacyMetadataHost,
Expand All @@ -389,6 +390,7 @@ export async function retrieveOrImportShare(params: {
useDkg = true,
serverTimeOffset,
checkCommitment = true,
useLinkedPasskey = false,
} = params;
await get<void>(
allowHost,
Expand Down Expand Up @@ -517,33 +519,63 @@ export async function retrieveOrImportShare(params: {
promiseArrRequest.push(p);
} else {
for (let i = 0; i < endpoints.length; i += 1) {
const p = post<JRPCResponse<ShareRequestResult>>(
endpoints[i],
generateJsonRPCObject(JRPC_METHODS.GET_SHARE_OR_KEY_ASSIGN, {
encrypted: "yes",
use_temp: true,
key_type: keyType,
distributed_metadata: true,
verifieridentifier: verifier,
temppubx: nodeSigs.length === 0 && !checkCommitment ? sessionPubX : "", // send session pub key x only if node signatures are not available (Ie. in non commitment flow)
temppuby: nodeSigs.length === 0 && !checkCommitment ? sessionPubY : "", // send session pub key y only if node signatures are not available (Ie. in non commitment flow)
item: [
{
if (useLinkedPasskey) {
const passkeyExtraParams = { ...extraParams } as TorusUtilsPasskeyExtraParams;
const p = post<JRPCResponse<ShareRequestResult>>(
endpoints[i],
generateJsonRPCObject(JRPC_METHODS.RETRIEVE_SHARES_WITH_LINKED_PASSKEY, {
encrypted: "yes",
use_temp: true,
key_type: keyType,
distributed_metadata: true,
verifier,
passkey_pub_key: verifierParams.verifier_id,
temp_pub_x: nodeSigs.length === 0 && !checkCommitment ? sessionPubX : "", // send session pub key x only if node signatures are not available (Ie. in non commitment flow)
temppuby: nodeSigs.length === 0 && !checkCommitment ? sessionPubY : "", // send session pub key y only if node signatures are not available (Ie. in non commitment flow)
passkey_auth_data: {
...verifierParams,
idtoken: idToken,
id_token: idToken,
key_type: keyType,
nodesignatures: nodeSigs,
verifieridentifier: verifier,
...extraParams,
node_signatures: nodeSigs,
verifier,
...passkeyExtraParams,
},
],
client_time: Math.floor(Date.now() / 1000).toString(),
one_key_flow: true,
}),
{},
{ logTracingHeader: config.logRequestTracing }
);
promiseArrRequest.push(p);
client_time: Math.floor(Date.now() / 1000).toString(),
one_key_flow: true,
}),
{},
{ logTracingHeader: config.logRequestTracing }
);
promiseArrRequest.push(p);
} else {
const p = post<JRPCResponse<ShareRequestResult>>(
endpoints[i],
generateJsonRPCObject(JRPC_METHODS.GET_SHARE_OR_KEY_ASSIGN, {
encrypted: "yes",
use_temp: true,
key_type: keyType,
distributed_metadata: true,
verifieridentifier: verifier,
temppubx: nodeSigs.length === 0 && !checkCommitment ? sessionPubX : "", // send session pub key x only if node signatures are not available (Ie. in non commitment flow)
temppuby: nodeSigs.length === 0 && !checkCommitment ? sessionPubY : "", // send session pub key y only if node signatures are not available (Ie. in non commitment flow)
item: [
{
...verifierParams,
idtoken: idToken,
key_type: keyType,
nodesignatures: nodeSigs,
verifieridentifier: verifier,
...extraParams,
},
],
client_time: Math.floor(Date.now() / 1000).toString(),
one_key_flow: true,
}),
{},
{ logTracingHeader: config.logRequestTracing }
);
promiseArrRequest.push(p);
}
}
}
return Some<
Expand Down
221 changes: 221 additions & 0 deletions src/helpers/passkeyConnectorUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
import { generateJsonRPCObject, post } from "@toruslabs/http-helpers";

import { config } from "../config";
import { JRPC_METHODS } from "../constants";
import { AuthMessageRequestResult, JRPCResponse } from "../interfaces";
import {
GetAuthMessageFromNodesParams,
LinkPasskeyParams,
ListLinkedPasskeysParams,
ListLinkedPasskeysResponse,
PasskeyListItem,
UnLinkPasskeyParams,
} from "../passkeyConnectorInterfaces";
import { Some } from "../some";
export const getAuthMessageFromNodes = (params: GetAuthMessageFromNodesParams) => {
const { verifier, verifierId, passkeyPubKey, endpoints } = params;
const threeFourthsThreshold = ~~((endpoints.length * 3) / 4) + 1;

if (!verifierId && !passkeyPubKey) {
throw new Error("Verifier ID or passkey pub key is required");
}
const promiseArr: Promise<JRPCResponse<AuthMessageRequestResult>>[] = [];
for (let i = 0; i < endpoints.length; i++) {
const p = post<JRPCResponse<AuthMessageRequestResult>>(
endpoints[i],
generateJsonRPCObject(JRPC_METHODS.GENERATE_AUTH_MESSAGE, {
verifier,
verifier_id: verifierId,
passkey_pub_key: passkeyPubKey,
}),
{},
{ logTracingHeader: config.logRequestTracing }
);
promiseArr.push(p);
}

return new Promise<JRPCResponse<AuthMessageRequestResult>[]>((resolve, reject) => {
Some<null | JRPCResponse<AuthMessageRequestResult>, (null | JRPCResponse<AuthMessageRequestResult>)[]>(promiseArr, (resultArr) => {
const completedRequests = resultArr.filter((x) => {
if (!x || typeof x !== "object") {
return false;
}
if (x.error) {
return false;
}
return true;
});
if (completedRequests.length >= threeFourthsThreshold) {
return Promise.resolve(completedRequests);
}
return Promise.reject(new Error("Failed to get auth message from threshold number of nodes"));
})
.then((resultArr: JRPCResponse<AuthMessageRequestResult>[]) => {
return resolve(resultArr);
})
.catch(reject);
});
};

export const linkPasskey = async (params: LinkPasskeyParams) => {
const { endpoints, message, label, passkeyPubKey, oAuthKeySignature, keyType, passkeyAuthData } = params;
const halfThreshold = ~~(endpoints.length / 2) + 1;

if (!endpoints || endpoints.length === 0) {
throw new Error("Endpoints are required");
}

const promiseArr: Promise<JRPCResponse<Record<string, never>>>[] = [];
for (let i = 0; i < endpoints.length; i++) {
const p = post<JRPCResponse<Record<string, never>>>(
endpoints[i],
generateJsonRPCObject(JRPC_METHODS.LINK_PASSKEY, {
message,
label,
passkey_pub_key: passkeyPubKey,
verifier_account_signature: oAuthKeySignature,
key_type: keyType,
passkey_auth_data: passkeyAuthData,
}),
{},
{ logTracingHeader: config.logRequestTracing }
);
promiseArr.push(p);
}

return new Promise<JRPCResponse<Record<string, never>>[]>((resolve, reject) => {
Some<null | JRPCResponse<Record<string, never>>, (null | JRPCResponse<Record<string, never>>)[]>(promiseArr, (resultArr) => {
const completedRequests = resultArr.filter((x) => {
if (!x || typeof x !== "object") {
return false;
}
if (x.error) {
return false;
}
return true;
});
if (completedRequests.length >= halfThreshold) {
return Promise.resolve(completedRequests);
}
return Promise.reject(new Error("Failed to get auth message from threshold number of nodes"));
})
.then((resultArr: JRPCResponse<Record<string, never>>[]) => {
return resolve(resultArr);
})
.catch(reject);
});
};

export const UnlinkPasskey = async (params: UnLinkPasskeyParams) => {
const { endpoints, message, passkeyPubKey, oAuthKeySignature, keyType } = params;
const halfThreshold = ~~(endpoints.length / 2) + 1;

if (!endpoints || endpoints.length === 0) {
throw new Error("Endpoints are required");
}

const promiseArr: Promise<JRPCResponse<Record<string, never>>>[] = [];
for (let i = 0; i < endpoints.length; i++) {
const p = post<JRPCResponse<Record<string, never>>>(
endpoints[i],
generateJsonRPCObject(JRPC_METHODS.UNLINK_PASSKEY, {
message,
passkey_pub_key: passkeyPubKey,
verifier_account_signature: oAuthKeySignature,
key_type: keyType,
}),
{},
{ logTracingHeader: config.logRequestTracing }
);
promiseArr.push(p);
}

return new Promise<JRPCResponse<Record<string, never>>[]>((resolve, reject) => {
Some<null | JRPCResponse<Record<string, never>>, (null | JRPCResponse<Record<string, never>>)[]>(promiseArr, (resultArr) => {
const completedRequests = resultArr.filter((x) => {
if (!x || typeof x !== "object") {
return false;
}
if (x.error) {
return false;
}
return true;
});
if (completedRequests.length >= halfThreshold) {
return Promise.resolve(completedRequests);
}
return Promise.reject(new Error("Failed to get auth message from threshold number of nodes"));
})
.then((resultArr: JRPCResponse<Record<string, never>>[]) => {
return resolve(resultArr);
})
.catch(reject);
});
};

export const ListLinkedPasskey = async (params: ListLinkedPasskeysParams) => {
const { endpoints, message, oAuthKeySignature, keyType } = params;
const halfThreshold = ~~(endpoints.length / 2) + 1;

if (!endpoints || endpoints.length === 0) {
throw new Error("Endpoints are required");
}

const promiseArr: Promise<JRPCResponse<ListLinkedPasskeysResponse>>[] = [];
for (let i = 0; i < endpoints.length; i++) {
const p = post<JRPCResponse<ListLinkedPasskeysResponse>>(
endpoints[i],
generateJsonRPCObject(JRPC_METHODS.GET_LINKED_PASSKEYS, {
message,
verifier_account_signature: oAuthKeySignature,
key_type: keyType,
}),
{},
{ logTracingHeader: config.logRequestTracing }
);
promiseArr.push(p);
}

return new Promise<PasskeyListItem[]>((resolve, reject) => {
Some<null | JRPCResponse<ListLinkedPasskeysResponse>, PasskeyListItem[]>(promiseArr, (resultArr) => {
const completedRequests = resultArr.filter((x) => {
if (!x || typeof x !== "object") {
return false;
}
if (x.error) {
return false;
}
return true;
});
if (completedRequests.length >= halfThreshold) {
// find all passkeys object which have same passkey_pub_key inside each complated request passkeys array object
// Find passkeys that appear in at least halfThreshold number of responses
const passkeyMap = new Map<string, { count: number; passkey: PasskeyListItem }>();

// Count occurrences of each passkey by pub_key
completedRequests.forEach((request) => {
request.result.passkeys.forEach((passkey) => {
const existing = passkeyMap.get(passkey.passkey_pub_key);
if (existing) {
existing.count++;
} else {
passkeyMap.set(passkey.passkey_pub_key, { count: 1, passkey });
}
});
});

// Filter passkeys that meet threshold requirement
const result = Array.from(passkeyMap.values())
.filter((item) => item.count >= halfThreshold)
.map((item) => item.passkey);

return Promise.resolve(result);
}
return Promise.reject(new Error("Failed to get auth message from threshold number of nodes"));
})
.then((resultArr: PasskeyListItem[]) => {
return resolve(resultArr);
})
.catch(reject);
});
};
5 changes: 5 additions & 0 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ export interface CommitmentRequestResult {
nodeindex: string;
pub_key_x: string;
}

export interface AuthMessageRequestResult {
message: string;
}
export interface JRPCResponse<T> {
id: number;
jsonrpc: "2.0";
Expand Down Expand Up @@ -291,6 +295,7 @@ export interface RetrieveSharesParams {
idToken: string;
nodePubkeys: INodePub[];
extraParams?: TorusUtilsExtraParams;
useLinkedPasskey?: boolean;
useDkg?: boolean;
checkCommitment?: boolean;
}
Loading

0 comments on commit 0c741e5

Please sign in to comment.