Skip to content

Commit

Permalink
feat: working remote sign for authenticator
Browse files Browse the repository at this point in the history
  • Loading branch information
ieow committed Oct 29, 2024
1 parent 427a9d3 commit eab02d5
Show file tree
Hide file tree
Showing 9 changed files with 266 additions and 190 deletions.
45 changes: 44 additions & 1 deletion src/helper/browserStorage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { IAsyncStorage, IStorage } from "../interfaces";
import { TKeyTSS } from "@tkey/tss";
import BN from "bn.js";

import { FIELD_ELEMENT_HEX_LEN } from "../constants";
import { IAsyncStorage, IStorage, TkeyLocalStoreData } from "../interfaces";
import CoreKitError from "./errors";

export class MemoryStorage implements IStorage {
Expand Down Expand Up @@ -66,3 +70,42 @@ export class AsyncStorage {
await this.storage.setItem(this._storeKey, JSON.stringify(store));
}
}

export class DeviceStorage {
private tKey: TKeyTSS;

private currentStorage: AsyncStorage;

constructor(tkeyInstance: TKeyTSS, currentStorage: AsyncStorage) {
this.tKey = tkeyInstance;
this.currentStorage = currentStorage;
}

// device factor
async setDeviceFactor(factorKey: BN, replace = false): Promise<void> {
if (!replace) {
const existingFactor = await this.getDeviceFactor();
if (existingFactor) {
throw CoreKitError.default("Device factor already exists");
}
}

const metadata = this.tKey.getMetadata();
const tkeyPubX = metadata.pubKey.x.toString(16, FIELD_ELEMENT_HEX_LEN);
await this.currentStorage.set(
tkeyPubX,
JSON.stringify({
factorKey: factorKey.toString("hex").padStart(64, "0"),
} as TkeyLocalStoreData)
);
}

async getDeviceFactor(): Promise<string | undefined> {
const metadata = this.tKey.getMetadata();

const tkeyPubX = metadata.pubKey.x.toString(16, FIELD_ELEMENT_HEX_LEN);
const tKeyLocalStoreString = await this.currentStorage.get<string>(tkeyPubX);
const tKeyLocalStore = JSON.parse(tKeyLocalStoreString || "{}") as TkeyLocalStoreData;
return tKeyLocalStore.factorKey;
}
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ export * from "./constants";
export * from "./helper";
export * from "./interfaces";
export * from "./mpcCoreKit";
export * from "./remoteFactorServices";
export * from "./utils";
export { factorKeyCurve } from "@tkey/tss";
5 changes: 3 additions & 2 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import type { tssLib as TssFrostLib } from "@toruslabs/tss-frost-lib";
import BN from "bn.js";

import { FactorKeyTypeShareDescription, TssShareType, USER_PATH, WEB3AUTH_NETWORK } from "./constants";
import { IRemoteClientState } from "./remoteSignInterfaces";
import { IRemoteClientState } from "./remoteFactorServices/remoteSignInterfaces";

export type CoreKitMode = UX_MODE_TYPE | "nodejs" | "react-native";

Expand Down Expand Up @@ -100,7 +100,7 @@ export interface EnableMFAParams {
/**
* Additional metadata information you want to be stored alongside this factor for easy identification.
*/
additionalMetadata?: Record<string, string>;
additionalMetadata?: Record<string, string | number>;
}

export interface CreateFactorParams extends EnableMFAParams {
Expand Down Expand Up @@ -443,6 +443,7 @@ export interface SessionData {
tssPubKey: string;
signatures: string[];
userInfo: UserInfo;
remoteClientState?: IRemoteClientState;
}

export interface TkeyLocalStoreData {
Expand Down
63 changes: 47 additions & 16 deletions src/mpcCoreKit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ import {
Web3AuthOptionsWithDefaults,
Web3AuthState,
} from "./interfaces";
import { IRemoteClientState, RefreshRemoteTssReturnType } from "./remoteSignInterfaces";
import { IRemoteClientState, RefreshRemoteTssReturnType } from "./remoteFactorServices/remoteSignInterfaces";
import {
deriveShareCoefficients,
ed25519,
Expand Down Expand Up @@ -617,7 +617,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
}

return this.atomicSync(async () => {
if (this.state.remoteClient && !this.state.factorKey) {
if (this.state.remoteClient) {
if (shareType === this.state.tssShareIndex) {
await this.remoteCopyFactorPub(factorPub, shareType);
} else {
Expand All @@ -627,7 +627,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
await this.copyOrCreateShare(shareType, factorPub);
}
await this.backupMetadataShare(factorKey);
await this.addFactorDescription(factorKey, shareDescription, additionalMetadata);
await this.addFactorDescription(factorKey, shareDescription, additionalMetadata, false);

return scalarBNToBufferSEC1(factorKey).toString("hex");
}).catch((reason: Error) => {
Expand Down Expand Up @@ -667,7 +667,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
public async sign(data: Buffer, hashed: boolean = false): Promise<Buffer> {
this.wasmLib = await this.loadTssWasm();
if (this.keyType === KeyType.secp256k1) {
if (this.state.remoteClient && !this.state.factorKey) {
if (this.state.remoteClient) {
const sig = await this.remoteSignSecp256k1(data, hashed);
return Buffer.concat([sig.r, sig.s, Buffer.from([sig.v])]);
}
Expand Down Expand Up @@ -699,7 +699,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
throw CoreKitError.factorInUseCannotBeDeleted("Cannot delete current active factor");
}

if (this.state.remoteClient && !this.state.factorKey) {
if (this.state.remoteClient) {
await this.remoteDeleteFactorPub(factorPub);
} else {
await this.tKey.deleteFactorPub({ factorKey: this.state.factorKey, deleteFactorPub: factorPub, authSignatures: this.signatures });
Expand Down Expand Up @@ -868,8 +868,15 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
}
}

async setupRemoteSigning(params: IRemoteClientState): Promise<Promise<void>> {
const { remoteClientUrl, remoteFactorPub, metadataShare, remoteClientToken, tssShareIndex } = params;
async setupRemoteSigning(params: Omit<IRemoteClientState, "tssShareIndex">): Promise<void> {
const { remoteClientUrl, remoteFactorPub, metadataShare, remoteClientToken } = params;
const details = this.getKeyDetails().shareDescriptions[remoteFactorPub];
if (!details) throw CoreKitError.default("factor description not found");

const parsedDescription = (details || [])[0] ? JSON.parse(details[0]) : {};
const { tssShareIndex } = parsedDescription;

if (!tssShareIndex) throw CoreKitError.default("tss share index not found");

const remoteClient: IRemoteClientState = {
remoteClientUrl: remoteClientUrl.at(-1) === "/" ? remoteClientUrl.slice(0, -1) : remoteClientUrl,
Expand All @@ -880,16 +887,15 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
};

const sharestore = ShareStore.fromJSON(JSON.parse(metadataShare));
this.tkey.inputShareStoreSafe(sharestore);
await this.tkey.inputShareStoreSafe(sharestore);
await this.tKey.reconstructKey();
const tssPubKey = this.tKey.getTSSPub().toSEC1(this.tkey.tssCurve, false);
// setup Tkey
// const tssPubKey = Point.fromTkeyPoint(this.tKey.getTSSPub()).toBufferSEC1(false);
this.updateState({ tssShareIndex, tssPubKey, remoteClient });

// // Finalize setup.
// setup provider
await this.createSession();
await this.createSessionRemoteClient();
}

/**
Expand Down Expand Up @@ -942,7 +948,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit {

const result = (
await post<{ data: RefreshRemoteTssReturnType }>(
`${remoteClient.remoteClientUrl}/api/mpc/refresh_tss`,
`${remoteClient.remoteClientUrl}/api/v3/mpc/refresh_tss`,
{ dataRequired },
{
headers: {
Expand Down Expand Up @@ -973,7 +979,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit {

const result = (
await post<{ data?: EncryptedMessage }>(
`${this.state.remoteClient.remoteClientUrl}/api/mpc/copy_tss_share`,
`${this.state.remoteClient.remoteClientUrl}/api/v3/mpc/copy_tss_share`,
{ dataRequired },
{
headers: {
Expand Down Expand Up @@ -1084,7 +1090,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
msgHash: msgData.toString("hex"),
};

const result = await post<{ data?: Record<string, string> }>(`${this.state.remoteClient.remoteClientUrl}/api/mpc/sign`, data, {
const result = await post<{ data?: Record<string, string> }>(`${this.state.remoteClient.remoteClientUrl}/api/v3/mpc/sign`, data, {
headers: {
Authorization: `Bearer ${this.state.remoteClient.remoteClientToken}`,
},
Expand All @@ -1104,6 +1110,31 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
}
}

private async createSessionRemoteClient() {
try {
const sessionId = SessionManager.generateRandomSessionKey();
this.sessionManager.sessionId = sessionId;
const { postBoxKey, userInfo, tssShareIndex, tssPubKey } = this.state;
if (!postBoxKey || !tssPubKey || !userInfo) {
throw CoreKitError.userNotLoggedIn();
}
const payload: SessionData = {
postBoxKey,
factorKey: "",
tssShareIndex: tssShareIndex as number,
tssPubKey: Buffer.from(tssPubKey).toString("hex"),
signatures: this.signatures,
userInfo,
remoteClientState: this.state.remoteClient,
};
await this.sessionManager.createSession(payload);
// to accommodate async storage
await this.currentStorage.set("sessionId", sessionId);
} catch (err) {
log.error("error creating session", err);
}
}

private async importTssKey(tssKey: string, factorPub: Point, newTSSIndex: TssShareType = TssShareType.DEVICE): Promise<void> {
if (!this.state.signatures) {
throw CoreKitError.signaturesNotPresent("Signatures not present in state when importing tss key.");
Expand Down Expand Up @@ -1182,9 +1213,9 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
// Store factor description.
await this.backupMetadataShare(factorKey);
if (this.options.disableHashedFactorKey) {
await this.addFactorDescription(factorKey, FactorKeyTypeShareDescription.Other);
await this.addFactorDescription(factorKey, FactorKeyTypeShareDescription.Other, undefined, false);
} else {
await this.addFactorDescription(factorKey, FactorKeyTypeShareDescription.HashedShare);
await this.addFactorDescription(factorKey, FactorKeyTypeShareDescription.HashedShare, undefined, false);
}
});
}
Expand Down Expand Up @@ -1400,7 +1431,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
private async addFactorDescription(
factorKey: BN,
shareDescription: FactorKeyTypeShareDescription,
additionalMetadata: Record<string, string> = {},
additionalMetadata: Record<string, string | number> = {},
updateMetadata = true
) {
const { tssIndex } = await this.tKey.getTSSShare(factorKey);
Expand Down
121 changes: 121 additions & 0 deletions src/remoteFactorServices/authenticator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { Point, secp256k1 } from "@tkey/common-types";
import { generatePrivateBN } from "@tkey/core";
import { post } from "@toruslabs/http-helpers";
import { keccak256 } from "@toruslabs/metadata-helpers";
import BN from "bn.js";
import base32 from "hi-base32";

import { WEB3AUTH_NETWORK_TYPE, Web3AuthMPCCoreKit } from "../";
import { IRemoteClientState } from "./remoteSignInterfaces";

// todo, to replace with ICorekit
export function registerAuthenticatorService(_params: { backendUrl: string; secret: string; corekitInstance: Web3AuthMPCCoreKit }) {
// corekitInstance create factor
// sign secret with the corekit
// AuthenticatorService register
}

// generate secret key for authenticator
export function generateSecretKey(): string {
const key = generatePrivateBN().toArray().slice(0, 20);
return base32.encode(key).toString().replace(/=/g, "");
}

export class AuthenticatorService {
public factorKey?: BN;

// publickey
public factorPub?: string;

private backendUrl: string;

private web3authNetwork: WEB3AUTH_NETWORK_TYPE;

private metadataUrl?: string;

constructor(params: { backendUrl: string; web3authNetwork: WEB3AUTH_NETWORK_TYPE; overrideMetadataUrl?: string }) {
const { backendUrl } = params;
this.backendUrl = backendUrl;
this.web3authNetwork = params.web3authNetwork;
this.metadataUrl = params.overrideMetadataUrl;
}

async register(secretKey: string, factorKey: string): Promise<{ success: boolean; message?: string }> {
// get pubkey
const privKeyPair = secp256k1.keyFromPrivate(factorKey);
const pubKey = privKeyPair.getPublic();
const point = Point.fromElliptic(pubKey);
// sign secret
const sig = secp256k1.sign(keccak256(Buffer.from(secretKey, "utf8")), privKeyPair.getPrivate().toBuffer());

const data = {
address: point.toSEC1(secp256k1, true).toString("hex"),
sig: {
r: sig.r.toString("hex").padStart(64, "0"),
s: sig.s.toString("hex").padStart(64, "0"),
v: new BN(sig.recoveryParam).toString(16, 2),
},
secretKey,
};

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

this.factorKey = new BN(factorKey, "hex");
this.factorPub = point.toSEC1(secp256k1, true).toString("hex");

return resp;
}

async verifyRegistration(code: string) {
if (!this.factorKey) throw new Error("factorKey is not defined");
if (!this.factorPub) throw new Error("address is not defined");
if (!code) throw new Error("code is not defined");

const data = {
address: this.factorPub.replaceAll("0x", ""),
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: this.factorKey.toString(16, 64),
},
};

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

// Verify the mfa code and return the factorKey
async verifyAuthenticatorRecovery(factorPub: string, code: string): Promise<BN | undefined> {
const verificationData = {
address: factorPub,
code,
};

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

// verify the mfa code and return the remote client setyp params
async verifyRemoteSetup(factorPub: string, code: string): Promise<Omit<IRemoteClientState, "tssShareIndex">> {
const verificationData = {
address: factorPub,
code,
web3authNetwork: this.web3authNetwork,
metadataUrl: this.metadataUrl,
};

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

return {
remoteClientUrl: this.backendUrl,
remoteFactorPub: data.factorPub,
metadataShare: data.metadataShare,
remoteClientToken: data.signature,
};
}
}
23 changes: 23 additions & 0 deletions src/remoteFactorServices/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ShareDescriptionMap } from "@tkey/common-types";
import log from "loglevel";

import { RemoteFactorType } from "./remoteSignInterfaces";

export * from "./authenticator";
export * from "./remoteSignInterfaces";
export * from "./smsOtp";

export function getFactorDetailsAndDescriptions(shareDescriptions: ShareDescriptionMap, factorType: RemoteFactorType) {
const arrayOfDescriptions = Object.entries(shareDescriptions).map(([key, value]) => {
const parsedDescription = (value || [])[0] ? JSON.parse(value[0]) : {};
return {
key,
description: parsedDescription,
};
});

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

return { shareDescriptionsMobile, factorPub: shareDescriptionsMobile?.key, tssIndex: shareDescriptionsMobile?.description.tssShareIndex };
}
Loading

0 comments on commit eab02d5

Please sign in to comment.