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

feat: remote sign refresh #75

Draft
wants to merge 7 commits into
base: feat/enable-signing-nodejs-server
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
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
798 changes: 208 additions & 590 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
"@toruslabs/eccrypto": "4.0.0",
"@toruslabs/fetch-node-details": "^13.0.1",
"@toruslabs/fnd-base": "^13.0.1",
"@toruslabs/metadata-helpers": "^5.x",
"@toruslabs/http-helpers": "^5.0.0",
"@toruslabs/metadata-helpers": "^4.x",
"@toruslabs/openlogin-session-manager": "^3.0.0",
"@toruslabs/torus.js": "^11.0.6",
"@toruslabs/tss-client": "^1.7.1",
Expand All @@ -57,18 +58,19 @@
"@web3auth/base-provider": "^7.0.1",
"bn.js": "^5.2.1",
"bowser": "^2.11.0",
"elliptic": "^6.5.4"
"elliptic": "^6.5.4",
"hi-base32": "^0.5.1"
},
"devDependencies": {
"@babel/register": "^7.22.15",
"@toruslabs/config": "^2.0.2",
"@toruslabs/eslint-config-typescript": "^3.0.1",
"@toruslabs/torus-scripts": "^5.0.5",
"@toruslabs/tss-lib-node": "^1.1.3",
"@types/chai": "^4.3.6",
"@types/elliptic": "^6.4.14",
"@types/node": "^20.6.3",
"@typescript-eslint/eslint-plugin": "^6.7.0",
"@toruslabs/tss-lib-node" : "^1.1.3",
"chai": "^4.3.8",
"cross-env": "^7.0.3",
"dotenv": "^16.3.1",
Expand Down
128 changes: 128 additions & 0 deletions src/helper/authenticator/authenticatorService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { generatePrivate } from "@toruslabs/eccrypto";
import { post } from "@toruslabs/http-helpers";
import { keccak256 } from "@toruslabs/metadata-helpers";
import { log } from "@web3auth/base";
import BN from "bn.js";
import type { ec } from "elliptic";
import base32 from "hi-base32";

import { CURVE } from "../../constants";
import { IRemoteClientState, Web3AuthMPCCoreKit } from "../../index";

export class AuthenticatorService {
private backendUrl: string;

private coreKitInstance: Web3AuthMPCCoreKit;

private authenticatorType: string = "authenticator";

private factorPub: string = "";

private tssIndex: number;

constructor(params: { backendUrl: string; coreKitInstance: Web3AuthMPCCoreKit; authenticatorType?: string }) {
const { backendUrl } = params;
this.backendUrl = backendUrl;
this.authenticatorType = params.authenticatorType || "authenticator";
this.coreKitInstance = params.coreKitInstance;
// this.remoteClient = remoteClient || false;
}

getDescriptionsAndUpdate() {
const arrayOfDescriptions = Object.entries(this.coreKitInstance.getKeyDetails().shareDescriptions).map(([key, value]) => {
const parsedDescription = (value || [])[0] ? JSON.parse(value[0]) : {};
return {
key,
description: parsedDescription,
};
});

const shareDescriptionsMobile = arrayOfDescriptions.find(({ description }) => description.authenticator === this.authenticatorType);
log.info("shareDescriptionsMobile", shareDescriptionsMobile);

if (shareDescriptionsMobile) {
this.factorPub = shareDescriptionsMobile.key;
this.tssIndex = shareDescriptionsMobile.description.tssShareIndex;
}

return shareDescriptionsMobile;
}

generateSecretKey(): string {
const key = generatePrivate().subarray(0, 20);
return base32.encode(key).toString().replace(/=/g, "");
}

async register(privKey: BN, secretKey: string): Promise<{ success: boolean; message?: string }> {
const privKeyPair: ec.KeyPair = CURVE.keyFromPrivate(privKey.toString(16, 64));
const pubKey = privKeyPair.getPublic();
const sig = CURVE.sign(keccak256(Buffer.from(secretKey, "utf8")), Buffer.from(privKey.toString(16, 64), "hex"));

const data = {
pubKey: {
x: pubKey.getX().toString(16, 64),
y: pubKey.getY().toString(16, 64),
},
sig: {
r: sig.r.toString(16, 64),
s: sig.s.toString(16, 64),
v: new BN(sig.recoveryParam as number).toString(16, 2),
},
secretKey,
};

const resp = await post<{
success: boolean;
message: string;
}>(`${this.backendUrl}/api/v1/register`, data);

return resp;
}

async addAuthenticatorRecovery(address: string, code: string, factorKey: BN) {
if (!factorKey) throw new Error("factorKey is not defined");
if (!address) throw new Error("address is not defined");
if (!code) throw new Error("code is not defined");

const data = {
address,
code,
data: {
// If the verification is complete, we save the factorKey for the user address.
// This factorKey is used to verify the user in the future on a new device and recover tss share.
factorKey: factorKey.toString(16, 64),
},
};

await post(`${this.backendUrl}/api/v1/verify`, data);
}

async verifyAuthenticatorRecovery(address: string, code: string): Promise<BN | undefined> {
const verificationData = {
address,
code,
};

const response = await post<{ data?: Record<string, string> }>(`${this.backendUrl}/api/v1/verify`, verificationData);
const { data } = response;
return data ? new BN(data.factorKey, "hex") : undefined;
}

async verifyRemoteSetup(address: string, code: string): Promise<IRemoteClientState & { tssShareIndex: string }> {
const verificationData = {
address,
code,
};

const response = await post<{ data?: Record<string, string> }>(`${this.backendUrl}/api/v1/verify_remote`, verificationData);
const { data } = response;

return {
tssShareIndex: this.tssIndex.toString(),
remoteClientUrl: this.backendUrl,
remoteFactorPub: this.factorPub,
metadataShare: data.metadataShare,
remoteClientToken: data.signature,
};
}
}
140 changes: 140 additions & 0 deletions src/helper/authenticator/smsService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { post } from "@toruslabs/http-helpers";
import { keccak256 } from "@toruslabs/metadata-helpers";
import { log } from "@web3auth/base";
import BN from "bn.js";
import type { ec } from "elliptic";

import { CURVE } from "../../constants";
import { IRemoteClientState } from "../../interfaces";
import { Web3AuthMPCCoreKit } from "../../mpcCoreKit";

export class SmsService {
private backendUrl: string;

private coreKitInstance: Web3AuthMPCCoreKit;

private authenticatorType: string = "sms";

private factorPub: string = "";

private tssIndex: number;

constructor(params: { backendUrl: string; coreKitInstance: Web3AuthMPCCoreKit; authenticatorType?: string }) {
const { backendUrl } = params;
this.backendUrl = backendUrl;
this.authenticatorType = params.authenticatorType || "sms";
this.coreKitInstance = params.coreKitInstance;
this.getDescriptionsAndUpdate();
}

getDescriptionsAndUpdate() {
const arrayOfDescriptions = Object.entries(this.coreKitInstance.getKeyDetails().shareDescriptions).map(([key, value]) => {
const parsedDescription = (value || [])[0] ? JSON.parse(value[0]) : {};
return {
key,
description: parsedDescription,
};
});

const shareDescriptionsMobile = arrayOfDescriptions.find(({ description }) => description.authenticator === this.authenticatorType);
log.info("shareDescriptionsMobile", shareDescriptionsMobile);

if (shareDescriptionsMobile) {
this.factorPub = shareDescriptionsMobile.key;
this.tssIndex = shareDescriptionsMobile.description.tssShareIndex;
}

return shareDescriptionsMobile;
}

async registerSmsOTP(privKey: BN, number: string): Promise<string | undefined> {
const privKeyPair: ec.KeyPair = CURVE.keyFromPrivate(privKey.toString(16, 64));
const pubKey = privKeyPair.getPublic();
const sig = CURVE.sign(keccak256(Buffer.from(number, "utf8")), Buffer.from(privKey.toString(16, 64), "hex"));

const data = {
pubKey: {
x: pubKey.getX().toString(16, 64),
y: pubKey.getY().toString(16, 64),
},
sig: {
r: sig.r.toString(16, 64),
s: sig.s.toString(16, 64),
v: new BN(sig.recoveryParam as number).toString(16, 2),
},
number,
};

await post<{
success: boolean;
id_token?: string;
message: string;
}>(`${this.backendUrl}/api/v1/register`, data);

// this is to send sms to the user instantly after registration.
const startData = {
address: `${pubKey.getX().toString(16, 64)}${pubKey.getY().toString(16, 64)}`,
};

// Sends the user sms.
const resp2 = await post<{ success: boolean; code?: string }>(`${this.backendUrl}/api/v1/start`, startData);
// if (resp2.status !== 200) throw new Error("Error sending sms");
return resp2.code;
}

async addSmsRecovery(address: string, code: string, factorKey: BN) {
if (!factorKey) throw new Error("factorKey is not defined");
if (!address) throw new Error("address is not defined");

const data = {
address,
code,
data: {
// If the verification is complete, we save the factorKey for the user address.
// This factorKey is used to verify the user in the future on a new device and recover tss share.
factorKey: factorKey.toString(16, 64),
},
};

await post(`${this.backendUrl}/api/v1/verify`, data);
}

async requestSMSOTP(address: string): Promise<string | undefined> {
const startData = {
address,
};
const resp2 = await post<{ success?: boolean; code?: string }>(`${this.backendUrl}/api/v1/start`, startData);
// eslint-disable-next-line no-console
console.log(resp2);
return resp2.code;
}

async verifySMSOTPRecovery(address: string, code: string): Promise<BN | undefined> {
const verificationData = {
address,
code,
};

const response = await post<{ data?: Record<string, string> }>(`${this.backendUrl}/api/v1/verify`, verificationData);
const { data } = response;
return data ? new BN(data.factorKey, "hex") : undefined;
}

async verifyRemoteSetup(address: string, code: string): Promise<IRemoteClientState & { tssShareIndex: string }> {
const verificationData = {
address,
code,
};

const response = await post<{ data?: Record<string, string> }>(`${this.backendUrl}/api/v1/verify_remote`, verificationData);
const { data } = response;

return {
tssShareIndex: this.tssIndex.toString(),
remoteClientUrl: this.backendUrl,
remoteFactorPub: this.factorPub,
metadataShare: data.metadataShare,
remoteClientToken: data.signature,
};
}
}
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export * from "./constants";
export * from "./helper";
export * from "./helper/authenticator/authenticatorService";
export * from "./helper/authenticator/smsService";
export * from "./interfaces";
export * from "./mpcCoreKit";
export * from "./point";
Expand Down
9 changes: 9 additions & 0 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,13 +117,21 @@ export interface IdTokenLoginParams {
additionalParams?: ExtraParams;
}

export interface IRemoteClientState {
remoteFactorPub: string;
remoteClientUrl: string;
remoteClientToken: string;
metadataShare: string;
}

export interface Web3AuthState {
oAuthKey?: string;
signatures?: string[];
userInfo?: UserInfo;
tssShareIndex?: number;
tssPubKey?: Buffer;
factorKey?: BN;
remoteClient?: IRemoteClientState;
}

export interface ICoreKit {
Expand Down Expand Up @@ -359,6 +367,7 @@ export interface SessionData {
tssPubKey: string;
signatures: string[];
userInfo: UserInfo;
remoteClient?: IRemoteClientState;
}

export interface TkeyLocalStoreData {
Expand Down
Loading