Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

passkey connector apis #169

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
passkey connector apis
  • Loading branch information
himanshuchawla009 committed Nov 1, 2024
commit 0c741e5b69cdbc4d94c746998e1e065ecb46d0bc
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
@@ -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;

5 changes: 5 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -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";
82 changes: 57 additions & 25 deletions src/helpers/nodeUtils.ts
Original file line number Diff line number Diff line change
@@ -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,
@@ -368,6 +368,7 @@ export async function retrieveOrImportShare(params: {
extraParams: TorusUtilsExtraParams;
newImportedShares?: ImportedShare[];
checkCommitment?: boolean;
useLinkedPasskey?: boolean;
}): Promise<TorusKey> {
const {
legacyMetadataHost,
@@ -389,6 +390,7 @@ export async function retrieveOrImportShare(params: {
useDkg = true,
serverTimeOffset,
checkCommitment = true,
useLinkedPasskey = false,
} = params;
await get<void>(
allowHost,
@@ -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<
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
@@ -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";
@@ -291,6 +295,7 @@ export interface RetrieveSharesParams {
idToken: string;
nodePubkeys: INodePub[];
extraParams?: TorusUtilsExtraParams;
useLinkedPasskey?: boolean;
useDkg?: boolean;
checkCommitment?: boolean;
}
Loading